source: trunk/src/ticker.cpp @ 1106

Last change on this file since 1106 was 1106, checked in by sam, 9 years ago

core: try to merge Ticker and Emcee. Still not very good.

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