source: trunk/src/ticker.cpp @ 2506

Last change on this file since 2506 was 2506, checked in by sam, 7 years ago

base: start removing occurrences of NULL on our long journey to nullptr.

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