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.
 
 
 

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