source: trunk/src/ticker.cpp @ 1309

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

core: make timers second-based rather than millisecond-based.

  • Property svn:keywords set to Id
File size: 11.6 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->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 deltams, 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 deltams to a fixed value */
185        if (data->recording && data->fps)
186        {
187            data->deltams = 1000.0f / data->fps;
188        }
189        else
190        {
191            data->deltams = 1000.0f * data->timer.Get();
192            data->bias += data->deltams;
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 not idle for game tick\n");
279                    e->m_tickstate = Entity::STATE_PRETICK_GAME;
280#endif
281                    e->TickGame(data->deltams);
282#if !LOL_RELEASE
283                    if (e->m_tickstate != Entity::STATE_POSTTICK_GAME)
284                        Log::Error("entity missed super game tick\n");
285                    e->m_tickstate = Entity::STATE_IDLE;
286#endif
287                }
288
289        Profiler::Stop(Profiler::STAT_TICK_GAME);
290
291        data->drawtick.Push(1);
292    }
293
294    data->drawtick.Push(0);
295
296    return NULL;
297}
298
299void *TickerData::DrawThreadMain(void *p)
300{
301    for (;;)
302    {
303        int tick = data->drawtick.Pop();
304        if (!tick)
305            break;
306
307        data->gametick.Push(1);
308    }
309
310    return NULL;
311}
312
313void *TickerData::DiskThreadMain(void *p)
314{
315    return NULL;
316}
317
318void Ticker::SetState(Entity *entity, uint32_t state)
319{
320
321}
322
323void Ticker::SetStateWhenMatch(Entity *entity, uint32_t state,
324                               Entity *other_entity, uint32_t other_state)
325{
326
327}
328
329void Ticker::Setup(float fps)
330{
331    data->fps = fps;
332
333    data->gamethread = new Thread(TickerData::GameThreadMain, NULL);
334    data->gametick.Push(1);
335
336    data->diskthread = new Thread(TickerData::DiskThreadMain, NULL);
337}
338
339void Ticker::TickDraw()
340{
341    data->drawtick.Pop();
342
343    Profiler::Start(Profiler::STAT_TICK_DRAW);
344
345    Video::Clear();
346
347    Scene::GetDefault();
348
349    /* Tick objects for the draw loop */
350    for (int i = Entity::DRAWGROUP_BEGIN; i < Entity::DRAWGROUP_END; i++)
351    {
352        switch (i)
353        {
354        case Entity::DRAWGROUP_HUD:
355            Scene::GetDefault()->Render();
356            Video::SetDepth(false);
357            break;
358        default:
359            Video::SetDepth(true);
360            break;
361        }
362
363        for (Entity *e = data->list[i]; e; e = e->m_drawnext)
364            if (!e->m_destroy)
365            {
366#if !LOL_RELEASE
367                if (e->m_tickstate != Entity::STATE_IDLE)
368                    Log::Error("entity not idle for draw tick\n");
369                e->m_tickstate = Entity::STATE_PRETICK_DRAW;
370#endif
371                e->TickDraw(data->deltams);
372#if !LOL_RELEASE
373                if (e->m_tickstate != Entity::STATE_POSTTICK_DRAW)
374                    Log::Error("entity missed super draw tick\n");
375                e->m_tickstate = Entity::STATE_IDLE;
376#endif
377            }
378    }
379
380    Scene::GetDefault()->Render();
381    Scene::Reset();
382
383    Profiler::Stop(Profiler::STAT_TICK_DRAW);
384    Profiler::Start(Profiler::STAT_TICK_BLIT);
385
386    /* Signal game thread that it can carry on */
387    data->gametick.Push(1);
388
389    /* Clamp FPS */
390    Profiler::Stop(Profiler::STAT_TICK_BLIT);
391
392    /* If framerate is fixed, force wait time to 1/FPS. Otherwise, set wait
393     * time to 0. */
394    float framems = data->fps ? 1000.0f / data->fps : 0.0f;
395
396    if (framems > data->bias + 200.0f)
397        framems = data->bias + 200.0f; // Don't go below 5 fps
398    if (framems > data->bias)
399        data->timer.Wait(1e-3f * (framems - data->bias));
400
401    /* If recording, do not try to compensate for lag. */
402    if (!data->recording)
403        data->bias -= framems;
404}
405
406void Ticker::StartRecording()
407{
408    data->recording++;
409}
410
411void Ticker::StopRecording()
412{
413    data->recording--;
414}
415
416int Ticker::GetFrameNum()
417{
418    return data->frame;
419}
420
421void Ticker::Shutdown()
422{
423    /* We're bailing out. Release all m_autorelease objects. */
424    while (data->autolist)
425    {
426        data->autolist->m_ref--;
427        data->autolist = data->autolist->m_autonext;
428    }
429
430    data->quit = 1;
431    data->quitframe = data->frame;
432}
433
434int Ticker::Finished()
435{
436    return !data->nentities;
437}
438
439} /* namespace lol */
440
Note: See TracBrowser for help on using the repository browser.