Browse Source

base: refactor Ticker so that it may one day work without threads.

legacy
Sam Hocevar sam 11 years ago
parent
commit
edffeb6d2e
3 changed files with 220 additions and 167 deletions
  1. +7
    -2
      src/core.h
  2. +2
    -0
      src/lol/sys/thread.h
  3. +211
    -165
      src/ticker.cpp

+ 7
- 2
src/core.h View File

@@ -16,12 +16,17 @@
#if !defined __LOL_CORE_H__
#define __LOL_CORE_H__

// CPU features
// System and CPU features
#undef LOL_FEATURE_THREADS
#undef LOL_FEATURE_CHEAP_BRANCHES
#undef LOL_FEATURE_VERY_CHEAP_BRANCHES

#if !defined EMSCRIPTEN
# define LOL_FEATURE_THREADS 1
#endif

#if !defined __CELLOS_LV2__
# define LOL_FEATURE_CHEAP_BRANCHES
# define LOL_FEATURE_CHEAP_BRANCHES 1
#endif

// Optimisation helpers


+ 2
- 0
src/lol/sys/thread.h View File

@@ -37,12 +37,14 @@ public:
Queue() : QueueBase<T, N>() {}
};

#if LOL_FEATURE_THREADS
class Thread : ThreadBase
{
public:
Thread(void *(*fn)(void *), void *data) : ThreadBase(fn, data) {}
virtual ~Thread() {}
};
#endif

} /* namespace lol */



+ 211
- 165
src/ticker.cpp View File

@@ -58,10 +58,12 @@ public:
Log::Debug("%i frames required to quit\n",
frame - quitframe);

#if LOL_FEATURE_THREADS
gametick.Push(0);
disktick.Push(0);
delete gamethread;
delete diskthread;
#endif
}

private:
@@ -78,12 +80,19 @@ private:
float keepalive;
#endif

/* Background threads */
/* The three main functions (for now) */
static void GameThreadTick();
static void DrawThreadTick();
static void DiskThreadTick();

#if LOL_FEATURE_THREADS
/* The associated background threads */
static void *GameThreadMain(void *p);
static void *DrawThreadMain(void *p); /* unused */
static void *DiskThreadMain(void *p);
Thread *gamethread, *drawthread, *diskthread;
Queue<int> gametick, drawtick, disktick;
#endif

/* Shutdown management */
int quit, quitframe, quitdelay, panic;
@@ -150,6 +159,7 @@ int Ticker::Unref(Entity *entity)
return --entity->m_ref;
}

