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.
 
 
 

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