ADD: callback function
This commit is contained in:
@@ -114,6 +114,12 @@ add_executable (vulkan_engine
|
||||
# compute
|
||||
compute/vk_compute.h
|
||||
compute/vk_compute.cpp
|
||||
# runtime
|
||||
runtime/i_game_callbacks.h
|
||||
runtime/time_manager.h
|
||||
runtime/time_manager.cpp
|
||||
runtime/game_runtime.h
|
||||
runtime/game_runtime.cpp
|
||||
)
|
||||
|
||||
set_property(TARGET vulkan_engine PROPERTY CXX_STANDARD 20)
|
||||
@@ -270,6 +276,7 @@ target_include_directories(vulkan_engine PUBLIC
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/render/graph"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/scene"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/compute"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/runtime"
|
||||
)
|
||||
|
||||
option(ENABLE_MIKKTS "Use MikkTSpace for tangent generation" ON)
|
||||
|
||||
@@ -594,61 +594,9 @@ void VulkanEngine::init_default_data()
|
||||
sphereMesh = _assetManager->createMesh(ci);
|
||||
}
|
||||
|
||||
// Register default primitives as dynamic scene instances
|
||||
if (_sceneManager)
|
||||
{
|
||||
_sceneManager->addMeshInstance("default.cube", cubeMesh,
|
||||
glm::translate(glm::mat4(1.f), glm::vec3(-2.f, 0.f, -2.f)));
|
||||
_sceneManager->addMeshInstance("default.sphere", sphereMesh,
|
||||
glm::translate(glm::mat4(1.f), glm::vec3(2.f, 0.f, -2.f)),
|
||||
BoundsType::Sphere);
|
||||
}
|
||||
|
||||
// Test textured primitives
|
||||
{
|
||||
AssetManager::MeshMaterialDesc matDesc;
|
||||
matDesc.kind = AssetManager::MeshMaterialDesc::Kind::Textured;
|
||||
matDesc.options.albedoPath = "textures/grass_albedo.png";
|
||||
matDesc.options.normalPath = "textures/grass_normal.png";
|
||||
matDesc.options.metalRoughPath = "textures/grass_mro.png";
|
||||
matDesc.options.occlusionPath = "textures/grass_ao.png";
|
||||
|
||||
addPrimitiveInstance("textured.cube",
|
||||
AssetManager::MeshGeometryDesc::Type::Cube,
|
||||
glm::translate(glm::mat4(1.f), glm::vec3(0.f, 1.f, -4.f)),
|
||||
matDesc);
|
||||
|
||||
addPrimitiveInstance("textured.sphere",
|
||||
AssetManager::MeshGeometryDesc::Type::Sphere,
|
||||
glm::translate(glm::mat4(1.f), glm::vec3(3.f, 1.f, -4.f)),
|
||||
matDesc);
|
||||
|
||||
addPrimitiveInstance("textured.plane",
|
||||
AssetManager::MeshGeometryDesc::Type::Plane,
|
||||
glm::scale(glm::translate(glm::mat4(1.f), glm::vec3(0.f, 0.f, -6.f)), glm::vec3(4.f)),
|
||||
matDesc);
|
||||
}
|
||||
|
||||
if (addGLTFInstance("mirage", "mirage2000/scene.gltf", glm::mat4(1.0f)))
|
||||
{
|
||||
preloadInstanceTextures("mirage");
|
||||
}
|
||||
|
||||
// Windmill animation test
|
||||
{
|
||||
glm::mat4 windmillTransform = glm::translate(glm::mat4(1.0f), glm::vec3(10.0f, 0.0f, 0.0f));
|
||||
windmillTransform = glm::scale(windmillTransform, glm::vec3(0.5f));
|
||||
if (addGLTFInstance("windmill", "windmill/scene.gltf", windmillTransform))
|
||||
{
|
||||
preloadInstanceTextures("windmill");
|
||||
// Enable first animation (index 0) with looping
|
||||
if (_sceneManager)
|
||||
{
|
||||
_sceneManager->setGLTFInstanceAnimation("windmill", 0, true);
|
||||
_sceneManager->setGLTFInstanceAnimationLoop("windmill", true);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Note: Default primitive meshes (cubeMesh, sphereMesh) are created above
|
||||
// but no longer spawned as scene instances. Use GameRuntime callbacks
|
||||
// or GameAPI to add objects to the scene at runtime.
|
||||
|
||||
_mainDeletionQueue.push_function([&]() {
|
||||
_resourceManager->destroy_image(_whiteImage);
|
||||
|
||||
@@ -1840,80 +1840,171 @@ namespace
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// Window visibility states for menu-bar toggles
|
||||
namespace
|
||||
{
|
||||
struct DebugWindowStates
|
||||
{
|
||||
bool show_overview{false};
|
||||
bool show_window{false};
|
||||
bool show_background{false};
|
||||
bool show_particles{false};
|
||||
bool show_shadows{false};
|
||||
bool show_render_graph{false};
|
||||
bool show_pipelines{false};
|
||||
bool show_ibl{false};
|
||||
bool show_postfx{false};
|
||||
bool show_scene{false};
|
||||
bool show_async_assets{false};
|
||||
bool show_textures{false};
|
||||
};
|
||||
static DebugWindowStates g_debug_windows;
|
||||
} // namespace
|
||||
|
||||
void vk_engine_draw_debug_ui(VulkanEngine *eng)
|
||||
{
|
||||
if (!eng) return;
|
||||
|
||||
ImGuizmo::BeginFrame();
|
||||
|
||||
// Consolidated debug window with tabs
|
||||
if (ImGui::Begin("Debug"))
|
||||
// Main menu bar at the top
|
||||
if (ImGui::BeginMainMenuBar())
|
||||
{
|
||||
const ImGuiTabBarFlags tf =
|
||||
ImGuiTabBarFlags_Reorderable | ImGuiTabBarFlags_AutoSelectNewTabs;
|
||||
if (ImGui::BeginTabBar("DebugTabs", tf))
|
||||
if (ImGui::BeginMenu("View"))
|
||||
{
|
||||
if (ImGui::BeginTabItem("Overview"))
|
||||
{
|
||||
ui_overview(eng);
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
if (ImGui::BeginTabItem("Window"))
|
||||
{
|
||||
ui_window(eng);
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
if (ImGui::BeginTabItem("Background"))
|
||||
{
|
||||
ui_background(eng);
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
if (ImGui::BeginTabItem("Particles"))
|
||||
{
|
||||
ui_particles(eng);
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
if (ImGui::BeginTabItem("Shadows"))
|
||||
{
|
||||
ui_shadows(eng);
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
if (ImGui::BeginTabItem("Render Graph"))
|
||||
{
|
||||
ui_render_graph(eng);
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
if (ImGui::BeginTabItem("Pipelines"))
|
||||
{
|
||||
ui_pipelines(eng);
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
if (ImGui::BeginTabItem("IBL"))
|
||||
{
|
||||
ui_ibl(eng);
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
if (ImGui::BeginTabItem("PostFX"))
|
||||
{
|
||||
ui_postfx(eng);
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
if (ImGui::BeginTabItem("Scene"))
|
||||
{
|
||||
ui_scene(eng);
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
if (ImGui::BeginTabItem("Async Assets"))
|
||||
{
|
||||
ui_async_assets(eng);
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
if (ImGui::BeginTabItem("Textures"))
|
||||
{
|
||||
ui_textures(eng);
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
ImGui::EndTabBar();
|
||||
ImGui::MenuItem("Overview", nullptr, &g_debug_windows.show_overview);
|
||||
ImGui::MenuItem("Window", nullptr, &g_debug_windows.show_window);
|
||||
ImGui::Separator();
|
||||
ImGui::MenuItem("Scene", nullptr, &g_debug_windows.show_scene);
|
||||
ImGui::MenuItem("Render Graph", nullptr, &g_debug_windows.show_render_graph);
|
||||
ImGui::MenuItem("Pipelines", nullptr, &g_debug_windows.show_pipelines);
|
||||
ImGui::Separator();
|
||||
ImGui::MenuItem("Shadows", nullptr, &g_debug_windows.show_shadows);
|
||||
ImGui::MenuItem("IBL", nullptr, &g_debug_windows.show_ibl);
|
||||
ImGui::MenuItem("PostFX", nullptr, &g_debug_windows.show_postfx);
|
||||
ImGui::MenuItem("Background", nullptr, &g_debug_windows.show_background);
|
||||
ImGui::Separator();
|
||||
ImGui::MenuItem("Particles", nullptr, &g_debug_windows.show_particles);
|
||||
ImGui::MenuItem("Textures", nullptr, &g_debug_windows.show_textures);
|
||||
ImGui::MenuItem("Async Assets", nullptr, &g_debug_windows.show_async_assets);
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
// Quick stats in menu bar
|
||||
ImGui::Separator();
|
||||
ImGui::Text("%.1f ms | %d tris | %d draws",
|
||||
eng->stats.frametime,
|
||||
eng->stats.triangle_count,
|
||||
eng->stats.drawcall_count);
|
||||
|
||||
ImGui::EndMainMenuBar();
|
||||
}
|
||||
|
||||
// Individual debug windows (only shown when toggled)
|
||||
if (g_debug_windows.show_overview)
|
||||
{
|
||||
if (ImGui::Begin("Overview", &g_debug_windows.show_overview))
|
||||
{
|
||||
ui_overview(eng);
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
if (g_debug_windows.show_window)
|
||||
{
|
||||
if (ImGui::Begin("Window Settings", &g_debug_windows.show_window))
|
||||
{
|
||||
ui_window(eng);
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
if (g_debug_windows.show_background)
|
||||
{
|
||||
if (ImGui::Begin("Background", &g_debug_windows.show_background))
|
||||
{
|
||||
ui_background(eng);
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
if (g_debug_windows.show_particles)
|
||||
{
|
||||
if (ImGui::Begin("Particles", &g_debug_windows.show_particles))
|
||||
{
|
||||
ui_particles(eng);
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
if (g_debug_windows.show_shadows)
|
||||
{
|
||||
if (ImGui::Begin("Shadows", &g_debug_windows.show_shadows))
|
||||
{
|
||||
ui_shadows(eng);
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
if (g_debug_windows.show_render_graph)
|
||||
{
|
||||
if (ImGui::Begin("Render Graph", &g_debug_windows.show_render_graph))
|
||||
{
|
||||
ui_render_graph(eng);
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
if (g_debug_windows.show_pipelines)
|
||||
{
|
||||
if (ImGui::Begin("Pipelines", &g_debug_windows.show_pipelines))
|
||||
{
|
||||
ui_pipelines(eng);
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
if (g_debug_windows.show_ibl)
|
||||
{
|
||||
if (ImGui::Begin("IBL", &g_debug_windows.show_ibl))
|
||||
{
|
||||
ui_ibl(eng);
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
if (g_debug_windows.show_postfx)
|
||||
{
|
||||
if (ImGui::Begin("PostFX", &g_debug_windows.show_postfx))
|
||||
{
|
||||
ui_postfx(eng);
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
if (g_debug_windows.show_scene)
|
||||
{
|
||||
if (ImGui::Begin("Scene", &g_debug_windows.show_scene))
|
||||
{
|
||||
ui_scene(eng);
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
if (g_debug_windows.show_async_assets)
|
||||
{
|
||||
if (ImGui::Begin("Async Assets", &g_debug_windows.show_async_assets))
|
||||
{
|
||||
ui_async_assets(eng);
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
if (g_debug_windows.show_textures)
|
||||
{
|
||||
if (ImGui::Begin("Textures", &g_debug_windows.show_textures))
|
||||
{
|
||||
ui_textures(eng);
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
84
src/main.cpp
84
src/main.cpp
@@ -1,14 +1,86 @@
|
||||
// Main entry point for Vulkan Engine
|
||||
//
|
||||
// Two modes are available:
|
||||
// 1. Legacy mode: Uses VulkanEngine::run() directly (simple, no game separation)
|
||||
// 2. GameRuntime mode: Uses GameRuntime for clean game/engine separation
|
||||
//
|
||||
// Set USE_GAME_RUNTIME to 1 to enable GameRuntime with example callbacks.
|
||||
|
||||
#define USE_GAME_RUNTIME 1
|
||||
|
||||
#include "core/engine.h"
|
||||
|
||||
#if USE_GAME_RUNTIME
|
||||
#include "runtime/game_runtime.h"
|
||||
#include <glm/gtx/transform.hpp>
|
||||
|
||||
// Example game implementation using IGameCallbacks
|
||||
class ExampleGame : public GameRuntime::IGameCallbacks
|
||||
{
|
||||
public:
|
||||
void on_init(GameRuntime::Runtime& runtime) override
|
||||
{
|
||||
// Example: Set up initial scene
|
||||
auto& api = runtime.api();
|
||||
|
||||
// Load a glTF model asynchronously
|
||||
// api.load_gltf_async("example_model", "models/example.gltf",
|
||||
// GameAPI::Transform{}.with_position({0, 0, 0}));
|
||||
|
||||
// Spawn a primitive
|
||||
// api.spawn_mesh_instance("test_cube", api.primitive_cube(),
|
||||
// GameAPI::Transform{}.with_position({2, 0, 0}));
|
||||
|
||||
// Set up camera
|
||||
// api.set_camera_position({0, 5, -10});
|
||||
// api.set_camera_rotation({-20, 0, 0});
|
||||
}
|
||||
|
||||
void on_update(float dt) override
|
||||
{
|
||||
// Called every frame with variable delta time
|
||||
// Use for rendering-dependent logic, input handling, camera control
|
||||
_elapsed += dt;
|
||||
}
|
||||
|
||||
void on_fixed_update(float fixed_dt) override
|
||||
{
|
||||
// Called at fixed intervals (default: 60Hz)
|
||||
// Use for physics updates, AI tick, game state simulation
|
||||
// Example: Apply physics forces, update AI state machines
|
||||
}
|
||||
|
||||
void on_shutdown() override
|
||||
{
|
||||
// Called before engine shutdown
|
||||
// Use for cleanup, saving game state, etc.
|
||||
}
|
||||
|
||||
private:
|
||||
float _elapsed{0.0f};
|
||||
};
|
||||
#endif // USE_GAME_RUNTIME
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
VulkanEngine engine;
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
|
||||
engine.init();
|
||||
VulkanEngine engine;
|
||||
engine.init();
|
||||
|
||||
engine.run();
|
||||
#if USE_GAME_RUNTIME
|
||||
// GameRuntime mode: clean separation between engine and game logic
|
||||
{
|
||||
GameRuntime::Runtime runtime(&engine);
|
||||
ExampleGame game;
|
||||
runtime.run(&game);
|
||||
}
|
||||
#else
|
||||
// Legacy mode: simple direct engine loop
|
||||
engine.run();
|
||||
#endif
|
||||
|
||||
engine.cleanup();
|
||||
|
||||
return 0;
|
||||
engine.cleanup();
|
||||
return 0;
|
||||
}
|
||||
211
src/runtime/game_runtime.cpp
Normal file
211
src/runtime/game_runtime.cpp
Normal file
@@ -0,0 +1,211 @@
|
||||
#include "game_runtime.h"
|
||||
#include "core/engine.h"
|
||||
#include "core/input/input_system.h"
|
||||
#include "core/ui/imgui_system.h"
|
||||
#include "scene/vk_scene.h"
|
||||
|
||||
#include "SDL2/SDL.h"
|
||||
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
|
||||
namespace GameRuntime
|
||||
{
|
||||
Runtime::Runtime(VulkanEngine *renderer)
|
||||
: _renderer(renderer)
|
||||
{
|
||||
_api = std::make_unique<GameAPI::Engine>(renderer);
|
||||
}
|
||||
|
||||
Runtime::~Runtime() = default;
|
||||
|
||||
void Runtime::set_physics_world(IPhysicsWorld *physics)
|
||||
{
|
||||
_physics = physics;
|
||||
}
|
||||
|
||||
void Runtime::set_audio_system(IAudioSystem *audio)
|
||||
{
|
||||
_audio = audio;
|
||||
}
|
||||
|
||||
void Runtime::sync_physics_to_render()
|
||||
{
|
||||
// TODO: When physics integration is added, sync physics body transforms
|
||||
// to their corresponding render instances here.
|
||||
// For each physics body with a render instance:
|
||||
// glm::mat4 transform;
|
||||
// _physics->get_body_transform(bodyId, transform);
|
||||
// _api->set_mesh_instance_transform(instanceName, GameAPI::Transform::from_matrix(transform));
|
||||
}
|
||||
|
||||
void Runtime::update_audio_listener()
|
||||
{
|
||||
if (!_audio || !_renderer || !_renderer->_sceneManager)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto &cam = _renderer->_sceneManager->getMainCamera();
|
||||
glm::vec3 pos = _renderer->_sceneManager->get_camera_local_position();
|
||||
|
||||
// Calculate forward and up from camera orientation quaternion
|
||||
glm::vec3 forward = glm::normalize(cam.orientation * glm::vec3(0.0f, 0.0f, -1.0f));
|
||||
glm::vec3 up = glm::normalize(cam.orientation * glm::vec3(0.0f, 1.0f, 0.0f));
|
||||
|
||||
_audio->set_listener(pos, forward, up);
|
||||
}
|
||||
|
||||
void Runtime::run(IGameCallbacks *game)
|
||||
{
|
||||
if (!game || !_renderer)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_quit_requested = false;
|
||||
|
||||
// Call game initialization
|
||||
game->on_init(*this);
|
||||
|
||||
// Main game loop
|
||||
while (!_quit_requested)
|
||||
{
|
||||
// --- Begin frame: time, input ---
|
||||
_time.begin_frame();
|
||||
|
||||
InputSystem *input = _renderer->input();
|
||||
if (input)
|
||||
{
|
||||
input->begin_frame();
|
||||
input->pump_events();
|
||||
|
||||
if (input->quit_requested())
|
||||
{
|
||||
_quit_requested = true;
|
||||
}
|
||||
|
||||
_renderer->freeze_rendering = input->window_minimized();
|
||||
|
||||
if (input->resize_requested())
|
||||
{
|
||||
_renderer->resize_requested = true;
|
||||
input->clear_resize_request();
|
||||
}
|
||||
}
|
||||
|
||||
// --- Process UI and input capture ---
|
||||
const bool ui_capture_mouse = _renderer->ui() && _renderer->ui()->want_capture_mouse();
|
||||
const bool ui_capture_keyboard = _renderer->ui() && _renderer->ui()->want_capture_keyboard();
|
||||
|
||||
// Dispatch native events to UI and picking
|
||||
if (input)
|
||||
{
|
||||
struct DispatchCtx
|
||||
{
|
||||
VulkanEngine *engine;
|
||||
bool ui_capture_mouse;
|
||||
} ctx{_renderer, ui_capture_mouse};
|
||||
|
||||
input->for_each_native_event([](void *user, InputSystem::NativeEventView view) {
|
||||
auto *c = static_cast<DispatchCtx *>(user);
|
||||
if (!c || !c->engine || view.backend != InputSystem::NativeBackend::SDL2 || view.data == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
const SDL_Event &e = *static_cast<const SDL_Event *>(view.data);
|
||||
if (c->engine->ui())
|
||||
{
|
||||
c->engine->ui()->process_event(e);
|
||||
}
|
||||
if (c->engine->picking())
|
||||
{
|
||||
c->engine->picking()->process_event(e, c->ui_capture_mouse);
|
||||
}
|
||||
}, &ctx);
|
||||
}
|
||||
|
||||
// --- Camera input (if not captured by UI) ---
|
||||
if (_renderer->_sceneManager && input)
|
||||
{
|
||||
_renderer->_sceneManager->getMainCamera().process_input(*input, ui_capture_keyboard, ui_capture_mouse);
|
||||
}
|
||||
|
||||
// --- Throttle when minimized ---
|
||||
if (_renderer->freeze_rendering)
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
continue;
|
||||
}
|
||||
|
||||
// --- Handle resize ---
|
||||
if (_renderer->resize_requested)
|
||||
{
|
||||
if (_renderer->_swapchainManager)
|
||||
{
|
||||
_renderer->_swapchainManager->resize_swapchain(_renderer->_window);
|
||||
if (_renderer->ui())
|
||||
{
|
||||
_renderer->ui()->on_swapchain_recreated();
|
||||
}
|
||||
_renderer->resize_requested = false;
|
||||
}
|
||||
}
|
||||
|
||||
// --- Fixed update loop (physics) ---
|
||||
while (_time.consume_fixed_step())
|
||||
{
|
||||
game->on_fixed_update(_time.fixed_delta_time());
|
||||
|
||||
if (_physics)
|
||||
{
|
||||
_physics->step(_time.fixed_delta_time());
|
||||
}
|
||||
}
|
||||
|
||||
// --- Sync physics transforms to render ---
|
||||
sync_physics_to_render();
|
||||
|
||||
// --- Variable update ---
|
||||
game->on_update(_time.delta_time());
|
||||
|
||||
// --- Audio listener update ---
|
||||
update_audio_listener();
|
||||
if (_audio)
|
||||
{
|
||||
_audio->update();
|
||||
}
|
||||
|
||||
// --- Wait for GPU and prepare frame ---
|
||||
VK_CHECK(vkWaitForFences(_renderer->_deviceManager->device(), 1,
|
||||
&_renderer->get_current_frame()._renderFence, true, 1000000000));
|
||||
|
||||
if (_renderer->_rayManager)
|
||||
{
|
||||
_renderer->_rayManager->flushPendingDeletes();
|
||||
_renderer->_rayManager->pump_blas_builds(1);
|
||||
}
|
||||
|
||||
// --- Flush per-frame resources ---
|
||||
_renderer->get_current_frame()._deletionQueue.flush();
|
||||
if (_renderer->_renderGraph)
|
||||
{
|
||||
_renderer->_renderGraph->resolve_timings();
|
||||
}
|
||||
_renderer->get_current_frame()._frameDescriptors.clear_pools(_renderer->_deviceManager->device());
|
||||
|
||||
// --- ImGui ---
|
||||
if (_renderer->ui())
|
||||
{
|
||||
_renderer->ui()->begin_frame();
|
||||
_renderer->ui()->end_frame();
|
||||
}
|
||||
|
||||
// --- Draw ---
|
||||
_renderer->draw();
|
||||
}
|
||||
|
||||
// Call game shutdown
|
||||
game->on_shutdown();
|
||||
}
|
||||
} // namespace GameRuntime
|
||||
145
src/runtime/game_runtime.h
Normal file
145
src/runtime/game_runtime.h
Normal file
@@ -0,0 +1,145 @@
|
||||
#pragma once
|
||||
|
||||
// GameRuntime: High-level game loop manager
|
||||
// Provides a clean separation between engine and game logic with proper
|
||||
// time management, fixed timestep for physics, and game callbacks.
|
||||
|
||||
#include "i_game_callbacks.h"
|
||||
#include "time_manager.h"
|
||||
#include "core/game_api.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
class VulkanEngine;
|
||||
|
||||
namespace GameRuntime
|
||||
{
|
||||
|
||||
// Forward declarations for future integrations
|
||||
class IPhysicsWorld;
|
||||
class IAudioSystem;
|
||||
|
||||
class Runtime
|
||||
{
|
||||
public:
|
||||
explicit Runtime(VulkanEngine* renderer);
|
||||
~Runtime();
|
||||
|
||||
// Non-copyable
|
||||
Runtime(const Runtime&) = delete;
|
||||
Runtime& operator=(const Runtime&) = delete;
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// External System Integration (optional)
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
// Set physics world (e.g., Jolt, Bullet, PhysX wrapper)
|
||||
void set_physics_world(IPhysicsWorld* physics);
|
||||
IPhysicsWorld* physics() const { return _physics; }
|
||||
|
||||
// Set audio system (e.g., FMOD, OpenAL wrapper)
|
||||
void set_audio_system(IAudioSystem* audio);
|
||||
IAudioSystem* audio() const { return _audio; }
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Time Management
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
// Get time manager for direct access
|
||||
TimeManager& time() { return _time; }
|
||||
const TimeManager& time() const { return _time; }
|
||||
|
||||
// Convenience accessors
|
||||
float delta_time() const { return _time.delta_time(); }
|
||||
float fixed_delta_time() const { return _time.fixed_delta_time(); }
|
||||
float time_scale() const { return _time.time_scale(); }
|
||||
void set_time_scale(float scale) { _time.set_time_scale(scale); }
|
||||
void set_fixed_delta_time(float dt) { _time.set_fixed_delta_time(dt); }
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Game API Access
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
// Get the high-level game API for engine interaction
|
||||
GameAPI::Engine& api() { return *_api; }
|
||||
const GameAPI::Engine& api() const { return *_api; }
|
||||
|
||||
// Get the underlying Vulkan engine (for advanced use)
|
||||
VulkanEngine* renderer() const { return _renderer; }
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Main Loop
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
// Run the game loop with the given callback handler.
|
||||
// Blocks until the game exits.
|
||||
void run(IGameCallbacks* game);
|
||||
|
||||
// Request quit (sets quit flag, loop will exit next frame)
|
||||
void request_quit() { _quit_requested = true; }
|
||||
|
||||
// Check if quit was requested
|
||||
bool quit_requested() const { return _quit_requested; }
|
||||
|
||||
private:
|
||||
VulkanEngine* _renderer{nullptr};
|
||||
std::unique_ptr<GameAPI::Engine> _api;
|
||||
TimeManager _time;
|
||||
|
||||
IPhysicsWorld* _physics{nullptr};
|
||||
IAudioSystem* _audio{nullptr};
|
||||
|
||||
bool _quit_requested{false};
|
||||
|
||||
// Internal helpers
|
||||
void sync_physics_to_render();
|
||||
void update_audio_listener();
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Physics Interface (for future integration)
|
||||
// ============================================================================
|
||||
|
||||
class IPhysicsWorld
|
||||
{
|
||||
public:
|
||||
virtual ~IPhysicsWorld() = default;
|
||||
|
||||
// Step the physics simulation by dt seconds
|
||||
virtual void step(float dt) = 0;
|
||||
|
||||
// Get transform of a physics body by ID
|
||||
virtual void get_body_transform(uint32_t id, glm::mat4& out) = 0;
|
||||
|
||||
// Raycast into the physics world
|
||||
struct RayHit
|
||||
{
|
||||
bool hit{false};
|
||||
glm::vec3 position{0.0f};
|
||||
glm::vec3 normal{0.0f};
|
||||
float distance{0.0f};
|
||||
uint32_t bodyId{0};
|
||||
};
|
||||
virtual RayHit raycast(const glm::vec3& origin, const glm::vec3& direction, float maxDistance) = 0;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Audio Interface (for future integration)
|
||||
// ============================================================================
|
||||
|
||||
class IAudioSystem
|
||||
{
|
||||
public:
|
||||
virtual ~IAudioSystem() = default;
|
||||
|
||||
// Set listener position and orientation
|
||||
virtual void set_listener(const glm::vec3& position, const glm::vec3& forward, const glm::vec3& up) = 0;
|
||||
|
||||
// Play a 3D sound at a position
|
||||
virtual void play_3d(const std::string& event, const glm::vec3& position) = 0;
|
||||
|
||||
// Update audio system (call once per frame)
|
||||
virtual void update() = 0;
|
||||
};
|
||||
|
||||
} // namespace GameRuntime
|
||||
35
src/runtime/i_game_callbacks.h
Normal file
35
src/runtime/i_game_callbacks.h
Normal file
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
// IGameCallbacks: Interface for game logic callbacks
|
||||
// Implement this interface and pass to GameRuntime::run() to receive game loop events.
|
||||
|
||||
namespace GameRuntime
|
||||
{
|
||||
|
||||
class Runtime;
|
||||
|
||||
class IGameCallbacks
|
||||
{
|
||||
public:
|
||||
virtual ~IGameCallbacks() = default;
|
||||
|
||||
// Called once after runtime initialization, before the first update.
|
||||
// Use this to load initial assets, spawn entities, set up the camera, etc.
|
||||
virtual void on_init(Runtime& runtime) = 0;
|
||||
|
||||
// Called every frame with variable delta time.
|
||||
// Use for rendering-dependent logic, input handling, camera control, etc.
|
||||
// @param dt: Frame delta time in seconds (clamped to 0.0-0.1)
|
||||
virtual void on_update(float dt) = 0;
|
||||
|
||||
// Called at fixed intervals for physics/simulation.
|
||||
// Use for physics updates, AI tick, game state simulation, etc.
|
||||
// @param fixed_dt: Fixed delta time in seconds (typically 1/60)
|
||||
virtual void on_fixed_update(float fixed_dt) = 0;
|
||||
|
||||
// Called once before shutdown.
|
||||
// Use for cleanup, saving state, etc.
|
||||
virtual void on_shutdown() = 0;
|
||||
};
|
||||
|
||||
} // namespace GameRuntime
|
||||
52
src/runtime/time_manager.cpp
Normal file
52
src/runtime/time_manager.cpp
Normal file
@@ -0,0 +1,52 @@
|
||||
#include "time_manager.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace GameRuntime
|
||||
{
|
||||
TimeManager::TimeManager()
|
||||
{
|
||||
_start_time = Clock::now();
|
||||
_last_time = _start_time;
|
||||
}
|
||||
|
||||
void TimeManager::begin_frame()
|
||||
{
|
||||
TimePoint now = Clock::now();
|
||||
auto elapsed = std::chrono::duration<float>(now - _last_time);
|
||||
_last_time = now;
|
||||
|
||||
// Clamp delta time to avoid spiral of death
|
||||
_unscaled_delta_time = std::min(elapsed.count(), k_max_delta_time);
|
||||
_delta_time = _unscaled_delta_time * _time_scale;
|
||||
|
||||
// Update total times
|
||||
_total_time += _delta_time;
|
||||
_unscaled_total_time += _unscaled_delta_time;
|
||||
|
||||
// Accumulate for fixed timestep
|
||||
_fixed_accumulator += _delta_time;
|
||||
|
||||
++_frame_count;
|
||||
}
|
||||
|
||||
void TimeManager::set_fixed_delta_time(float dt)
|
||||
{
|
||||
_fixed_delta_time = std::clamp(dt, 1.0f / 240.0f, 1.0f / 10.0f);
|
||||
}
|
||||
|
||||
void TimeManager::set_time_scale(float scale)
|
||||
{
|
||||
_time_scale = std::max(0.0f, scale);
|
||||
}
|
||||
|
||||
bool TimeManager::consume_fixed_step()
|
||||
{
|
||||
if (_fixed_accumulator >= _fixed_delta_time)
|
||||
{
|
||||
_fixed_accumulator -= _fixed_delta_time;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
} // namespace GameRuntime
|
||||
71
src/runtime/time_manager.h
Normal file
71
src/runtime/time_manager.h
Normal file
@@ -0,0 +1,71 @@
|
||||
#pragma once
|
||||
|
||||
// TimeManager: Manages game time, time scale, and fixed timestep accumulation.
|
||||
|
||||
#include <chrono>
|
||||
|
||||
namespace GameRuntime
|
||||
{
|
||||
|
||||
class TimeManager
|
||||
{
|
||||
public:
|
||||
TimeManager();
|
||||
|
||||
// Begin a new frame. Call at the start of each frame.
|
||||
void begin_frame();
|
||||
|
||||
// Get delta time in seconds since last frame (scaled by time_scale, clamped).
|
||||
float delta_time() const { return _delta_time; }
|
||||
|
||||
// Get unscaled delta time in seconds since last frame (clamped).
|
||||
float unscaled_delta_time() const { return _unscaled_delta_time; }
|
||||
|
||||
// Get fixed delta time for physics updates.
|
||||
float fixed_delta_time() const { return _fixed_delta_time; }
|
||||
|
||||
// Set fixed delta time for physics updates (default: 1/60).
|
||||
void set_fixed_delta_time(float dt);
|
||||
|
||||
// Get time scale multiplier (default: 1.0).
|
||||
float time_scale() const { return _time_scale; }
|
||||
|
||||
// Set time scale multiplier (0 = paused, 0.5 = half speed, 2 = double speed).
|
||||
void set_time_scale(float scale);
|
||||
|
||||
// Get total elapsed time in seconds since start.
|
||||
float total_time() const { return _total_time; }
|
||||
|
||||
// Get total unscaled elapsed time in seconds since start.
|
||||
float unscaled_total_time() const { return _unscaled_total_time; }
|
||||
|
||||
// Get accumulated fixed time (for physics step loop).
|
||||
float fixed_accumulator() const { return _fixed_accumulator; }
|
||||
|
||||
// Consume fixed timestep from accumulator. Returns true if step should run.
|
||||
bool consume_fixed_step();
|
||||
|
||||
// Get frame count since start.
|
||||
uint64_t frame_count() const { return _frame_count; }
|
||||
|
||||
private:
|
||||
using Clock = std::chrono::high_resolution_clock;
|
||||
using TimePoint = std::chrono::time_point<Clock>;
|
||||
|
||||
TimePoint _last_time;
|
||||
TimePoint _start_time;
|
||||
|
||||
float _delta_time{0.0f};
|
||||
float _unscaled_delta_time{0.0f};
|
||||
float _fixed_delta_time{1.0f / 60.0f};
|
||||
float _time_scale{1.0f};
|
||||
float _total_time{0.0f};
|
||||
float _unscaled_total_time{0.0f};
|
||||
float _fixed_accumulator{0.0f};
|
||||
|
||||
uint64_t _frame_count{0};
|
||||
|
||||
static constexpr float k_max_delta_time = 0.1f; // 100ms cap
|
||||
};
|
||||
|
||||
} // namespace GameRuntime
|
||||
Reference in New Issue
Block a user