#if LOL_FEATURE_THREADS
void *TickerData::GameThreadMain(void * /* p */)
{
#if LOL_DEBUG
@@ -162,155 +172,7 @@ void *TickerData::GameThreadMain(void * /* p */)
if (!tick)
break;

Profiler::Stop(Profiler::STAT_TICK_FRAME);
Profiler::Start(Profiler::STAT_TICK_FRAME);

Profiler::Start(Profiler::STAT_TICK_GAME);

#if 0
Log::Debug("-------------------------------------\n");
for (int i = 0; i < Entity::ALLGROUP_END; i++)
{
Log::Debug("%s Group %i\n",
(i < Entity::GAMEGROUP_END) ? "Game" : "Draw", i);

for (Entity *e = data->list[i]; e; )
{
Log::Debug(" \\-- %s (m_ref %i, destroy %i)\n", e->GetName(), e->m_ref, e->m_destroy);
e = (i < Entity::GAMEGROUP_END) ? e->m_gamenext : e->m_drawnext;
}
}
#endif

data->frame++;

/* Ensure some randomness */
rand<int>();

/* If recording with fixed framerate, set deltatime to a fixed value */
if (data->recording && data->fps)
{
data->deltatime = 1.f / data->fps;
}
else
{
data->deltatime = data->timer.Get();
data->bias += data->deltatime;
}

/* Do not go below 15 fps */
if (data->deltatime > 1.f / 15.f)
{
data->deltatime = 1.f / 15.f;
data->bias = 0.f;
}

#if LOL_DEBUG
data->keepalive += data->deltatime;
if (data->keepalive > 10.f)
{
Log::Info("ticker keepalive: tick!\n");
data->keepalive = 0.f;
}
#endif

/* If shutdown is stuck, kick the first entity we meet and see
* whether it makes things better. Note that it is always a bug to
* have referenced entities after 20 frames, but at least this
* safeguard makes it possible to exit the program cleanly. */
if (data->quit && !((data->frame - data->quitframe) % data->quitdelay))
{
int n = 0;
data->panic = 2 * (data->panic + 1);

for (int i = 0; i < Entity::ALLGROUP_END && n < data->panic; i++)
for (Entity *e = data->list[i]; e && n < data->panic; e = e->m_gamenext)
if (e->m_ref)
{
#if !LOL_RELEASE
Log::Error("poking %s\n", e->GetName());
#endif
e->m_ref--;
n++;
}

#if !LOL_RELEASE
if (n)
Log::Error("%i entities stuck after %i frames, poked %i\n",
data->nentities, data->quitdelay, n);
#endif

data->quitdelay = data->quitdelay > 1 ? data->quitdelay / 2 : 1;
}

/* Garbage collect objects that can be destroyed. We can do this
* before inserting awaiting objects, because only objects already
* inthe tick lists can be marked for destruction. */
for (int i = 0; i < Entity::ALLGROUP_END; i++)
for (Entity *e = data->list[i], *prev = nullptr; e; )
{
if (e->m_destroy && i < Entity::GAMEGROUP_END)
{
/* If entity is to be destroyed, remove it from the
* game tick list. */
(prev ? prev->m_gamenext : data->list[i]) = e->m_gamenext;

e = e->m_gamenext;
}
else if (e->m_destroy)
{
/* If entity is to be destroyed, remove it from the
* draw tick list and destroy it. */
(prev ? prev->m_drawnext : data->list[i]) = e->m_drawnext;

Entity *tmp = e;
e = e->m_drawnext; /* Can only be in a draw group list */
delete tmp;

data->nentities--;
}
else
{
if (e->m_ref <= 0 && i >= Entity::DRAWGROUP_BEGIN)
e->m_destroy = 1;
prev = e;
e = (i < Entity::GAMEGROUP_END) ? e->m_gamenext : e->m_drawnext;
}
}

/* Insert waiting objects into the appropriate lists */
while (data->todolist)
{
Entity *e = data->todolist;
data->todolist = e->m_gamenext;

e->m_gamenext = data->list[e->m_gamegroup];
data->list[e->m_gamegroup] = e;
e->m_drawnext = data->list[e->m_drawgroup];
data->list[e->m_drawgroup] = e;
}

/* Tick objects for the game loop */
for (int i = Entity::GAMEGROUP_BEGIN; i < Entity::GAMEGROUP_END; i++)
for (Entity *e = data->list[i]; e; e = e->m_gamenext)
if (!e->m_destroy)
{
#if !LOL_RELEASE
if (e->m_tickstate != Entity::STATE_IDLE)
Log::Error("entity %s [%p] not idle for game tick\n",
e->GetName(), e);
e->m_tickstate = Entity::STATE_PRETICK_GAME;
#endif
e->TickGame(data->deltatime);
#if !LOL_RELEASE
if (e->m_tickstate != Entity::STATE_POSTTICK_GAME)
Log::Error("entity %s [%p] missed super game tick\n",
e->GetName(), e);
e->m_tickstate = Entity::STATE_IDLE;
#endif
}

Profiler::Stop(Profiler::STAT_TICK_GAME);
GameThreadTick();

data->drawtick.Push(1);
}
@@ -323,21 +185,35 @@ void *TickerData::GameThreadMain(void * /* p */)

