Olympe Engine 2.0
2D Game Engine with ECS Architecture
Loading...
Searching...
No Matches
TaskSystem.cpp
Go to the documentation of this file.
1/**
2 * @file TaskSystem.cpp
3 * @brief Implementation of TaskSystem: Phase 2.C AtomicTask lifecycle.
4 * @author Olympe Engine
5 * @date 2026-02-22
6 *
7 * @details
8 * Implements the AtomicTask lifecycle in TaskSystem so that multi-frame tasks
9 * (returning TaskStatus::Running) are handled correctly across frames using
10 * runner.activeTask, and Abort() is invoked when a running task is cancelled.
11 *
12 * ### Lifecycle summary
13 * - At node entry, if runner.activeTask == nullptr, a task instance is created
14 * via AtomicTaskRegistry::Create(node.AtomicTaskID) and stored in activeTask.
15 * - Each tick calls activeTask->Execute(params).
16 * - Running => keep activeTask; accumulate StateTimer; return.
17 * - Success/Failure => reset activeTask; set LastStatus; reset StateTimer;
18 * advance CurrentNodeID via TransitionToNextNode().
19 * - If CurrentNodeID is NODE_INDEX_NONE when ExecuteNode() is called and
20 * activeTask is non-null, activeTask->Abort() is called before resetting.
21 *
22 * C++14 compliant - no C++17/20 features.
23 */
24
25#include "TaskSystem.h"
26#include "AtomicTaskRegistry.h"
27#include "IAtomicTask.h"
28#include "LocalBlackboard.h"
29#include "../TaskSystem/AtomicTaskContext.h"
30#include "../system/system_utils.h"
31
32namespace Olympe {
33
34// ============================================================================
35// Static member definitions
36// ============================================================================
37
39
40// ============================================================================
41// SetEditorPublishCallback
42// ============================================================================
43
48
49// ============================================================================
50// Constructor
51// ============================================================================
52
54{
55 // Component signature is set when registering with World in the runtime.
56 // In runtime builds: requiredSignature.set(GetComponentTypeID_Static<TaskRunnerComponent>());
57}
58
59// ============================================================================
60// Process
61// ============================================================================
62
64{
65 // Delta-time placeholder for headless / test contexts.
66 // In the runtime engine, use GameEngine::fDt passed from the game loop.
67 const float dt = 0.016f;
68
69 for (const EntityID& entity : m_entities)
70 {
71 // In the runtime engine, retrieve the actual component via:
72 // TaskRunnerComponent& runner =
73 // World::Get().GetComponent<TaskRunnerComponent>(entity);
74 //
75 // In test contexts (m_entities populated manually), tasks are driven
76 // directly via ExecuteNode() / ExecuteVSFrame(), so the default runner
77 // below is only reached when the entity set is populated by the ECS.
79
80 // Resolve the template. If graphAssetPath is set and no template is
81 // bound by AssetID, attempt to load from file.
82 const TaskGraphTemplate* tmpl =
83 AssetManager::Get().GetTaskGraph(runner.GraphTemplateID);
84
85 if (tmpl == nullptr)
86 {
87 SYSTEM_LOG << "[TaskSystem] Entity " << entity
88 << " skipped: no valid TaskGraphTemplate bound.\n";
89 continue;
90 }
91
92 // Primary execution path: ATS Visual Script.
93 ExecuteVSFrame(entity, runner, tmpl, dt);
94 }
95}
96
97// ============================================================================
98// ExecuteVSFrame (public)
99// ============================================================================
100
103 const TaskGraphTemplate* tmpl,
104 float dt)
105{
106 // Initialize LocalBlackboard from Blackboard entries if not yet populated.
107 // Heuristic: if runner.LocalBlackboard is empty AND tmpl->Blackboard is not empty.
108 if (runner.LocalBlackboard.empty() && !tmpl->Blackboard.empty())
109 {
110 for (size_t i = 0; i < tmpl->Blackboard.size(); ++i)
111 {
112 const BlackboardEntry& entry = tmpl->Blackboard[i];
113 runner.LocalBlackboard[entry.Key] = entry.Default;
114 }
115 }
116
117 // Build a LocalBlackboard instance for VSGraphExecutor, bridging from
118 // runner.LocalBlackboard (unordered_map) to the typed LocalBlackboard class.
121
122 // Populate localBB with current runner values (override defaults).
123 for (auto it = runner.LocalBlackboard.begin();
124 it != runner.LocalBlackboard.end(); ++it)
125 {
126 if (localBB.HasVariable(it->first))
127 {
128 try
129 {
130 localBB.SetValue(it->first, it->second);
131 }
132 catch (...)
133 {
134 // Type mismatch or unknown variable — silently skip.
135 }
136 }
137 }
138
139 World* worldPtr = nullptr; // TODO Phase 3: inject real World
140
142
143 // Resync runner.LocalBlackboard from localBB after execution.
144 const std::vector<std::string>& keys = localBB.GetVariableNames();
145 for (size_t i = 0; i < keys.size(); ++i)
146 {
147 try
148 {
149 runner.LocalBlackboard[keys[i]] = localBB.GetValue(keys[i]);
150 }
151 catch (...)
152 {
153 // Skip variables that fail to read.
154 }
155 }
156}
157
158// ============================================================================
159// ExecuteNode
160// ============================================================================
161
164 const TaskGraphTemplate* tmpl,
165 float dt)
166{
167 // NODE_INDEX_NONE signals that there is no active node (graph finished or
168 // externally interrupted). Abort any lingering task and return.
169 if (runner.CurrentNodeID == NODE_INDEX_NONE)
170 {
171 if (runner.activeTask)
172 {
173 SYSTEM_LOG << "[TaskSystem] Entity " << entity
174 << ": node index is NODE_INDEX_NONE with active task"
175 << " - calling Abort()\n";
176 runner.activeTask->Abort();
177 runner.activeTask.reset();
178 }
179 return;
180 }
181
182 // Look up the current node by its NodeID.
183 const TaskNodeDefinition* node = tmpl->GetNode(runner.CurrentNodeID);
184 if (node == nullptr)
185 {
186 SYSTEM_LOG << "[TaskSystem] Entity " << entity
187 << ": node ID " << runner.CurrentNodeID
188 << " not found in template '" << tmpl->Name << "'\n";
189 // Abort any active task associated with the missing node.
190 if (runner.activeTask)
191 {
192 runner.activeTask->Abort();
193 runner.activeTask.reset();
194 }
195 return;
196 }
197
198 // Dispatch to the appropriate node type.
199 switch (node->Type)
200 {
202 ExecuteAtomicTask(entity, runner, *node, tmpl, dt);
203 break;
204
205 default:
206 // Sequence / Selector / Parallel control nodes are not fully
207 // implemented in Phase 2.C. Log and skip.
208 SYSTEM_LOG << "[TaskSystem] Entity " << entity
209 << ": control-flow node type "
210 << static_cast<int>(node->Type)
211 << " not yet supported in Phase 2.C\n";
212 break;
213 }
214}
215
216// ============================================================================
217// ExecuteAtomicTask (private)
218// ============================================================================
219
223 const TaskGraphTemplate* tmpl,
224 float dt)
225{
226 // Create the task instance on first entry to this node.
227 if (!runner.activeTask)
228 {
229 runner.activeTask = AtomicTaskRegistry::Get().Create(node.AtomicTaskID);
230 if (!runner.activeTask)
231 {
232 SYSTEM_LOG << "[TaskSystem] Entity " << entity
233 << ": unknown AtomicTaskID '" << node.AtomicTaskID << "'\n";
236 return;
237 }
238 }
239
240 // Initialize LocalBlackboard from template defaults.
243
244 // Populate from runner.LocalBlackboard (typed per-entity state).
245 for (auto it = runner.LocalBlackboard.begin(); it != runner.LocalBlackboard.end(); ++it)
246 {
247 if (bb.HasVariable(it->first))
248 {
249 try { bb.SetValue(it->first, it->second); }
250 catch (...) { /* type mismatch — skip */ }
251 }
252 }
253
254 // Build parameter map from the node's literal and LocalVariable bindings.
256
257 for (const auto& kv : node.Parameters)
258 {
259 if (kv.second.Type == ParameterBindingType::Literal)
260 {
261 params[kv.first] = kv.second.LiteralValue;
262 }
263 else if (kv.second.Type == ParameterBindingType::LocalVariable)
264 {
265 if (bb.HasVariable(kv.second.VariableName))
266 {
267 params[kv.first] = bb.GetValue(kv.second.VariableName);
268 }
269 else
270 {
271 SYSTEM_LOG << "[TaskSystem] Entity " << entity
272 << ": LocalVariable '" << kv.second.VariableName
273 << "' not found in template '" << tmpl->Name << "' - skipping binding\n";
274 }
275 }
276 }
277
278 // Tick the task for this frame.
280 ctx.Entity = entity;
281 ctx.WorldPtr = nullptr; // World integration deferred to Phase 1.5
282 ctx.LocalBB = &bb;
283 ctx.DeltaTime = dt;
284 ctx.StateTimer = runner.StateTimer;
285
286 TaskStatus status = runner.activeTask->ExecuteWithContext(ctx, params);
287
288 // Persist LocalBlackboard state so values survive across frames.
289 const std::vector<std::string>& keys = bb.GetVariableNames();
290 for (size_t i = 0; i < keys.size(); ++i)
291 {
292 try { runner.LocalBlackboard[keys[i]] = bb.GetValue(keys[i]); }
293 catch (...) {}
294 }
295
296 // Accumulate time spent in this node on every tick.
297 runner.StateTimer += dt;
298
299 if (status == TaskStatus::Running)
300 {
301 // Task is still in progress: keep activeTask for the next frame.
302 // Publish live runtime info to the editor if a callback is registered.
303 if (s_EditorPublishFn != nullptr)
304 {
305 s_EditorPublishFn(entity, runner.CurrentNodeID, &bb);
306 }
307 return;
308 }
309
310 // Task completed (Success or Failure): clean up and transition.
311 runner.activeTask.reset();
312
313 runner.LastStatus = (status == TaskStatus::Success)
316
318}
319
320// ============================================================================
321// TransitionToNextNode (private)
322// ============================================================================
323
326 bool success)
327{
328 // Advance to the next NodeID. NODE_INDEX_NONE means the graph is complete.
329 runner.CurrentNodeID = success ? node.NextOnSuccess : node.NextOnFailure;
330
331 // Reset the per-node timer on every transition.
332 runner.StateTimer = 0.0f;
333}
334
335// ============================================================================
336// AbortActiveTask (public)
337// ============================================================================
338
340{
341 if (runner.activeTask)
342 {
343 runner.activeTask->Abort();
344 runner.activeTask.reset();
346 }
347}
348
349} // namespace Olympe
Singleton registry for atomic task factories.
ComponentTypeID GetComponentTypeID_Static()
Definition ECS_Entity.h:56
std::uint64_t EntityID
Definition ECS_Entity.h:21
Interface for atomic tasks in the Atomic Task System.
Runtime key-value store for task graph variables.
ECS system that iterates TaskRunnerComponent entities and drives ATS Visual Script graph execution ea...
std::set< EntityID > m_entities
Definition ECS_Systems.h:42
const TaskGraphTemplate * GetTaskGraph(AssetID id) const
Returns a non-owning pointer to the cached TaskGraphTemplate.
static AssetManager & Get()
Returns the singleton AssetManager instance.
std::unique_ptr< IAtomicTask > Create(const std::string &id) const
Creates a new instance of the task identified by id.
static AtomicTaskRegistry & Get()
Returns the singleton instance.
std::unordered_map< std::string, TaskValue > ParameterMap
Convenience alias for the parameter map passed to Execute().
Definition IAtomicTask.h:67
Simple map-based blackboard for task graph runtime state.
void Initialize(const TaskGraphTemplate &tmpl)
Initialises the blackboard from a template.
void InitializeFromEntries(const std::vector< BlackboardEntry > &entries)
Initializes the blackboard from a vector of BlackboardEntry (ATS VS schema v4).
Immutable, shareable task graph asset.
virtual void Process() override
Processes all entities registered with this system for one frame.
void ExecuteAtomicTask(EntityID entity, TaskRunnerComponent &runner, const TaskNodeDefinition &node, const TaskGraphTemplate *tmpl, float dt)
Executes one tick of an AtomicTask node.
void ExecuteVSFrame(EntityID entity, TaskRunnerComponent &runner, const TaskGraphTemplate *tmpl, float dt)
Dispatches VS-graph execution to VSGraphExecutor.
static void SetEditorPublishCallback(TaskEditorPublishFn fn)
Register a callback that receives live task-runner state each frame while a task is executing.
TaskSystem()
Constructs the system and configures the required component signature.
void AbortActiveTask(TaskRunnerComponent &runner)
Aborts the active atomic task on a runner, if any.
void ExecuteNode(EntityID entity, TaskRunnerComponent &runner, const TaskGraphTemplate *tmpl, float dt)
Advances execution of one node in the task graph for the given entity.
static TaskEditorPublishFn s_EditorPublishFn
Callback registered by the editor to receive live runtime info.
Definition TaskSystem.h:214
void TransitionToNextNode(TaskRunnerComponent &runner, const TaskNodeDefinition &node, bool success)
Advances runner.CurrentNodeID after a node completes.
static void ExecuteFrame(EntityID entity, TaskRunnerComponent &runner, const TaskGraphTemplate &tmpl, LocalBlackboard &localBB, World *worldPtr, float dt)
Point d'entrée principal : exécute le graphe pour une entité.
Core ECS manager and world coordinator.
Definition World.h:210
< Provides AssetID and INVALID_ASSET_ID
void(*)(EntityID entity, int nodeIndex, const LocalBlackboard *bb) TaskEditorPublishFn
Signature of the editor publish callback.
Definition TaskSystem.h:62
TaskStatus
Result code returned by IAtomicTask::Execute().
Definition IAtomicTask.h:38
@ Success
Task completed successfully.
@ Running
Task is still in progress (multi-frame tasks)
@ LocalVariable
Value is read from the local blackboard at runtime.
@ Literal
Value is embedded directly in the template.
@ AtomicTask
Leaf node that executes a single atomic task.
constexpr int32_t NODE_INDEX_NONE
Sentinel value for "no node" in node index / ID fields.
Lightweight context bundle passed to IAtomicTask::ExecuteWithContext().
EntityID Entity
The entity whose task graph is being executed.
Single entry in the graph's declared blackboard schema (local or global).
Full description of a single node in the task graph.
Per-entity runtime state for task graph execution.
@ Success
The node completed successfully.
@ Aborted
Execution was interrupted externally.
#define SYSTEM_LOG