Olympe Engine 2.0
2D Game Engine with ECS Architecture
Loading...
Searching...
No Matches
GlobalTemplateBlackboard.cpp
Go to the documentation of this file.
1/**
2 * @file GlobalTemplateBlackboard.cpp
3 * @brief Implementation of GlobalTemplateBlackboard singleton
4 * @author Olympe Engine
5 * @date 2026-03-26
6 */
7
9#include "../system/system_utils.h"
10#include "../json_helper.h"
11
12#include <fstream>
13#include <iostream>
14#include <algorithm>
15#include <cctype>
16#include <chrono>
17#include <ctime>
18
19namespace Olympe {
20
22{
24 static bool initialized = false;
25
26 // Auto-load register on first access
27 if (!initialized)
28 {
29 initialized = true;
30
31 if (instance.m_variables.empty())
32 {
33 // Try multiple path variations
34 const std::vector<std::string> pathsToTry = {
35 "./Config/global_blackboard_register.json",
36 "Config/global_blackboard_register.json",
37 "../Config/global_blackboard_register.json",
38 "../../Config/global_blackboard_register.json"
39 };
40
41 bool loaded = false;
42 for (const auto& path : pathsToTry)
43 {
44 if (instance.LoadFromFile(path))
45 {
46 loaded = true;
47 break;
48 }
49 }
50
51 if (!loaded)
52 {
53 SYSTEM_LOG << "[GlobalTemplateBlackboard::Get] WARNING: Failed to load register from any path\n";
54 }
55 }
56 }
57
58 return instance;
59}
60
62{
63 // Clear the current registry
64 Get().Clear();
65
66 // Force reload from file
67 const std::vector<std::string> pathsToTry = {
68 "./Config/global_blackboard_register.json",
69 "Config/global_blackboard_register.json",
70 "../Config/global_blackboard_register.json",
71 "../../Config/global_blackboard_register.json"
72 };
73
74 bool loaded = false;
75 for (const auto& path : pathsToTry)
76 {
77 if (Get().LoadFromFile(path))
78 {
79 SYSTEM_LOG << "[GlobalTemplateBlackboard::Reload] Successfully reloaded from: " << path << "\n";
80 loaded = true;
81 break;
82 }
83 }
84
85 if (!loaded)
86 {
87 SYSTEM_LOG << "[GlobalTemplateBlackboard::Reload] WARNING: Failed to reload registry from any path\n";
88 }
89}
90
92{
93 Clear();
94
95 std::ifstream ifs(configPath);
96 if (!ifs.is_open())
97 {
98 return false;
99 }
100
101 try
102 {
103 json root;
104 ifs >> root;
105 ifs.close();
106
107 if (!root.contains("variables") || !root["variables"].is_array())
108 {
109 return true;
110 }
111
112 const json& varsArray = root["variables"];
113 SYSTEM_LOG << "[GlobalTemplateBlackboard::LoadFromFile] Found variables array with " << varsArray.size() << " entries\n";
114
115 int loadedCount = 0;
116 for (const auto& varObj : varsArray)
117 {
118 if (!varObj.contains("key") || !varObj["key"].is_string())
119 continue;
120
121 const std::string key = varObj["key"].get<std::string>();
123
124 if (varObj.contains("type") && varObj["type"].is_string())
126
127 if (type == VariableType::None)
128 continue;
129
130 TaskValue defaultValue = GetDefaultValueForType(type);
131 if (varObj.contains("defaultValue"))
132 {
133 try
134 {
135 const auto& valNode = varObj["defaultValue"];
136 switch (type)
137 {
139 if (valNode.is_boolean())
140 defaultValue = TaskValue(valNode.get<bool>());
141 break;
143 if (valNode.is_number_integer())
144 defaultValue = TaskValue(valNode.get<int>());
145 break;
147 if (valNode.is_number())
148 defaultValue = TaskValue(static_cast<float>(valNode.get<double>()));
149 break;
151 if (valNode.is_string())
152 defaultValue = TaskValue(valNode.get<std::string>());
153 break;
155 if (valNode.is_object() && valNode.contains("x") && valNode.contains("y") && valNode.contains("z"))
156 {
157 const float x = valNode["x"].get<float>();
158 const float y = valNode["y"].get<float>();
159 const float z = valNode["z"].get<float>();
160 defaultValue = TaskValue(::Vector{x, y, z});
161 }
162 break;
164 if (valNode.is_number_integer())
165 defaultValue = TaskValue(valNode.get<int>());
166 break;
167 default:
168 break;
169 }
170 }
171 catch (...) {}
172 }
173 // Phase 24: Also check "value" field (backward compat with register file format)
174 else if (varObj.contains("value"))
175 {
176 try
177 {
178 const auto& valNode = varObj["value"];
179 switch (type)
180 {
182 if (valNode.is_boolean())
183 defaultValue = TaskValue(valNode.get<bool>());
184 break;
186 if (valNode.is_number_integer())
187 defaultValue = TaskValue(valNode.get<int>());
188 break;
190 if (valNode.is_number())
191 defaultValue = TaskValue(static_cast<float>(valNode.get<double>()));
192 break;
194 if (valNode.is_string())
195 defaultValue = TaskValue(valNode.get<std::string>());
196 break;
198 if (valNode.is_object() && valNode.contains("x") && valNode.contains("y") && valNode.contains("z"))
199 {
200 const float x = valNode["x"].get<float>();
201 const float y = valNode["y"].get<float>();
202 const float z = valNode["z"].get<float>();
203 defaultValue = TaskValue(::Vector{x, y, z});
204 }
205 break;
207 if (valNode.is_number_integer())
208 defaultValue = TaskValue(valNode.get<int>());
209 break;
210 default:
211 break;
212 }
213 }
214 catch (...) {}
215 }
216
217 std::string description;
218 if (varObj.contains("description") && varObj["description"].is_string())
219 description = varObj["description"].get<std::string>();
220
221 bool isPersistent = false;
222 if (varObj.contains("isPersistent") && varObj["isPersistent"].is_boolean())
223 isPersistent = varObj["isPersistent"].get<bool>();
224 else if (varObj.contains("persistent") && varObj["persistent"].is_boolean())
225 isPersistent = varObj["persistent"].get<bool>();
226
227 AddVariable(key, type, defaultValue, description, isPersistent);
228 loadedCount++;
229 }
230
231 // Track the successfully loaded path for later saves
233
234 SYSTEM_LOG << "[GlobalTemplateBlackboard::LoadFromFile] Successfully loaded " << loadedCount << " variables\n";
235 return true;
236 }
237 catch (const std::exception& e)
238 {
239 ifs.close();
240 return false;
241 }
242}
243
245{
246 // If no path provided, use the last loaded path
247 std::string pathToUse = configPath.empty() ? m_lastLoadedPath : configPath;
248
249 // If still no path, use default
250 if (pathToUse.empty())
251 {
252 pathToUse = "./Config/global_blackboard_register.json";
253 }
254
255 SYSTEM_LOG << "[GlobalTemplateBlackboard] SaveToFile: '" << pathToUse << "'\n";
256
257 try
258 {
259 json root;
260
261 // Try to preserve existing metadata from the file
262 std::ifstream existingFile(pathToUse);
263 if (existingFile.is_open())
264 {
265 try
266 {
269 existingFile.close();
270
271 // Preserve schema_version, name, description from existing file
272 if (existingRoot.contains("schema_version"))
273 root["schema_version"] = existingRoot["schema_version"];
274 else
275 root["schema_version"] = 1;
276
277 if (existingRoot.contains("name"))
278 root["name"] = existingRoot["name"];
279 else
280 root["name"] = "Global Blackboard Register";
281
282 if (existingRoot.contains("description"))
283 root["description"] = existingRoot["description"];
284 else
285 root["description"] = "Project-scope global variable definitions accessible to all entities";
286 }
287 catch (...)
288 {
289 // If we can't parse existing file, start fresh
290 root["schema_version"] = 1;
291 root["name"] = "Global Blackboard Register";
292 root["description"] = "Project-scope global variable definitions accessible to all entities";
293 }
294 }
295 else
296 {
297 // New file - set defaults
298 root["schema_version"] = 1;
299 root["name"] = "Global Blackboard Register";
300 root["description"] = "Project-scope global variable definitions accessible to all entities";
301 }
302
303 // Get current timestamp in ISO8601 format
304 auto now = std::chrono::system_clock::now();
305 auto time_t_now = std::chrono::system_clock::to_time_t(now);
306 char timestamp[32];
307 std::tm tm_info;
308#ifdef _WIN32
310#else
312#endif
313 strftime(timestamp, sizeof(timestamp), "%Y-%m-%dT%H:%M:%SZ", &tm_info);
314 root["lastModified"] = std::string(timestamp);
315
316 // Build the variables array
317 json varsArray = json::array();
318 for (const auto& var : m_variables)
319 {
320 json varObj;
321 varObj["key"] = var.Key;
322 varObj["type"] = VariableTypeToString(var.Type);
323 varObj["description"] = var.Description;
324 varObj["isPersistent"] = var.IsPersistent;
325
326 // Use "value" field for backward compatibility with existing registry
327 if (!var.DefaultValue.IsNone())
328 {
329 switch (var.Type)
330 {
332 varObj["value"] = var.DefaultValue.AsBool();
333 break;
335 varObj["value"] = var.DefaultValue.AsInt();
336 break;
338 varObj["value"] = var.DefaultValue.AsFloat();
339 break;
341 varObj["value"] = var.DefaultValue.AsString();
342 break;
344 {
345 const ::Vector v = var.DefaultValue.AsVector();
346 json vec;
347 vec["x"] = v.x;
348 vec["y"] = v.y;
349 vec["z"] = v.z;
350 varObj["value"] = vec;
351 break;
352 }
354 varObj["value"] = static_cast<int>(var.DefaultValue.AsEntityID());
355 break;
356 default:
357 break;
358 }
359 }
360 else
361 {
362 // Set default value even if DefaultValue is None
363 switch (var.Type)
364 {
366 varObj["value"] = false;
367 break;
369 varObj["value"] = 0;
370 break;
372 varObj["value"] = 0.0f;
373 break;
375 varObj["value"] = "";
376 break;
378 {
379 json vec;
380 vec["x"] = 0.0f;
381 vec["y"] = 0.0f;
382 vec["z"] = 0.0f;
383 varObj["value"] = vec;
384 break;
385 }
387 varObj["value"] = 0;
388 break;
389 default:
390 break;
391 }
392 }
393
394 varsArray.push_back(varObj);
395 }
396
397 root["variables"] = varsArray;
398
399 std::ofstream ofs(pathToUse);
400 if (!ofs.is_open())
401 {
402 SYSTEM_LOG << "[GlobalTemplateBlackboard] SaveToFile FAILED: Could not open file for writing\n";
403 return false;
404 }
405
406 ofs << root.dump(2);
407 ofs.close();
408
409 SYSTEM_LOG << "[GlobalTemplateBlackboard] SaveToFile SUCCESS: Saved " << m_variables.size() << " variables to '" << pathToUse << "'\n";
410 return true;
411 }
412 catch (const std::exception& e)
413 {
414 SYSTEM_LOG << "[GlobalTemplateBlackboard] SaveToFile EXCEPTION: " << e.what() << "\n";
415 return false;
416 }
417}
418
420{
421 m_nameToIndex.clear();
422 m_variables.clear();
423 m_lastLoadedPath.clear(); // Reset path when clearing
424}
425
427 VariableType type,
428 const TaskValue& defaultValue,
429 const std::string& description,
430 bool isPersistent)
431{
432 if (key.empty() || !IsValidVariableName(key) || type == VariableType::None)
433 return false;
434
435 if (!defaultValue.IsNone() && defaultValue.GetType() != type)
436 return false;
437
438 auto it = m_nameToIndex.find(key);
439 if (it != m_nameToIndex.end())
440 {
441 if (m_variables[it->second].Type != type)
442 return false;
443 m_variables[it->second].DefaultValue = defaultValue;
444 m_variables[it->second].Description = description;
445 m_variables[it->second].IsPersistent = isPersistent;
446 return true;
447 }
448
449 size_t newIndex = m_variables.size();
451 def.Key = key;
452 def.Type = type;
453 def.DefaultValue = defaultValue.IsNone() ? GetDefaultValueForType(type) : defaultValue;
454 def.Description = description;
455 def.IsPersistent = isPersistent;
456
457 m_variables.push_back(def);
459
460 return true;
461}
462
464 const TaskValue& defaultValue,
465 const std::string& description)
466{
467 auto it = m_nameToIndex.find(key);
468 if (it == m_nameToIndex.end())
469 return false;
470
471 GlobalEntryDefinition& def = m_variables[it->second];
472
473 if (!defaultValue.IsNone() && defaultValue.GetType() != def.Type)
474 return false;
475
476 def.DefaultValue = defaultValue;
477 if (!description.empty())
478 def.Description = description;
479
480 return true;
481}
482
484{
485 auto it = m_nameToIndex.find(key);
486 if (it == m_nameToIndex.end())
487 return false;
488
489 size_t idx = it->second;
490 m_variables.erase(m_variables.begin() + idx);
491 m_nameToIndex.erase(it);
492
493 m_nameToIndex.clear();
494 for (size_t i = 0; i < m_variables.size(); ++i)
496
497 return true;
498}
499
500bool GlobalTemplateBlackboard::HasVariable(const std::string& key) const
501{
502 return m_nameToIndex.find(key) != m_nameToIndex.end();
503}
504
506{
507 auto it = m_nameToIndex.find(key);
508 if (it == m_nameToIndex.end())
509 return nullptr;
510 return &m_variables[it->second];
511}
512
514{
516 return def ? def->Type : VariableType::None;
517}
518
520{
522 return def ? def->DefaultValue : TaskValue();
523}
524
525const std::vector<GlobalEntryDefinition>& GlobalTemplateBlackboard::GetAllVariables() const
526{
527 return m_variables;
528}
529
531{
532 return m_variables.size();
533}
534
541
543{
544 for (const auto& var : m_variables)
545 {
546 if (var.Type == VariableType::None || var.Key.empty())
547 return true;
548 }
549 return false;
550}
551
553{
554 std::string summary = "[GlobalTemplateBlackboard] " + std::to_string(m_variables.size()) + " variables\n";
555 for (const auto& var : m_variables)
556 {
557 summary += " - " + var.Key + " (" + VariableTypeToString(var.Type) + ")\n";
558 }
559 return summary;
560}
561
566
568{
569 if (name.empty())
570 return false;
571
572 if (!std::isalpha(name[0]) && name[0] != '_')
573 return false;
574
575 for (size_t i = 1; i < name.length(); ++i)
576 {
577 if (!std::isalnum(name[i]) && name[i] != '_')
578 return false;
579 }
580
581 return true;
582}
583
584} // namespace Olympe
ComponentTypeID GetComponentTypeID_Static()
Definition ECS_Entity.h:56
Project-scope blackboard variable registry.
const GlobalEntryDefinition * GetVariable(const std::string &key) const
bool SaveToFile(const std::string &configPath="") const
static void Reload()
Force reload of the registry from file (useful for hot reload)
bool UpdateVariable(const std::string &key, const TaskValue &defaultValue, const std::string &description="")
std::string m_lastLoadedPath
Track the last successfully loaded path for consistent saves.
bool LoadFromFile(const std::string &configPath="./Config/global_blackboard_register.json")
static GlobalTemplateBlackboard & Get()
std::unordered_map< std::string, size_t > m_nameToIndex
bool HasVariable(const std::string &key) const
VariableType GetVariableType(const std::string &key) const
std::vector< GlobalEntryDefinition > m_variables
static bool IsValidVariableName(const std::string &name)
static bool IsTypeCompatible(VariableType existingType, VariableType newType)
bool AddVariable(const std::string &key, VariableType type, const TaskValue &defaultValue, const std::string &description="", bool isPersistent=false)
bool RemoveVariable(const std::string &key)
const std::vector< GlobalEntryDefinition > & GetAllVariables() const
TaskValue GetDefaultValue(const std::string &key) const
C++14-compliant type-safe value container for task parameters.
bool IsNone() const
Returns true if the value has not been set (type == None).
VariableType GetType() const
Returns the VariableType tag of the stored value.
< Provides AssetID and INVALID_ASSET_ID
nlohmann::json json
VariableType
Type tags used by TaskValue to identify stored data.
@ Int
32-bit signed integer
@ Float
Single-precision float.
@ String
std::string
@ Vector
3-component vector (Vector from vector.h)
@ None
Uninitialized / empty value.
@ EntityID
Entity identifier (uint64_t)
static std::string VariableTypeToString(VariableType type)
Converts a VariableType to its canonical string representation.
static VariableType StringToVariableType(const std::string &str)
static TaskValue GetDefaultValueForType(VariableType type)
Returns a correctly-typed default TaskValue for the given VariableType.
#define SYSTEM_LOG