source: trunk/src/ticker.cpp @ 735

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

core: get rid of now useless <cstdio> includes.

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