Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.
 
 
 

449 lignes
12 KiB

  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. #if defined __CELLOS_LV2__
  272. /* FIXME: temporary hack to avoid crashes */
  273. Timer t;
  274. for (;;)
  275. {
  276. t.Get();
  277. t.Wait(0.001f);
  278. }
  279. #endif
  280. return NULL;
  281. }
  282. void Ticker::SetState(Entity * /* entity */, uint32_t /* state */)
  283. {
  284. }
  285. void Ticker::SetStateWhenMatch(Entity * /* entity */, uint32_t /* state */,
  286. Entity * /* other_entity */, uint32_t /* other_state */)
  287. {
  288. }
  289. void Ticker::Setup(float fps)
  290. {
  291. data->fps = fps;
  292. data->gamethread = new Thread(TickerData::GameThreadMain, NULL);
  293. data->gametick.Push(1);
  294. data->diskthread = new Thread(TickerData::DiskThreadMain, NULL);
  295. }
  296. void Ticker::TickDraw()
  297. {
  298. data->drawtick.Pop();
  299. Profiler::Start(Profiler::STAT_TICK_DRAW);
  300. /* Tick objects for the draw loop */
  301. for (int i = Entity::DRAWGROUP_BEGIN; i < Entity::DRAWGROUP_END; i++)
  302. {
  303. switch (i)
  304. {
  305. case Entity::DRAWGROUP_BEGIN:
  306. Scene::GetDefault()->Reset();
  307. Video::Clear();
  308. break;
  309. case Entity::DRAWGROUP_HUD:
  310. Video::SetDepth(false);
  311. break;
  312. default:
  313. Video::SetDepth(true);
  314. break;
  315. }
  316. for (Entity *e = data->list[i]; e; e = e->m_drawnext)
  317. if (!e->m_destroy)
  318. {
  319. #if !LOL_RELEASE
  320. if (e->m_tickstate != Entity::STATE_IDLE)
  321. Log::Error("entity not idle for draw tick\n");
  322. e->m_tickstate = Entity::STATE_PRETICK_DRAW;
  323. #endif
  324. e->TickDraw(data->deltatime);
  325. #if !LOL_RELEASE
  326. if (e->m_tickstate != Entity::STATE_POSTTICK_DRAW)
  327. Log::Error("entity missed super draw tick\n");
  328. e->m_tickstate = Entity::STATE_IDLE;
  329. #endif
  330. }
  331. /* Do this render step */
  332. Scene::GetDefault()->Render();
  333. }
  334. Profiler::Stop(Profiler::STAT_TICK_DRAW);
  335. Profiler::Start(Profiler::STAT_TICK_BLIT);
  336. /* Signal game thread that it can carry on */
  337. data->gametick.Push(1);
  338. /* Clamp FPS */
  339. Profiler::Stop(Profiler::STAT_TICK_BLIT);
  340. /* If framerate is fixed, force wait time to 1/FPS. Otherwise, set wait
  341. * time to 0. */
  342. float frametime = data->fps ? 1.f / data->fps : 0.f;
  343. if (frametime > data->bias + .2f)
  344. frametime = data->bias + .2f; // Don't go below 5 fps
  345. if (frametime > data->bias)
  346. data->timer.Wait(frametime - data->bias);
  347. /* If recording, do not try to compensate for lag. */
  348. if (!data->recording)
  349. data->bias -= frametime;
  350. }
  351. void Ticker::StartRecording()
  352. {
  353. data->recording++;
  354. }
  355. void Ticker::StopRecording()
  356. {
  357. data->recording--;
  358. }
  359. int Ticker::GetFrameNum()
  360. {
  361. return data->frame;
  362. }
  363. void Ticker::Shutdown()
  364. {
  365. /* We're bailing out. Release all m_autorelease objects. */
  366. while (data->autolist)
  367. {
  368. data->autolist->m_ref--;
  369. data->autolist = data->autolist->m_autonext;
  370. }
  371. data->quit = 1;
  372. data->quitframe = data->frame;
  373. }
  374. int Ticker::Finished()
  375. {
  376. return !data->nentities;
  377. }
  378. } /* namespace lol */