return nullptr;
}
#endif /* LOL_FEATURE_THREADS */

#if LOL_FEATURE_THREADS
void *TickerData::DrawThreadMain(void * /* p */)
{
#if LOL_DEBUG
Log::Info("ticker draw thread initialised\n");
#endif

for (;;)
{
int tick = data->drawtick.Pop();
if (!tick)
break;

DrawThreadTick();

data->gametick.Push(1);
}

#if LOL_DEBUG
Log::Info("ticker draw thread terminated\n");
#endif

return nullptr;
}
#endif /* LOL_FEATURE_THREADS */

#if LOL_FEATURE_THREADS
void *TickerData::DiskThreadMain(void * /* p */)
{
/* FIXME: temporary hack to avoid crashes on the PS3 */
@@ -345,32 +221,163 @@ void *TickerData::DiskThreadMain(void * /* p */)

return nullptr;
}
#endif /* LOL_FEATURE_THREADS */

void Ticker::SetState(Entity * /* entity */, uint32_t /* state */)
void TickerData::GameThreadTick()
{
Profiler::Stop(Profiler::STAT_TICK_FRAME);
Profiler::Start(Profiler::STAT_TICK_FRAME);

}
Profiler::Start(Profiler::STAT_TICK_GAME);

void Ticker::SetStateWhenMatch(Entity * /* entity */, uint32_t /* state */,
Entity * /* other_entity */, uint32_t /* other_state */)
{
#if 0
Log::Debug("-------------------------------------\n");
for (int i = 0; i < Entity::ALLGROUP_END; i++)
{
Log::Debug("%s Group %i\n",
(i < Entity::GAMEGROUP_END) ? "Game" : "Draw", i);

}
for (Entity *e = data->list[i]; e; )
{
Log::Debug(" \\-- %s (m_ref %i, destroy %i)\n", e->GetName(), e->m_ref, e->m_destroy);
e = (i < Entity::GAMEGROUP_END) ? e->m_gamenext : e->m_drawnext;
}
}
#endif

