Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

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