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.

пре 14 година
пре 14 година
пре 14 година
пре 14 година
пре 14 година
пре 14 година
пре 14 година
пре 14 година
пре 14 година
пре 14 година
пре 14 година
пре 14 година
пре 14 година
пре 14 година
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439
  1. //
  2. // Lol Engine
  3. //
  4. // Copyright: (c) 2010-2012 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://sam.zoy.org/projects/COPYING.WTFPL 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. todolist(0), autolist(0),
  27. nentities(0),
  28. frame(0), recording(0), deltatime(0), bias(0), fps(0),
  29. quit(0), quitframe(0), quitdelay(20), panic(0)
  30. {
  31. for (int i = 0; i < Entity::ALLGROUP_END; i++)
  32. list[i] = NULL;
  33. }
  34. ~TickerData()
  35. {
  36. #if !LOL_RELEASE
  37. if (nentities)
  38. Log::Error("still %i entities in ticker\n", nentities);
  39. if (autolist)
  40. {
  41. int count = 0;
  42. for (Entity *e = autolist; e; e = e->m_autonext, count++)
  43. ;
  44. Log::Error("still %i autoreleased entities\n", count);
  45. }
  46. Log::Debug("%i frames required to quit\n",
  47. frame - quitframe);
  48. #endif
  49. gametick.Push(0);
  50. delete gamethread;
  51. delete diskthread;
  52. }
  53. private:
  54. /* Entity management */
  55. Entity *todolist, *autolist;
  56. Entity *list[Entity::ALLGROUP_END];
  57. int nentities;
  58. /* Fixed framerate management */
  59. int frame, recording;
  60. Timer timer;
  61. float deltatime, bias, fps;
  62. /* Background threads */
  63. static void *GameThreadMain(void *p);
  64. static void *DrawThreadMain(void *p); /* unused */
  65. static void *DiskThreadMain(void *p);
  66. Thread *gamethread, *drawthread, *diskthread;
  67. Queue<int> gametick, drawtick;
  68. /* Shutdown management */
  69. int quit, quitframe, quitdelay, panic;
  70. }
  71. tickerdata;
  72. static TickerData * const data = &tickerdata;
  73. /*
  74. * Ticker public class
  75. */
  76. void Ticker::Register(Entity *entity)
  77. {
  78. /* If we are called from its constructor, the object's vtable is not
  79. * ready yet, so we do not know which group this entity belongs to. Wait
  80. * until the first tick. */
  81. entity->m_gamenext = data->todolist;
  82. data->todolist = entity;
  83. /* Objects are autoreleased by default. Put them in a circular list. */
  84. entity->m_autorelease = 1;
  85. entity->m_autonext = data->autolist;
  86. data->autolist = entity;
  87. entity->m_ref = 1;
  88. data->nentities++;
  89. }
  90. void Ticker::Ref(Entity *entity)
  91. {
  92. #if !LOL_RELEASE
  93. if (!entity)
  94. {
  95. Log::Error("referencing NULL entity\n");
  96. return;
  97. }
  98. if (entity->m_destroy)
  99. Log::Error("referencing entity scheduled for destruction\n");
  100. #endif
  101. if (entity->m_autorelease)
  102. {
  103. /* Get the entity out of the m_autorelease list. This is usually
  104. * very fast since the first entry in autolist is the last
  105. * registered entity. */
  106. for (Entity *e = data->autolist, *prev = NULL; e;
  107. prev = e, e = e->m_autonext)
  108. {
  109. if (e == entity)
  110. {
  111. (prev ? prev->m_autonext : data->autolist) = e->m_autonext;
  112. break;
  113. }
  114. }
  115. entity->m_autorelease = 0;
  116. }
  117. else
  118. entity->m_ref++;
  119. }
  120. int Ticker::Unref(Entity *entity)
  121. {
  122. #if !LOL_RELEASE
  123. if (!entity)
  124. {
  125. Log::Error("dereferencing NULL entity\n");
  126. return 0;
  127. }
  128. if (entity->m_ref <= 0)
  129. Log::Error("dereferencing unreferenced entity\n");
  130. if (entity->m_autorelease)
  131. Log::Error("dereferencing autoreleased entity\n");
  132. #endif
  133. return --entity->m_ref;
  134. }
  135. void *TickerData::GameThreadMain(void * /* p */)
  136. {
  137. for (;;)
  138. {
  139. int tick = data->gametick.Pop();
  140. if (!tick)
  141. break;
  142. Profiler::Stop(Profiler::STAT_TICK_FRAME);
  143. Profiler::Start(Profiler::STAT_TICK_FRAME);
  144. Profiler::Start(Profiler::STAT_TICK_GAME);
  145. #if 0
  146. Log::Debug("-------------------------------------\n");
  147. for (int i = 0; i < Entity::ALLGROUP_END; i++)
  148. {
  149. Log::Debug("%s Group %i\n",
  150. (i < Entity::GAMEGROUP_END) ? "Game" : "Draw", i);
  151. for (Entity *e = data->list[i]; e; )
  152. {
  153. Log::Debug(" \\-- %s (m_ref %i, destroy %i)\n", e->GetName(), e->m_ref, e->m_destroy);
  154. e = (i < Entity::GAMEGROUP_END) ? e->m_gamenext : e->m_drawnext;
  155. }
  156. }
  157. #endif
  158. data->frame++;
  159. /* If recording with fixed framerate, set deltatime to a fixed value */
  160. if (data->recording && data->fps)
  161. {
  162. data->deltatime = 1.f / data->fps;
  163. }
  164. else
  165. {
  166. data->deltatime = data->timer.Get();
  167. data->bias += data->deltatime;
  168. }
  169. /* If shutdown is stuck, kick the first entity we meet and see
  170. * whether it makes things better. Note that it is always a bug to
  171. * have referenced entities after 20 frames, but at least this
  172. * safeguard makes it possible to exit the program cleanly. */
  173. if (data->quit && !((data->frame - data->quitframe) % data->quitdelay))
  174. {
  175. int n = 0;
  176. data->panic = 2 * (data->panic + 1);
  177. for (int i = 0; i < Entity::ALLGROUP_END && n < data->panic; i++)
  178. for (Entity *e = data->list[i]; e && n < data->panic; e = e->m_gamenext)
  179. if (e->m_ref)
  180. {
  181. #if !LOL_RELEASE
  182. Log::Error("poking %s\n", e->GetName());
  183. #endif
  184. e->m_ref--;
  185. n++;
  186. }
  187. #if !LOL_RELEASE
  188. if (n)
  189. Log::Error("%i entities stuck after %i frames, poked %i\n",
  190. data->nentities, data->quitdelay, n);
  191. #endif
  192. data->quitdelay = data->quitdelay > 1 ? data->quitdelay / 2 : 1;
  193. }
  194. /* Garbage collect objects that can be destroyed. We can do this
  195. * before inserting awaiting objects, because only objects already
  196. * inthe tick lists can be marked for destruction. */
  197. for (int i = 0; i < Entity::ALLGROUP_END; i++)
  198. for (Entity *e = data->list[i], *prev = NULL; e; )
  199. {
  200. if (e->m_destroy && i < Entity::GAMEGROUP_END)
  201. {
  202. /* If entity is to be destroyed, remove it from the
  203. * game tick list. */
  204. (prev ? prev->m_gamenext : data->list[i]) = e->m_gamenext;
  205. e = e->m_gamenext;
  206. }
  207. else if (e->m_destroy)
  208. {
  209. /* If entity is to be destroyed, remove it from the
  210. * draw tick list and destroy it. */
  211. (prev ? prev->m_drawnext : data->list[i]) = e->m_drawnext;
  212. Entity *tmp = e;
  213. e = e->m_drawnext; /* Can only be in a draw group list */
  214. delete tmp;
  215. data->nentities--;
  216. }
  217. else
  218. {
  219. if (e->m_ref <= 0 && i >= Entity::DRAWGROUP_BEGIN)
  220. e->m_destroy = 1;
  221. prev = e;
  222. e = (i < Entity::GAMEGROUP_END) ? e->m_gamenext : e->m_drawnext;
  223. }
  224. }
  225. /* Insert waiting objects into the appropriate lists */
  226. while (data->todolist)
  227. {
  228. Entity *e = data->todolist;
  229. data->todolist = e->m_gamenext;
  230. e->m_gamenext = data->list[e->m_gamegroup];
  231. data->list[e->m_gamegroup] = e;
  232. e->m_drawnext = data->list[e->m_drawgroup];
  233. data->list[e->m_drawgroup] = e;
  234. }
  235. /* Tick objects for the game loop */
  236. for (int i = Entity::GAMEGROUP_BEGIN; i < Entity::GAMEGROUP_END; i++)
  237. for (Entity *e = data->list[i]; e; e = e->m_gamenext)
  238. if (!e->m_destroy)
  239. {
  240. #if !LOL_RELEASE
  241. if (e->m_tickstate != Entity::STATE_IDLE)
  242. Log::Error("entity not idle for game tick\n");
  243. e->m_tickstate = Entity::STATE_PRETICK_GAME;
  244. #endif
  245. e->TickGame(data->deltatime);
  246. #if !LOL_RELEASE
  247. if (e->m_tickstate != Entity::STATE_POSTTICK_GAME)
  248. Log::Error("entity missed super game tick\n");
  249. e->m_tickstate = Entity::STATE_IDLE;
  250. #endif
  251. }
  252. Profiler::Stop(Profiler::STAT_TICK_GAME);
  253. data->drawtick.Push(1);
  254. }
  255. data->drawtick.Push(0);
  256. return NULL;
  257. }
  258. void *TickerData::DrawThreadMain(void * /* p */)
  259. {
  260. for (;;)
  261. {
  262. int tick = data->drawtick.Pop();
  263. if (!tick)
  264. break;
  265. data->gametick.Push(1);
  266. }
  267. return NULL;
  268. }
  269. void *TickerData::DiskThreadMain(void * /* p */)
  270. {
  271. return NULL;
  272. }
  273. void Ticker::SetState(Entity * /* entity */, uint32_t /* state */)
  274. {
  275. }
  276. void Ticker::SetStateWhenMatch(Entity * /* entity */, uint32_t /* state */,
  277. Entity * /* other_entity */, uint32_t /* other_state */)
  278. {
  279. }
  280. void Ticker::Setup(float fps)
  281. {
  282. data->fps = fps;
  283. data->gamethread = new Thread(TickerData::GameThreadMain, NULL);
  284. data->gametick.Push(1);
  285. data->diskthread = new Thread(TickerData::DiskThreadMain, NULL);
  286. }
  287. void Ticker::TickDraw()
  288. {
  289. data->drawtick.Pop();
  290. Profiler::Start(Profiler::STAT_TICK_DRAW);
  291. /* Tick objects for the draw loop */
  292. for (int i = Entity::DRAWGROUP_BEGIN; i < Entity::DRAWGROUP_END; i++)
  293. {
  294. switch (i)
  295. {
  296. case Entity::DRAWGROUP_BEGIN:
  297. Scene::GetDefault()->Reset();
  298. Video::Clear();
  299. break;
  300. case Entity::DRAWGROUP_HUD:
  301. Video::SetDepth(false);
  302. break;
  303. default:
  304. Video::SetDepth(true);
  305. break;
  306. }
  307. for (Entity *e = data->list[i]; e; e = e->m_drawnext)
  308. if (!e->m_destroy)
  309. {
  310. #if !LOL_RELEASE
  311. if (e->m_tickstate != Entity::STATE_IDLE)
  312. Log::Error("entity not idle for draw tick\n");
  313. e->m_tickstate = Entity::STATE_PRETICK_DRAW;
  314. #endif
  315. e->TickDraw(data->deltatime);
  316. #if !LOL_RELEASE
  317. if (e->m_tickstate != Entity::STATE_POSTTICK_DRAW)
  318. Log::Error("entity missed super draw tick\n");
  319. e->m_tickstate = Entity::STATE_IDLE;
  320. #endif
  321. }
  322. /* Do this render step */
  323. Scene::GetDefault()->Render();
  324. }
  325. Profiler::Stop(Profiler::STAT_TICK_DRAW);
  326. Profiler::Start(Profiler::STAT_TICK_BLIT);
  327. /* Signal game thread that it can carry on */
  328. data->gametick.Push(1);
  329. /* Clamp FPS */
  330. Profiler::Stop(Profiler::STAT_TICK_BLIT);
  331. /* If framerate is fixed, force wait time to 1/FPS. Otherwise, set wait
  332. * time to 0. */
  333. float frametime = data->fps ? 1.f / data->fps : 0.f;
  334. if (frametime > data->bias + .2f)
  335. frametime = data->bias + .2f; // Don't go below 5 fps
  336. if (frametime > data->bias)
  337. data->timer.Wait(frametime - data->bias);
  338. /* If recording, do not try to compensate for lag. */
  339. if (!data->recording)
  340. data->bias -= frametime;
  341. }
  342. void Ticker::StartRecording()
  343. {
  344. data->recording++;
  345. }
  346. void Ticker::StopRecording()
  347. {
  348. data->recording--;
  349. }
  350. int Ticker::GetFrameNum()
  351. {
  352. return data->frame;
  353. }
  354. void Ticker::Shutdown()
  355. {
  356. /* We're bailing out. Release all m_autorelease objects. */
  357. while (data->autolist)
  358. {
  359. data->autolist->m_ref--;
  360. data->autolist = data->autolist->m_autonext;
  361. }
  362. data->quit = 1;
  363. data->quitframe = data->frame;
  364. }
  365. int Ticker::Finished()
  366. {
  367. return !data->nentities;
  368. }
  369. } /* namespace lol */