source: trunk/src/ticker.cpp @ 2370

Last change on this file since 2370 was 2216, checked in by touky, 7 years ago

New year copyright update.

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