source: trunk/src/ticker.cpp @ 170

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

Properly implement program termination, including in the GTK program.

  • Property svn:keywords set to Id
File size: 6.7 KB
Line 
1//
2// Deus Hax (working title)
3// Copyright (c) 2010 Sam Hocevar <sam@hocevar.net>
4//
5
6#if defined HAVE_CONFIG_H
7#   include "config.h"
8#endif
9
10#include <cstdlib>
11#include <cstdio>
12#include <stdint.h>
13
14#include "core.h"
15
16/*
17 * Ticker implementation class
18 */
19
20static class TickerData
21{
22    friend class Ticker;
23
24public:
25    TickerData() :
26        todolist(0), autolist(0),
27        nentities(0),
28        frame(0), deltams(0), bias(0)
29    {
30        for (int i = 0; i < Entity::GROUP_COUNT; i++)
31            list[i] = NULL;
32    }
33
34    ~TickerData()
35    {
36#if !FINAL_RELEASE
37        if (nentities)
38            fprintf(stderr, "ERROR: still %i entities in ticker\n", nentities);
39        if (autolist)
40        {
41            int count = 0;
42            for (Entity *e = autolist; e; e = e->autonext, count++)
43                ;
44            fprintf(stderr, "ERROR: still %i autoreleased entities\n", count);
45        }
46        fprintf(stderr, "INFO: %i frames required to quit\n",
47                frame - quitframe);
48#endif
49    }
50
51private:
52    /* Entity management */
53    Entity *todolist, *autolist;
54    Entity *list[Entity::GROUP_COUNT];
55    int nentities;
56
57    /* Fixed framerate management */
58    int frame, quitframe;
59    Timer timer;
60    float deltams, bias;
61}
62tickerdata;
63
64static TickerData * const data = &tickerdata;
65
66/*
67 * Ticker public class
68 */
69
70void Ticker::Register(Entity *entity)
71{
72    /* If we are called from its constructor, the object's vtable is not
73     * ready yet, so we do not know which group this entity belongs to. Wait
74     * until the first tick. */
75    entity->next = data->todolist;
76    data->todolist = entity;
77    /* Objects are autoreleased by default. Put them in a circular list. */
78    entity->autorelease = 1;
79    entity->autonext = data->autolist;
80    data->autolist = entity;
81    entity->ref = 1;
82
83    data->nentities++;
84}
85
86void Ticker::Ref(Entity *entity)
87{
88#if !FINAL_RELEASE
89    if (entity->destroy)
90        fprintf(stderr, "ERROR: refing entity scheduled for destruction\n");
91#endif
92    if (entity->autorelease)
93    {
94        /* Get the entity out of the autorelease list. This is usually
95         * very fast since the first entry in autolist is the last
96         * registered entity. */
97        for (Entity *e = data->autolist, *prev = NULL; e;
98             prev = e, e = e->autonext)
99        {
100            if (e == entity)
101            {
102                if (prev)
103                    prev->autonext = e->autonext;
104                else
105                    data->autolist = e->autonext;
106                break;
107            }
108        }
109        entity->autorelease = 0;
110    }
111    else
112        entity->ref++;
113}
114
115int Ticker::Unref(Entity *entity)
116{
117#if !FINAL_RELEASE
118    if (entity->ref <= 0)
119        fprintf(stderr, "ERROR: dereferencing unreferenced entity\n");
120    if (entity->autorelease)
121        fprintf(stderr, "ERROR: dereferencing autoreleased entity\n");
122#endif
123    return --entity->ref;
124}
125
126void Ticker::TickGame()
127{
128    Profiler::Stop(Profiler::STAT_TICK_FRAME);
129    Profiler::Start(Profiler::STAT_TICK_FRAME);
130
131    Profiler::Start(Profiler::STAT_TICK_GAME);
132
133#if 0
134    fprintf(stderr, "-------------------------------------\n");
135    for (int i = 0; i < Entity::GROUP_COUNT; i++)
136    {
137        fprintf(stderr, "Group %i\n", i);
138
139        for (Entity *e = data->list[i]; e; e = e->next)
140            fprintf(stderr, "  \\-- %s (ref %i, destroy %i)\n", e->GetName(), e->ref, e->destroy);
141    }
142#endif
143
144    data->frame++;
145
146    data->deltams = data->timer.GetMs();
147    data->bias += data->deltams;
148
149    /* Garbage collect objects that can be destroyed. We can do this
150     * before inserting awaiting objects, because there is no way these
151     * are already marked for destruction. */
152    for (int i = 0; i < Entity::GROUP_COUNT; i++)
153        for (Entity *e = data->list[i], *prev = NULL; e; )
154        {
155            if (e->destroy)
156            {
157                if (prev)
158                    prev->next = e->next;
159                else
160                    data->list[i] = e->next;
161
162                Entity *tmp = e;
163                e = e->next;
164                delete tmp;
165
166                data->nentities--;
167            }
168            else
169            {
170                if (e->ref <= 0)
171                    e->destroy = 1;
172                prev = e;
173                e = e->next;
174            }
175        }
176
177    /* Insert waiting objects into the appropriate lists */
178    while (data->todolist)
179    {
180        Entity *e = data->todolist;
181        data->todolist = e->next;
182
183        int i = e->GetGroup();
184        e->next = data->list[i];
185        data->list[i] = e;
186    }
187
188    /* Tick objects for the game loop */
189    for (int i = 0; i < Entity::GROUP_COUNT; i++)
190        for (Entity *e = data->list[i]; e; e = e->next)
191            if (!e->destroy)
192            {
193#if !FINAL_RELEASE
194                if (e->state != Entity::STATE_IDLE)
195                    fprintf(stderr, "ERROR: entity not idle for game tick\n");
196                e->state = Entity::STATE_PRETICK_GAME;
197#endif
198                e->TickGame(data->deltams);
199#if !FINAL_RELEASE
200                if (e->state != Entity::STATE_POSTTICK_GAME)
201                    fprintf(stderr, "ERROR: entity missed super game tick\n");
202                e->state = Entity::STATE_IDLE;
203#endif
204            }
205
206    Profiler::Stop(Profiler::STAT_TICK_GAME);
207}
208
209void Ticker::TickDraw()
210{
211    Profiler::Start(Profiler::STAT_TICK_DRAW);
212
213    /* Tick objects for the draw loop */
214    for (int i = 0; i < Entity::GROUP_COUNT; i++)
215        for (Entity *e = data->list[i]; e; e = e->next)
216            if (!e->destroy)
217            {
218#if !FINAL_RELEASE
219                if (e->state != Entity::STATE_IDLE)
220                    fprintf(stderr, "ERROR: entity not idle for draw tick\n");
221                e->state = Entity::STATE_PRETICK_DRAW;
222#endif
223                e->TickDraw(data->deltams);
224#if !FINAL_RELEASE
225                if (e->state != Entity::STATE_POSTTICK_DRAW)
226                    fprintf(stderr, "ERROR: entity missed super draw tick\n");
227                e->state = Entity::STATE_IDLE;
228#endif
229            }
230
231    Profiler::Stop(Profiler::STAT_TICK_DRAW);
232    Profiler::Start(Profiler::STAT_TICK_BLIT);
233}
234
235void Ticker::ClampFps(float deltams)
236{
237    Profiler::Stop(Profiler::STAT_TICK_BLIT);
238
239    if (deltams > data->bias + 200.0f)
240        deltams = data->bias + 200.0f; // Don't go below 5 fps
241    if (deltams > data->bias)
242        data->timer.WaitMs(deltams - data->bias);
243    data->bias -= deltams;
244}
245
246int Ticker::GetFrameNum()
247{
248    return data->frame;
249}
250
251void Ticker::Shutdown()
252{
253    /* We're bailing out. Release all autorelease objects. */
254    while (data->autolist)
255    {
256        data->autolist->ref--;
257        data->autolist = data->autolist->autonext;
258    }
259
260    data->quitframe = data->frame;
261}
262
263int Ticker::Finished()
264{
265    return !data->nentities;
266}
267
Note: See TracBrowser for help on using the repository browser.