Threaded ticks

TODO make movable objects use velocity so rendering still looks smooth when things move
This commit is contained in:
2025-01-08 00:58:27 -05:00
parent 45ccc88794
commit fd98c308f5
14 changed files with 187 additions and 55 deletions

View File

@@ -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")

View File

@@ -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;

9
include/Engine/Logger.h Normal file
View File

@@ -0,0 +1,9 @@
#pragma once
#include <jlog/Logger.hpp>
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};
}

View File

@@ -0,0 +1,24 @@
#pragma once
#include <thread>
#include <utility>
#include <atomic>
#include <sstream>
namespace Engine {
class ConcurrentWorker;
}
class Engine::ConcurrentWorker {
private:
std::atomic<bool> work_complete{ false };
std::thread worker_thread;
public:
[[nodiscard]] bool Done();
public:
~ConcurrentWorker();
template <typename Function, typename... Args>
explicit ConcurrentWorker(Function&& func, Args&&... args) : worker_thread([this, func = std::forward<Function>(func),
...args = std::forward<Args>(args)]() mutable { func(std::forward<Args>(args) ...); work_complete = true; })
{ worker_thread.detach(); }
};

View File

@@ -5,6 +5,9 @@
#include <Engine/types/entity/Entity.h>
#include <Engine/types/entity/Hud.h>
#include <JGL/types/RenderTarget.h>
#include <Engine/types/ConcurrentWorker.h>
#include <rewindow/types/window.h>
#include <thread>
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<InstancedTexture*> instanced_textures{};
std::vector<InstancedTexture*> 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();
};

View File

@@ -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;
};

View File

@@ -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();
}

View File

@@ -0,0 +1,14 @@
#include <Engine/types/ConcurrentWorker.h>
#include <Engine/Logger.h>
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;
}

View File

@@ -1,8 +1,11 @@
#include <Engine/Globals.h>
#include <Engine/types/Scene.h>
#include <Engine/types/entity/Camera.h>
#include <Engine/types/entity/InstancedSprite.h>
#include <Engine/Logger.h>
#include <JGL/JGL.h>
#include <algorithm>
#include <thread>
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<std::chrono::microseconds>(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<Renderable*> display_list{};
for (auto* e : Entity::GetAllDescendents(entity_list))
if (auto* r = dynamic_cast<Renderable*>(e))
for (auto *e: Entity::GetAllDescendents(entity_list))
if (auto *r = dynamic_cast<Renderable *>(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;
}

View File

@@ -1,6 +1,5 @@
#include <J3ML/J3ML.hpp>
#include <Engine/types/entity/Entity.h>
#include <Engine/Globals.h>
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();
}

View File

@@ -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 {

View File

@@ -2,6 +2,7 @@
#include <Engine/Globals.h>
void Game::Box::Update() {
if (Globals::Window->IsKeyDown(Keys::W))
MoveY(-500);
if (Globals::Window->IsKeyDown(Keys::S))

View File

@@ -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);
}

View File

@@ -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<int>(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);
}