source: trunk/src/ticker.cpp @ 1637

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

core: display names for entities that forget to call Entity::Tick*.

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