您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 
 
 

526 行
13 KiB

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