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.
 
 
 

515 rivejä
12 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. 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 %i entities in ticker\n", nentities);
  38. ASSERT(m_autolist.Count() == 0,
  39. "still %i autoreleased entities\n", m_autolist.Count());
  40. Log::Debug("%i 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. int nentities;
  53. /* Fixed framerate management */
  54. int frame, recording;
  55. Timer timer;
  56. float deltatime, bias, fps;
  57. #if LOL_BUILD_DEBUG
  58. float keepalive;
  59. #endif
  60. /* The three main functions (for now) */
  61. static void GameThreadTick();
  62. static void DrawThreadTick();
  63. static void DiskThreadTick();
  64. #if LOL_FEATURE_THREADS
  65. /* The associated background threads */
  66. static void *GameThreadMain(void *p);
  67. static void *DrawThreadMain(void *p); /* unused */
  68. static void *DiskThreadMain(void *p);
  69. Thread *gamethread, *drawthread, *diskthread;
  70. Queue<int> gametick, drawtick, disktick;
  71. #endif
  72. /* Shutdown management */
  73. int quit, quitframe, quitdelay, panic;
  74. }
  75. tickerdata;
  76. static TickerData * const data = &tickerdata;
  77. /*
  78. * Ticker public class
  79. */
  80. void Ticker::Register(Entity *entity)
  81. {
  82. /* If we are called from its constructor, the object's vtable is not
  83. * ready yet, so we do not know which group this entity belongs to. Wait
  84. * until the first tick. */
  85. data->m_todolist.Push(entity);
  86. /* Objects are autoreleased by default. Put them in a list. */
  87. data->m_autolist.Push(entity);
  88. entity->m_autorelease = 1;
  89. entity->m_ref = 1;
  90. data->nentities++;
  91. }
  92. void Ticker::Ref(Entity *entity)
  93. {
  94. ASSERT(entity, "dereferencing nullptr entity\n");
  95. ASSERT(!entity->m_destroy,
  96. "referencing entity scheduled for destruction %s\n",
  97. entity->GetName());
  98. if (entity->m_autorelease)
  99. {
  100. /* Get the entity out of the m_autorelease list. This is usually
  101. * very fast since the last entry in autolist is the last
  102. * registered entity. */
  103. for (int i = data->m_autolist.Count(); --i; )
  104. {
  105. if (data->m_autolist[i] == entity)
  106. {
  107. data->m_autolist.RemoveSwap(i);
  108. break;
  109. }
  110. }
  111. entity->m_autorelease = 0;
  112. }
  113. else
  114. entity->m_ref++;
  115. }
  116. int Ticker::Unref(Entity *entity)
  117. {
  118. ASSERT(entity, "dereferencing null entity\n");
  119. ASSERT(entity->m_ref > 0, "dereferencing unreferenced entity %s\n",
  120. entity->GetName())
  121. ASSERT(!entity->m_autorelease, "dereferencing autoreleased entity %s\n",
  122. entity->GetName())
  123. return --entity->m_ref;
  124. }
  125. #if LOL_FEATURE_THREADS
  126. void *TickerData::GameThreadMain(void * /* p */)
  127. {
  128. #if LOL_BUILD_DEBUG
  129. Log::Info("ticker game thread initialised\n");
  130. #endif
  131. for (;;)
  132. {
  133. int tick = data->gametick.Pop();
  134. if (!tick)
  135. break;
  136. GameThreadTick();
  137. data->drawtick.Push(1);
  138. }
  139. data->drawtick.Push(0);
  140. #if LOL_BUILD_DEBUG
  141. Log::Info("ticker game thread terminated\n");
  142. #endif
  143. return nullptr;
  144. }
  145. #endif /* LOL_FEATURE_THREADS */
  146. #if LOL_FEATURE_THREADS
  147. void *TickerData::DrawThreadMain(void * /* p */)
  148. {
  149. #if LOL_BUILD_DEBUG
  150. Log::Info("ticker draw thread initialised\n");
  151. #endif
  152. for (;;)
  153. {
  154. int tick = data->drawtick.Pop();
  155. if (!tick)
  156. break;
  157. DrawThreadTick();
  158. data->gametick.Push(1);
  159. }
  160. #if LOL_BUILD_DEBUG
  161. Log::Info("ticker draw thread terminated\n");
  162. #endif
  163. return nullptr;
  164. }
  165. #endif /* LOL_FEATURE_THREADS */
  166. #if LOL_FEATURE_THREADS
  167. void *TickerData::DiskThreadMain(void * /* p */)
  168. {
  169. /* FIXME: temporary hack to avoid crashes on the PS3 */
  170. data->disktick.Pop();
  171. return nullptr;
  172. }
  173. #endif /* LOL_FEATURE_THREADS */
  174. void TickerData::GameThreadTick()
  175. {
  176. Profiler::Stop(Profiler::STAT_TICK_FRAME);
  177. Profiler::Start(Profiler::STAT_TICK_FRAME);
  178. Profiler::Start(Profiler::STAT_TICK_GAME);
  179. #if 0
  180. Log::Debug("-------------------------------------\n");
  181. for (int g = 0; g < Entity::ALLGROUP_END; ++g)
  182. {
  183. Log::Debug("%s Group %i\n",
  184. (g < Entity::GAMEGROUP_END) ? "Game" : "Draw", g);
  185. for (int i = 0; i < data->m_list[g].Count(); ++i)
  186. {
  187. Entity *e = data->m_list[g][i];
  188. Log::Debug(" \\-- %s (m_ref %i, destroy %i)\n", 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. Log::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. Log::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. Log::Error("%i entities stuck after %i frames, poked %i\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. * inthe tick lists can be marked for destruction. */
  250. for (int g = 0; g < Entity::ALLGROUP_END; ++g)
  251. {
  252. for (int i = 0; i < data->m_list[g].Count(); ++i)
  253. {
  254. Entity *e = data->m_list[g][i];
  255. if (e->m_destroy && g < Entity::GAMEGROUP_END)
  256. {
  257. /* If entity is to be destroyed, remove it from the
  258. * game tick list. */
  259. data->m_list[g].RemoveSwap(i);
  260. }
  261. else if (e->m_destroy)
  262. {
  263. /* If entity is to be destroyed, remove it from the
  264. * draw tick list and destroy it. */
  265. data->m_list[g].RemoveSwap(i);
  266. delete e;
  267. data->nentities--;
  268. }
  269. else
  270. {
  271. if (e->m_ref <= 0 && g >= Entity::DRAWGROUP_BEGIN)
  272. e->m_destroy = 1;
  273. }
  274. }
  275. }
  276. /* Insert waiting objects into the appropriate lists */
  277. while (data->m_todolist.Count())
  278. {
  279. Entity *e = data->m_todolist.Last();
  280. data->m_todolist.Remove(-1);
  281. data->m_list[e->m_gamegroup].Push(e);
  282. data->m_list[e->m_drawgroup].Push(e);
  283. }
  284. /* Tick objects for the game loop */
  285. for (int g = Entity::GAMEGROUP_BEGIN; g < Entity::GAMEGROUP_END; ++g)
  286. for (int i = 0; i < data->m_list[g].Count(); ++i)
  287. {
  288. Entity *e = data->m_list[g][i];
  289. if (!e->m_destroy)
  290. {
  291. #if !LOL_BUILD_RELEASE
  292. if (e->m_tickstate != Entity::STATE_IDLE)
  293. Log::Error("entity %s [%p] not idle for game tick\n",
  294. e->GetName(), e);
  295. e->m_tickstate = Entity::STATE_PRETICK_GAME;
  296. #endif
  297. e->TickGame(data->deltatime);
  298. #if !LOL_BUILD_RELEASE
  299. if (e->m_tickstate != Entity::STATE_POSTTICK_GAME)
  300. Log::Error("entity %s [%p] missed super game tick\n",
  301. e->GetName(), e);
  302. e->m_tickstate = Entity::STATE_IDLE;
  303. #endif
  304. }
  305. }
  306. Profiler::Stop(Profiler::STAT_TICK_GAME);
  307. }
  308. void TickerData::DrawThreadTick()
  309. {
  310. Profiler::Start(Profiler::STAT_TICK_DRAW);
  311. /* Tick objects for the draw loop */
  312. for (int g = Entity::DRAWGROUP_BEGIN; g < Entity::DRAWGROUP_END; ++g)
  313. {
  314. switch (g)
  315. {
  316. case Entity::DRAWGROUP_BEGIN:
  317. g_scene->Reset();
  318. g_renderer->Clear(ClearMask::All);
  319. break;
  320. default:
  321. break;
  322. }
  323. for (int i = 0; i < data->m_list[g].Count(); ++i)
  324. {
  325. Entity *e = data->m_list[g][i];
  326. if (!e->m_destroy)
  327. {
  328. #if !LOL_BUILD_RELEASE
  329. if (e->m_tickstate != Entity::STATE_IDLE)
  330. Log::Error("entity %s [%p] not idle for draw tick\n",
  331. e->GetName(), e);
  332. e->m_tickstate = Entity::STATE_PRETICK_DRAW;
  333. #endif
  334. e->TickDraw(data->deltatime);
  335. #if !LOL_BUILD_RELEASE
  336. if (e->m_tickstate != Entity::STATE_POSTTICK_DRAW)
  337. Log::Error("entity %s [%p] missed super draw tick\n",
  338. e->GetName(), e);
  339. e->m_tickstate = Entity::STATE_IDLE;
  340. #endif
  341. }
  342. }
  343. /* Do this render step */
  344. g_scene->Render();
  345. }
  346. Profiler::Stop(Profiler::STAT_TICK_DRAW);
  347. }
  348. void TickerData::DiskThreadTick()
  349. {
  350. ;
  351. }
  352. void Ticker::SetState(Entity * /* entity */, uint32_t /* state */)
  353. {
  354. }
  355. void Ticker::SetStateWhenMatch(Entity * /* entity */, uint32_t /* state */,
  356. Entity * /* other_entity */, uint32_t /* other_state */)
  357. {
  358. }
  359. void Ticker::Setup(float fps)
  360. {
  361. data->fps = fps;
  362. #if LOL_FEATURE_THREADS
  363. data->gamethread = new Thread(TickerData::GameThreadMain, nullptr);
  364. data->gametick.Push(1);
  365. data->diskthread = new Thread(TickerData::DiskThreadMain, nullptr);
  366. #endif
  367. }
  368. void Ticker::TickDraw()
  369. {
  370. #if LOL_FEATURE_THREADS
  371. data->drawtick.Pop();
  372. #else
  373. TickerData::GameThreadTick();
  374. #endif
  375. TickerData::DrawThreadTick();
  376. Profiler::Start(Profiler::STAT_TICK_BLIT);
  377. /* Signal game thread that it can carry on */
  378. #if LOL_FEATURE_THREADS
  379. data->gametick.Push(1);
  380. #else
  381. TickerData::DiskThreadTick();
  382. #endif
  383. /* Clamp FPS */
  384. Profiler::Stop(Profiler::STAT_TICK_BLIT);
  385. #if !EMSCRIPTEN
  386. /* If framerate is fixed, force wait time to 1/FPS. Otherwise, set wait
  387. * time to 0. */
  388. float frametime = data->fps ? 1.f / data->fps : 0.f;
  389. if (frametime > data->bias + .2f)
  390. frametime = data->bias + .2f; /* Don't go below 5 fps */
  391. if (frametime > data->bias)
  392. data->timer.Wait(frametime - data->bias);
  393. /* If recording, do not try to compensate for lag. */
  394. if (!data->recording)
  395. data->bias -= frametime;
  396. #endif
  397. }
  398. void Ticker::StartRecording()
  399. {
  400. data->recording++;
  401. }
  402. void Ticker::StopRecording()
  403. {
  404. data->recording--;
  405. }
  406. int Ticker::GetFrameNum()
  407. {
  408. return data->frame;
  409. }
  410. void Ticker::Shutdown()
  411. {
  412. /* We're bailing out. Release all m_autorelease objects. */
  413. while (data->m_autolist.Count())
  414. {
  415. data->m_autolist.Last()->m_ref--;
  416. data->m_autolist.Remove(-1);
  417. }
  418. data->quit = 1;
  419. data->quitframe = data->frame;
  420. }
  421. int Ticker::Finished()
  422. {
  423. return !data->nentities;
  424. }
  425. } /* namespace lol */