diff --git a/doc/tutorial/07_input.cpp b/doc/tutorial/07_input.cpp index e30f62db..6316eb08 100644 --- a/doc/tutorial/07_input.cpp +++ b/doc/tutorial/07_input.cpp @@ -55,8 +55,6 @@ public: m_text = new Text("", "data/font/ascii.png"); m_text->SetPos(vec3(5, 30, 1)); Ticker::Ref(m_text); - - m_ready = false; } ~InputTutorial() @@ -64,7 +62,7 @@ public: Ticker::Unref(m_text); } - virtual void tick_game(float seconds) + virtual void tick_game(float seconds) override { WorldEntity::tick_game(seconds); @@ -124,40 +122,39 @@ public: m_matrix = proj * view * model * anim; } - virtual void tick_draw(float seconds, Scene &scene) + virtual bool init_draw() override { - WorldEntity::tick_draw(seconds, scene); + m_shader = Shader::Create(LOLFX_RESOURCE_NAME(07_input)); - if (!m_ready) - { - m_shader = Shader::Create(LOLFX_RESOURCE_NAME(07_input)); + m_mvp = m_shader->GetUniformLocation("u_matrix"); + m_coord = m_shader->GetAttribLocation(VertexUsage::Position, 0); + m_color = m_shader->GetAttribLocation(VertexUsage::Color, 0); - m_mvp = m_shader->GetUniformLocation("u_matrix"); - m_coord = m_shader->GetAttribLocation(VertexUsage::Position, 0); - m_color = m_shader->GetAttribLocation(VertexUsage::Color, 0); + m_vdecl = std::make_shared( + VertexStream(VertexUsage::Position, + VertexUsage::Color)); - m_vdecl = std::make_shared( - VertexStream(VertexUsage::Position, - VertexUsage::Color)); + m_vbo = std::make_shared(m_mesh.bytes()); + void *mesh = m_vbo->Lock(0, 0); + memcpy(mesh, &m_mesh[0], m_mesh.bytes()); + m_vbo->Unlock(); - m_vbo = std::make_shared(m_mesh.bytes()); - void *mesh = m_vbo->Lock(0, 0); - memcpy(mesh, &m_mesh[0], m_mesh.bytes()); - m_vbo->Unlock(); + m_lines_ibo = std::make_shared(m_lines_indices.bytes()); + void *indices = m_lines_ibo->Lock(0, 0); + memcpy(indices, &m_lines_indices[0], m_lines_indices.bytes()); + m_lines_ibo->Unlock(); - m_lines_ibo = std::make_shared(m_lines_indices.bytes()); - void *indices = m_lines_ibo->Lock(0, 0); - memcpy(indices, &m_lines_indices[0], m_lines_indices.bytes()); - m_lines_ibo->Unlock(); + m_faces_ibo = std::make_shared(m_faces_indices.bytes()); + indices = m_faces_ibo->Lock(0, 0); + memcpy(indices, &m_faces_indices[0], m_faces_indices.bytes()); + m_faces_ibo->Unlock(); - m_faces_ibo = std::make_shared(m_faces_indices.bytes()); - indices = m_faces_ibo->Lock(0, 0); - memcpy(indices, &m_faces_indices[0], m_faces_indices.bytes()); - m_faces_ibo->Unlock(); + return WorldEntity::init_draw(); + } - /* FIXME: this object never cleans up */ - m_ready = true; - } + virtual void tick_draw(float seconds, Scene &scene) override + { + WorldEntity::tick_draw(seconds, scene); scene.get_renderer()->SetClearColor(vec4(0.0f, 0.0f, 0.0f, 1.0f)); @@ -178,6 +175,16 @@ public: m_vdecl->Unbind(); } + virtual bool release_draw() override + { + m_shader.reset(); + m_vdecl.reset(); + m_vbo.reset(); + m_lines_ibo.reset(); + m_faces_ibo.reset(); + return true; + } + private: bool m_autorot; float m_pitch_angle; @@ -194,7 +201,6 @@ private: std::shared_ptr m_lines_ibo, m_faces_ibo; Text *m_text; - bool m_ready; }; int main(int argc, char **argv) diff --git a/src/engine/entity.h b/src/engine/entity.h index 0fcc25fd..ff117e81 100644 --- a/src/engine/entity.h +++ b/src/engine/entity.h @@ -36,17 +36,15 @@ class entity public: virtual std::string GetName() const; - inline bool IsTicked() { return !!m_ref && !has_flags(flags::autorelease); } - enum class flags : uint16_t { none = 0, - init_game = 1 << 0, - init_draw = 1 << 1, - fini_game = 1 << 2, - fini_draw = 1 << 4, - destroying = 1 << 5, - autorelease = 1 << 6, + init_game = 1 << 0, + init_draw = 1 << 1, + release_game = 1 << 2, + release_draw = 1 << 4, + destroying = 1 << 5, + autorelease = 1 << 6, }; inline void add_flags(flags f); @@ -59,6 +57,8 @@ protected: virtual bool init_game() { return true; } virtual bool init_draw() { return true; } + virtual bool release_game() { return true; } + virtual bool release_draw() { return true; } virtual void tick_game(float seconds); virtual void tick_draw(float seconds, class Scene &scene); @@ -66,7 +66,6 @@ protected: #if !LOL_BUILD_RELEASE tickable::state m_tickstate; #endif - tickable::group::game m_gamegroup; tickable::group::draw m_drawgroup; @@ -121,8 +120,5 @@ template struct entity_dict std::map m_cache2; }; -// The old API -typedef entity Entity; - } /* namespace lol */ diff --git a/src/engine/ticker.cpp b/src/engine/ticker.cpp index 32518889..cd8a7e5a 100644 --- a/src/engine/ticker.cpp +++ b/src/engine/ticker.cpp @@ -30,11 +30,11 @@ class ticker_data public: ticker_data() : DEPRECATED_nentities(0), - frame(0), recording(0), deltatime(0), bias(0), fps(0), + m_frame(0), m_recording(0), deltatime(0), bias(0), fps(0), #if LOL_BUILD_DEBUG keepalive(0), #endif - quit(0), quitframe(0), quitdelay(20), panic(0) + m_quit(0), m_quitframe(0), m_quitdelay(20), m_panic(0) { } @@ -44,7 +44,7 @@ public: "still %d entities in ticker\n", DEPRECATED_nentities); ASSERT(DEPRECATED_m_autolist.count() == 0, "still %d autoreleased entities\n", DEPRECATED_m_autolist.count()); - msg::debug("%d frames required to quit\n", frame - quitframe); + msg::debug("%d frames required to quit\n", m_frame - m_quitframe); #if LOL_FEATURE_THREADS gametick.push(0); @@ -55,6 +55,9 @@ public: #endif } + void handle_shutdown(); + void collect_garbage(); + private: // Tickables waiting to be inserted queue> m_todo; @@ -68,7 +71,7 @@ private: int DEPRECATED_nentities; /* Fixed framerate management */ - int frame, recording; + int m_frame, m_recording; timer m_timer; float deltatime, bias, fps; #if LOL_BUILD_DEBUG @@ -90,7 +93,7 @@ private: #endif /* Shutdown management */ - int quit, quitframe, quitdelay, panic; + int m_quit, m_quitframe, m_quitdelay, m_panic; }; static std::unique_ptr data; @@ -248,13 +251,13 @@ void ticker_data::GameThreadTick() } #endif - data->frame++; + data->m_frame++; /* Ensure some randomness */ - rand(); + (void)rand(); /* If recording with fixed framerate, set deltatime to a fixed value */ - if (data->recording && data->fps) + if (data->m_recording && data->fps) { data->deltatime = 1.f / data->fps; } @@ -280,96 +283,8 @@ void ticker_data::GameThreadTick() } #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 g = 0; g < (int)tickable::group::all::end && n < data->panic; ++g) - for (int i = 0; i < data->DEPRECATED_m_list[g].count() && n < data->panic; ++i) - { - entity * e = data->DEPRECATED_m_list[g][i]; - if (e->m_ref) - { -#if !LOL_BUILD_RELEASE - msg::error("poking %s\n", e->GetName().c_str()); -#endif - e->m_ref--; - n++; - } - } - -#if !LOL_BUILD_RELEASE - if (n) - msg::error("%d entities stuck after %d frames, poked %d\n", - data->DEPRECATED_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 - * in the tick lists can be marked for destruction. */ - array destroy_list; - bool do_reserve = true; - for (int g = 0; g < (int)tickable::group::all::end; ++g) - { - for (int i = data->DEPRECATED_m_list[g].count(); i--;) - { - entity *e = data->DEPRECATED_m_list[g][i]; - - if (e->has_flags(entity::flags::destroying) && g < (int)tickable::group::game::end) - { - /* Game tick list: - * If entity is to be destroyed, remove it */ - data->DEPRECATED_m_list[g].remove_swap(i); - if (do_reserve) - { - do_reserve = false; - destroy_list.reserve(data->DEPRECATED_nentities); //Should it be less ? - } - destroy_list.push_unique(e); - } - else if (e->has_flags(entity::flags::destroying)) - { - /* Draw tick list: - * If entity is to be destroyed, - * remove it and store it. */ - data->DEPRECATED_m_list[g].remove_swap(i); - int removal_count = 0; - for (int j = 0; j < Scene::GetCount(); j++) - { - //If entity is concerned by this scene, add it in the list - if (Scene::GetScene(j).IsRelevant(e)) - removal_count++; - //Update scene index - data->DEPRECATED_m_scenes[(int)e->m_drawgroup][j] -= removal_count; - } - if (do_reserve) - { - do_reserve = false; - destroy_list.reserve(data->DEPRECATED_nentities); //Should it be less ? - } - destroy_list.push_unique(e); - } - else - { - if (e->m_ref <= 0 && g >= (int)tickable::group::draw::begin) - e->add_flags(entity::flags::destroying); - } - } - } - if (!!destroy_list.count()) - { - data->DEPRECATED_nentities -= destroy_list.count(); - for (entity* e : destroy_list) - delete e; - } + data->handle_shutdown(); + data->collect_garbage(); /* Insert waiting objects into the appropriate lists */ while (data->DEPRECATED_m_todolist.count()) @@ -402,21 +317,41 @@ void ticker_data::GameThreadTick() data->DEPRECATED_m_scenes[(int)e->m_drawgroup][i] += added_count; } } - - // Initialize the entity - e->init_game(); } data->DEPRECATED_m_todolist = data->DEPRECATED_m_todolist_delayed; data->DEPRECATED_m_todolist_delayed.clear(); + for (int g = (int)tickable::group::game::begin; g < (int)tickable::group::game::end; ++g) + { + for (int i = 0; i < data->DEPRECATED_m_list[g].count(); ++i) + { + entity *e = data->DEPRECATED_m_list[g][i]; + + if (!e->has_flags(entity::flags::init_game)) + { + // Ensure the entity is initialised on the game thread + if (e->init_game()) + e->add_flags(entity::flags::init_game); + } + else if (e->has_flags(entity::flags::destroying)) + { + // If entity is being destroyed, call the release code + if (!e->has_flags(entity::flags::release_game) && e->release_game()) + e->add_flags(entity::flags::release_game); + } + } + } + /* Tick objects for the game loop */ - for (int g = (int)tickable::group::game::begin; g < (int)tickable::group::game::end && !data->quit /* Stop as soon as required */; ++g) + for (int g = (int)tickable::group::game::begin; g < (int)tickable::group::game::end && !data->m_quit /* Stop as soon as required */; ++g) { - for (int i = 0; i < data->DEPRECATED_m_list[g].count() && !data->quit /* Stop as soon as required */; ++i) + for (int i = 0; i < data->DEPRECATED_m_list[g].count() && !data->m_quit /* Stop as soon as required */; ++i) { entity *e = data->DEPRECATED_m_list[g][i]; - if (!e->has_flags(entity::flags::destroying)) + + if (e->has_flags(entity::flags::init_game) + && !e->has_flags(entity::flags::destroying)) { #if !LOL_BUILD_RELEASE if (e->m_tickstate != tickable::state::idle) @@ -443,8 +378,29 @@ void ticker_data::DrawThreadTick() { Profiler::Start(Profiler::STAT_TICK_DRAW); + for (int g = (int)tickable::group::draw::begin; g < (int)tickable::group::draw::end; ++g) + { + for (int i = 0; i < data->DEPRECATED_m_list[g].count(); ++i) + { + entity *e = data->DEPRECATED_m_list[g][i]; + + if (!e->has_flags(entity::flags::init_draw)) + { + // Ensure the entity is initialised on the draw thread + if (e->init_draw()) + e->add_flags(entity::flags::init_draw); + } + else if (e->has_flags(entity::flags::destroying)) + { + // If entity is being destroyed, call the release code + if (!e->has_flags(entity::flags::release_draw) && e->release_draw()) + e->add_flags(entity::flags::release_draw); + } + } + } + /* Render each scene one after the other */ - for (int idx = 0; idx < Scene::GetCount() && !data->quit /* Stop as soon as required */; ++idx) + for (int idx = 0; idx < Scene::GetCount() && !data->m_quit /* Stop as soon as required */; ++idx) { Scene& scene = Scene::GetScene(idx); @@ -455,7 +411,7 @@ void ticker_data::DrawThreadTick() scene.pre_render(data->deltatime); /* Tick objects for the draw loop */ - for (int g = (int)tickable::group::draw::begin; g < (int)tickable::group::draw::end && !data->quit /* Stop as soon as required */; ++g) + for (int g = (int)tickable::group::draw::begin; g < (int)tickable::group::draw::end && !data->m_quit /* Stop as soon as required */; ++g) { switch (g) { @@ -466,11 +422,12 @@ void ticker_data::DrawThreadTick() break; } - for (int i = 0; i < data->DEPRECATED_m_list[g].count() && !data->quit /* Stop as soon as required */; ++i) + for (int i = 0; i < data->DEPRECATED_m_list[g].count() && !data->m_quit /* Stop as soon as required */; ++i) { entity *e = data->DEPRECATED_m_list[g][i]; - if (!e->has_flags(entity::flags::destroying)) + if (e->has_flags(entity::flags::init_draw) + && !e->has_flags(entity::flags::destroying)) { #if !LOL_BUILD_RELEASE if (e->m_tickstate != tickable::state::idle) @@ -501,6 +458,94 @@ void ticker_data::DrawThreadTick() Profiler::Stop(Profiler::STAT_TICK_DRAW); } +// 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. +void ticker_data::handle_shutdown() +{ + if (m_quit && !((m_frame - m_quitframe) % m_quitdelay)) + { + int n = 0; + m_panic = 2 * (m_panic + 1); + + for (int g = 0; g < (int)tickable::group::all::end && n < m_panic; ++g) + for (int i = 0; i < DEPRECATED_m_list[g].count() && n < m_panic; ++i) + { + entity * e = DEPRECATED_m_list[g][i]; + if (e->m_ref) + { +#if !LOL_BUILD_RELEASE + msg::error("poking %s\n", e->GetName().c_str()); +#endif + e->m_ref--; + n++; + } + } + +#if !LOL_BUILD_RELEASE + if (n) + msg::error("%d entities stuck after %d frames, poked %d\n", + DEPRECATED_nentities, m_quitdelay, n); +#endif + + m_quitdelay = m_quitdelay > 1 ? m_quitdelay / 2 : 1; + } +} + +void ticker_data::collect_garbage() +{ + /* Garbage collect objects that can be destroyed. We can do this + * before inserting awaiting objects, because only objects already + * in the tick lists can be marked for destruction. */ + array destroy_list; + for (int g = 0; g < (int)tickable::group::all::end; ++g) + { + for (int i = DEPRECATED_m_list[g].count(); i--;) + { + entity *e = DEPRECATED_m_list[g][i]; + + if (!e->has_flags(entity::flags::destroying)) + { + if (e->m_ref <= 0 && g >= (int)tickable::group::draw::begin) + e->add_flags(entity::flags::destroying); + continue; + } + + // If entity is being destroyed but not released yet, retry later. + if (!e->has_flags(entity::flags::release_game) + || !e->has_flags(entity::flags::release_draw)) + continue; + + // If entity is to be destroyed, remove it. + DEPRECATED_m_list[g].remove_swap(i); + + // Draw group specific logic + if (g >= (int)tickable::group::game::end) + { + int removal_count = 0; + for (int j = 0; j < Scene::GetCount(); j++) + { + // If entity is concerned by this scene, add it in the list + if (Scene::GetScene(j).IsRelevant(e)) + removal_count++; + // Update scene index + DEPRECATED_m_scenes[(int)e->m_drawgroup][j] -= removal_count; + } + } + + destroy_list.push_unique(e); + } + } + + if (!!destroy_list.count()) + { + DEPRECATED_nentities -= destroy_list.count(); + for (entity* e : destroy_list) + delete e; + } +} + void ticker_data::DiskThreadTick() { ; @@ -570,24 +615,24 @@ void ticker::tick_draw() data->m_timer.wait(frametime - data->bias); /* If recording, do not try to compensate for lag. */ - if (!data->recording) + if (!data->m_recording) data->bias -= frametime; #endif } void Ticker::StartRecording() { - data->recording++; + ++data->m_recording; } void Ticker::StopRecording() { - data->recording--; + --data->m_recording; } int Ticker::GetFrameNum() { - return data->frame; + return data->m_frame; } void Ticker::Shutdown() @@ -599,8 +644,8 @@ void Ticker::Shutdown() data->DEPRECATED_m_autolist.remove(-1); } - data->quit = 1; - data->quitframe = data->frame; + data->m_quit = 1; + data->m_quitframe = data->m_frame; } int Ticker::Finished()