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.
 
 
 

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