source: trunk/src/ticker.cpp @ 2394

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

base: start being more aggressive in the error reportings; most error
messages in the Ticker class are now full asserts.

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