diff --git a/CMakeLists.txt b/CMakeLists.txt index 236a40d..fea0b50 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -297,7 +297,13 @@ set(WORKFLOW_SOURCES src/services/impl/workflow_config_migration_step.cpp src/services/impl/workflow_config_schema_step.cpp src/services/impl/workflow_default_step_registrar.cpp + src/services/impl/workflow_list_filter_equals_step.cpp + src/services/impl/workflow_list_map_add_step.cpp + src/services/impl/workflow_list_reduce_sum_step.cpp + src/services/impl/workflow_number_add_step.cpp src/services/impl/workflow_runtime_config_step.cpp + src/services/impl/workflow_value_copy_step.cpp + src/services/impl/workflow_value_default_step.cpp ) set(FRAME_WORKFLOW_SOURCES diff --git a/ROADMAP.md b/ROADMAP.md index c9c07c1..014808d 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -63,7 +63,7 @@ Treat JSON config as a declarative control plane that compiles into scene, resou - Execute render graph schedule in the renderer (attachments, lifetimes, view ordering). - Add runtime probe hooks and map probe severity to crash recovery policies. - Enforce shader uniform compatibility using reflection + material metadata. -- Finish soundboard workflow steps (`soundboard.catalog.scan`, `soundboard.gui`, `soundboard.audio`) with real plugins. +- Validate soundboard workflow steps (catalog/GUI/audio) in runtime and retire the Lua soundboard UI path. - Port Lua demos (seed/gui/soundboard/quake3) into JSON-first packages and workflows. - Add tests for schema/merge rules, render graph validation, and budget enforcement. - Start service refactor program for large modules (approaching 2K LOC). @@ -259,13 +259,12 @@ Option B: per-shader only - [x] Workflow schema: `config/schema/workflow_v1.schema.json` supports n8n nodes + connections. - [x] Templates: `config/workflows/templates/boot_default.json`, `config/workflows/templates/frame_default.json`. - [x] Package workflows converted to n8n nodes (`seed`, `gui`, `soundboard`, `quake3`, `engine_tester`). -- [x] Workflow steps implemented: `frame.bullet_physics`, `frame.camera`, `validation.tour.checkpoint`. -- [ ] Workflow steps pending: `soundboard.catalog.scan`, `soundboard.gui`, `soundboard.audio`. +- [x] Workflow steps implemented: `frame.bullet_physics`, `frame.camera`, `validation.tour.checkpoint`, `soundboard.catalog.scan`, `soundboard.gui`, `soundboard.audio`. +- [x] Render coordinator supports workflow-supplied GUI command overrides (bypass Lua GUI path). ### Next Steps -- [ ] Implement soundboard workflow steps with real plugins (catalog scan, GUI render, audio dispatch). - [ ] Publish gameplay workflow templates (FPS/passive camera variants, bullet physics, validation/teleport checks). -- [ ] Add JSON-driven GUI step(s) to replace Lua GUI scripts in demo packages. +- [ ] Expand JSON-driven GUI steps beyond soundboard (replace Lua GUI scripts in demo packages). - [ ] Add workflow step analytics (probe events with JSON path + node id). ## Feature Matrix (What You Get, When You Get It) @@ -293,7 +292,7 @@ Option B: per-shader only - [x] `config/schema/` with versioned JSON Schema and migration notes - [x] n8n workflow schema + parser support (nodes + connections) - [x] n8n templates + package workflow conversion -- [~] Workflow step plugins for camera + validation checkpoints + bullet physics (soundboard steps pending) +- [x] Workflow step plugins for camera + validation checkpoints + bullet physics + soundboard catalog/GUI/audio - [x] `src/services/impl/config_compiler_service.*` for JSON -> IR compilation - [x] `src/services/impl/render_graph_service.*` for graph build and scheduling - [x] `src/services/interfaces/i_probe_service.hpp` plus report/event types diff --git a/src/services/impl/frame_workflow_step_registrar.cpp b/src/services/impl/frame_workflow_step_registrar.cpp index 426f86a..761cd76 100644 --- a/src/services/impl/frame_workflow_step_registrar.cpp +++ b/src/services/impl/frame_workflow_step_registrar.cpp @@ -8,10 +8,16 @@ #include "workflow_frame_physics_step.hpp" #include "workflow_frame_render_step.hpp" #include "workflow_frame_scene_step.hpp" +#include "workflow_list_filter_equals_step.hpp" +#include "workflow_list_map_add_step.hpp" +#include "workflow_list_reduce_sum_step.hpp" +#include "workflow_number_add_step.hpp" #include "workflow_soundboard_audio_step.hpp" #include "workflow_soundboard_catalog_scan_step.hpp" #include "workflow_soundboard_gui_step.hpp" #include "workflow_step_registry.hpp" +#include "workflow_value_copy_step.hpp" +#include "workflow_value_default_step.hpp" #include "workflow_validation_checkpoint_step.hpp" #include @@ -91,6 +97,24 @@ void FrameWorkflowStepRegistrar::RegisterUsedSteps( soundboardStateService_, logger_)); } + if (plugins.contains("value.copy")) { + registry->RegisterStep(std::make_shared(logger_)); + } + if (plugins.contains("value.default")) { + registry->RegisterStep(std::make_shared(logger_)); + } + if (plugins.contains("number.add")) { + registry->RegisterStep(std::make_shared(logger_)); + } + if (plugins.contains("list.filter.equals")) { + registry->RegisterStep(std::make_shared(logger_)); + } + if (plugins.contains("list.map.add")) { + registry->RegisterStep(std::make_shared(logger_)); + } + if (plugins.contains("list.reduce.sum")) { + registry->RegisterStep(std::make_shared(logger_)); + } } } // namespace sdl3cpp::services::impl diff --git a/src/services/impl/workflow_default_step_registrar.cpp b/src/services/impl/workflow_default_step_registrar.cpp index 1606bbb..a8fd6f6 100644 --- a/src/services/impl/workflow_default_step_registrar.cpp +++ b/src/services/impl/workflow_default_step_registrar.cpp @@ -4,6 +4,12 @@ #include "workflow_config_schema_step.hpp" #include "workflow_config_version_step.hpp" #include "workflow_runtime_config_step.hpp" +#include "workflow_list_filter_equals_step.hpp" +#include "workflow_list_map_add_step.hpp" +#include "workflow_list_reduce_sum_step.hpp" +#include "workflow_number_add_step.hpp" +#include "workflow_value_copy_step.hpp" +#include "workflow_value_default_step.hpp" #include #include @@ -43,6 +49,24 @@ void WorkflowDefaultStepRegistrar::RegisterUsedSteps( if (plugins.contains("runtime.config.build")) { registry->RegisterStep(std::make_shared(logger_)); } + if (plugins.contains("value.copy")) { + registry->RegisterStep(std::make_shared(logger_)); + } + if (plugins.contains("value.default")) { + registry->RegisterStep(std::make_shared(logger_)); + } + if (plugins.contains("number.add")) { + registry->RegisterStep(std::make_shared(logger_)); + } + if (plugins.contains("list.filter.equals")) { + registry->RegisterStep(std::make_shared(logger_)); + } + if (plugins.contains("list.map.add")) { + registry->RegisterStep(std::make_shared(logger_)); + } + if (plugins.contains("list.reduce.sum")) { + registry->RegisterStep(std::make_shared(logger_)); + } } } // namespace sdl3cpp::services::impl diff --git a/src/services/impl/workflow_list_filter_equals_step.cpp b/src/services/impl/workflow_list_filter_equals_step.cpp new file mode 100644 index 0000000..cef6251 --- /dev/null +++ b/src/services/impl/workflow_list_filter_equals_step.cpp @@ -0,0 +1,72 @@ +#include "workflow_list_filter_equals_step.hpp" +#include "workflow_step_io_resolver.hpp" + +#include +#include +#include +#include + +namespace sdl3cpp::services::impl { + +WorkflowListFilterEqualsStep::WorkflowListFilterEqualsStep(std::shared_ptr logger) + : logger_(std::move(logger)) {} + +std::string WorkflowListFilterEqualsStep::GetPluginId() const { + return "list.filter.equals"; +} + +void WorkflowListFilterEqualsStep::Execute(const WorkflowStepDefinition& step, WorkflowContext& context) { + WorkflowStepIoResolver resolver; + const std::string listKey = resolver.GetRequiredInputKey(step, "list"); + const std::string valueKey = resolver.GetRequiredInputKey(step, "value"); + const std::string outputKey = resolver.GetRequiredOutputKey(step, "list"); + + if (const auto* list = context.TryGet>(listKey)) { + const auto* value = context.TryGet(valueKey); + if (!value) { + throw std::runtime_error("list.filter.equals missing numeric value input '" + valueKey + "'"); + } + std::vector filtered; + filtered.reserve(list->size()); + for (double entry : *list) { + if (entry == *value) { + filtered.push_back(entry); + } + } + context.Set(outputKey, std::move(filtered)); + if (logger_) { + logger_->Trace("WorkflowListFilterEqualsStep", "Execute", + "type=double, input=" + listKey + + ", output=" + outputKey, + "Filtered numeric list"); + } + return; + } + + if (const auto* list = context.TryGet>(listKey)) { + const auto* value = context.TryGet(valueKey); + if (!value) { + throw std::runtime_error("list.filter.equals missing string value input '" + valueKey + "'"); + } + std::vector filtered; + filtered.reserve(list->size()); + for (const auto& entry : *list) { + if (entry == *value) { + filtered.push_back(entry); + } + } + context.Set(outputKey, std::move(filtered)); + if (logger_) { + logger_->Trace("WorkflowListFilterEqualsStep", "Execute", + "type=string, input=" + listKey + + ", output=" + outputKey, + "Filtered string list"); + } + return; + } + + throw std::runtime_error("list.filter.equals requires list input '" + listKey + + "' to be vector or vector"); +} + +} // namespace sdl3cpp::services::impl diff --git a/src/services/impl/workflow_list_filter_equals_step.hpp b/src/services/impl/workflow_list_filter_equals_step.hpp new file mode 100644 index 0000000..34ec835 --- /dev/null +++ b/src/services/impl/workflow_list_filter_equals_step.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include "../interfaces/i_logger.hpp" +#include "../interfaces/i_workflow_step.hpp" + +#include + +namespace sdl3cpp::services::impl { + +class WorkflowListFilterEqualsStep final : public IWorkflowStep { +public: + explicit WorkflowListFilterEqualsStep(std::shared_ptr logger); + + std::string GetPluginId() const override; + void Execute(const WorkflowStepDefinition& step, WorkflowContext& context) override; + +private: + std::shared_ptr logger_; +}; + +} // namespace sdl3cpp::services::impl diff --git a/src/services/impl/workflow_list_map_add_step.cpp b/src/services/impl/workflow_list_map_add_step.cpp new file mode 100644 index 0000000..e7f3ccc --- /dev/null +++ b/src/services/impl/workflow_list_map_add_step.cpp @@ -0,0 +1,48 @@ +#include "workflow_list_map_add_step.hpp" +#include "workflow_step_io_resolver.hpp" + +#include +#include +#include + +namespace sdl3cpp::services::impl { + +WorkflowListMapAddStep::WorkflowListMapAddStep(std::shared_ptr logger) + : logger_(std::move(logger)) {} + +std::string WorkflowListMapAddStep::GetPluginId() const { + return "list.map.add"; +} + +void WorkflowListMapAddStep::Execute(const WorkflowStepDefinition& step, WorkflowContext& context) { + WorkflowStepIoResolver resolver; + const std::string listKey = resolver.GetRequiredInputKey(step, "list"); + const std::string valueKey = resolver.GetRequiredInputKey(step, "value"); + const std::string outputKey = resolver.GetRequiredOutputKey(step, "list"); + + const auto* list = context.TryGet>(listKey); + if (!list) { + throw std::runtime_error("list.map.add missing numeric list input '" + listKey + "'"); + } + const auto* value = context.TryGet(valueKey); + if (!value) { + throw std::runtime_error("list.map.add missing numeric value input '" + valueKey + "'"); + } + + std::vector mapped; + mapped.reserve(list->size()); + for (double entry : *list) { + mapped.push_back(entry + *value); + } + context.Set(outputKey, std::move(mapped)); + + if (logger_) { + logger_->Trace("WorkflowListMapAddStep", "Execute", + "input=" + listKey + + ", add=" + std::to_string(*value) + + ", output=" + outputKey, + "Mapped numeric list"); + } +} + +} // namespace sdl3cpp::services::impl diff --git a/src/services/impl/workflow_list_map_add_step.hpp b/src/services/impl/workflow_list_map_add_step.hpp new file mode 100644 index 0000000..453cba0 --- /dev/null +++ b/src/services/impl/workflow_list_map_add_step.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include "../interfaces/i_logger.hpp" +#include "../interfaces/i_workflow_step.hpp" + +#include + +namespace sdl3cpp::services::impl { + +class WorkflowListMapAddStep final : public IWorkflowStep { +public: + explicit WorkflowListMapAddStep(std::shared_ptr logger); + + std::string GetPluginId() const override; + void Execute(const WorkflowStepDefinition& step, WorkflowContext& context) override; + +private: + std::shared_ptr logger_; +}; + +} // namespace sdl3cpp::services::impl diff --git a/src/services/impl/workflow_list_reduce_sum_step.cpp b/src/services/impl/workflow_list_reduce_sum_step.cpp new file mode 100644 index 0000000..241593f --- /dev/null +++ b/src/services/impl/workflow_list_reduce_sum_step.cpp @@ -0,0 +1,41 @@ +#include "workflow_list_reduce_sum_step.hpp" +#include "workflow_step_io_resolver.hpp" + +#include +#include +#include + +namespace sdl3cpp::services::impl { + +WorkflowListReduceSumStep::WorkflowListReduceSumStep(std::shared_ptr logger) + : logger_(std::move(logger)) {} + +std::string WorkflowListReduceSumStep::GetPluginId() const { + return "list.reduce.sum"; +} + +void WorkflowListReduceSumStep::Execute(const WorkflowStepDefinition& step, WorkflowContext& context) { + WorkflowStepIoResolver resolver; + const std::string listKey = resolver.GetRequiredInputKey(step, "list"); + const std::string outputKey = resolver.GetRequiredOutputKey(step, "value"); + + const auto* list = context.TryGet>(listKey); + if (!list) { + throw std::runtime_error("list.reduce.sum missing numeric list input '" + listKey + "'"); + } + + double sum = 0.0; + for (double entry : *list) { + sum += entry; + } + context.Set(outputKey, sum); + + if (logger_) { + logger_->Trace("WorkflowListReduceSumStep", "Execute", + "input=" + listKey + + ", output=" + outputKey, + "Reduced numeric list"); + } +} + +} // namespace sdl3cpp::services::impl diff --git a/src/services/impl/workflow_list_reduce_sum_step.hpp b/src/services/impl/workflow_list_reduce_sum_step.hpp new file mode 100644 index 0000000..1907264 --- /dev/null +++ b/src/services/impl/workflow_list_reduce_sum_step.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include "../interfaces/i_logger.hpp" +#include "../interfaces/i_workflow_step.hpp" + +#include + +namespace sdl3cpp::services::impl { + +class WorkflowListReduceSumStep final : public IWorkflowStep { +public: + explicit WorkflowListReduceSumStep(std::shared_ptr logger); + + std::string GetPluginId() const override; + void Execute(const WorkflowStepDefinition& step, WorkflowContext& context) override; + +private: + std::shared_ptr logger_; +}; + +} // namespace sdl3cpp::services::impl diff --git a/src/services/impl/workflow_number_add_step.cpp b/src/services/impl/workflow_number_add_step.cpp new file mode 100644 index 0000000..b6b956c --- /dev/null +++ b/src/services/impl/workflow_number_add_step.cpp @@ -0,0 +1,40 @@ +#include "workflow_number_add_step.hpp" +#include "workflow_step_io_resolver.hpp" + +#include +#include + +namespace sdl3cpp::services::impl { + +WorkflowNumberAddStep::WorkflowNumberAddStep(std::shared_ptr logger) + : logger_(std::move(logger)) {} + +std::string WorkflowNumberAddStep::GetPluginId() const { + return "number.add"; +} + +void WorkflowNumberAddStep::Execute(const WorkflowStepDefinition& step, WorkflowContext& context) { + WorkflowStepIoResolver resolver; + const std::string leftKey = resolver.GetRequiredInputKey(step, "left"); + const std::string rightKey = resolver.GetRequiredInputKey(step, "right"); + const std::string outputKey = resolver.GetRequiredOutputKey(step, "value"); + + const auto* left = context.TryGet(leftKey); + const auto* right = context.TryGet(rightKey); + if (!left || !right) { + throw std::runtime_error("number.add missing inputs '" + leftKey + "' or '" + rightKey + "'"); + } + + const double sum = *left + *right; + context.Set(outputKey, sum); + + if (logger_) { + logger_->Trace("WorkflowNumberAddStep", "Execute", + "left=" + std::to_string(*left) + + ", right=" + std::to_string(*right) + + ", output=" + outputKey, + "Added workflow numbers"); + } +} + +} // namespace sdl3cpp::services::impl