387 líneas
10 KiB

  1. //
  2. // Lol Engine
  3. //
  4. // Copyright: (c) 2010-2011 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 <cstdio>
  15. #include <stdint.h>
  16. #include "core.h"
  17. /*
  18. * Ticker implementation class
  19. */
  20. static class TickerData
  21. {
  22. friend class Ticker;
  23. public:
  24. TickerData() :
  25. todolist(0), autolist(0),
  26. nentities(0),
  27. frame(0), benchmark(0), recording(0), deltams(0), bias(0), fps(0),
  28. quit(0), quitframe(0), quitdelay(20), panic(0)
  29. {
  30. for (int i = 0; i < Entity::ALLGROUP_END; i++)
  31. list[i] = NULL;
  32. }
  33. ~TickerData()
  34. {
  35. #if !FINAL_RELEASE
  36. if (nentities)
  37. fprintf(stderr, "ERROR: still %i entities in ticker\n", nentities);
  38. if (autolist)
  39. {
  40. int count = 0;
  41. for (Entity *e = autolist; e; e = e->autonext, count++)
  42. ;
  43. fprintf(stderr, "ERROR: still %i autoreleased entities\n", count);
  44. }
  45. fprintf(stderr, "INFO: %i frames required to quit\n",
  46. frame - quitframe);
  47. #endif
  48. }
  49. private:
  50. /* Entity management */
  51. Entity *todolist, *autolist;
  52. Entity *list[Entity::ALLGROUP_END];
  53. int nentities;
  54. /* Fixed framerate management */
  55. int frame, benchmark, recording;
  56. Timer timer;
  57. float deltams, bias, fps;
  58. /* Shutdown management */
  59. int quit, quitframe, quitdelay, panic;
  60. }
  61. tickerdata;
  62. static TickerData * const data = &tickerdata;
  63. /*
  64. * Ticker public class
  65. */
  66. void Ticker::Register(Entity *entity)
  67. {
  68. /* If we are called from its constructor, the object's vtable is not
  69. * ready yet, so we do not know which group this entity belongs to. Wait
  70. * until the first tick. */
  71. entity->gamenext = data->todolist;
  72. data->todolist = entity;
  73. /* Objects are autoreleased by default. Put them in a circular list. */
  74. entity->autorelease = 1;
  75. entity->autonext = data->autolist;
  76. data->autolist = entity;
  77. entity->ref = 1;
  78. data->nentities++;
  79. }
  80. void Ticker::Ref(Entity *entity)
  81. {
  82. #if !FINAL_RELEASE
  83. if (!entity)
  84. {
  85. fprintf(stderr, "ERROR: refing NULL entity\n");
  86. return;
  87. }
  88. if (entity->destroy)
  89. fprintf(stderr, "ERROR: refing entity scheduled for destruction\n");
  90. #endif
  91. if (entity->autorelease)
  92. {
  93. /* Get the entity out of the autorelease list. This is usually
  94. * very fast since the first entry in autolist is the last
  95. * registered entity. */
  96. for (Entity *e = data->autolist, *prev = NULL; e;
  97. prev = e, e = e->autonext)
  98. {
  99. if (e == entity)
  100. {
  101. (prev ? prev->autonext : data->autolist) = e->autonext;
  102. break;
  103. }
  104. }
  105. entity->autorelease = 0;
  106. }
  107. else
  108. entity->ref++;
  109. }
  110. int Ticker::Unref(Entity *entity)
  111. {
  112. #if !FINAL_RELEASE
  113. if (!entity)
  114. {
  115. fprintf(stderr, "ERROR: dereferencing NULL entity\n");
  116. return 0;
  117. }
  118. if (entity->ref <= 0)
  119. fprintf(stderr, "ERROR: dereferencing unreferenced entity\n");
  120. if (entity->autorelease)
  121. fprintf(stderr, "ERROR: dereferencing autoreleased entity\n");
  122. #endif
  123. return --entity->ref;
  124. }
  125. void Ticker::Setup(float fps)
  126. {
  127. data->fps = fps;
  128. }
  129. void Ticker::TickGame()
  130. {
  131. Profiler::Stop(Profiler::STAT_TICK_FRAME);
  132. Profiler::Start(Profiler::STAT_TICK_FRAME);
  133. Profiler::Start(Profiler::STAT_TICK_GAME);
  134. #if 0
  135. fprintf(stderr, "-------------------------------------\n");
  136. for (int i = 0; i < Entity::ALLGROUP_END; i++)
  137. {
  138. fprintf(stderr, "%s Group %i\n",
  139. (i < Entity::GAMEGROUP_END) ? "Game" : "Draw", i);
  140. for (Entity *e = data->list[i]; e; )
  141. {
  142. fprintf(stderr, " \\-- %s (ref %i, destroy %i)\n", e->GetName(), e->ref, e->destroy);
  143. e = (i < Entity::GAMEGROUP_END) ? e->gamenext : e->drawnext;
  144. }
  145. }
  146. #endif
  147. data->frame++;
  148. if (data->recording && data->fps)
  149. {
  150. data->deltams = 1000.0f / data->fps;
  151. }
  152. else
  153. {
  154. data->deltams = data->timer.GetMs();
  155. data->bias += data->deltams;
  156. }
  157. /* If shutdown is stuck, kick the first entity we meet and see
  158. * whether it makes things better. Note that it is always a bug to
  159. * have referenced entities after 20 frames, but at least this
  160. * safeguard makes it possible to exit the program cleanly. */
  161. if (data->quit && !((data->frame - data->quitframe) % data->quitdelay))
  162. {
  163. int n = 0;
  164. data->panic = 2 * (data->panic + 1);
  165. for (int i = 0; i < Entity::ALLGROUP_END && n < data->panic; i++)
  166. for (Entity *e = data->list[i]; e && n < data->panic; e = e->gamenext)
  167. if (e->ref)
  168. {
  169. #if !FINAL_RELEASE
  170. fprintf(stderr, "ERROR: poking %s\n", e->GetName());
  171. #endif
  172. e->ref--;
  173. n++;
  174. }
  175. #if !FINAL_RELEASE
  176. if (n)
  177. fprintf(stderr, "ERROR: %i entities stuck after %i frames, "
  178. "poked %i\n", data->nentities, data->quitdelay, n);
  179. #endif
  180. data->quitdelay = data->quitdelay > 1 ? data->quitdelay / 2 : 1;
  181. }
  182. /* Garbage collect objects that can be destroyed. We can do this
  183. * before inserting awaiting objects, because only objects already in
  184. * the tick lists can be marked for destruction. */
  185. for (int i = 0; i < Entity::ALLGROUP_END; i++)
  186. for (Entity *e = data->list[i], *prev = NULL; e; )
  187. {
  188. if (e->destroy && i < Entity::GAMEGROUP_END)
  189. {
  190. /* If entity is to be destroyed, remove it from the
  191. * game tick list. */
  192. (prev ? prev->gamenext : data->list[i]) = e->gamenext;
  193. e = e->gamenext;
  194. }
  195. else if (e->destroy)
  196. {
  197. /* If entity is to be destroyed, remove it from the
  198. * draw tick list and destroy it. */
  199. (prev ? prev->drawnext : data->list[i]) = e->drawnext;
  200. Entity *tmp = e;
  201. e = e->drawnext; /* Can only be in a draw group list */
  202. delete tmp;
  203. data->nentities--;
  204. }
  205. else
  206. {
  207. if (e->ref <= 0 && i >= Entity::DRAWGROUP_BEGIN)
  208. e->destroy = 1;
  209. prev = e;
  210. e = (i < Entity::GAMEGROUP_END) ? e->gamenext : e->drawnext;
  211. }
  212. }
  213. /* Insert waiting objects into the appropriate lists */
  214. while (data->todolist)
  215. {
  216. Entity *e = data->todolist;
  217. data->todolist = e->gamenext;
  218. e->gamenext = data->list[e->gamegroup];
  219. data->list[e->gamegroup] = e;
  220. e->drawnext = data->list[e->drawgroup];
  221. data->list[e->drawgroup] = e;
  222. }
  223. /* Tick objects for the game loop */
  224. for (int i = Entity::GAMEGROUP_BEGIN; i < Entity::GAMEGROUP_END; i++)
  225. for (Entity *e = data->list[i]; e; e = e->gamenext)
  226. if (!e->destroy)
  227. {
  228. #if !FINAL_RELEASE
  229. if (e->state != Entity::STATE_IDLE)
  230. fprintf(stderr, "ERROR: entity not idle for game tick\n");
  231. e->state = Entity::STATE_PRETICK_GAME;
  232. #endif
  233. e->TickGame(data->deltams);
  234. #if !FINAL_RELEASE
  235. if (e->state != Entity::STATE_POSTTICK_GAME)
  236. fprintf(stderr, "ERROR: entity missed super game tick\n");
  237. e->state = Entity::STATE_IDLE;
  238. #endif
  239. }
  240. Profiler::Stop(Profiler::STAT_TICK_GAME);
  241. }
  242. void Ticker::TickDraw()
  243. {
  244. Profiler::Start(Profiler::STAT_TICK_DRAW);
  245. Video::Clear();
  246. Scene::GetDefault();
  247. /* Tick objects for the draw loop */
  248. for (int i = Entity::DRAWGROUP_BEGIN; i < Entity::DRAWGROUP_END; i++)
  249. {
  250. switch (i)
  251. {
  252. case Entity::DRAWGROUP_HUD:
  253. Scene::GetDefault()->Render();
  254. Video::SetDepth(false);
  255. break;
  256. default:
  257. Video::SetDepth(true);
  258. break;
  259. }
  260. for (Entity *e = data->list[i]; e; e = e->drawnext)
  261. if (!e->destroy)
  262. {
  263. #if !FINAL_RELEASE
  264. if (e->state != Entity::STATE_IDLE)
  265. fprintf(stderr, "ERROR: entity not idle for draw tick\n");
  266. e->state = Entity::STATE_PRETICK_DRAW;
  267. #endif
  268. e->TickDraw(data->deltams);
  269. #if !FINAL_RELEASE
  270. if (e->state != Entity::STATE_POSTTICK_DRAW)
  271. fprintf(stderr, "ERROR: entity missed super draw tick\n");
  272. e->state = Entity::STATE_IDLE;
  273. #endif
  274. }
  275. }
  276. Scene::Reset();
  277. Profiler::Stop(Profiler::STAT_TICK_DRAW);
  278. Profiler::Start(Profiler::STAT_TICK_BLIT);
  279. }
  280. void Ticker::ClampFps()
  281. {
  282. Profiler::Stop(Profiler::STAT_TICK_BLIT);
  283. /* If benchmarking, set wait time to 0. If FPS are fixed, force wait
  284. * time to 1/FPS. Otherwise, set wait time to 0. */
  285. float framems = (!data->benchmark && data->fps) ? 1000.0f / data->fps
  286. : 0.0f;
  287. if (!data->benchmark)
  288. {
  289. if (framems > data->bias + 200.0f)
  290. framems = data->bias + 200.0f; // Don't go below 5 fps
  291. if (framems > data->bias)
  292. data->timer.WaitMs(framems - data->bias);
  293. }
  294. if (!data->recording)
  295. data->bias -= framems;
  296. }
  297. void Ticker::StartBenchmark()
  298. {
  299. data->benchmark++;
  300. }
  301. void Ticker::StopBenchmark()
  302. {
  303. data->benchmark--;
  304. }
  305. void Ticker::StartRecording()
  306. {
  307. data->recording++;
  308. }
  309. void Ticker::StopRecording()
  310. {
  311. data->recording--;
  312. }
  313. int Ticker::GetFrameNum()
  314. {
  315. return data->frame;
  316. }
  317. void Ticker::Shutdown()
  318. {
  319. /* We're bailing out. Release all autorelease objects. */
  320. while (data->autolist)
  321. {
  322. data->autolist->ref--;
  323. data->autolist = data->autolist->autonext;
  324. }
  325. data->quit = 1;
  326. data->quitframe = data->frame;
  327. }
  328. int Ticker::Finished()
  329. {
  330. return !data->nentities;
  331. }