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.
 
 
 

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