source: trunk/src/ticker.cpp @ 1579

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

ps3: fix CPU and stack size performance issues on the PS3.

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