source: trunk/src/ticker.cpp @ 342

Last change on this file since 342 was 342, checked in by sam, 10 years ago

Add a safeguard mechanism that gets rid of stuck entities upon shutdown.

  • Property svn:keywords set to Id
File size: 8.8 KB
Line 
1//
2// Lol Engine
3//
4// Copyright: (c) 2010-2011 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 <cstdio>
17#include <stdint.h>
18
19#include "core.h"
20
21/*
22 * Ticker implementation class
23 */
24
25static class TickerData
26{
27    friend class Ticker;
28
29public:
30    TickerData() :
31        todolist(0), autolist(0),
32        nentities(0),
33        frame(0), deltams(0), bias(0),
34        quit(0), quitframe(0), quitdelay(20)
35    {
36        for (int i = 0; i < Entity::ALLGROUP_END; i++)
37            list[i] = NULL;
38    }
39
40    ~TickerData()
41    {
42#if !FINAL_RELEASE
43        if (nentities)
44            fprintf(stderr, "ERROR: still %i entities in ticker\n", nentities);
45        if (autolist)
46        {
47            int count = 0;
48            for (Entity *e = autolist; e; e = e->autonext, count++)
49                ;
50            fprintf(stderr, "ERROR: still %i autoreleased entities\n", count);
51        }
52        fprintf(stderr, "INFO: %i frames required to quit\n",
53                frame - quitframe);
54#endif
55    }
56
57private:
58    /* Entity management */
59    Entity *todolist, *autolist;
60    Entity *list[Entity::ALLGROUP_END];
61    int nentities;
62
63    /* Fixed framerate management */
64    int frame;
65    Timer timer;
66    float deltams, bias;
67
68    /* Shutdown management */
69    int quit, quitframe, quitdelay;
70}
71tickerdata;
72
73static TickerData * const data = &tickerdata;
74
75/*
76 * Ticker public class
77 */
78
79void Ticker::Register(Entity *entity)
80{
81    /* If we are called from its constructor, the object's vtable is not
82     * ready yet, so we do not know which group this entity belongs to. Wait
83     * until the first tick. */
84    entity->gamenext = data->todolist;
85    data->todolist = entity;
86    /* Objects are autoreleased by default. Put them in a circular list. */
87    entity->autorelease = 1;
88    entity->autonext = data->autolist;
89    data->autolist = entity;
90    entity->ref = 1;
91
92    data->nentities++;
93}
94
95void Ticker::Ref(Entity *entity)
96{
97#if !FINAL_RELEASE
98    if (entity->destroy)
99        fprintf(stderr, "ERROR: refing entity scheduled for destruction\n");
100#endif
101    if (entity->autorelease)
102    {
103        /* Get the entity out of the autorelease list. This is usually
104         * very fast since the first entry in autolist is the last
105         * registered entity. */
106        for (Entity *e = data->autolist, *prev = NULL; e;
107             prev = e, e = e->autonext)
108        {
109            if (e == entity)
110            {
111                (prev ? prev->autonext : data->autolist) = e->autonext;
112                break;
113            }
114        }
115        entity->autorelease = 0;
116    }
117    else
118        entity->ref++;
119}
120
121int Ticker::Unref(Entity *entity)
122{
123#if !FINAL_RELEASE
124    if (entity->ref <= 0)
125        fprintf(stderr, "ERROR: dereferencing unreferenced entity\n");
126    if (entity->autorelease)
127        fprintf(stderr, "ERROR: dereferencing autoreleased entity\n");
128#endif
129    return --entity->ref;
130}
131
132void Ticker::TickGame()
133{
134    Profiler::Stop(Profiler::STAT_TICK_FRAME);
135    Profiler::Start(Profiler::STAT_TICK_FRAME);
136
137    Profiler::Start(Profiler::STAT_TICK_GAME);
138
139#if 0
140    fprintf(stderr, "-------------------------------------\n");
141    for (int i = 0; i < Entity::ALLGROUP_END; i++)
142    {
143        fprintf(stderr, "%s Group %i\n",
144                (i < Entity::GAMEGROUP_END) ? "Game" : "Draw", i);
145
146        for (Entity *e = data->list[i]; e; )
147        {
148            fprintf(stderr, "  \\-- %s (ref %i, destroy %i)\n", e->GetName(), e->ref, e->destroy);
149            e = (i < Entity::GAMEGROUP_END) ? e->gamenext : e->drawnext;
150        }
151    }
152#endif
153
154    data->frame++;
155
156    data->deltams = data->timer.GetMs();
157    data->bias += data->deltams;
158
159    /* If shutdown is stuck, kick the first entity we meet and see
160     * whether it makes things better. Note that it is always a bug to
161     * have referenced entities after 20 frames, but at least this
162     * safeguard makes it possible to exit the program cleanly. */
163    if (data->quit && !((data->frame - data->quitframe) % data->quitdelay))
164    {
165        Entity *entity = NULL;
166        for (int i = 0; i < Entity::ALLGROUP_END && !entity; i++)
167            entity = data->list[i];
168        if (entity && entity->ref)
169        {
170#if !FINAL_RELEASE
171            fprintf(stderr, "ERROR: %i entities stuck after %i frames\n",
172                    data->nentities, data->quitdelay);
173#endif
174            entity->ref--;
175            data->quitdelay = data->quitdelay > 1 ? data->quitdelay / 2 : 1;
176        }
177    }
178
179    /* Garbage collect objects that can be destroyed. We can do this
180     * before inserting awaiting objects, because only objects already in
181     * the tick lists can be marked for destruction. */
182    for (int i = 0; i < Entity::ALLGROUP_END; i++)
183        for (Entity *e = data->list[i], *prev = NULL; e; )
184        {
185            if (e->destroy && i < Entity::GAMEGROUP_END)
186            {
187                /* If entity is to be destroyed, remove it from the
188                 * game tick list. */
189                (prev ? prev->gamenext : data->list[i]) = e->gamenext;
190
191                e = e->gamenext;
192            }
193            else if (e->destroy)
194            {
195                /* If entity is to be destroyed, remove it from the
196                 * draw tick list and destroy it. */
197                (prev ? prev->drawnext : data->list[i]) = e->drawnext;
198
199                Entity *tmp = e;
200                e = e->drawnext; /* Can only be in a draw group list */
201                delete tmp;
202
203                data->nentities--;
204            }
205            else
206            {
207                if (e->ref <= 0 && i >= Entity::DRAWGROUP_BEGIN)
208                    e->destroy = 1;
209                prev = e;
210                e = (i < Entity::GAMEGROUP_END) ? e->gamenext : e->drawnext;
211            }
212        }
213
214    /* Insert waiting objects into the appropriate lists */
215    while (data->todolist)
216    {
217        Entity *e = data->todolist;
218        data->todolist = e->gamenext;
219
220        e->gamenext = data->list[e->gamegroup];
221        data->list[e->gamegroup] = e;
222        e->drawnext = data->list[e->drawgroup];
223        data->list[e->drawgroup] = e;
224    }
225
226    /* Tick objects for the game loop */
227    for (int i = Entity::GAMEGROUP_BEGIN; i < Entity::GAMEGROUP_END; i++)
228        for (Entity *e = data->list[i]; e; e = e->gamenext)
229            if (!e->destroy)
230            {
231#if !FINAL_RELEASE
232                if (e->state != Entity::STATE_IDLE)
233                    fprintf(stderr, "ERROR: entity not idle for game tick\n");
234                e->state = Entity::STATE_PRETICK_GAME;
235#endif
236                e->TickGame(data->deltams);
237#if !FINAL_RELEASE
238                if (e->state != Entity::STATE_POSTTICK_GAME)
239                    fprintf(stderr, "ERROR: entity missed super game tick\n");
240                e->state = Entity::STATE_IDLE;
241#endif
242            }
243
244    Profiler::Stop(Profiler::STAT_TICK_GAME);
245}
246
247void Ticker::TickDraw()
248{
249    Profiler::Start(Profiler::STAT_TICK_DRAW);
250
251    Scene::GetDefault();
252
253    /* Tick objects for the draw loop */
254    for (int i = Entity::DRAWGROUP_BEGIN; i < Entity::DRAWGROUP_END; i++)
255    {
256        switch (i)
257        {
258        case Entity::DRAWGROUP_HUD:
259            Scene::GetDefault()->Render();
260            Video::SetDepth(false);
261            break;
262        default:
263            Video::SetDepth(true);
264            break;
265        }
266
267        for (Entity *e = data->list[i]; e; e = e->drawnext)
268            if (!e->destroy)
269            {
270#if !FINAL_RELEASE
271                if (e->state != Entity::STATE_IDLE)
272                    fprintf(stderr, "ERROR: entity not idle for draw tick\n");
273                e->state = Entity::STATE_PRETICK_DRAW;
274#endif
275                e->TickDraw(data->deltams);
276#if !FINAL_RELEASE
277                if (e->state != Entity::STATE_POSTTICK_DRAW)
278                    fprintf(stderr, "ERROR: entity missed super draw tick\n");
279                e->state = Entity::STATE_IDLE;
280#endif
281            }
282    }
283
284    Scene::Reset();
285
286    Profiler::Stop(Profiler::STAT_TICK_DRAW);
287    Profiler::Start(Profiler::STAT_TICK_BLIT);
288}
289
290void Ticker::ClampFps(float deltams)
291{
292    Profiler::Stop(Profiler::STAT_TICK_BLIT);
293
294    if (deltams > data->bias + 200.0f)
295        deltams = data->bias + 200.0f; // Don't go below 5 fps
296    if (deltams > data->bias)
297        data->timer.WaitMs(deltams - data->bias);
298    data->bias -= deltams;
299}
300
301int Ticker::GetFrameNum()
302{
303    return data->frame;
304}
305
306void Ticker::Shutdown()
307{
308    /* We're bailing out. Release all autorelease objects. */
309    while (data->autolist)
310    {
311        data->autolist->ref--;
312        data->autolist = data->autolist->autonext;
313    }
314
315    data->quit = 1;
316    data->quitframe = data->frame;
317}
318
319int Ticker::Finished()
320{
321    return !data->nentities;
322}
323
Note: See TracBrowser for help on using the repository browser.