void Ticker::Setup(float fps)
{
data->fps = fps;
data->frame++;

data->gamethread = new Thread(TickerData::GameThreadMain, nullptr);
data->gametick.Push(1);
/* Ensure some randomness */
rand<int>();

data->diskthread = new Thread(TickerData::DiskThreadMain, nullptr);
/* If recording with fixed framerate, set deltatime to a fixed value */
if (data->recording && data->fps)
{
data->deltatime = 1.f / data->fps;
}
else
{
data->deltatime = data->timer.Get();
data->bias += data->deltatime;
}

/* Do not go below 15 fps */
if (data->deltatime > 1.f / 15.f)
{
data->deltatime = 1.f / 15.f;
data->bias = 0.f;
}

#if LOL_DEBUG
data->keepalive += data->deltatime;
if (data->keepalive > 10.f)
{
Log::Info("ticker keepalive: tick!\n");
data->keepalive = 0.f;
}
#endif

/* If shutdown is stuck, kick the first entity we meet and see
* whether it makes things better. Note that it is always a bug to
* have referenced entities after 20 frames, but at least this
* safeguard makes it possible to exit the program cleanly. */
if (data->quit && !((data->frame - data->quitframe) % data->quitdelay))
{
int n = 0;
data->panic = 2 * (data->panic + 1);

for (int i = 0; i < Entity::ALLGROUP_END && n < data->panic; i++)
for (Entity *e = data->list[i]; e && n < data->panic; e = e->m_gamenext)
if (e->m_ref)
{
#if !LOL_RELEASE
Log::Error("poking %s\n", e->GetName());
#endif
e->m_ref--;
n++;
}

#if !LOL_RELEASE
if (n)
Log::Error("%i entities stuck after %i frames, poked %i\n",
data->nentities, data->quitdelay, n);
#endif

data->quitdelay = data->quitdelay > 1 ? data->quitdelay / 2 : 1;
}

/* Garbage collect objects that can be destroyed. We can do this
* before inserting awaiting objects, because only objects already
* inthe tick lists can be marked for destruction. */
for (int i = 0; i < Entity::ALLGROUP_END; i++)
for (Entity *e = data->list[i], *prev = nullptr; e; )
{
if (e->m_destroy && i < Entity::GAMEGROUP_END)
{
/* If entity is to be destroyed, remove it from the
* game tick list. */
(prev ? prev->m_gamenext : data->list[i]) = e->m_gamenext;

e = e->m_gamenext;
}
else if (e->m_destroy)
{
/* If entity is to be destroyed, remove it from the
* draw tick list and destroy it. */
(prev ? prev->m_drawnext : data->list[i]) = e->m_drawnext;

Entity *tmp = e;
e = e->m_drawnext; /* Can only be in a draw group list */
delete tmp;

data->nentities--;
}
else
{
if (e->m_ref <= 0 && i >= Entity::DRAWGROUP_BEGIN)
e->m_destroy = 1;
prev = e;
e = (i < Entity::GAMEGROUP_END) ? e->m_gamenext : e->m_drawnext;
}
}

/* Insert waiting objects into the appropriate lists */
while (data->todolist)
{
Entity *e = data->todolist;
data->todolist = e->m_gamenext;

e->m_gamenext = data->list[e->m_gamegroup];
data->list[e->m_gamegroup] = e;
e->m_drawnext = data->list[e->m_drawgroup];
data->list[e->m_drawgroup] = e;
}

/* Tick objects for the game loop */
for (int i = Entity::GAMEGROUP_BEGIN; i < Entity::GAMEGROUP_END; i++)
for (Entity *e = data->list[i]; e; e = e->m_gamenext)
if (!e->m_destroy)
{
#if !LOL_RELEASE
if (e->m_tickstate != Entity::STATE_IDLE)
Log::Error("entity %s [%p] not idle for game tick\n",
e->GetName(), e);
e->m_tickstate = Entity::STATE_PRETICK_GAME;
#endif
e->TickGame(data->deltatime);
#if !LOL_RELEASE
if (e->m_tickstate != Entity::STATE_POSTTICK_GAME)
Log::Error("entity %s [%p] missed super game tick\n",
e->GetName(), e);
e->m_tickstate = Entity::STATE_IDLE;
#endif
}

Profiler::Stop(Profiler::STAT_TICK_GAME);
}

void Ticker::TickDraw()
void TickerData::DrawThreadTick()
{
data->drawtick.Pop();

Profiler::Start(Profiler::STAT_TICK_DRAW);

/* Tick objects for the draw loop */
@@ -413,10 +420,49 @@ void Ticker::TickDraw()
}

Profiler::Stop(Profiler::STAT_TICK_DRAW);
}

void Ticker::SetState(Entity * /* entity */, uint32_t /* state */)
{

}

void Ticker::SetStateWhenMatch(Entity * /* entity */, uint32_t /* state */,
Entity * /* other_entity */, uint32_t /* other_state */)
{

}

void Ticker::Setup(float fps)
{
data->fps = fps;

#if LOL_FEATURE_THREADS
data->gamethread = new Thread(TickerData::GameThreadMain, nullptr);
data->gametick.Push(1);

data->diskthread = new Thread(TickerData::DiskThreadMain, nullptr);
#endif
}

void Ticker::TickDraw()
{
#if LOL_FEATURE_THREADS
data->drawtick.Pop();
#else
TickerData::GameThreadTick();
#endif

TickerData::DrawThreadTick();

Profiler::Start(Profiler::STAT_TICK_BLIT);

/* Signal game thread that it can carry on */
#if LOL_FEATURE_THREADS
data->gametick.Push(1);
#else
TickerData::DiskThreadTick();
#endif

/* Clamp FPS */
Profiler::Stop(Profiler::STAT_TICK_BLIT);


Loading…
Cancel
Save