source: trunk/src/ticker.cpp @ 2816

Last change on this file since 2816 was 2816, checked in by lolbot, 8 years ago

fixed 542 files out of 2754:

  • removed 0 CR characters
  • removed 0 trailing whitespaces
  • replaced 0 tabs with spaces
  • fixed 542 svn:eol-style properties
  • Property svn:eol-style set to LF
  • Property svn:keywords set to Id
File size: 13.1 KB
Line 
1//
2// Lol Engine
3//
4// Copyright: (c) 2010-2013 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://www.wtfpl.net/ 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#if LOL_BUILD_DEBUG
37        keepalive(0),
38#endif
39        quit(0), quitframe(0), quitdelay(20), panic(0)
40    {
41        for (int i = 0; i < Entity::ALLGROUP_END; i++)
42            list[i] = nullptr;
43    }
44
45    ~TickerData()
46    {
47        ASSERT(nentities == 0,
48               "still %i entities in ticker\n", nentities);
49#if !LOL_BUILD_RELEASE
50        if (autolist)
51        {
52            int count = 0;
53            for (Entity *e = autolist; e; e = e->m_autonext, count++)
54                ;
55            ASSERT(count == 0, "still %i autoreleased entities\n", count);
56        }
57#endif
58        Log::Debug("%i frames required to quit\n",
59                frame - quitframe);
60
61#if LOL_FEATURE_THREADS
62        gametick.Push(0);
63        disktick.Push(0);
64        delete gamethread;
65        delete diskthread;
66#endif
67    }
68
69private:
70    /* Entity management */
71    Entity *todolist, *autolist;
72    Entity *list[Entity::ALLGROUP_END];
73    int nentities;
74
75    /* Fixed framerate management */
76    int frame, recording;
77    Timer timer;
78    float deltatime, bias, fps;
79#if LOL_BUILD_DEBUG
80    float keepalive;
81#endif
82
83    /* The three main functions (for now) */
84    static void GameThreadTick();
85    static void DrawThreadTick();
86    static void DiskThreadTick();
87
88#if LOL_FEATURE_THREADS
89    /* The associated background threads */
90    static void *GameThreadMain(void *p);
91    static void *DrawThreadMain(void *p); /* unused */
92    static void *DiskThreadMain(void *p);
93    Thread *gamethread, *drawthread, *diskthread;
94    Queue<int> gametick, drawtick, disktick;
95#endif
96
97    /* Shutdown management */
98    int quit, quitframe, quitdelay, panic;
99}
100tickerdata;
101
102static TickerData * const data = &tickerdata;
103
104/*
105 * Ticker public class
106 */
107
108void Ticker::Register(Entity *entity)
109{
110    /* If we are called from its constructor, the object's vtable is not
111     * ready yet, so we do not know which group this entity belongs to. Wait
112     * until the first tick. */
113    entity->m_gamenext = data->todolist;
114    data->todolist = entity;
115    /* Objects are autoreleased by default. Put them in a circular list. */
116    entity->m_autorelease = 1;
117    entity->m_autonext = data->autolist;
118    data->autolist = entity;
119    entity->m_ref = 1;
120
121    data->nentities++;
122}
123
124void Ticker::Ref(Entity *entity)
125{
126    ASSERT(entity, "dereferencing nullptr entity\n");
127    ASSERT(!entity->m_destroy,
128           "referencing entity scheduled for destruction %s\n",
129           entity->GetName());
130
131    if (entity->m_autorelease)
132    {
133        /* Get the entity out of the m_autorelease list. This is usually
134         * very fast since the first entry in autolist is the last
135         * registered entity. */
136        for (Entity *e = data->autolist, *prev = nullptr; e;
137             prev = e, e = e->m_autonext)
138        {
139            if (e == entity)
140            {
141                (prev ? prev->m_autonext : data->autolist) = e->m_autonext;
142                break;
143            }
144        }
145        entity->m_autorelease = 0;
146    }
147    else
148        entity->m_ref++;
149}
150
151int Ticker::Unref(Entity *entity)
152{
153    ASSERT(entity, "dereferencing null entity\n");
154    ASSERT(entity->m_ref > 0, "dereferencing unreferenced entity %s\n",
155           entity->GetName())
156    ASSERT(!entity->m_autorelease, "dereferencing autoreleased entity %s\n",
157           entity->GetName())
158
159    return --entity->m_ref;
160}
161
162#if LOL_FEATURE_THREADS
163void *TickerData::GameThreadMain(void * /* p */)
164{
165#if LOL_BUILD_DEBUG
166    Log::Info("ticker game thread initialised\n");
167#endif
168
169    for (;;)
170    {
171        int tick = data->gametick.Pop();
172        if (!tick)
173            break;
174
175        GameThreadTick();
176
177        data->drawtick.Push(1);
178    }
179
180    data->drawtick.Push(0);
181
182#if LOL_BUILD_DEBUG
183    Log::Info("ticker game thread terminated\n");
184#endif
185
186    return nullptr;
187}
188#endif /* LOL_FEATURE_THREADS */
189
190#if LOL_FEATURE_THREADS
191void *TickerData::DrawThreadMain(void * /* p */)
192{
193#if LOL_BUILD_DEBUG
194    Log::Info("ticker draw thread initialised\n");
195#endif
196
197    for (;;)
198    {
199        int tick = data->drawtick.Pop();
200        if (!tick)
201            break;
202
203        DrawThreadTick();
204
205        data->gametick.Push(1);
206    }
207
208#if LOL_BUILD_DEBUG
209    Log::Info("ticker draw thread terminated\n");
210#endif
211
212    return nullptr;
213}
214#endif /* LOL_FEATURE_THREADS */
215
216#if LOL_FEATURE_THREADS
217void *TickerData::DiskThreadMain(void * /* p */)
218{
219    /* FIXME: temporary hack to avoid crashes on the PS3 */
220    data->disktick.Pop();
221
222    return nullptr;
223}
224#endif /* LOL_FEATURE_THREADS */
225
226void TickerData::GameThreadTick()
227{
228    Profiler::Stop(Profiler::STAT_TICK_FRAME);
229    Profiler::Start(Profiler::STAT_TICK_FRAME);
230
231    Profiler::Start(Profiler::STAT_TICK_GAME);
232
233#if 0
234    Log::Debug("-------------------------------------\n");
235    for (int i = 0; i < Entity::ALLGROUP_END; i++)
236    {
237        Log::Debug("%s Group %i\n",
238                   (i < Entity::GAMEGROUP_END) ? "Game" : "Draw", i);
239
240        for (Entity *e = data->list[i]; e; )
241        {
242            Log::Debug("  \\-- %s (m_ref %i, destroy %i)\n", e->GetName(), e->m_ref, e->m_destroy);
243            e = (i < Entity::GAMEGROUP_END) ? e->m_gamenext : e->m_drawnext;
244        }
245    }
246#endif
247
248    data->frame++;
249
250    /* Ensure some randomness */
251    rand<int>();
252
253    /* If recording with fixed framerate, set deltatime to a fixed value */
254    if (data->recording && data->fps)
255    {
256        data->deltatime = 1.f / data->fps;
257    }
258    else
259    {
260        data->deltatime = data->timer.Get();
261        data->bias += data->deltatime;
262    }
263
264    /* Do not go below 15 fps */
265    if (data->deltatime > 1.f / 15.f)
266    {
267        data->deltatime = 1.f / 15.f;
268        data->bias = 0.f;
269    }
270
271#if LOL_BUILD_DEBUG
272    data->keepalive += data->deltatime;
273    if (data->keepalive > 10.f)
274    {
275        Log::Info("ticker keepalive: tick!\n");
276        data->keepalive = 0.f;
277    }
278#endif
279
280    /* If shutdown is stuck, kick the first entity we meet and see
281     * whether it makes things better. Note that it is always a bug to
282     * have referenced entities after 20 frames, but at least this
283     * safeguard makes it possible to exit the program cleanly. */
284    if (data->quit && !((data->frame - data->quitframe) % data->quitdelay))
285    {
286        int n = 0;
287        data->panic = 2 * (data->panic + 1);
288
289        for (int i = 0; i < Entity::ALLGROUP_END && n < data->panic; i++)
290        for (Entity *e = data->list[i]; e && n < data->panic; e = e->m_gamenext)
291            if (e->m_ref)
292            {
293#if !LOL_BUILD_RELEASE
294                Log::Error("poking %s\n", e->GetName());
295#endif
296                e->m_ref--;
297                n++;
298            }
299
300#if !LOL_BUILD_RELEASE
301        if (n)
302            Log::Error("%i entities stuck after %i frames, poked %i\n",
303                       data->nentities, data->quitdelay, n);
304#endif
305
306        data->quitdelay = data->quitdelay > 1 ? data->quitdelay / 2 : 1;
307    }
308
309    /* Garbage collect objects that can be destroyed. We can do this
310     * before inserting awaiting objects, because only objects already
311     * inthe tick lists can be marked for destruction. */
312    for (int i = 0; i < Entity::ALLGROUP_END; i++)
313        for (Entity *e = data->list[i], *prev = nullptr; e; )
314        {
315            if (e->m_destroy && i < Entity::GAMEGROUP_END)
316            {
317                /* If entity is to be destroyed, remove it from the
318                 * game tick list. */
319                (prev ? prev->m_gamenext : data->list[i]) = e->m_gamenext;
320
321                e = e->m_gamenext;
322            }
323            else if (e->m_destroy)
324            {
325                /* If entity is to be destroyed, remove it from the
326                 * draw tick list and destroy it. */
327                (prev ? prev->m_drawnext : data->list[i]) = e->m_drawnext;
328
329                Entity *tmp = e;
330                e = e->m_drawnext; /* Can only be in a draw group list */
331                delete tmp;
332
333                data->nentities--;
334            }
335            else
336            {
337                if (e->m_ref <= 0 && i >= Entity::DRAWGROUP_BEGIN)
338                    e->m_destroy = 1;
339                prev = e;
340                e = (i < Entity::GAMEGROUP_END) ? e->m_gamenext : e->m_drawnext;
341            }
342        }
343
344    /* Insert waiting objects into the appropriate lists */
345    while (data->todolist)
346    {
347        Entity *e = data->todolist;
348        data->todolist = e->m_gamenext;
349
350        e->m_gamenext = data->list[e->m_gamegroup];
351        data->list[e->m_gamegroup] = e;
352        e->m_drawnext = data->list[e->m_drawgroup];
353        data->list[e->m_drawgroup] = e;
354    }
355
356    /* Tick objects for the game loop */
357    for (int i = Entity::GAMEGROUP_BEGIN; i < Entity::GAMEGROUP_END; i++)
358        for (Entity *e = data->list[i]; e; e = e->m_gamenext)
359            if (!e->m_destroy)
360            {
361#if !LOL_BUILD_RELEASE
362                if (e->m_tickstate != Entity::STATE_IDLE)
363                    Log::Error("entity %s [%p] not idle for game tick\n",
364                               e->GetName(), e);
365                e->m_tickstate = Entity::STATE_PRETICK_GAME;
366#endif
367                e->TickGame(data->deltatime);
368#if !LOL_BUILD_RELEASE
369                if (e->m_tickstate != Entity::STATE_POSTTICK_GAME)
370                    Log::Error("entity %s [%p] missed super game tick\n",
371                               e->GetName(), e);
372                e->m_tickstate = Entity::STATE_IDLE;
373#endif
374            }
375
376    Profiler::Stop(Profiler::STAT_TICK_GAME);
377}
378
379void TickerData::DrawThreadTick()
380{
381    Profiler::Start(Profiler::STAT_TICK_DRAW);
382
383    /* Tick objects for the draw loop */
384    for (int i = Entity::DRAWGROUP_BEGIN; i < Entity::DRAWGROUP_END; i++)
385    {
386        switch (i)
387        {
388        case Entity::DRAWGROUP_BEGIN:
389            g_scene->Reset();
390            g_renderer->Clear(ClearMask::All);
391            break;
392        default:
393            break;
394        }
395
396        for (Entity *e = data->list[i]; e; e = e->m_drawnext)
397            if (!e->m_destroy)
398            {
399#if !LOL_BUILD_RELEASE
400                if (e->m_tickstate != Entity::STATE_IDLE)
401                    Log::Error("entity %s [%p] not idle for draw tick\n",
402                               e->GetName(), e);
403                e->m_tickstate = Entity::STATE_PRETICK_DRAW;
404#endif
405                e->TickDraw(data->deltatime);
406#if !LOL_BUILD_RELEASE
407                if (e->m_tickstate != Entity::STATE_POSTTICK_DRAW)
408                    Log::Error("entity %s [%p] missed super draw tick\n",
409                               e->GetName(), e);
410                e->m_tickstate = Entity::STATE_IDLE;
411#endif
412            }
413
414        /* Do this render step */
415        g_scene->Render();
416    }
417
418    Profiler::Stop(Profiler::STAT_TICK_DRAW);
419}
420
421void TickerData::DiskThreadTick()
422{
423    ;
424}
425
426void Ticker::SetState(Entity * /* entity */, uint32_t /* state */)
427{
428
429}
430
431void Ticker::SetStateWhenMatch(Entity * /* entity */, uint32_t /* state */,
432                               Entity * /* other_entity */, uint32_t /* other_state */)
433{
434
435}
436
437void Ticker::Setup(float fps)
438{
439    data->fps = fps;
440
441#if LOL_FEATURE_THREADS
442    data->gamethread = new Thread(TickerData::GameThreadMain, nullptr);
443    data->gametick.Push(1);
444
445    data->diskthread = new Thread(TickerData::DiskThreadMain, nullptr);
446#endif
447}
448
449void Ticker::TickDraw()
450{
451#if LOL_FEATURE_THREADS
452    data->drawtick.Pop();
453#else
454    TickerData::GameThreadTick();
455#endif
456
457    TickerData::DrawThreadTick();
458
459    Profiler::Start(Profiler::STAT_TICK_BLIT);
460
461    /* Signal game thread that it can carry on */
462#if LOL_FEATURE_THREADS
463    data->gametick.Push(1);
464#else
465    TickerData::DiskThreadTick();
466#endif
467
468    /* Clamp FPS */
469    Profiler::Stop(Profiler::STAT_TICK_BLIT);
470
471#if !EMSCRIPTEN
472    /* If framerate is fixed, force wait time to 1/FPS. Otherwise, set wait
473     * time to 0. */
474    float frametime = data->fps ? 1.f / data->fps : 0.f;
475
476    if (frametime > data->bias + .2f)
477        frametime = data->bias + .2f; /* Don't go below 5 fps */
478    if (frametime > data->bias)
479        data->timer.Wait(frametime - data->bias);
480
481    /* If recording, do not try to compensate for lag. */
482    if (!data->recording)
483        data->bias -= frametime;
484#endif
485}
486
487void Ticker::StartRecording()
488{
489    data->recording++;
490}
491
492void Ticker::StopRecording()
493{
494    data->recording--;
495}
496
497int Ticker::GetFrameNum()
498{
499    return data->frame;
500}
501
502void Ticker::Shutdown()
503{
504    /* We're bailing out. Release all m_autorelease objects. */
505    while (data->autolist)
506    {
507        data->autolist->m_ref--;
508        data->autolist = data->autolist->m_autonext;
509    }
510
511    data->quit = 1;
512    data->quitframe = data->frame;
513}
514
515int Ticker::Finished()
516{
517    return !data->nentities;
518}
519
520} /* namespace lol */
521
Note: See TracBrowser for help on using the repository browser.