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.

пре 12 година
пре 14 година
пре 14 година
пре 14 година
пре 14 година
пре 14 година
пре 14 година
пре 14 година
пре 14 година
пре 14 година
пре 14 година
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520
  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(" \\-- [%p] %s (m_ref %i, destroy %i)\n",
  189. e, e->GetName(), e->m_ref, e->m_destroy);
  190. }
  191. }
  192. #endif
  193. data->frame++;
  194. /* Ensure some randomness */
  195. rand<int>();
  196. /* If recording with fixed framerate, set deltatime to a fixed value */
  197. if (data->recording && data->fps)
  198. {
  199. data->deltatime = 1.f / data->fps;
  200. }
  201. else
  202. {
  203. data->deltatime = data->timer.Get();
  204. data->bias += data->deltatime;
  205. }
  206. /* Do not go below 15 fps */
  207. if (data->deltatime > 1.f / 15.f)
  208. {
  209. data->deltatime = 1.f / 15.f;
  210. data->bias = 0.f;
  211. }
  212. #if LOL_BUILD_DEBUG
  213. data->keepalive += data->deltatime;
  214. if (data->keepalive > 10.f)
  215. {
  216. Log::Info("ticker keepalive: tick!\n");
  217. data->keepalive = 0.f;
  218. }
  219. #endif
  220. /* If shutdown is stuck, kick the first entity we meet and see
  221. * whether it makes things better. Note that it is always a bug to
  222. * have referenced entities after 20 frames, but at least this
  223. * safeguard makes it possible to exit the program cleanly. */
  224. if (data->quit && !((data->frame - data->quitframe) % data->quitdelay))
  225. {
  226. int n = 0;
  227. data->panic = 2 * (data->panic + 1);
  228. for (int g = 0; g < Entity::ALLGROUP_END && n < data->panic; ++g)
  229. for (int i = 0; i < data->m_list[g].Count() && n < data->panic; ++i)
  230. {
  231. Entity * e = data->m_list[g][i];
  232. if (e->m_ref)
  233. {
  234. #if !LOL_BUILD_RELEASE
  235. Log::Error("poking %s\n", e->GetName());
  236. #endif
  237. e->m_ref--;
  238. n++;
  239. }
  240. }
  241. #if !LOL_BUILD_RELEASE
  242. if (n)
  243. Log::Error("%i entities stuck after %i frames, poked %i\n",
  244. data->nentities, data->quitdelay, n);
  245. #endif
  246. data->quitdelay = data->quitdelay > 1 ? data->quitdelay / 2 : 1;
  247. }
  248. /* Garbage collect objects that can be destroyed. We can do this
  249. * before inserting awaiting objects, because only objects already
  250. * in the tick lists can be marked for destruction. */
  251. for (int g = 0; g < Entity::ALLGROUP_END; ++g)
  252. {
  253. for (int i = data->m_list[g].Count(); i--; )
  254. {
  255. Entity *e = data->m_list[g][i];
  256. if (e->m_destroy && g < Entity::GAMEGROUP_END)
  257. {
  258. /* If entity is to be destroyed, remove it from the
  259. * game tick list. */
  260. data->m_list[g].RemoveSwap(i);
  261. }
  262. else if (e->m_destroy)
  263. {
  264. /* If entity is to be destroyed, remove it from the
  265. * draw tick list and destroy it. */
  266. data->m_list[g].RemoveSwap(i);
  267. delete e;
  268. data->nentities--;
  269. }
  270. else
  271. {
  272. if (e->m_ref <= 0 && g >= Entity::DRAWGROUP_BEGIN)
  273. e->m_destroy = 1;
  274. }
  275. }
  276. }
  277. /* Insert waiting objects into the appropriate lists */
  278. while (data->m_todolist.Count())
  279. {
  280. Entity *e = data->m_todolist.Last();
  281. data->m_todolist.Remove(-1);
  282. data->m_list[e->m_gamegroup].Push(e);
  283. data->m_list[e->m_drawgroup].Push(e);
  284. // Initialize the entity
  285. e->InitGame();
  286. }
  287. /* Tick objects for the game loop */
  288. for (int g = Entity::GAMEGROUP_BEGIN; g < Entity::GAMEGROUP_END; ++g)
  289. for (int i = 0; i < data->m_list[g].Count(); ++i)
  290. {
  291. Entity *e = data->m_list[g][i];
  292. if (!e->m_destroy)
  293. {
  294. #if !LOL_BUILD_RELEASE
  295. if (e->m_tickstate != Entity::STATE_IDLE)
  296. Log::Error("entity %s [%p] not idle for game tick\n",
  297. e->GetName(), e);
  298. e->m_tickstate = Entity::STATE_PRETICK_GAME;
  299. #endif
  300. e->TickGame(data->deltatime);
  301. #if !LOL_BUILD_RELEASE
  302. if (e->m_tickstate != Entity::STATE_POSTTICK_GAME)
  303. Log::Error("entity %s [%p] missed super game tick\n",
  304. e->GetName(), e);
  305. e->m_tickstate = Entity::STATE_IDLE;
  306. #endif
  307. }
  308. }
  309. Profiler::Stop(Profiler::STAT_TICK_GAME);
  310. }
  311. void TickerData::DrawThreadTick()
  312. {
  313. Profiler::Start(Profiler::STAT_TICK_DRAW);
  314. /* Tick objects for the draw loop */
  315. for (int g = Entity::DRAWGROUP_BEGIN; g < Entity::DRAWGROUP_END; ++g)
  316. {
  317. switch (g)
  318. {
  319. case Entity::DRAWGROUP_BEGIN:
  320. g_scene->Reset();
  321. g_renderer->Clear(ClearMask::All);
  322. break;
  323. default:
  324. break;
  325. }
  326. for (int i = 0; i < data->m_list[g].Count(); ++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);
  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 */