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.

14 年之前
14 年之前
14 年之前
14 年之前
14 年之前
14 年之前
14 年之前
14 年之前
14 年之前
14 年之前
14 年之前
14 年之前
14 年之前
14 年之前
14 年之前
14 年之前
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379
  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 */