diff --git a/CMakeLists.txt b/CMakeLists.txt index 08f4ba0..86b8342 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -284,6 +284,17 @@ set(JSON_CONFIG_SOURCES src/services/impl/json_config_migration_service.cpp ) +set(WORKFLOW_SOURCES + src/services/impl/workflow_step_io_resolver.cpp + src/services/impl/workflow_step_registry.cpp + src/services/impl/workflow_executor.cpp + src/services/impl/workflow_definition_parser.cpp + src/services/impl/workflow_config_load_step.cpp + src/services/impl/workflow_config_version_step.cpp + src/services/impl/workflow_config_schema_step.cpp + src/services/impl/workflow_default_step_registrar.cpp +) + if(BUILD_SDL3_APP) add_executable(sdl3_app src/main.cpp @@ -291,6 +302,7 @@ if(BUILD_SDL3_APP) src/di/service_registry.cpp src/events/event_bus.cpp ${JSON_CONFIG_SOURCES} + ${WORKFLOW_SOURCES} src/services/impl/config_compiler_service.cpp src/services/impl/command_line_service.cpp src/services/impl/json_config_writer_service.cpp diff --git a/ROADMAP.md b/ROADMAP.md index a21e269..23ae9d5 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -226,6 +226,22 @@ Option B: per-shader only } ``` +## Workflow Engine (n8n-Style Micro Steps) +### Goals +- Describe boot + frame pipelines as a declarative JSON workflow graph. +- Keep each step tiny (<100 LOC), with explicit inputs/outputs and DI-backed plugin lookup. +- Package common pipelines as templates so users don't start from scratch. + +### Status +- [~] Workflow core: step registry + executor + JSON definition parser. +- [~] Default step package: `config.load`, `config.version.validate`, `config.schema.validate`. +- [x] Workflow schema: `config/schema/workflow_v1.schema.json`. +- [x] Template package: `config/workflows/templates/boot_default.json`. + +### Next Steps +- Wire boot pipeline to use workflow executor (config load/validate/migrate). +- Add frame workflow template (BeginFrame → RenderGraph → Capture → Validate). + ## Feature Matrix (What You Get, When You Get It) | Feature | Status | Starter | Pro | Ultra | Enterprise | @@ -267,6 +283,7 @@ Option B: per-shader only - [~] Budget enforcement tests (GUI cache pruning + texture tracker covered; transient pool pending) - [~] Config-driven validation tour (checkpoint captures + image/ratio/luma/sample-point checks) - [ ] Smoke test: cube demo boots with config-first scene definition +- [ ] Workflow parser tests (template loading + invalid step diagnostics) ## Test Strategy (Solid Coverage Plan) ### Goals diff --git a/config/schema/workflow_v1.schema.json b/config/schema/workflow_v1.schema.json new file mode 100644 index 0000000..9fa2eb7 --- /dev/null +++ b/config/schema/workflow_v1.schema.json @@ -0,0 +1,45 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "SDL3CPP Workflow v1", + "type": "object", + "properties": { + "template": { + "type": "string" + }, + "steps": { + "type": "array", + "items": { + "type": "object", + "required": [ + "id", + "plugin" + ], + "properties": { + "id": { + "type": "string" + }, + "plugin": { + "type": "string" + }, + "inputs": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "outputs": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "additionalProperties": false + } + } + }, + "required": [ + "steps" + ], + "additionalProperties": false +} diff --git a/config/workflows/templates/boot_default.json b/config/workflows/templates/boot_default.json new file mode 100644 index 0000000..19e684f --- /dev/null +++ b/config/workflows/templates/boot_default.json @@ -0,0 +1,34 @@ +{ + "template": "boot.default", + "steps": [ + { + "id": "load_config", + "plugin": "config.load", + "inputs": { + "path": "config.path" + }, + "outputs": { + "document": "config.document" + } + }, + { + "id": "validate_version", + "plugin": "config.version.validate", + "inputs": { + "document": "config.document", + "path": "config.path" + }, + "outputs": { + "version": "config.version" + } + }, + { + "id": "validate_schema", + "plugin": "config.schema.validate", + "inputs": { + "document": "config.document", + "path": "config.path" + } + } + ] +} diff --git a/src/app/service_based_app.cpp b/src/app/service_based_app.cpp index 20940e3..8cbd62d 100644 --- a/src/app/service_based_app.cpp +++ b/src/app/service_based_app.cpp @@ -37,11 +37,16 @@ #include "services/impl/logger_service.hpp" #include "services/impl/pipeline_compiler_service.hpp" #include "services/impl/validation_tour_service.hpp" +#include "services/impl/workflow_default_step_registrar.hpp" +#include "services/impl/workflow_executor.hpp" +#include "services/impl/workflow_step_registry.hpp" #include "services/interfaces/i_platform_service.hpp" #include "services/interfaces/i_probe_service.hpp" #include "services/interfaces/i_render_graph_service.hpp" #include "services/interfaces/i_shader_system_registry.hpp" #include "services/interfaces/i_validation_tour_service.hpp" +#include "services/interfaces/i_workflow_executor.hpp" +#include "services/interfaces/i_workflow_step_registry.hpp" #include "services/interfaces/i_config_compiler_service.hpp" #include #include @@ -223,6 +228,16 @@ void ServiceBasedApp::RegisterServices() { registry_.RegisterService( registry_.GetService()); + // Workflow step registry + executor (declarative boot/frame pipelines) + registry_.RegisterService(); + registry_.RegisterService( + registry_.GetService(), + registry_.GetService()); + services::impl::WorkflowDefaultStepRegistrar workflowRegistrar( + registry_.GetService(), + registry_.GetService()); + workflowRegistrar.RegisterDefaults(registry_.GetService()); + // Configuration service registry_.RegisterService( registry_.GetService(), diff --git a/src/services/impl/workflow_config_load_step.cpp b/src/services/impl/workflow_config_load_step.cpp new file mode 100644 index 0000000..50874f9 --- /dev/null +++ b/src/services/impl/workflow_config_load_step.cpp @@ -0,0 +1,40 @@ +#include "workflow_config_load_step.hpp" +#include "json_config_document_loader.hpp" +#include "workflow_step_io_resolver.hpp" + +#include + +#include +#include +#include +#include + +namespace sdl3cpp::services::impl { + +WorkflowConfigLoadStep::WorkflowConfigLoadStep(std::shared_ptr logger) + : logger_(std::move(logger)) {} + +std::string WorkflowConfigLoadStep::GetPluginId() const { + return "config.load"; +} + +void WorkflowConfigLoadStep::Execute(const WorkflowStepDefinition& step, WorkflowContext& context) { + WorkflowStepIoResolver resolver; + const std::string pathKey = resolver.GetRequiredInputKey(step, "path"); + std::filesystem::path pathValue; + if (const auto* path = context.TryGet(pathKey)) { + pathValue = *path; + } else if (const auto* pathString = context.TryGet(pathKey)) { + pathValue = *pathString; + } else { + throw std::runtime_error("Workflow config.load missing path input '" + pathKey + "'"); + } + + json_config::JsonConfigDocumentLoader loader(logger_); + auto document = std::make_shared(loader.Load(pathValue)); + + const std::string outputKey = resolver.GetRequiredOutputKey(step, "document"); + context.Set(outputKey, std::move(document)); +} + +} // namespace sdl3cpp::services::impl diff --git a/src/services/impl/workflow_config_load_step.hpp b/src/services/impl/workflow_config_load_step.hpp new file mode 100644 index 0000000..22cbdda --- /dev/null +++ b/src/services/impl/workflow_config_load_step.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include "../interfaces/i_workflow_step.hpp" +#include "../interfaces/i_logger.hpp" + +#include + +namespace sdl3cpp::services::impl { + +class WorkflowConfigLoadStep : public IWorkflowStep { +public: + explicit WorkflowConfigLoadStep(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_config_schema_step.cpp b/src/services/impl/workflow_config_schema_step.cpp new file mode 100644 index 0000000..1810c1e --- /dev/null +++ b/src/services/impl/workflow_config_schema_step.cpp @@ -0,0 +1,45 @@ +#include "workflow_config_schema_step.hpp" +#include "json_config_schema_validator.hpp" +#include "workflow_step_io_resolver.hpp" + +#include + +#include +#include +#include +#include + +namespace sdl3cpp::services::impl { + +WorkflowConfigSchemaStep::WorkflowConfigSchemaStep(std::shared_ptr logger, + std::shared_ptr probeService) + : logger_(std::move(logger)), + probeService_(std::move(probeService)) {} + +std::string WorkflowConfigSchemaStep::GetPluginId() const { + return "config.schema.validate"; +} + +void WorkflowConfigSchemaStep::Execute(const WorkflowStepDefinition& step, WorkflowContext& context) { + WorkflowStepIoResolver resolver; + const std::string documentKey = resolver.GetRequiredInputKey(step, "document"); + const std::string pathKey = resolver.GetRequiredInputKey(step, "path"); + const auto* document = context.TryGet>(documentKey); + if (!document || !(*document)) { + throw std::runtime_error("Workflow config.schema.validate missing document input '" + documentKey + "'"); + } + + std::filesystem::path pathValue; + if (const auto* path = context.TryGet(pathKey)) { + pathValue = *path; + } else if (const auto* pathString = context.TryGet(pathKey)) { + pathValue = *pathString; + } else { + throw std::runtime_error("Workflow config.schema.validate missing path input '" + pathKey + "'"); + } + + json_config::JsonConfigSchemaValidator validator(logger_, probeService_); + validator.ValidateOrThrow(**document, pathValue); +} + +} // namespace sdl3cpp::services::impl diff --git a/src/services/impl/workflow_config_schema_step.hpp b/src/services/impl/workflow_config_schema_step.hpp new file mode 100644 index 0000000..ee8492d --- /dev/null +++ b/src/services/impl/workflow_config_schema_step.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include "../interfaces/i_workflow_step.hpp" +#include "../interfaces/i_logger.hpp" +#include "../interfaces/i_probe_service.hpp" + +#include + +namespace sdl3cpp::services::impl { + +class WorkflowConfigSchemaStep : public IWorkflowStep { +public: + WorkflowConfigSchemaStep(std::shared_ptr logger, + std::shared_ptr probeService); + + std::string GetPluginId() const override; + void Execute(const WorkflowStepDefinition& step, WorkflowContext& context) override; + +private: + std::shared_ptr logger_; + std::shared_ptr probeService_; +}; + +} // namespace sdl3cpp::services::impl diff --git a/src/services/impl/workflow_config_version_step.cpp b/src/services/impl/workflow_config_version_step.cpp new file mode 100644 index 0000000..4ee4d31 --- /dev/null +++ b/src/services/impl/workflow_config_version_step.cpp @@ -0,0 +1,46 @@ +#include "workflow_config_version_step.hpp" +#include "json_config_version_validator.hpp" +#include "workflow_step_io_resolver.hpp" + +#include + +#include +#include +#include +#include + +namespace sdl3cpp::services::impl { + +WorkflowConfigVersionStep::WorkflowConfigVersionStep(std::shared_ptr logger) + : logger_(std::move(logger)) {} + +std::string WorkflowConfigVersionStep::GetPluginId() const { + return "config.version.validate"; +} + +void WorkflowConfigVersionStep::Execute(const WorkflowStepDefinition& step, WorkflowContext& context) { + WorkflowStepIoResolver resolver; + const std::string documentKey = resolver.GetRequiredInputKey(step, "document"); + const std::string pathKey = resolver.GetRequiredInputKey(step, "path"); + const auto* document = context.TryGet>(documentKey); + if (!document || !(*document)) { + throw std::runtime_error("Workflow config.version.validate missing document input '" + documentKey + "'"); + } + + std::filesystem::path pathValue; + if (const auto* path = context.TryGet(pathKey)) { + pathValue = *path; + } else if (const auto* pathString = context.TryGet(pathKey)) { + pathValue = *pathString; + } else { + throw std::runtime_error("Workflow config.version.validate missing path input '" + pathKey + "'"); + } + + json_config::JsonConfigVersionValidator validator(logger_); + const auto version = validator.Validate(**document, pathValue); + + const std::string outputKey = resolver.GetRequiredOutputKey(step, "version"); + context.Set(outputKey, version); +} + +} // namespace sdl3cpp::services::impl diff --git a/src/services/impl/workflow_config_version_step.hpp b/src/services/impl/workflow_config_version_step.hpp new file mode 100644 index 0000000..9d782a5 --- /dev/null +++ b/src/services/impl/workflow_config_version_step.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include "../interfaces/i_workflow_step.hpp" +#include "../interfaces/i_logger.hpp" + +#include + +namespace sdl3cpp::services::impl { + +class WorkflowConfigVersionStep : public IWorkflowStep { +public: + explicit WorkflowConfigVersionStep(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_default_step_registrar.cpp b/src/services/impl/workflow_default_step_registrar.cpp new file mode 100644 index 0000000..f1380ba --- /dev/null +++ b/src/services/impl/workflow_default_step_registrar.cpp @@ -0,0 +1,24 @@ +#include "workflow_default_step_registrar.hpp" +#include "workflow_config_load_step.hpp" +#include "workflow_config_schema_step.hpp" +#include "workflow_config_version_step.hpp" + +#include + +namespace sdl3cpp::services::impl { + +WorkflowDefaultStepRegistrar::WorkflowDefaultStepRegistrar(std::shared_ptr logger, + std::shared_ptr probeService) + : logger_(std::move(logger)), + probeService_(std::move(probeService)) {} + +void WorkflowDefaultStepRegistrar::RegisterDefaults(const std::shared_ptr& registry) const { + if (!registry) { + throw std::runtime_error("WorkflowDefaultStepRegistrar: registry is null"); + } + registry->RegisterStep(std::make_shared(logger_)); + registry->RegisterStep(std::make_shared(logger_)); + registry->RegisterStep(std::make_shared(logger_, probeService_)); +} + +} // namespace sdl3cpp::services::impl diff --git a/src/services/impl/workflow_default_step_registrar.hpp b/src/services/impl/workflow_default_step_registrar.hpp new file mode 100644 index 0000000..ec8b60f --- /dev/null +++ b/src/services/impl/workflow_default_step_registrar.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include "../interfaces/i_logger.hpp" +#include "../interfaces/i_probe_service.hpp" +#include "../interfaces/i_workflow_step_registry.hpp" + +namespace sdl3cpp::services::impl { + +class WorkflowDefaultStepRegistrar { +public: + WorkflowDefaultStepRegistrar(std::shared_ptr logger, + std::shared_ptr probeService); + + void RegisterDefaults(const std::shared_ptr& registry) const; + +private: + std::shared_ptr logger_; + std::shared_ptr probeService_; +}; + +} // namespace sdl3cpp::services::impl diff --git a/src/services/impl/workflow_definition_parser.cpp b/src/services/impl/workflow_definition_parser.cpp new file mode 100644 index 0000000..bf04fac --- /dev/null +++ b/src/services/impl/workflow_definition_parser.cpp @@ -0,0 +1,71 @@ +#include "workflow_definition_parser.hpp" +#include "json_config_document_parser.hpp" + +#include + +#include +#include +#include + +namespace sdl3cpp::services::impl { + +namespace { +std::string ReadRequiredString(const rapidjson::Value& object, const char* name) { + if (!object.HasMember(name) || !object[name].IsString()) { + throw std::runtime_error("Workflow member '" + std::string(name) + "' must be a string"); + } + return object[name].GetString(); +} + +std::unordered_map ReadStringMap(const rapidjson::Value& object, + const char* name) { + std::unordered_map result; + if (!object.HasMember(name)) { + return result; + } + const auto& mapValue = object[name]; + if (!mapValue.IsObject()) { + throw std::runtime_error("Workflow member '" + std::string(name) + "' must be an object"); + } + for (auto it = mapValue.MemberBegin(); it != mapValue.MemberEnd(); ++it) { + if (!it->value.IsString()) { + throw std::runtime_error("Workflow map '" + std::string(name) + "' must map to strings"); + } + result[it->name.GetString()] = it->value.GetString(); + } + return result; +} +} // namespace + +WorkflowDefinition WorkflowDefinitionParser::ParseFile(const std::filesystem::path& path) const { + json_config::JsonConfigDocumentParser parser; + rapidjson::Document document = parser.Parse(path, "workflow file"); + + if (!document.HasMember("steps") || !document["steps"].IsArray()) { + throw std::runtime_error("Workflow must contain a 'steps' array"); + } + + WorkflowDefinition workflow; + if (document.HasMember("template")) { + if (!document["template"].IsString()) { + throw std::runtime_error("Workflow member 'template' must be a string"); + } + workflow.templateName = document["template"].GetString(); + } + + for (const auto& entry : document["steps"].GetArray()) { + if (!entry.IsObject()) { + throw std::runtime_error("Workflow steps must be objects"); + } + WorkflowStepDefinition step; + step.id = ReadRequiredString(entry, "id"); + step.plugin = ReadRequiredString(entry, "plugin"); + step.inputs = ReadStringMap(entry, "inputs"); + step.outputs = ReadStringMap(entry, "outputs"); + workflow.steps.push_back(std::move(step)); + } + + return workflow; +} + +} // namespace sdl3cpp::services::impl diff --git a/src/services/impl/workflow_definition_parser.hpp b/src/services/impl/workflow_definition_parser.hpp new file mode 100644 index 0000000..4a77a86 --- /dev/null +++ b/src/services/impl/workflow_definition_parser.hpp @@ -0,0 +1,14 @@ +#pragma once + +#include "../interfaces/workflow_definition.hpp" + +#include + +namespace sdl3cpp::services::impl { + +class WorkflowDefinitionParser { +public: + WorkflowDefinition ParseFile(const std::filesystem::path& path) const; +}; + +} // namespace sdl3cpp::services::impl diff --git a/src/services/impl/workflow_executor.cpp b/src/services/impl/workflow_executor.cpp new file mode 100644 index 0000000..9de5bfb --- /dev/null +++ b/src/services/impl/workflow_executor.cpp @@ -0,0 +1,34 @@ +#include "workflow_executor.hpp" + +#include + +namespace sdl3cpp::services::impl { + +WorkflowExecutor::WorkflowExecutor(std::shared_ptr registry, + std::shared_ptr logger) + : registry_(std::move(registry)), + logger_(std::move(logger)) { + if (!registry_) { + throw std::runtime_error("WorkflowExecutor requires a step registry"); + } +} + +void WorkflowExecutor::Execute(const WorkflowDefinition& workflow, WorkflowContext& context) { + if (logger_) { + logger_->Trace("WorkflowExecutor", "Execute", + "steps=" + std::to_string(workflow.steps.size()), + "Starting workflow execution"); + } + for (const auto& step : workflow.steps) { + auto handler = registry_->GetStep(step.plugin); + if (!handler) { + throw std::runtime_error("WorkflowExecutor: no step registered for plugin '" + step.plugin + "'"); + } + handler->Execute(step, context); + } + if (logger_) { + logger_->Trace("WorkflowExecutor", "Execute", "", "Workflow execution complete"); + } +} + +} // namespace sdl3cpp::services::impl diff --git a/src/services/impl/workflow_executor.hpp b/src/services/impl/workflow_executor.hpp new file mode 100644 index 0000000..a6a8b6e --- /dev/null +++ b/src/services/impl/workflow_executor.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include "../interfaces/i_workflow_executor.hpp" +#include "../interfaces/i_workflow_step_registry.hpp" +#include "../interfaces/i_logger.hpp" + +namespace sdl3cpp::services::impl { + +class WorkflowExecutor : public IWorkflowExecutor { +public: + WorkflowExecutor(std::shared_ptr registry, + std::shared_ptr logger); + + void Execute(const WorkflowDefinition& workflow, WorkflowContext& context) override; + +private: + std::shared_ptr registry_; + std::shared_ptr logger_; +}; + +} // namespace sdl3cpp::services::impl diff --git a/src/services/impl/workflow_step_io_resolver.cpp b/src/services/impl/workflow_step_io_resolver.cpp new file mode 100644 index 0000000..8015684 --- /dev/null +++ b/src/services/impl/workflow_step_io_resolver.cpp @@ -0,0 +1,25 @@ +#include "workflow_step_io_resolver.hpp" + +#include + +namespace sdl3cpp::services::impl { + +std::string WorkflowStepIoResolver::GetRequiredInputKey(const WorkflowStepDefinition& step, + const std::string& name) const { + auto it = step.inputs.find(name); + if (it == step.inputs.end()) { + throw std::runtime_error("Workflow step '" + step.id + "' missing input '" + name + "'"); + } + return it->second; +} + +std::string WorkflowStepIoResolver::GetRequiredOutputKey(const WorkflowStepDefinition& step, + const std::string& name) const { + auto it = step.outputs.find(name); + if (it == step.outputs.end()) { + throw std::runtime_error("Workflow step '" + step.id + "' missing output '" + name + "'"); + } + return it->second; +} + +} // namespace sdl3cpp::services::impl diff --git a/src/services/impl/workflow_step_io_resolver.hpp b/src/services/impl/workflow_step_io_resolver.hpp new file mode 100644 index 0000000..e36f9ed --- /dev/null +++ b/src/services/impl/workflow_step_io_resolver.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include "../interfaces/workflow_step_definition.hpp" + +#include + +namespace sdl3cpp::services::impl { + +class WorkflowStepIoResolver { +public: + std::string GetRequiredInputKey(const WorkflowStepDefinition& step, const std::string& name) const; + std::string GetRequiredOutputKey(const WorkflowStepDefinition& step, const std::string& name) const; +}; + +} // namespace sdl3cpp::services::impl diff --git a/src/services/impl/workflow_step_registry.cpp b/src/services/impl/workflow_step_registry.cpp new file mode 100644 index 0000000..4a3284a --- /dev/null +++ b/src/services/impl/workflow_step_registry.cpp @@ -0,0 +1,26 @@ +#include "workflow_step_registry.hpp" + +#include + +namespace sdl3cpp::services::impl { + +void WorkflowStepRegistry::RegisterStep(std::shared_ptr step) { + if (!step) { + throw std::runtime_error("WorkflowStepRegistry::RegisterStep: step is null"); + } + const std::string pluginId = step->GetPluginId(); + auto [it, inserted] = steps_.emplace(pluginId, std::move(step)); + if (!inserted) { + throw std::runtime_error("WorkflowStepRegistry::RegisterStep: duplicate plugin '" + pluginId + "'"); + } +} + +std::shared_ptr WorkflowStepRegistry::GetStep(const std::string& pluginId) const { + auto it = steps_.find(pluginId); + if (it == steps_.end()) { + return nullptr; + } + return it->second; +} + +} // namespace sdl3cpp::services::impl diff --git a/src/services/impl/workflow_step_registry.hpp b/src/services/impl/workflow_step_registry.hpp new file mode 100644 index 0000000..05ab74f --- /dev/null +++ b/src/services/impl/workflow_step_registry.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include "../interfaces/i_workflow_step_registry.hpp" + +#include + +namespace sdl3cpp::services::impl { + +class WorkflowStepRegistry : public IWorkflowStepRegistry { +public: + void RegisterStep(std::shared_ptr step) override; + std::shared_ptr GetStep(const std::string& pluginId) const override; + +private: + std::unordered_map> steps_; +}; + +} // namespace sdl3cpp::services::impl diff --git a/src/services/interfaces/i_workflow_executor.hpp b/src/services/interfaces/i_workflow_executor.hpp new file mode 100644 index 0000000..84013a3 --- /dev/null +++ b/src/services/interfaces/i_workflow_executor.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include "workflow_context.hpp" +#include "workflow_definition.hpp" + +namespace sdl3cpp::services { + +class IWorkflowExecutor { +public: + virtual ~IWorkflowExecutor() = default; + + virtual void Execute(const WorkflowDefinition& workflow, WorkflowContext& context) = 0; +}; + +} // namespace sdl3cpp::services diff --git a/src/services/interfaces/i_workflow_step.hpp b/src/services/interfaces/i_workflow_step.hpp new file mode 100644 index 0000000..b3c6618 --- /dev/null +++ b/src/services/interfaces/i_workflow_step.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include "workflow_context.hpp" +#include "workflow_step_definition.hpp" + +#include + +namespace sdl3cpp::services { + +class IWorkflowStep { +public: + virtual ~IWorkflowStep() = default; + + virtual std::string GetPluginId() const = 0; + virtual void Execute(const WorkflowStepDefinition& step, WorkflowContext& context) = 0; +}; + +} // namespace sdl3cpp::services diff --git a/src/services/interfaces/i_workflow_step_registry.hpp b/src/services/interfaces/i_workflow_step_registry.hpp new file mode 100644 index 0000000..e2f27d9 --- /dev/null +++ b/src/services/interfaces/i_workflow_step_registry.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include "i_workflow_step.hpp" + +#include +#include + +namespace sdl3cpp::services { + +class IWorkflowStepRegistry { +public: + virtual ~IWorkflowStepRegistry() = default; + + virtual void RegisterStep(std::shared_ptr step) = 0; + virtual std::shared_ptr GetStep(const std::string& pluginId) const = 0; +}; + +} // namespace sdl3cpp::services diff --git a/src/services/interfaces/workflow_context.hpp b/src/services/interfaces/workflow_context.hpp new file mode 100644 index 0000000..37441a2 --- /dev/null +++ b/src/services/interfaces/workflow_context.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include +#include +#include + +namespace sdl3cpp::services { + +class WorkflowContext { +public: + template + void Set(const std::string& key, T value) { + values_[key] = std::move(value); + } + + bool Contains(const std::string& key) const { + return values_.find(key) != values_.end(); + } + + template + const T* TryGet(const std::string& key) const { + auto it = values_.find(key); + if (it == values_.end()) { + return nullptr; + } + return std::any_cast(&it->second); + } + +private: + std::unordered_map values_; +}; + +} // namespace sdl3cpp::services diff --git a/src/services/interfaces/workflow_definition.hpp b/src/services/interfaces/workflow_definition.hpp new file mode 100644 index 0000000..fe03ed4 --- /dev/null +++ b/src/services/interfaces/workflow_definition.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include "workflow_step_definition.hpp" + +#include +#include + +namespace sdl3cpp::services { + +struct WorkflowDefinition { + std::string templateName; + std::vector steps; +}; + +} // namespace sdl3cpp::services diff --git a/src/services/interfaces/workflow_step_definition.hpp b/src/services/interfaces/workflow_step_definition.hpp new file mode 100644 index 0000000..e9c253a --- /dev/null +++ b/src/services/interfaces/workflow_step_definition.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include +#include + +namespace sdl3cpp::services { + +struct WorkflowStepDefinition { + std::string id; + std::string plugin; + std::unordered_map inputs; + std::unordered_map outputs; +}; + +} // namespace sdl3cpp::services