ROADMAP.md

This commit is contained in:
2026-01-10 01:59:21 +00:00
parent cadc1d4aa0
commit 5931a2c407
13 changed files with 234 additions and 4 deletions

View File

@@ -302,6 +302,7 @@ set(WORKFLOW_SOURCES
set(FRAME_WORKFLOW_SOURCES
src/services/impl/workflow_frame_begin_step.cpp
src/services/impl/workflow_frame_camera_step.cpp
src/services/impl/workflow_frame_bullet_physics_step.cpp
src/services/impl/workflow_frame_physics_step.cpp
src/services/impl/workflow_frame_scene_step.cpp

View File

@@ -16,6 +16,9 @@
"position": [260, 0],
"inputs": {
"delta": "frame.delta"
},
"outputs": {
"view_state": "frame.view_state"
}
},
{
@@ -39,7 +42,8 @@
"plugin": "frame.render",
"position": [1040, 0],
"inputs": {
"elapsed": "frame.elapsed"
"elapsed": "frame.elapsed",
"view_state": "frame.view_state"
}
},
{

View File

@@ -307,6 +307,7 @@ void ServiceBasedApp::RegisterServices() {
registry_.RegisterService<services::IFrameWorkflowService, services::impl::FrameWorkflowService>(
registry_.GetService<services::ILogger>(),
registry_.GetService<services::IConfigService>(),
registry_.GetService<services::IAudioService>(),
registry_.GetService<services::IInputService>(),
registry_.GetService<services::IPhysicsService>(),

View File

@@ -9,6 +9,7 @@
namespace sdl3cpp::services::impl {
FrameWorkflowService::FrameWorkflowService(std::shared_ptr<ILogger> logger,
std::shared_ptr<IConfigService> configService,
std::shared_ptr<IAudioService> audioService,
std::shared_ptr<IInputService> inputService,
std::shared_ptr<IPhysicsService> physicsService,
@@ -27,6 +28,7 @@ FrameWorkflowService::FrameWorkflowService(std::shared_ptr<ILogger> logger,
workflow_ = parser.ParseFile(path);
FrameWorkflowStepRegistrar registrar(logger_,
std::move(configService),
std::move(audioService),
std::move(inputService),
std::move(physicsService),

View File

@@ -3,6 +3,7 @@
#include "../interfaces/i_frame_workflow_service.hpp"
#include "../interfaces/i_logger.hpp"
#include "../interfaces/i_audio_service.hpp"
#include "../interfaces/i_config_service.hpp"
#include "../interfaces/i_input_service.hpp"
#include "../interfaces/i_physics_service.hpp"
#include "../interfaces/i_render_coordinator_service.hpp"
@@ -21,6 +22,7 @@ namespace sdl3cpp::services::impl {
class FrameWorkflowService final : public IFrameWorkflowService {
public:
FrameWorkflowService(std::shared_ptr<ILogger> logger,
std::shared_ptr<IConfigService> configService,
std::shared_ptr<IAudioService> audioService,
std::shared_ptr<IInputService> inputService,
std::shared_ptr<IPhysicsService> physicsService,

View File

@@ -2,6 +2,7 @@
#include "workflow_frame_audio_step.hpp"
#include "workflow_frame_begin_step.hpp"
#include "workflow_frame_camera_step.hpp"
#include "workflow_frame_bullet_physics_step.hpp"
#include "workflow_frame_gui_step.hpp"
#include "workflow_frame_physics_step.hpp"
@@ -16,6 +17,7 @@
namespace sdl3cpp::services::impl {
FrameWorkflowStepRegistrar::FrameWorkflowStepRegistrar(std::shared_ptr<ILogger> logger,
std::shared_ptr<IConfigService> configService,
std::shared_ptr<IAudioService> audioService,
std::shared_ptr<IInputService> inputService,
std::shared_ptr<IPhysicsService> physicsService,
@@ -23,6 +25,7 @@ FrameWorkflowStepRegistrar::FrameWorkflowStepRegistrar(std::shared_ptr<ILogger>
std::shared_ptr<IRenderCoordinatorService> renderService,
std::shared_ptr<IValidationTourService> validationTourService)
: logger_(std::move(logger)),
configService_(std::move(configService)),
audioService_(std::move(audioService)),
inputService_(std::move(inputService)),
physicsService_(std::move(physicsService)),
@@ -50,6 +53,9 @@ void FrameWorkflowStepRegistrar::RegisterUsedSteps(
if (plugins.contains("frame.bullet_physics")) {
registry->RegisterStep(std::make_shared<WorkflowFrameBulletPhysicsStep>(physicsService_, logger_));
}
if (plugins.contains("frame.camera")) {
registry->RegisterStep(std::make_shared<WorkflowFrameCameraStep>(configService_, logger_));
}
if (plugins.contains("frame.scene")) {
registry->RegisterStep(std::make_shared<WorkflowFrameSceneStep>(sceneService_, logger_));
}

View File

@@ -8,6 +8,7 @@
namespace sdl3cpp::services {
class IAudioService;
class IConfigService;
class IInputService;
class IPhysicsService;
class IRenderCoordinatorService;
@@ -20,6 +21,7 @@ namespace sdl3cpp::services::impl {
class FrameWorkflowStepRegistrar {
public:
FrameWorkflowStepRegistrar(std::shared_ptr<ILogger> logger,
std::shared_ptr<IConfigService> configService,
std::shared_ptr<IAudioService> audioService,
std::shared_ptr<IInputService> inputService,
std::shared_ptr<IPhysicsService> physicsService,
@@ -32,6 +34,7 @@ public:
private:
std::shared_ptr<ILogger> logger_;
std::shared_ptr<IConfigService> configService_;
std::shared_ptr<IAudioService> audioService_;
std::shared_ptr<IInputService> inputService_;
std::shared_ptr<IPhysicsService> physicsService_;

View File

@@ -132,6 +132,14 @@ void RenderCoordinatorService::ConfigureRenderGraphPasses() {
}
void RenderCoordinatorService::RenderFrame(float time) {
RenderFrameInternal(time, nullptr);
}
void RenderCoordinatorService::RenderFrameWithViewState(float time, const ViewState& viewState) {
RenderFrameInternal(time, &viewState);
}
void RenderCoordinatorService::RenderFrameInternal(float time, const ViewState* overrideView) {
if (logger_) {
logger_->Trace("RenderCoordinatorService", "RenderFrame", "time=" + std::to_string(time), "Entering");
}
@@ -244,7 +252,7 @@ void RenderCoordinatorService::RenderFrame(float time) {
validationPlan = validationTourService_->BeginFrame(aspect);
}
ViewState viewState = sceneScriptService_->GetViewState(aspect);
ViewState viewState = overrideView ? *overrideView : sceneScriptService_->GetViewState(aspect);
if (validationPlan.active && validationPlan.overrideView) {
viewState = validationPlan.viewState;
}

View File

@@ -30,9 +30,9 @@ public:
~RenderCoordinatorService() override = default;
void RenderFrame(float time) override;
void RenderFrameWithViewState(float time, const ViewState& viewState) override;
private:
void ConfigureRenderGraphPasses();
std::shared_ptr<ILogger> logger_;
std::shared_ptr<IConfigService> configService_;
@@ -49,6 +49,9 @@ private:
bool shadersLoaded_ = false;
bool geometryUploaded_ = false;
bool configFirstLogged_ = false;
void ConfigureRenderGraphPasses();
void RenderFrameInternal(float time, const ViewState* overrideView);
};
} // namespace sdl3cpp::services::impl

View File

@@ -0,0 +1,160 @@
#include "workflow_frame_camera_step.hpp"
#include "workflow_step_io_resolver.hpp"
#include "../interfaces/graphics_types.hpp"
#include <rapidjson/document.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <cmath>
#include <cstring>
#include <stdexcept>
namespace sdl3cpp::services::impl {
namespace {
struct CameraConfig {
std::array<float, 3> position{0.0f, 0.0f, 5.0f};
std::array<float, 3> lookAt{0.0f, 0.0f, 0.0f};
std::array<float, 3> up{0.0f, 1.0f, 0.0f};
float fov = 0.78f;
float nearPlane = 0.1f;
float farPlane = 1000.0f;
};
std::array<float, 16> ToArray(const glm::mat4& matrix) {
std::array<float, 16> result{};
std::memcpy(result.data(), glm::value_ptr(matrix), sizeof(float) * result.size());
return result;
}
bool ReadVec3(const rapidjson::Value& object, const char* name, std::array<float, 3>& out) {
if (!object.HasMember(name)) {
return false;
}
const auto& value = object[name];
if (!value.IsArray() || value.Size() != 3) {
throw std::runtime_error(std::string("frame.camera: '") + name + "' must be a 3-element array");
}
for (rapidjson::SizeType i = 0; i < 3; ++i) {
if (!value[i].IsNumber()) {
throw std::runtime_error(std::string("frame.camera: '") + name + "' must contain numbers");
}
out[i] = static_cast<float>(value[i].GetDouble());
}
return true;
}
bool ReadNumber(const rapidjson::Value& object, const char* name, float& out) {
if (!object.HasMember(name)) {
return false;
}
const auto& value = object[name];
if (!value.IsNumber()) {
throw std::runtime_error(std::string("frame.camera: '") + name + "' must be a number");
}
out = static_cast<float>(value.GetDouble());
return true;
}
CameraConfig ReadCameraConfig(const std::string& json) {
rapidjson::Document document;
document.Parse(json.c_str());
if (document.HasParseError()) {
throw std::runtime_error("frame.camera: failed to parse config JSON");
}
CameraConfig config{};
if (!document.HasMember("scene") || !document["scene"].IsObject()) {
return config;
}
const auto& scene = document["scene"];
if (!scene.HasMember("camera") || !scene["camera"].IsObject()) {
return config;
}
const auto& camera = scene["camera"];
ReadVec3(camera, "position", config.position);
bool hasLookAt = ReadVec3(camera, "look_at", config.lookAt);
if (!hasLookAt && camera.HasMember("lookAt")) {
ReadVec3(camera, "lookAt", config.lookAt);
hasLookAt = true;
}
if (!hasLookAt) {
config.lookAt = {config.position[0], config.position[1], config.position[2] - 1.0f};
}
ReadVec3(camera, "up", config.up);
if (ReadNumber(camera, "fov_degrees", config.fov)) {
config.fov = glm::radians(config.fov);
} else if (ReadNumber(camera, "fov", config.fov) && config.fov > 3.2f) {
config.fov = glm::radians(config.fov);
}
ReadNumber(camera, "near", config.nearPlane);
ReadNumber(camera, "far", config.farPlane);
return config;
}
ViewState BuildViewState(const CameraConfig& config, float aspect) {
glm::vec3 position(config.position[0], config.position[1], config.position[2]);
glm::vec3 lookAt(config.lookAt[0], config.lookAt[1], config.lookAt[2]);
glm::vec3 up(config.up[0], config.up[1], config.up[2]);
glm::mat4 view = glm::lookAt(position, lookAt, up);
float safeAspect = aspect <= 0.0f ? 1.0f : aspect;
glm::mat4 proj = glm::perspective(config.fov, safeAspect, config.nearPlane, config.farPlane);
ViewState state{};
state.view = ToArray(view);
state.proj = ToArray(proj);
state.viewProj = ToArray(proj * view);
state.cameraPosition = config.position;
return state;
}
} // namespace
WorkflowFrameCameraStep::WorkflowFrameCameraStep(std::shared_ptr<IConfigService> configService,
std::shared_ptr<ILogger> logger)
: configService_(std::move(configService)),
logger_(std::move(logger)) {}
std::string WorkflowFrameCameraStep::GetPluginId() const {
return "frame.camera";
}
void WorkflowFrameCameraStep::Execute(const WorkflowStepDefinition& step, WorkflowContext& context) {
if (!configService_) {
throw std::runtime_error("frame.camera requires an IConfigService");
}
const std::string& configJson = configService_->GetConfigJson();
if (configJson.empty()) {
throw std::runtime_error("frame.camera requires config JSON to be available");
}
WorkflowStepIoResolver resolver;
const std::string deltaKey = resolver.GetRequiredInputKey(step, "delta");
const std::string outputKey = resolver.GetRequiredOutputKey(step, "view_state");
const auto* delta = context.TryGet<double>(deltaKey);
if (!delta) {
throw std::runtime_error("frame.camera missing delta input");
}
CameraConfig cameraConfig = ReadCameraConfig(configJson);
float aspect = 1.0f;
uint32_t width = configService_->GetWindowWidth();
uint32_t height = configService_->GetWindowHeight();
if (width > 0 && height > 0) {
aspect = static_cast<float>(width) / static_cast<float>(height);
}
ViewState viewState = BuildViewState(cameraConfig, aspect);
context.Set(outputKey, viewState);
if (logger_) {
logger_->Trace("WorkflowFrameCameraStep", "Execute",
"delta=" + std::to_string(*delta) +
", output=" + outputKey +
", aspect=" + std::to_string(aspect),
"Camera view state updated");
}
}
} // namespace sdl3cpp::services::impl

View File

@@ -0,0 +1,25 @@
#pragma once
#include "../interfaces/i_config_service.hpp"
#include "../interfaces/i_logger.hpp"
#include "../interfaces/i_workflow_step.hpp"
#include <memory>
#include <string>
namespace sdl3cpp::services::impl {
class WorkflowFrameCameraStep final : public IWorkflowStep {
public:
WorkflowFrameCameraStep(std::shared_ptr<IConfigService> configService,
std::shared_ptr<ILogger> logger);
std::string GetPluginId() const override;
void Execute(const WorkflowStepDefinition& step, WorkflowContext& context) override;
private:
std::shared_ptr<IConfigService> configService_;
std::shared_ptr<ILogger> logger_;
};
} // namespace sdl3cpp::services::impl

View File

@@ -24,7 +24,19 @@ void WorkflowFrameRenderStep::Execute(const WorkflowStepDefinition& step, Workfl
if (!elapsed) {
throw std::runtime_error("frame.render missing elapsed input");
}
renderService_->RenderFrame(static_cast<float>(*elapsed));
const ViewState* viewState = nullptr;
auto it = step.inputs.find("view_state");
if (it != step.inputs.end()) {
viewState = context.TryGet<ViewState>(it->second);
if (!viewState) {
throw std::runtime_error("frame.render missing view_state input");
}
}
if (viewState) {
renderService_->RenderFrameWithViewState(static_cast<float>(*elapsed), *viewState);
} else {
renderService_->RenderFrame(static_cast<float>(*elapsed));
}
if (logger_) {
logger_->Trace("WorkflowFrameRenderStep", "Execute",
"elapsed=" + std::to_string(*elapsed),

View File

@@ -1,5 +1,7 @@
#pragma once
#include "graphics_types.hpp"
namespace sdl3cpp::services {
class IRenderCoordinatorService {
@@ -7,6 +9,7 @@ public:
virtual ~IRenderCoordinatorService() = default;
virtual void RenderFrame(float time) = 0;
virtual void RenderFrameWithViewState(float time, const ViewState& viewState) = 0;
};
} // namespace sdl3cpp::services