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.

586 lines
16 KiB

  1. //
  2. // Lol Engine
  3. //
  4. // Copyright © 2010—2018 Sam Hocevar <sam@hocevar.net>
  5. //
  6. // Lol Engine is free software. It comes without any warranty, to
  7. // the extent permitted by applicable law. You can redistribute it
  8. // and/or modify it under the terms of the Do What the Fuck You Want
  9. // to Public License, Version 2, as published by the WTFPL Task Force.
  10. // See http://www.wtfpl.net/ for more details.
  11. //
  12. #include <lol/engine-internal.h>
  13. #include <cstdlib>
  14. #include <stdint.h>
  15. #include <functional>
  16. namespace lol
  17. {
  18. /*
  19. * Ticker implementation class
  20. */
  21. static class TickerData
  22. {
  23. friend class Ticker;
  24. public:
  25. TickerData() :
  26. nentities(0),
  27. frame(0), recording(0), deltatime(0), bias(0), fps(0),
  28. #if LOL_BUILD_DEBUG
  29. keepalive(0),
  30. #endif
  31. quit(0), quitframe(0), quitdelay(20), panic(0)
  32. {
  33. }
  34. ~TickerData()
  35. {
  36. ASSERT(nentities == 0,
  37. "still %d entities in ticker\n", nentities);
  38. ASSERT(m_autolist.count() == 0,
  39. "still %d autoreleased entities\n", m_autolist.count());
  40. msg::debug("%d frames required to quit\n", frame - quitframe);
  41. #if LOL_FEATURE_THREADS
  42. gametick.push(0);
  43. disktick.push(0);
  44. delete gamethread;
  45. delete diskthread;
  46. #endif
  47. }
  48. private:
  49. /* Entity management */
  50. array<Entity *> m_todolist, m_todolist_delayed, m_autolist;
  51. array<Entity *> m_list[Entity::ALLGROUP_END];
  52. array<int> m_scenes[Entity::ALLGROUP_END];
  53. int nentities;
  54. /* Fixed framerate management */
  55. int frame, recording;
  56. timer m_timer;
  57. float deltatime, bias, fps;
  58. #if LOL_BUILD_DEBUG
  59. float keepalive;
  60. #endif
  61. /* The three main functions (for now) */
  62. static void GameThreadTick();
  63. static void DrawThreadTick();
  64. static void DiskThreadTick();
  65. #if LOL_FEATURE_THREADS
  66. /* The associated background threads */
  67. void GameThreadMain();
  68. void DrawThreadMain(); /* unused */
  69. void DiskThreadMain();
  70. thread *gamethread, *drawthread, *diskthread;
  71. queue<int> gametick, drawtick, disktick;
  72. #endif
  73. /* Shutdown management */
  74. int quit, quitframe, quitdelay, panic;
  75. }
  76. tickerdata;
  77. static TickerData * const data = &tickerdata;
  78. /*
  79. * Ticker public class
  80. */
  81. void Ticker::Register(Entity *entity)
  82. {
  83. /* If we are called from its constructor, the object's vtable is not
  84. * ready yet, so we do not know which group this entity belongs to. Wait
  85. * until the first tick. */
  86. data->m_todolist_delayed.push(entity);
  87. /* Objects are autoreleased by default. Put them in a list. */
  88. data->m_autolist.push(entity);
  89. entity->m_autorelease = 1;
  90. entity->m_ref = 1;
  91. data->nentities++;
  92. }
  93. void Ticker::Ref(Entity *entity)
  94. {
  95. ASSERT(entity, "dereferencing nullptr entity\n");
  96. ASSERT(!entity->m_destroy,
  97. "referencing entity scheduled for destruction %s\n",
  98. entity->GetName().c_str());
  99. if (entity->m_autorelease)
  100. {
  101. /* Get the entity out of the m_autorelease list. This is usually
  102. * very fast since the last entry in autolist is the last
  103. * registered entity. */
  104. for (int i = data->m_autolist.count(); i--; )
  105. {
  106. if (data->m_autolist[i] == entity)
  107. {
  108. data->m_autolist.remove_swap(i);
  109. break;
  110. }
  111. }
  112. entity->m_autorelease = 0;
  113. }
  114. else
  115. entity->m_ref++;
  116. }
  117. int Ticker::Unref(Entity *entity)
  118. {
  119. ASSERT(entity, "dereferencing null entity\n");
  120. ASSERT(entity->m_ref > 0, "dereferencing unreferenced entity %s\n",
  121. entity->GetName().c_str());
  122. ASSERT(!entity->m_autorelease, "dereferencing autoreleased entity %s\n",
  123. entity->GetName().c_str());
  124. return --entity->m_ref;
  125. }
  126. #if LOL_FEATURE_THREADS
  127. void TickerData::GameThreadMain()
  128. {
  129. #if LOL_BUILD_DEBUG
  130. msg::debug("ticker game thread initialised\n");
  131. #endif
  132. for (;;)
  133. {
  134. int tick = gametick.pop();
  135. if (!tick)
  136. break;
  137. GameThreadTick();
  138. drawtick.push(1);
  139. }
  140. drawtick.push(0);
  141. #if LOL_BUILD_DEBUG
  142. msg::debug("ticker game thread terminated\n");
  143. #endif
  144. }
  145. #endif /* LOL_FEATURE_THREADS */
  146. #if LOL_FEATURE_THREADS
  147. void TickerData::DrawThreadMain() /* unused */
  148. {
  149. #if LOL_BUILD_DEBUG
  150. msg::debug("ticker draw thread initialised\n");
  151. #endif
  152. for (;;)
  153. {
  154. int tick = drawtick.pop();
  155. if (!tick)
  156. break;
  157. DrawThreadTick();
  158. gametick.push(1);
  159. }
  160. #if LOL_BUILD_DEBUG
  161. msg::debug("ticker draw thread terminated\n");
  162. #endif
  163. }
  164. #endif /* LOL_FEATURE_THREADS */
  165. #if LOL_FEATURE_THREADS
  166. void TickerData::DiskThreadMain()
  167. {
  168. /* FIXME: temporary hack to avoid crashes on the PS3 */
  169. disktick.pop();
  170. }
  171. #endif /* LOL_FEATURE_THREADS */
  172. //-----------------------------------------------------------------------------
  173. void TickerData::GameThreadTick()
  174. {
  175. Profiler::Stop(Profiler::STAT_TICK_FRAME);
  176. Profiler::Start(Profiler::STAT_TICK_FRAME);
  177. Profiler::Start(Profiler::STAT_TICK_GAME);
  178. #if 0
  179. msg::debug("-------------------------------------\n");
  180. for (int g = 0; g < Entity::ALLGROUP_END; ++g)
  181. {
  182. msg::debug("%s Group %d\n",
  183. (g < Entity::GAMEGROUP_END) ? "Game" : "Draw", g);
  184. for (int i = 0; i < data->m_list[g].count(); ++i)
  185. {
  186. Entity *e = data->m_list[g][i];
  187. msg::debug(" \\-- [%p] %s (m_ref %d, destroy %d)\n",
  188. e, e->GetName().c_str(), e->m_ref, e->m_destroy);
  189. }
  190. }
  191. #endif
  192. data->frame++;
  193. /* Ensure some randomness */
  194. rand<int>();
  195. /* If recording with fixed framerate, set deltatime to a fixed value */
  196. if (data->recording && data->fps)
  197. {
  198. data->deltatime = 1.f / data->fps;
  199. }
  200. else
  201. {
  202. data->deltatime = data->m_timer.get();
  203. data->bias += data->deltatime;
  204. }
  205. /* Do not go below 15 fps */
  206. if (data->deltatime > 1.f / 15.f)
  207. {
  208. data->deltatime = 1.f / 15.f;
  209. data->bias = 0.f;
  210. }
  211. #if LOL_BUILD_DEBUG
  212. data->keepalive += data->deltatime;
  213. if (data->keepalive > 10.f)
  214. {
  215. msg::debug("ticker keepalive: tick!\n");
  216. data->keepalive = 0.f;
  217. }
  218. #endif
  219. /* If shutdown is stuck, kick the first entity we meet and see
  220. * whether it makes things better. Note that it is always a bug to
  221. * have referenced entities after 20 frames, but at least this
  222. * safeguard makes it possible to exit the program cleanly. */
  223. if (data->quit && !((data->frame - data->quitframe) % data->quitdelay))
  224. {
  225. int n = 0;
  226. data->panic = 2 * (data->panic + 1);
  227. for (int g = 0; g < Entity::ALLGROUP_END && n < data->panic; ++g)
  228. for (int i = 0; i < data->m_list[g].count() && n < data->panic; ++i)
  229. {
  230. Entity * e = data->m_list[g][i];
  231. if (e->m_ref)
  232. {
  233. #if !LOL_BUILD_RELEASE
  234. msg::error("poking %s\n", e->GetName().c_str());
  235. #endif
  236. e->m_ref--;
  237. n++;
  238. }
  239. }
  240. #if !LOL_BUILD_RELEASE
  241. if (n)
  242. msg::error("%d entities stuck after %d frames, poked %d\n",
  243. data->nentities, data->quitdelay, n);
  244. #endif
  245. data->quitdelay = data->quitdelay > 1 ? data->quitdelay / 2 : 1;
  246. }
  247. /* Garbage collect objects that can be destroyed. We can do this
  248. * before inserting awaiting objects, because only objects already
  249. * in the tick lists can be marked for destruction. */
  250. array<Entity*> destroy_list;
  251. bool do_reserve = true;
  252. for (int g = 0; g < Entity::ALLGROUP_END; ++g)
  253. {
  254. for (int i = data->m_list[g].count(); i--;)
  255. {
  256. Entity *e = data->m_list[g][i];
  257. if (e->m_destroy && g < Entity::GAMEGROUP_END)
  258. {
  259. /* Game tick list:
  260. * If entity is to be destroyed, remove it */
  261. data->m_list[g].remove_swap(i);
  262. if (do_reserve)
  263. {
  264. do_reserve = false;
  265. destroy_list.reserve(data->nentities); //Should it be less ?
  266. }
  267. destroy_list.push_unique(e);
  268. }
  269. else if (e->m_destroy)
  270. {
  271. /* Draw tick list:
  272. * If entity is to be destroyed,
  273. * remove it and store it. */
  274. data->m_list[g].remove_swap(i);
  275. int removal_count = 0;
  276. for (int j = 0; j < Scene::GetCount(); j++)
  277. {
  278. //If entity is concerned by this scene, add it in the list
  279. if (Scene::GetScene(j).IsRelevant(e))
  280. removal_count++;
  281. //Update scene index
  282. data->m_scenes[e->m_drawgroup][j] -= removal_count;
  283. }
  284. if (do_reserve)
  285. {
  286. do_reserve = false;
  287. destroy_list.reserve(data->nentities); //Should it be less ?
  288. }
  289. destroy_list.push_unique(e);
  290. }
  291. else
  292. {
  293. if (e->m_ref <= 0 && g >= Entity::DRAWGROUP_BEGIN)
  294. e->m_destroy = 1;
  295. }
  296. }
  297. }
  298. if (!!destroy_list.count())
  299. {
  300. data->nentities -= destroy_list.count();
  301. for (Entity* e : destroy_list)
  302. delete e;
  303. }
  304. /* Insert waiting objects into the appropriate lists */
  305. while (data->m_todolist.count())
  306. {
  307. Entity *e = data->m_todolist.last();
  308. //If the entity has no mask, default it
  309. if (e->m_scene_mask == 0)
  310. {
  311. Scene::GetScene().Link(e);
  312. }
  313. data->m_todolist.remove(-1);
  314. data->m_list[e->m_gamegroup].push(e);
  315. if (e->m_drawgroup != Entity::DRAWGROUP_NONE)
  316. {
  317. if (data->m_scenes[e->m_drawgroup].count() < Scene::GetCount())
  318. data->m_scenes[e->m_drawgroup].resize(Scene::GetCount());
  319. int added_count = 0;
  320. for (int i = 0; i < Scene::GetCount(); i++)
  321. {
  322. //If entity is concerned by this scene, add it in the list
  323. if (Scene::GetScene(i).IsRelevant(e))
  324. {
  325. data->m_list[e->m_drawgroup].insert(e, data->m_scenes[e->m_drawgroup][i]);
  326. added_count++;
  327. }
  328. //Update scene index
  329. data->m_scenes[e->m_drawgroup][i] += added_count;
  330. }
  331. }
  332. // Initialize the entity
  333. e->InitGame();
  334. }
  335. data->m_todolist = data->m_todolist_delayed;
  336. data->m_todolist_delayed.clear();
  337. /* Tick objects for the game loop */
  338. for (int g = Entity::GAMEGROUP_BEGIN; g < Entity::GAMEGROUP_END && !data->quit /* Stop as soon as required */; ++g)
  339. {
  340. for (int i = 0; i < data->m_list[g].count() && !data->quit /* Stop as soon as required */; ++i)
  341. {
  342. Entity *e = data->m_list[g][i];
  343. if (!e->m_destroy)
  344. {
  345. #if !LOL_BUILD_RELEASE
  346. if (e->m_tickstate != Entity::STATE_IDLE)
  347. msg::error("entity %s [%p] not idle for game tick\n",
  348. e->GetName().c_str(), e);
  349. e->m_tickstate = Entity::STATE_PRETICK_GAME;
  350. #endif
  351. e->tick_game(data->deltatime);
  352. #if !LOL_BUILD_RELEASE
  353. if (e->m_tickstate != Entity::STATE_POSTTICK_GAME)
  354. msg::error("entity %s [%p] missed super game tick\n",
  355. e->GetName().c_str(), e);
  356. e->m_tickstate = Entity::STATE_IDLE;
  357. #endif
  358. }
  359. }
  360. }
  361. Profiler::Stop(Profiler::STAT_TICK_GAME);
  362. }
  363. //-----------------------------------------------------------------------------
  364. void TickerData::DrawThreadTick()
  365. {
  366. Profiler::Start(Profiler::STAT_TICK_DRAW);
  367. /* Render each scene one after the other */
  368. for (int idx = 0; idx < Scene::GetCount() && !data->quit /* Stop as soon as required */; ++idx)
  369. {
  370. Scene& scene = Scene::GetScene(idx);
  371. /* Enable display */
  372. scene.EnableDisplay();
  373. Renderer::Get(idx)->Clear(ClearMask::All);
  374. scene.pre_render(data->deltatime);
  375. /* Tick objects for the draw loop */
  376. for (int g = Entity::DRAWGROUP_BEGIN; g < Entity::DRAWGROUP_END && !data->quit /* Stop as soon as required */; ++g)
  377. {
  378. switch (g)
  379. {
  380. case Entity::DRAWGROUP_BEGIN:
  381. scene.Reset();
  382. break;
  383. default:
  384. break;
  385. }
  386. for (int i = 0; i < data->m_list[g].count() && !data->quit /* Stop as soon as required */; ++i)
  387. {
  388. Entity *e = data->m_list[g][i];
  389. if (!e->m_destroy)
  390. {
  391. #if !LOL_BUILD_RELEASE
  392. if (e->m_tickstate != Entity::STATE_IDLE)
  393. msg::error("entity %s [%p] not idle for draw tick\n",
  394. e->GetName().c_str(), e);
  395. e->m_tickstate = Entity::STATE_PRETICK_DRAW;
  396. #endif
  397. e->tick_draw(data->deltatime, scene);
  398. #if !LOL_BUILD_RELEASE
  399. if (e->m_tickstate != Entity::STATE_POSTTICK_DRAW)
  400. msg::error("entity %s [%p] missed super draw tick\n",
  401. e->GetName().c_str(), e);
  402. e->m_tickstate = Entity::STATE_IDLE;
  403. #endif
  404. }
  405. }
  406. }
  407. /* Do the render step */
  408. scene.render(data->deltatime);
  409. scene.post_render(data->deltatime);
  410. /* Disable display */
  411. scene.DisableDisplay();
  412. }
  413. Profiler::Stop(Profiler::STAT_TICK_DRAW);
  414. }
  415. void TickerData::DiskThreadTick()
  416. {
  417. ;
  418. }
  419. void Ticker::SetState(Entity * /* entity */, uint32_t /* state */)
  420. {
  421. }
  422. void Ticker::SetStateWhenMatch(Entity * /* entity */, uint32_t /* state */,
  423. Entity * /* other_entity */, uint32_t /* other_state */)
  424. {
  425. }
  426. //-----------------------------------------------------------------------------
  427. void Ticker::Setup(float fps)
  428. {
  429. data->fps = fps;
  430. #if LOL_FEATURE_THREADS
  431. data->gamethread = new thread(std::bind(&TickerData::GameThreadMain, data));
  432. data->drawtick.push(1);
  433. data->diskthread = new thread(std::bind(&TickerData::DiskThreadMain, data));
  434. #endif
  435. }
  436. void Ticker::tick_draw()
  437. {
  438. #if LOL_FEATURE_THREADS
  439. data->drawtick.pop();
  440. #else
  441. TickerData::GameThreadTick();
  442. #endif
  443. TickerData::DrawThreadTick();
  444. Profiler::Start(Profiler::STAT_TICK_BLIT);
  445. /* Signal game thread that it can carry on */
  446. #if LOL_FEATURE_THREADS
  447. data->gametick.push(1);
  448. #else
  449. TickerData::DiskThreadTick();
  450. #endif
  451. /* Clamp FPS */
  452. Profiler::Stop(Profiler::STAT_TICK_BLIT);
  453. #if !EMSCRIPTEN
  454. /* If framerate is fixed, force wait time to 1/FPS. Otherwise, set wait
  455. * time to 0. */
  456. float frametime = data->fps ? 1.f / data->fps : 0.f;
  457. if (frametime > data->bias + .2f)
  458. frametime = data->bias + .2f; /* Don't go below 5 fps */
  459. if (frametime > data->bias)
  460. data->m_timer.wait(frametime - data->bias);
  461. /* If recording, do not try to compensate for lag. */
  462. if (!data->recording)
  463. data->bias -= frametime;
  464. #endif
  465. }
  466. void Ticker::StartRecording()
  467. {
  468. data->recording++;
  469. }
  470. void Ticker::StopRecording()
  471. {
  472. data->recording--;
  473. }
  474. int Ticker::GetFrameNum()
  475. {
  476. return data->frame;
  477. }
  478. void Ticker::Shutdown()
  479. {
  480. /* We're bailing out. Release all m_autorelease objects. */
  481. while (data->m_autolist.count())
  482. {
  483. data->m_autolist.last()->m_ref--;
  484. data->m_autolist.remove(-1);
  485. }
  486. data->quit = 1;
  487. data->quitframe = data->frame;
  488. }
  489. int Ticker::Finished()
  490. {
  491. return !data->nentities;
  492. }
  493. } /* namespace lol */