source: trunk/src/ticker.cpp @ 2444

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

build: fix compiler warnings and Linux and PS3 build issues.

  • 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        /* Ensure some randomness */
188        (void)std::rand();
189
190        /* If recording with fixed framerate, set deltatime to a fixed value */
191        if (data->recording && data->fps)
192        {
193            data->deltatime = 1.f / data->fps;
194        }
195        else
196        {
197            data->deltatime = data->timer.Get();
198            data->bias += data->deltatime;
199        }
200
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
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
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);
225
226            for (int i = 0; i < Entity::ALLGROUP_END && n < data->panic; i++)
227            for (Entity *e = data->list[i]; e && n < data->panic; e = e->m_gamenext)
228                if (e->m_ref)
229                {
230#if !LOL_RELEASE
231                    Log::Error("poking %s\n", e->GetName());
232#endif
233                    e->m_ref--;
234                    n++;
235                }
236
237#if !LOL_RELEASE
238            if (n)
239                Log::Error("%i entities stuck after %i frames, poked %i\n",
240                           data->nentities, data->quitdelay, n);
241#endif
242
243            data->quitdelay = data->quitdelay > 1 ? data->quitdelay / 2 : 1;
244        }
245
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++)
250            for (Entity *e = data->list[i], *prev = NULL; e; )
251            {
252                if (e->m_destroy && i < Entity::GAMEGROUP_END)
253                {
254                    /* If entity is to be destroyed, remove it from the
255                     * game tick list. */
256                    (prev ? prev->m_gamenext : data->list[i]) = e->m_gamenext;
257
258                    e = e->m_gamenext;
259                }
260                else if (e->m_destroy)
261                {
262                    /* If entity is to be destroyed, remove it from the
263                     * draw tick list and destroy it. */
264                    (prev ? prev->m_drawnext : data->list[i]) = e->m_drawnext;
265
266                    Entity *tmp = e;
267                    e = e->m_drawnext; /* Can only be in a draw group list */
268                    delete tmp;
269
270                    data->nentities--;
271                }
272                else
273                {
274                    if (e->m_ref <= 0 && i >= Entity::DRAWGROUP_BEGIN)
275                        e->m_destroy = 1;
276                    prev = e;
277                    e = (i < Entity::GAMEGROUP_END) ? e->m_gamenext : e->m_drawnext;
278                }
279            }
280
281        /* Insert waiting objects into the appropriate lists */
282        while (data->todolist)
283        {
284            Entity *e = data->todolist;
285            data->todolist = e->m_gamenext;
286
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;
291        }
292
293        /* Tick objects for the game loop */
294        for (int i = Entity::GAMEGROUP_BEGIN; i < Entity::GAMEGROUP_END; i++)
295            for (Entity *e = data->list[i]; e; e = e->m_gamenext)
296                if (!e->m_destroy)
297                {
298#if !LOL_RELEASE
299                    if (e->m_tickstate != Entity::STATE_IDLE)
300                        Log::Error("entity %s [%p] not idle for game tick\n",
301                                   e->GetName(), e);
302                    e->m_tickstate = Entity::STATE_PRETICK_GAME;
303#endif
304                    e->TickGame(data->deltatime);
305#if !LOL_RELEASE
306                    if (e->m_tickstate != Entity::STATE_POSTTICK_GAME)
307                        Log::Error("entity %s [%p] missed super game tick\n",
308                                   e->GetName(), e);
309                    e->m_tickstate = Entity::STATE_IDLE;
310#endif
311                }
312
313        Profiler::Stop(Profiler::STAT_TICK_GAME);
314
315        data->drawtick.Push(1);
316    }
317
318    data->drawtick.Push(0);
319
320#if LOL_DEBUG
321    Log::Info("ticker game thread terminated\n");
322#endif
323
324    return NULL;
325}
326
327void *TickerData::DrawThreadMain(void * /* p */)
328{
329    for (;;)
330    {
331        int tick = data->drawtick.Pop();
332        if (!tick)
333            break;
334
335        data->gametick.Push(1);
336    }
337
338    return NULL;
339}
340
341void *TickerData::DiskThreadMain(void * /* p */)
342{
343    /* FIXME: temporary hack to avoid crashes on the PS3 */
344    data->disktick.Pop();
345
346    return NULL;
347}
348
349void Ticker::SetState(Entity * /* entity */, uint32_t /* state */)
350{
351
352}
353
354void Ticker::SetStateWhenMatch(Entity * /* entity */, uint32_t /* state */,
355                               Entity * /* other_entity */, uint32_t /* other_state */)
356{
357
358}
359
360void Ticker::Setup(float fps)
361{
362    data->fps = fps;
363
364    data->gamethread = new Thread(TickerData::GameThreadMain, NULL);
365    data->gametick.Push(1);
366
367    data->diskthread = new Thread(TickerData::DiskThreadMain, NULL);
368}
369
370void Ticker::TickDraw()
371{
372    data->drawtick.Pop();
373
374    Profiler::Start(Profiler::STAT_TICK_DRAW);
375
376    /* Tick objects for the draw loop */
377    for (int i = Entity::DRAWGROUP_BEGIN; i < Entity::DRAWGROUP_END; i++)
378    {
379        switch (i)
380        {
381        case Entity::DRAWGROUP_BEGIN:
382            Scene::GetDefault()->Reset();
383            Video::Clear(ClearMask::All);
384            break;
385        case Entity::DRAWGROUP_HUD:
386            Video::SetDepth(false);
387            break;
388        default:
389            Video::SetDepth(true);
390            break;
391        }
392
393        for (Entity *e = data->list[i]; e; e = e->m_drawnext)
394            if (!e->m_destroy)
395            {
396#if !LOL_RELEASE
397                if (e->m_tickstate != Entity::STATE_IDLE)
398                    Log::Error("entity %s [%p] not idle for draw tick\n",
399                               e->GetName(), e);
400                e->m_tickstate = Entity::STATE_PRETICK_DRAW;
401#endif
402                e->TickDraw(data->deltatime);
403#if !LOL_RELEASE
404                if (e->m_tickstate != Entity::STATE_POSTTICK_DRAW)
405                    Log::Error("entity %s [%p] missed super draw tick\n",
406                               e->GetName(), e);
407                e->m_tickstate = Entity::STATE_IDLE;
408#endif
409            }
410
411        /* Do this render step */
412        Scene::GetDefault()->Render();
413    }
414
415    Profiler::Stop(Profiler::STAT_TICK_DRAW);
416    Profiler::Start(Profiler::STAT_TICK_BLIT);
417
418    /* Signal game thread that it can carry on */
419    data->gametick.Push(1);
420
421    /* Clamp FPS */
422    Profiler::Stop(Profiler::STAT_TICK_BLIT);
423
424    /* If framerate is fixed, force wait time to 1/FPS. Otherwise, set wait
425     * time to 0. */
426    float frametime = data->fps ? 1.f / data->fps : 0.f;
427
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);
432
433    /* If recording, do not try to compensate for lag. */
434    if (!data->recording)
435        data->bias -= frametime;
436}
437
438void Ticker::StartRecording()
439{
440    data->recording++;
441}
442
443void Ticker::StopRecording()
444{
445    data->recording--;
446}
447
448int Ticker::GetFrameNum()
449{
450    return data->frame;
451}
452
453void Ticker::Shutdown()
454{
455    /* We're bailing out. Release all m_autorelease objects. */
456    while (data->autolist)
457    {
458        data->autolist->m_ref--;
459        data->autolist = data->autolist->m_autonext;
460    }
461
462    data->quit = 1;
463    data->quitframe = data->frame;
464}
465
466int Ticker::Finished()
467{
468    return !data->nentities;
469}
470
471} /* namespace lol */
472
Note: See TracBrowser for help on using the repository browser.