diff --git a/CMakeLists.txt b/CMakeLists.txt index 39e97f2..7bd2412 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,7 +22,7 @@ CPMAddPackage( URL https://git.redacted.cc/Redacted/ReWindow/archive/Prerelease-26.zip ) -#set(CMAKE_CXX_FLAGS "-O3 -Wall -Wextra") +set(CMAKE_CXX_FLAGS "-O3 -Wall -Wextra") file(GLOB_RECURSE HEADERS "include/*.h" "include/*.hpp") file(GLOB_RECURSE SOURCES "src/*.c" "src/*.cpp") diff --git a/include/Engine/Globals.h b/include/Engine/Globals.h index 8f305e3..abb796e 100644 --- a/include/Engine/Globals.h +++ b/include/Engine/Globals.h @@ -10,8 +10,18 @@ namespace Globals { inline Scene* CurrentScene = nullptr; inline DemoGameWindow* Window = nullptr; - inline float DeltaTime() { return Window->GetDeltaTime(); } - inline void ChangeScene(Scene* scene) { if (CurrentScene != nullptr) CurrentScene->Unload(); CurrentScene = scene; if (CurrentScene != nullptr) CurrentScene->Init(); } + inline void ChangeScene(Scene* scene) + { + if (CurrentScene != nullptr) + CurrentScene->Unload(); + + CurrentScene = scene; + + if (CurrentScene != nullptr) { + CurrentScene->Init(); + CurrentScene->Run(); + } + } inline bool ChangeScene (const std::string& scene_name) { Scene* scene = nullptr; diff --git a/include/Engine/Logger.h b/include/Engine/Logger.h new file mode 100644 index 0000000..7ee1a91 --- /dev/null +++ b/include/Engine/Logger.h @@ -0,0 +1,9 @@ +#pragma once +#include + +namespace Engine::Logger { + inline jlog::GenericLogger Info {"Engine::Info", jlog::GlobalLogFile, Colors::Gray, Colors::Gray, Colors::Gray, Colors::Gray}; + inline jlog::GenericLogger Fatal {"Engine::Fatal", jlog::GlobalLogFile, Colors::Reds::Crimson, Colors::Gray, Colors::Gray, Colors::Reds::Crimson, Colors::White}; + inline jlog::GenericLogger Debug {"Engine::Debug", jlog::GlobalLogFile, Colors::Purples::Purple, Colors::Gray, Colors::Gray, Colors::Purples::Purple, Colors::White}; + +} \ No newline at end of file diff --git a/include/Engine/types/ConcurrentWorker.h b/include/Engine/types/ConcurrentWorker.h new file mode 100644 index 0000000..391411b --- /dev/null +++ b/include/Engine/types/ConcurrentWorker.h @@ -0,0 +1,24 @@ +#pragma once +#include +#include +#include +#include + +namespace Engine { + class ConcurrentWorker; +} + +class Engine::ConcurrentWorker { +private: + std::atomic work_complete{ false }; + std::thread worker_thread; +public: + [[nodiscard]] bool Done(); +public: + ~ConcurrentWorker(); + + template + explicit ConcurrentWorker(Function&& func, Args&&... args) : worker_thread([this, func = std::forward(func), + ...args = std::forward(args)]() mutable { func(std::forward(args) ...); work_complete = true; }) + { worker_thread.detach(); } +}; \ No newline at end of file diff --git a/include/Engine/types/Scene.h b/include/Engine/types/Scene.h index aacd15d..9323945 100644 --- a/include/Engine/types/Scene.h +++ b/include/Engine/types/Scene.h @@ -5,6 +5,9 @@ #include #include #include +#include +#include +#include namespace Engine { class Camera; @@ -12,13 +15,23 @@ namespace Engine { } class Engine::Scene { +private: + bool destroying = false; + bool last_rendered_to_window = false; + float render_target_frame_delta = 0; protected: + // 25ms. + u8 tick_rate = 40; + bool should_tick = true; + ConcurrentWorker* update_worker; std::string name; Camera* active_camera = nullptr; Entity* entity_list = nullptr; - std::vector instanced_textures{}; std::vector instanced_alpha_masks{}; +protected: + virtual void Update(); + virtual void Render(); public: [[nodiscard]] Entity* GetEntityList() { return entity_list; } [[nodiscard]] size_t EntityCount() const; @@ -26,16 +39,22 @@ public: [[nodiscard]] Camera* GetActiveCamera() const; [[nodiscard]] InstancedTexture* GetInstancedTexture(const InstancedSprite* user); [[nodiscard]] InstancedTexture* GetInstancedAlphaMask(const InstancedSprite* user); + [[nodiscard]] bool ShouldTick() const; + [[nodiscard]] float TickDeltaTime() const; + [[nodiscard]] float FrameDeltaTime() const; public: + void ShouldTick(bool state); void SetActiveCamera(Camera* camera) { active_camera = camera; }; void DestroyInstancedTexture(const InstancedTexture* itx); void DestroyInstancedAlphaMask(const InstancedTexture* alpha_mask); - virtual void Unload(); - virtual void Init() {} - virtual void Update(); - virtual void Render(JGL::RenderTarget* render_target = nullptr); + virtual void Init() {}; + void Run(); + + void UpdateLoop(); + void Render(JGL::RenderTarget* render_target); + void Render(ReWindow::RWindow* window); public: - explicit Scene(const std::string& name) : name(name) { entity_list = new Entity(); } + explicit Scene(const std::string& name, const u8 tick_rate = 40) : tick_rate(tick_rate), name(name), entity_list(new Entity()) {} virtual ~Scene(); }; diff --git a/include/Game/scene/Loading.h b/include/Game/scene/Loading.h index 0764af9..df36ff2 100644 --- a/include/Game/scene/Loading.h +++ b/include/Game/scene/Loading.h @@ -11,7 +11,6 @@ public: explicit LoadingScreen(const std::string& name) : Scene(name) {} void Init() final; - void Update() final; - void Render(JGL::RenderTarget* render_target = nullptr) final; + void Render() final; ~LoadingScreen() final; }; diff --git a/src/Engine/GameWindow.cpp b/src/Engine/GameWindow.cpp index 773fe77..63deb17 100644 --- a/src/Engine/GameWindow.cpp +++ b/src/Engine/GameWindow.cpp @@ -17,11 +17,8 @@ void Engine::DemoGameWindow::Display() { glMatrixMode(GL_MODELVIEW); glLoadIdentity(); - // These are checked separately so that if Update() changes the scene we don't crash. if (Globals::CurrentScene) - Globals::CurrentScene->Update(); - if (Globals::CurrentScene) - Globals::CurrentScene->Render(); + Globals::CurrentScene->Render(this); Engine::DemoGameWindow::GLSwapBuffers(); } diff --git a/src/Engine/types/ConcurrentWorker.cpp b/src/Engine/types/ConcurrentWorker.cpp new file mode 100644 index 0000000..d028518 --- /dev/null +++ b/src/Engine/types/ConcurrentWorker.cpp @@ -0,0 +1,14 @@ +#include +#include + +using namespace Engine; +ConcurrentWorker::~ConcurrentWorker() { + if (!work_complete) { + std::stringstream thread_id; thread_id << worker_thread.get_id(); + Logger::Fatal("ConcurrentWorker was destroyed for thread " + thread_id.str() + " but the work wasn't finished?"); + } +} + +bool ConcurrentWorker::Done() { + return work_complete; +} diff --git a/src/Engine/types/Scene.cpp b/src/Engine/types/Scene.cpp index 78ffd17..1d161f8 100644 --- a/src/Engine/types/Scene.cpp +++ b/src/Engine/types/Scene.cpp @@ -1,8 +1,11 @@ +#include #include #include #include +#include #include #include +#include using namespace Engine; size_t Scene::EntityCount() const { @@ -10,45 +13,78 @@ size_t Scene::EntityCount() const { } void Scene::Update() { + auto target_tick_time = std::chrono::microseconds(1000000 / tick_rate); + + auto start = std::chrono::high_resolution_clock::now(); + entity_list->UpdateChildren(); + + auto stop = std::chrono::high_resolution_clock::now(); + auto elapsed = std::chrono::duration_cast(stop - start); + + if (elapsed < target_tick_time) + std::this_thread::sleep_for(std::chrono::microseconds(target_tick_time - elapsed)); } -void Scene::Render(RenderTarget* render_target) { - // The Camera and the HUD are ignored here because they're special cases. +void Scene::UpdateLoop() { + while (!destroying) + if (should_tick) + Update(); + else { + auto target_tick_time = std::chrono::microseconds(1000000 / tick_rate); + std::this_thread::sleep_for(std::chrono::microseconds(target_tick_time)); + } +} + +void Scene::Render() { std::vector display_list{}; - for (auto* e : Entity::GetAllDescendents(entity_list)) - if (auto* r = dynamic_cast(e)) + for (auto *e: Entity::GetAllDescendents(entity_list)) + if (auto *r = dynamic_cast(e)) if (r->ShouldRender()) display_list.push_back(r); - std::sort(display_list.begin(), display_list.end(), [](Renderable* a, Renderable* b) { + std::sort(display_list.begin(), display_list.end(), [](Renderable *a, Renderable *b) { return a->GetLayer() > b->GetLayer(); }); - J2D::Begin( render_target, true); - if (active_camera) active_camera->Render(); - for (auto& r : display_list) + for (auto &r: display_list) if (r->ShouldRender()) r->Render(); - J2D::End(); } +void Scene::Render(JGL::RenderTarget* render_target) { + if (!render_target) + Logger::Fatal("Rendering the current scene to a RenderTarget that is nullptr?"); + + J2D::Begin(render_target, true); + Render(); + J2D::End(); + last_rendered_to_window = false; +} + +void Scene::Render(ReWindow::RWindow* window) { + if (!window) + Logger::Fatal("Rendering the current scene to a ReWindow that is nullptr?"); + + J2D::Begin(nullptr, true); + Render(); + J2D::End(); + last_rendered_to_window = true; +} + Scene::~Scene() { - for (auto* itx : instanced_textures) - delete itx; - - for (auto* a : instanced_alpha_masks) - delete a; - - delete entity_list;; - delete active_camera; + Scene::Unload(); } void Scene::Unload() { + destroying = true; + while (!update_worker->Done()) {} + delete update_worker; + for (auto* itx : instanced_textures) delete itx; @@ -104,3 +140,25 @@ void Scene::DestroyInstancedAlphaMask(const Engine::InstancedTexture* alpha_mask delete *it, instanced_alpha_masks.erase(it); } +void Scene::Run() { + update_worker = new ConcurrentWorker([this] { UpdateLoop(); }); +} + +bool Scene::ShouldTick() const { + return should_tick; +} + +void Scene::ShouldTick(bool state) { + should_tick = state; +} + +float Scene::TickDeltaTime() const { + return (1000000.f / tick_rate) / 1000000.f; +} + +float Scene::FrameDeltaTime() const { + if (last_rendered_to_window && Globals::Window) + return Globals::Window->GetDeltaTime(); + return render_target_frame_delta; +} + diff --git a/src/Engine/types/entity/Entity.cpp b/src/Engine/types/entity/Entity.cpp index bf4f0cc..d7af8e8 100644 --- a/src/Engine/types/entity/Entity.cpp +++ b/src/Engine/types/entity/Entity.cpp @@ -1,6 +1,5 @@ #include #include -#include void Engine::Entity::AppendChild(Entity* entity) { if (entity->parent) @@ -17,7 +16,11 @@ Engine::Entity::~Entity() { void Engine::Entity::UpdateChildren() { for (auto& e : children) { + if (!e) + continue; + e->Update(); + if (!e->children.empty()) e->UpdateChildren(); } diff --git a/src/Engine/types/entity/Moby.cpp b/src/Engine/types/entity/Moby.cpp index 6158bf9..fd97bff 100644 --- a/src/Engine/types/entity/Moby.cpp +++ b/src/Engine/types/entity/Moby.cpp @@ -3,16 +3,16 @@ using namespace J3ML; void Engine::Movable::MoveX(float speed) { - position.x = position.x + (speed * Globals::Window->GetDeltaTime()); + position.x = position.x + (speed * Globals::CurrentScene->TickDeltaTime()); } void Engine::Movable::MoveY(float speed) { - position.y = position.y + (speed * Globals::DeltaTime()); + position.y = position.y + (speed * Globals::CurrentScene->TickDeltaTime()); } void Engine::Movable::Move(float angle_rad, float speed) { - position.x = position.x + (speed * Globals::DeltaTime()) * Math::Cos(angle_rad); - position.y = position.y + (speed * Globals::DeltaTime()) * Math::Sin(angle_rad); + position.x = position.x + (speed * Globals::CurrentScene->TickDeltaTime()) * Math::Cos(angle_rad); + position.y = position.y + (speed * Globals::CurrentScene->TickDeltaTime()) * Math::Sin(angle_rad); } void Engine::Movable::MoveForward(float speed) { @@ -21,18 +21,18 @@ void Engine::Movable::MoveForward(float speed) { void Engine::Movable::MoveBackward(float speed) { speed = -speed; - position.x = position.x + (speed * Globals::DeltaTime()) * Math::Cos(face_angle); - position.y = position.y + (speed * Globals::DeltaTime()) * Math::Sin(face_angle); + position.x = position.x + (speed * Globals::CurrentScene->TickDeltaTime()) * Math::Cos(face_angle); + position.y = position.y + (speed * Globals::CurrentScene->TickDeltaTime()) * Math::Sin(face_angle); } void Engine::Movable::MoveLeft(float speed) { - position.x = position.x + (speed * Globals::DeltaTime()) * -Math::Sin(face_angle); - position.y = position.y + (speed * Globals::DeltaTime()) * Math::Cos(face_angle); + position.x = position.x + (speed * Globals::CurrentScene->TickDeltaTime()) * -Math::Sin(face_angle); + position.y = position.y + (speed * Globals::CurrentScene->TickDeltaTime()) * Math::Cos(face_angle); } void Engine::Movable::MoveRight(float speed) { - position.x = position.x + (speed * Globals::DeltaTime()) * Math::Sin(face_angle); - position.y = position.y + (speed * Globals::DeltaTime()) * -Math::Cos(face_angle); + position.x = position.x + (speed * Globals::CurrentScene->TickDeltaTime()) * Math::Sin(face_angle); + position.y = position.y + (speed * Globals::CurrentScene->TickDeltaTime()) * -Math::Cos(face_angle); } void Engine::Movable::SetRotation(float new_face_angle) { @@ -45,7 +45,7 @@ void Engine::Movable::SetRotation(float new_face_angle) { } void Engine::Movable::Rotate(float speed) { - SetRotation(face_angle + speed * Globals::DeltaTime()); + SetRotation(face_angle + speed * Globals::CurrentScene->TickDeltaTime()); } float Engine::Movable::GetRotation() const { diff --git a/src/Game/Entities/Box.cpp b/src/Game/Entities/Box.cpp index 0505462..a62a580 100644 --- a/src/Game/Entities/Box.cpp +++ b/src/Game/Entities/Box.cpp @@ -2,6 +2,7 @@ #include void Game::Box::Update() { + if (Globals::Window->IsKeyDown(Keys::W)) MoveY(-500); if (Globals::Window->IsKeyDown(Keys::S)) diff --git a/src/Game/Scene/ControllableBox.cpp b/src/Game/Scene/ControllableBox.cpp index 6bb9d92..bcd4140 100644 --- a/src/Game/Scene/ControllableBox.cpp +++ b/src/Game/Scene/ControllableBox.cpp @@ -4,7 +4,9 @@ void ControllableBox::Init() { auto* hud = new Game::DemoGameHud(); - auto *b = new Game::Box({0, 0}); - entity_list->AppendChild(b); + for (int i = 0; i < 10; i++) { + auto *b = new Game::Box({0, 0}); + entity_list->AppendChild(b); + } entity_list->AppendChild(hud); } diff --git a/src/Game/Scene/Loading.cpp b/src/Game/Scene/Loading.cpp index 5900bf2..1370935 100644 --- a/src/Game/Scene/Loading.cpp +++ b/src/Game/Scene/Loading.cpp @@ -6,26 +6,22 @@ void LoadingScreen::Init() { RedactedSoftware = new JGL::Texture("assets/sprites/Re3D.png"); } -void LoadingScreen::Update() { - angle += (elapsed * 4) * Globals::DeltaTime(); - elapsed += Globals::DeltaTime(); +void LoadingScreen::Render() { + angle += (elapsed * 4) * Globals::CurrentScene->FrameDeltaTime(); + elapsed += Globals::CurrentScene->FrameDeltaTime(); // Pretend we're actually loading something idk. if (elapsed >= 2.75) Globals::ChangeScene("Scene0"); -} -void LoadingScreen::Render(RenderTarget* render_target) { auto text_length = JGL::Fonts::Jupiteroid.MeasureString("Loading ....", 24); std::string text = "Loading"; int dots = static_cast(floor(elapsed / 0.35)) % 4; for (int i = 0; i < dots; ++i) text += "."; - J2D::Begin(render_target, true); - J2D::DrawSprite(RedactedSoftware, {Globals::Window->GetSize().x - RedactedSoftware->GetDimensions().x, Globals::Window->GetSize().y - RedactedSoftware->GetDimensions().y}, angle, {0.5, 0.5}); - J2D::DrawString(Colors::Whites::Ivory, text, (Globals::Window->GetSize().x - text_length.x) - RedactedSoftware->GetDimensions().x , Globals::Window->GetSize().y - text_length.y * 1.125, 1, 24); - J2D::End(); + J2D::DrawSprite(RedactedSoftware, {Globals::Window->GetSize().x - RedactedSoftware->GetDimensions().x, Globals::Window->GetSize().y - RedactedSoftware->GetDimensions().y}, angle, {0.5, 0.5}); + J2D::DrawString(Colors::Whites::Ivory, text, (Globals::Window->GetSize().x - text_length.x) - RedactedSoftware->GetDimensions().x , Globals::Window->GetSize().y - text_length.y * 1.125, 1, 24); }