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.
 
 
 

591 rivejä
16 KiB

  1. //
  2. // Lol Engine
  3. //
  4. // Copyright © 2010—2015 Sam Hocevar <sam@hocevar.net>
  5. //
  6. // This library 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_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 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.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());
  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());
  122. ASSERT(!entity->m_autorelease, "dereferencing autoreleased entity %s\n",
  123. entity->GetName());
  124. return --entity->m_ref;
  125. }
  126. #if LOL_FEATURE_THREADS
  127. void TickerData::GameThreadMain()
  128. {
  129. #if LOL_BUILD_DEBUG
  130. msg::info("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::info("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::info("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::info("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(), 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->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::info("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());
  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. /* Tick objects for the game loop */
  336. for (int g = Entity::GAMEGROUP_BEGIN; g < Entity::GAMEGROUP_END && !data->quit /* Stop as soon as required */; ++g)
  337. {
  338. for (int i = 0; i < data->m_list[g].count() && !data->quit /* Stop as soon as required */; ++i)
  339. {
  340. Entity *e = data->m_list[g][i];
  341. if (!e->m_destroy)
  342. {
  343. #if !LOL_BUILD_RELEASE
  344. if (e->m_tickstate != Entity::STATE_IDLE)
  345. msg::error("entity %s [%p] not idle for game tick\n",
  346. e->GetName(), e);
  347. e->m_tickstate = Entity::STATE_PRETICK_GAME;
  348. #endif
  349. e->TickGame(data->deltatime);
  350. #if !LOL_BUILD_RELEASE
  351. if (e->m_tickstate != Entity::STATE_POSTTICK_GAME)
  352. msg::error("entity %s [%p] missed super game tick\n",
  353. e->GetName(), e);
  354. e->m_tickstate = Entity::STATE_IDLE;
  355. #endif
  356. }
  357. }
  358. }
  359. Profiler::Stop(Profiler::STAT_TICK_GAME);
  360. }
  361. //-----------------------------------------------------------------------------
  362. void TickerData::DrawThreadTick()
  363. {
  364. Profiler::Start(Profiler::STAT_TICK_DRAW);
  365. /* Tick objects for the draw loop */
  366. for (int g = Entity::DRAWGROUP_BEGIN; g < Entity::DRAWGROUP_END && !data->quit /* Stop as soon as required */; ++g)
  367. {
  368. switch (g)
  369. {
  370. case Entity::DRAWGROUP_BEGIN:
  371. for (int i = 0; i < Scene::GetCount(); i++)
  372. Scene::GetScene(i).Reset();
  373. break;
  374. default:
  375. break;
  376. }
  377. int scene_idx = 0;
  378. for (int i = 0; i < data->m_list[g].count() && !data->quit /* Stop as soon as required */; ++i)
  379. {
  380. //We're outside of the range of the current scene, on to the next
  381. if (i >= data->m_scenes[g][scene_idx])
  382. ++scene_idx;
  383. Entity *e = data->m_list[g][i];
  384. if (!e->m_destroy)
  385. {
  386. #if !LOL_BUILD_RELEASE
  387. if (e->m_tickstate != Entity::STATE_IDLE)
  388. msg::error("entity %s [%p] not idle for draw tick\n",
  389. e->GetName(), e);
  390. e->m_tickstate = Entity::STATE_PRETICK_DRAW;
  391. #endif
  392. e->TickDraw(data->deltatime, Scene::GetScene(scene_idx));
  393. #if !LOL_BUILD_RELEASE
  394. if (e->m_tickstate != Entity::STATE_POSTTICK_DRAW)
  395. msg::error("entity %s [%p] missed super draw tick\n",
  396. e->GetName(), e);
  397. e->m_tickstate = Entity::STATE_IDLE;
  398. #endif
  399. }
  400. }
  401. }
  402. //Do the scene render loop
  403. for (int idx = 0; idx < Scene::GetCount() && !data->quit /* Stop as soon as required */; ++idx)
  404. {
  405. Scene& scene = Scene::GetScene(idx);
  406. /* Enable display */
  407. scene.EnableDisplay();
  408. Renderer::Get(idx)->Clear(ClearMask::All);
  409. /* Do the render step */
  410. scene.RenderPrimitives();
  411. scene.RenderTiles();
  412. scene.RenderLines(data->deltatime);
  413. /* Disable display */
  414. scene.DisableDisplay();
  415. //break;
  416. }
  417. Profiler::Stop(Profiler::STAT_TICK_DRAW);
  418. }
  419. void TickerData::DiskThreadTick()
  420. {
  421. ;
  422. }
  423. void Ticker::SetState(Entity * /* entity */, uint32_t /* state */)
  424. {
  425. }
  426. void Ticker::SetStateWhenMatch(Entity * /* entity */, uint32_t /* state */,
  427. Entity * /* other_entity */, uint32_t /* other_state */)
  428. {
  429. }
  430. //-----------------------------------------------------------------------------
  431. void Ticker::Setup(float fps)
  432. {
  433. data->fps = fps;
  434. #if LOL_FEATURE_THREADS
  435. data->gamethread = new thread(std::bind(&TickerData::GameThreadMain, data));
  436. data->gametick.push(1);
  437. data->diskthread = new thread(std::bind(&TickerData::DiskThreadMain, data));
  438. #endif
  439. }
  440. void Ticker::TickDraw()
  441. {
  442. #if LOL_FEATURE_THREADS
  443. data->drawtick.pop();
  444. #else
  445. TickerData::GameThreadTick();
  446. #endif
  447. TickerData::DrawThreadTick();
  448. Profiler::Start(Profiler::STAT_TICK_BLIT);
  449. /* Signal game thread that it can carry on */
  450. #if LOL_FEATURE_THREADS
  451. data->gametick.push(1);
  452. #else
  453. TickerData::DiskThreadTick();
  454. #endif
  455. /* Clamp FPS */
  456. Profiler::Stop(Profiler::STAT_TICK_BLIT);
  457. #if !EMSCRIPTEN
  458. /* If framerate is fixed, force wait time to 1/FPS. Otherwise, set wait
  459. * time to 0. */
  460. float frametime = data->fps ? 1.f / data->fps : 0.f;
  461. if (frametime > data->bias + .2f)
  462. frametime = data->bias + .2f; /* Don't go below 5 fps */
  463. if (frametime > data->bias)
  464. data->timer.Wait(frametime - data->bias);
  465. /* If recording, do not try to compensate for lag. */
  466. if (!data->recording)
  467. data->bias -= frametime;
  468. #endif
  469. }
  470. void Ticker::StartRecording()
  471. {
  472. data->recording++;
  473. }
  474. void Ticker::StopRecording()
  475. {
  476. data->recording--;
  477. }
  478. int Ticker::GetFrameNum()
  479. {
  480. return data->frame;
  481. }
  482. void Ticker::Shutdown()
  483. {
  484. /* We're bailing out. Release all m_autorelease objects. */
  485. while (data->m_autolist.count())
  486. {
  487. data->m_autolist.last()->m_ref--;
  488. data->m_autolist.remove(-1);
  489. }
  490. data->quit = 1;
  491. data->quitframe = data->frame;
  492. }
  493. int Ticker::Finished()
  494. {
  495. return !data->nentities;
  496. }
  497. } /* namespace lol */