You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

преди 12 години
преди 14 години
преди 12 години
преди 14 години
преди 14 години
преди 12 години
преди 14 години
преди 12 години
преди 14 години
преди 14 години
преди 14 години
преди 14 години
преди 14 години
преди 14 години
преди 14 години
преди 14 години
преди 14 години
преди 14 години
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514
  1. //
  2. // Lol Engine
  3. //
  4. // Copyright: (c) 2010-2013 Sam Hocevar <sam@hocevar.net>
  5. // This program is free software; you can redistribute it and/or
  6. // modify it under the terms of the Do What The Fuck You Want To
  7. // Public License, Version 2, as published by Sam Hocevar. See
  8. // http://www.wtfpl.net/ for more details.
  9. //
  10. #if defined HAVE_CONFIG_H
  11. # include "config.h"
  12. #endif
  13. #include <cstdlib>
  14. #include <stdint.h>
  15. #include "core.h"
  16. namespace lol
  17. {
  18. /*
  19. * Ticker implementation class
  20. */
  21. static class TickerData
  22. {
  23. friend class Ticker;
  24. public:
  25. TickerData() :
  26. nentities(0),
  27. frame(0), recording(0), deltatime(0), bias(0), fps(0),
  28. #if LOL_BUILD_DEBUG
  29. keepalive(0),
  30. #endif
  31. quit(0), quitframe(0), quitdelay(20), panic(0)
  32. {
  33. }
  34. ~TickerData()
  35. {
  36. ASSERT(nentities == 0,
  37. "still %i entities in ticker\n", nentities);
  38. ASSERT(m_autolist.Count() == 0,
  39. "still %i autoreleased entities\n", m_autolist.Count());
  40. Log::Debug("%i frames required to quit\n", frame - quitframe);
  41. #if LOL_FEATURE_THREADS
  42. gametick.Push(0);
  43. disktick.Push(0);
  44. delete gamethread;
  45. delete diskthread;
  46. #endif
  47. }
  48. private:
  49. /* Entity management */
  50. Array<Entity *> m_todolist, m_autolist;
  51. Array<Entity *> m_list[Entity::ALLGROUP_END];
  52. int nentities;
  53. /* Fixed framerate management */
  54. int frame, recording;
  55. Timer timer;
  56. float deltatime, bias, fps;
  57. #if LOL_BUILD_DEBUG
  58. float keepalive;
  59. #endif
  60. /* The three main functions (for now) */
  61. static void GameThreadTick();
  62. static void DrawThreadTick();
  63. static void DiskThreadTick();
  64. #if LOL_FEATURE_THREADS
  65. /* The associated background threads */
  66. static void *GameThreadMain(void *p);
  67. static void *DrawThreadMain(void *p); /* unused */
  68. static void *DiskThreadMain(void *p);
  69. Thread *gamethread, *drawthread, *diskthread;
  70. Queue<int> gametick, drawtick, disktick;
  71. #endif
  72. /* Shutdown management */
  73. int quit, quitframe, quitdelay, panic;
  74. }
  75. tickerdata;
  76. static TickerData * const data = &tickerdata;
  77. /*
  78. * Ticker public class
  79. */
  80. void Ticker::Register(Entity *entity)
  81. {
  82. /* If we are called from its constructor, the object's vtable is not
  83. * ready yet, so we do not know which group this entity belongs to. Wait
  84. * until the first tick. */
  85. data->m_todolist.Push(entity);
  86. /* Objects are autoreleased by default. Put them in a list. */
  87. data->m_autolist.Push(entity);
  88. entity->m_autorelease = 1;
  89. entity->m_ref = 1;
  90. data->nentities++;
  91. }
  92. void Ticker::Ref(Entity *entity)
  93. {
  94. ASSERT(entity, "dereferencing nullptr entity\n");
  95. ASSERT(!entity->m_destroy,
  96. "referencing entity scheduled for destruction %s\n",
  97. entity->GetName());
  98. if (entity->m_autorelease)
  99. {
  100. /* Get the entity out of the m_autorelease list. This is usually
  101. * very fast since the last entry in autolist is the last
  102. * registered entity. */
  103. for (int i = data->m_autolist.Count(); --i; )
  104. {
  105. if (data->m_autolist[i] == entity)
  106. {
  107. data->m_autolist.RemoveSwap(i);
  108. break;
  109. }
  110. }
  111. entity->m_autorelease = 0;
  112. }
  113. else
  114. entity->m_ref++;
  115. }
  116. int Ticker::Unref(Entity *entity)
  117. {
  118. ASSERT(entity, "dereferencing null entity\n");
  119. ASSERT(entity->m_ref > 0, "dereferencing unreferenced entity %s\n",
  120. entity->GetName())
  121. ASSERT(!entity->m_autorelease, "dereferencing autoreleased entity %s\n",
  122. entity->GetName())
  123. return --entity->m_ref;
  124. }
  125. #if LOL_FEATURE_THREADS
  126. void *TickerData::GameThreadMain(void * /* p */)
  127. {
  128. #if LOL_BUILD_DEBUG
  129. Log::Info("ticker game thread initialised\n");
  130. #endif
  131. for (;;)
  132. {
  133. int tick = data->gametick.Pop();
  134. if (!tick)
  135. break;
  136. GameThreadTick();
  137. data->drawtick.Push(1);
  138. }
  139. data->drawtick.Push(0);
  140. #if LOL_BUILD_DEBUG
  141. Log::Info("ticker game thread terminated\n");
  142. #endif
  143. return nullptr;
  144. }
  145. #endif /* LOL_FEATURE_THREADS */
  146. #if LOL_FEATURE_THREADS
  147. void *TickerData::DrawThreadMain(void * /* p */)
  148. {
  149. #if LOL_BUILD_DEBUG
  150. Log::Info("ticker draw thread initialised\n");
  151. #endif
  152. for (;;)
  153. {
  154. int tick = data->drawtick.Pop();
  155. if (!tick)
  156. break;
  157. DrawThreadTick();
  158. data->gametick.Push(1);
  159. }
  160. #if LOL_BUILD_DEBUG
  161. Log::Info("ticker draw thread terminated\n");
  162. #endif
  163. return nullptr;
  164. }
  165. #endif /* LOL_FEATURE_THREADS */
  166. #if LOL_FEATURE_THREADS
  167. void *TickerData::DiskThreadMain(void * /* p */)
  168. {
  169. /* FIXME: temporary hack to avoid crashes on the PS3 */
  170. data->disktick.Pop();
  171. return nullptr;
  172. }
  173. #endif /* LOL_FEATURE_THREADS */
  174. void TickerData::GameThreadTick()
  175. {
  176. Profiler::Stop(Profiler::STAT_TICK_FRAME);
  177. Profiler::Start(Profiler::STAT_TICK_FRAME);
  178. Profiler::Start(Profiler::STAT_TICK_GAME);
  179. #if 0
  180. Log::Debug("-------------------------------------\n");
  181. for (int g = 0; g < Entity::ALLGROUP_END; ++g)
  182. {
  183. Log::Debug("%s Group %i\n",
  184. (g < Entity::GAMEGROUP_END) ? "Game" : "Draw", g);
  185. for (int i = 0; i < data->m_list[g].Count(); ++i)
  186. {
  187. Entity *e = data->m_list[g][i];
  188. Log::Debug(" \\-- %s (m_ref %i, destroy %i)\n", e->GetName(), e->m_ref, e->m_destroy);
  189. }
  190. }
  191. #endif
  192. data->frame++;
  193. /* Ensure some randomness */
  194. rand<int>();
  195. /* If recording with fixed framerate, set deltatime to a fixed value */
  196. if (data->recording && data->fps)
  197. {
  198. data->deltatime = 1.f / data->fps;
  199. }
  200. else
  201. {
  202. data->deltatime = data->timer.Get();
  203. data->bias += data->deltatime;
  204. }
  205. /* Do not go below 15 fps */
  206. if (data->deltatime > 1.f / 15.f)
  207. {
  208. data->deltatime = 1.f / 15.f;
  209. data->bias = 0.f;
  210. }
  211. #if LOL_BUILD_DEBUG
  212. data->keepalive += data->deltatime;
  213. if (data->keepalive > 10.f)
  214. {
  215. Log::Info("ticker keepalive: tick!\n");
  216. data->keepalive = 0.f;
  217. }
  218. #endif
  219. /* If shutdown is stuck, kick the first entity we meet and see
  220. * whether it makes things better. Note that it is always a bug to
  221. * have referenced entities after 20 frames, but at least this
  222. * safeguard makes it possible to exit the program cleanly. */
  223. if (data->quit && !((data->frame - data->quitframe) % data->quitdelay))
  224. {
  225. int n = 0;
  226. data->panic = 2 * (data->panic + 1);
  227. for (int g = 0; g < Entity::ALLGROUP_END && n < data->panic; ++g)
  228. for (int i = 0; i < data->m_list[g].Count() && n < data->panic; ++i)
  229. {
  230. Entity * e = data->m_list[g][i];
  231. if (e->m_ref)
  232. {
  233. #if !LOL_BUILD_RELEASE
  234. Log::Error("poking %s\n", e->GetName());
  235. #endif
  236. e->m_ref--;
  237. n++;
  238. }
  239. }
  240. #if !LOL_BUILD_RELEASE
  241. if (n)
  242. Log::Error("%i entities stuck after %i frames, poked %i\n",
  243. data->nentities, data->quitdelay, n);
  244. #endif
  245. data->quitdelay = data->quitdelay > 1 ? data->quitdelay / 2 : 1;
  246. }
  247. /* Garbage collect objects that can be destroyed. We can do this
  248. * before inserting awaiting objects, because only objects already
  249. * inthe tick lists can be marked for destruction. */
  250. for (int g = 0; g < Entity::ALLGROUP_END; ++g)
  251. {
  252. for (int i = 0; i < data->m_list[g].Count(); ++i)
  253. {
  254. Entity *e = data->m_list[g][i];
  255. if (e->m_destroy && g < Entity::GAMEGROUP_END)
  256. {
  257. /* If entity is to be destroyed, remove it from the
  258. * game tick list. */
  259. data->m_list[g].RemoveSwap(i);
  260. }
  261. else if (e->m_destroy)
  262. {
  263. /* If entity is to be destroyed, remove it from the
  264. * draw tick list and destroy it. */
  265. data->m_list[g].RemoveSwap(i);
  266. delete e;
  267. data->nentities--;
  268. }
  269. else
  270. {
  271. if (e->m_ref <= 0 && g >= Entity::DRAWGROUP_BEGIN)
  272. e->m_destroy = 1;
  273. }
  274. }
  275. }
  276. /* Insert waiting objects into the appropriate lists */
  277. while (data->m_todolist.Count())
  278. {
  279. Entity *e = data->m_todolist.Last();
  280. data->m_todolist.Remove(-1);
  281. data->m_list[e->m_gamegroup].Push(e);
  282. data->m_list[e->m_drawgroup].Push(e);
  283. }
  284. /* Tick objects for the game loop */
  285. for (int g = Entity::GAMEGROUP_BEGIN; g < Entity::GAMEGROUP_END; ++g)
  286. for (int i = 0; i < data->m_list[g].Count(); ++i)
  287. {
  288. Entity *e = data->m_list[g][i];
  289. if (!e->m_destroy)
  290. {
  291. #if !LOL_BUILD_RELEASE
  292. if (e->m_tickstate != Entity::STATE_IDLE)
  293. Log::Error("entity %s [%p] not idle for game tick\n",
  294. e->GetName(), e);
  295. e->m_tickstate = Entity::STATE_PRETICK_GAME;
  296. #endif
  297. e->TickGame(data->deltatime);
  298. #if !LOL_BUILD_RELEASE
  299. if (e->m_tickstate != Entity::STATE_POSTTICK_GAME)
  300. Log::Error("entity %s [%p] missed super game tick\n",
  301. e->GetName(), e);
  302. e->m_tickstate = Entity::STATE_IDLE;
  303. #endif
  304. }
  305. }
  306. Profiler::Stop(Profiler::STAT_TICK_GAME);
  307. }
  308. void TickerData::DrawThreadTick()
  309. {
  310. Profiler::Start(Profiler::STAT_TICK_DRAW);
  311. /* Tick objects for the draw loop */
  312. for (int g = Entity::DRAWGROUP_BEGIN; g < Entity::DRAWGROUP_END; ++g)
  313. {
  314. switch (g)
  315. {
  316. case Entity::DRAWGROUP_BEGIN:
  317. g_scene->Reset();
  318. g_renderer->Clear(ClearMask::All);
  319. break;
  320. default:
  321. break;
  322. }
  323. for (int i = 0; i < data->m_list[g].Count(); ++i)
  324. {
  325. Entity *e = data->m_list[g][i];
  326. if (!e->m_destroy)
  327. {
  328. #if !LOL_BUILD_RELEASE
  329. if (e->m_tickstate != Entity::STATE_IDLE)
  330. Log::Error("entity %s [%p] not idle for draw tick\n",
  331. e->GetName(), e);
  332. e->m_tickstate = Entity::STATE_PRETICK_DRAW;
  333. #endif
  334. e->TickDraw(data->deltatime);
  335. #if !LOL_BUILD_RELEASE
  336. if (e->m_tickstate != Entity::STATE_POSTTICK_DRAW)
  337. Log::Error("entity %s [%p] missed super draw tick\n",
  338. e->GetName(), e);
  339. e->m_tickstate = Entity::STATE_IDLE;
  340. #endif
  341. }
  342. }
  343. /* Do this render step */
  344. g_scene->Render();
  345. }
  346. Profiler::Stop(Profiler::STAT_TICK_DRAW);
  347. }
  348. void TickerData::DiskThreadTick()
  349. {
  350. ;
  351. }
  352. void Ticker::SetState(Entity * /* entity */, uint32_t /* state */)
  353. {
  354. }
  355. void Ticker::SetStateWhenMatch(Entity * /* entity */, uint32_t /* state */,
  356. Entity * /* other_entity */, uint32_t /* other_state */)
  357. {
  358. }
  359. void Ticker::Setup(float fps)
  360. {
  361. data->fps = fps;
  362. #if LOL_FEATURE_THREADS
  363. data->gamethread = new Thread(TickerData::GameThreadMain, nullptr);
  364. data->gametick.Push(1);
  365. data->diskthread = new Thread(TickerData::DiskThreadMain, nullptr);
  366. #endif
  367. }
  368. void Ticker::TickDraw()
  369. {
  370. #if LOL_FEATURE_THREADS
  371. data->drawtick.Pop();
  372. #else
  373. TickerData::GameThreadTick();
  374. #endif
  375. TickerData::DrawThreadTick();
  376. Profiler::Start(Profiler::STAT_TICK_BLIT);
  377. /* Signal game thread that it can carry on */
  378. #if LOL_FEATURE_THREADS
  379. data->gametick.Push(1);
  380. #else
  381. TickerData::DiskThreadTick();
  382. #endif
  383. /* Clamp FPS */
  384. Profiler::Stop(Profiler::STAT_TICK_BLIT);
  385. #if !EMSCRIPTEN
  386. /* If framerate is fixed, force wait time to 1/FPS. Otherwise, set wait
  387. * time to 0. */
  388. float frametime = data->fps ? 1.f / data->fps : 0.f;
  389. if (frametime > data->bias + .2f)
  390. frametime = data->bias + .2f; /* Don't go below 5 fps */
  391. if (frametime > data->bias)
  392. data->timer.Wait(frametime - data->bias);
  393. /* If recording, do not try to compensate for lag. */
  394. if (!data->recording)
  395. data->bias -= frametime;
  396. #endif
  397. }
  398. void Ticker::StartRecording()
  399. {
  400. data->recording++;
  401. }
  402. void Ticker::StopRecording()
  403. {
  404. data->recording--;
  405. }
  406. int Ticker::GetFrameNum()
  407. {
  408. return data->frame;
  409. }
  410. void Ticker::Shutdown()
  411. {
  412. /* We're bailing out. Release all m_autorelease objects. */
  413. while (data->m_autolist.Count())
  414. {
  415. data->m_autolist.Last()->m_ref--;
  416. data->m_autolist.Remove(-1);
  417. }
  418. data->quit = 1;
  419. data->quitframe = data->frame;
  420. }
  421. int Ticker::Finished()
  422. {
  423. return !data->nentities;
  424. }
  425. } /* namespace lol */