source: trunk/src/ticker.cpp @ 1675

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

core: the IO thread tweak needn't be PS3-specific.

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