source: trunk/src/ticker.cpp @ 1676

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

core: make the IO thread exit cleanly.

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