source: trunk/src/ticker.cpp @ 2817

Last change on this file since 2817 was 2817, checked in by sam, 8 years ago

ticker: replace linked lists wih dynamic arrays for entity groups.

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