Olympe Engine 2.0
2D Game Engine with ECS Architecture
Loading...
Searching...
No Matches
BlueprintValidator.cpp
Go to the documentation of this file.
1/*
2 * Olympe Blueprint Editor - Blueprint Validator Implementation
3 */
4
7#include "../third_party/imgui/imgui.h"
8#include <iostream>
9
10namespace Olympe
11{
15
19
20 std::vector<ValidationError> BlueprintValidator::ValidateGraph(const NodeGraph* graph)
21 {
22 std::vector<ValidationError> errors;
23
24 if (!graph)
25 {
26 errors.push_back(ValidationError(-1, "", "Graph is null", ErrorSeverity::Critical, "Graph"));
27 return errors;
28 }
29
30 // Validate each node
31 auto nodes = graph->GetAllNodes();
32 for (const GraphNode* node : nodes)
33 {
34 ValidateNodeType(graph, node, errors);
37 }
38
39 // Graph-level validations
40 if (graph->rootNodeId < 0)
41 {
42 errors.push_back(ValidationError(-1, graph->name,
43 "No root node defined", ErrorSeverity::Warning, "Graph"));
44 }
45
46 return errors;
47 }
48
49 std::vector<ValidationError> BlueprintValidator::ValidateNode(const NodeGraph* graph, int nodeId)
50 {
51 std::vector<ValidationError> errors;
52
53 if (!graph)
54 {
55 errors.push_back(ValidationError(nodeId, "", "Graph is null", ErrorSeverity::Critical, "Graph"));
56 return errors;
57 }
58
59 const GraphNode* node = graph->GetNode(nodeId);
60 if (!node)
61 {
62 errors.push_back(ValidationError(nodeId, "",
63 "Node not found", ErrorSeverity::Critical, "Node"));
64 return errors;
65 }
66
67 ValidateNodeType(graph, node, errors);
70
71 return errors;
72 }
73
75 {
76 auto errors = ValidateGraph(graph);
77
78 // Check if there are any critical or error level issues
79 for (const auto& error : errors)
80 {
81 if (error.severity == ErrorSeverity::Error || error.severity == ErrorSeverity::Critical)
82 {
83 return false;
84 }
85 }
86
87 return true;
88 }
89
90 int BlueprintValidator::GetErrorCount(const std::vector<ValidationError>& errors, ErrorSeverity severity) const
91 {
92 int count = 0;
93 for (const auto& error : errors)
94 {
95 if (error.severity == severity)
96 count++;
97 }
98 return count;
99 }
100
102 {
103 switch (severity)
104 {
105 case ErrorSeverity::Info: return "Info";
106 case ErrorSeverity::Warning: return "Warning";
107 case ErrorSeverity::Error: return "Error";
108 case ErrorSeverity::Critical: return "Critical";
109 default: return "Unknown";
110 }
111 }
112
114 {
115 switch (severity)
116 {
117 case ErrorSeverity::Info: return ImVec4(0.5f, 0.5f, 1.0f, 1.0f); // Blue
118 case ErrorSeverity::Warning: return ImVec4(1.0f, 1.0f, 0.0f, 1.0f); // Yellow
119 case ErrorSeverity::Error: return ImVec4(1.0f, 0.5f, 0.0f, 1.0f); // Orange
120 case ErrorSeverity::Critical: return ImVec4(1.0f, 0.0f, 0.0f, 1.0f); // Red
121 default: return ImVec4(1.0f, 1.0f, 1.0f, 1.0f);
122 }
123 }
124
126 std::vector<ValidationError>& errors)
127 {
128 // Validate Action nodes
129 if (node->type == NodeType::BT_Action)
130 {
131 if (node->actionType.empty())
132 {
133 errors.push_back(ValidationError(node->id, node->name,
134 "Action node has no action type specified", ErrorSeverity::Error, "Type"));
135 return;
136 }
137
138 if (!EnumCatalogManager::Get().IsValidActionType(node->actionType))
139 {
140 errors.push_back(ValidationError(node->id, node->name,
141 "Invalid or deprecated ActionType: " + node->actionType,
142 ErrorSeverity::Error, "Type"));
143 }
144 }
145 // Validate Condition nodes
146 else if (node->type == NodeType::BT_Condition)
147 {
148 if (node->conditionType.empty())
149 {
150 errors.push_back(ValidationError(node->id, node->name,
151 "Condition node has no condition type specified", ErrorSeverity::Error, "Type"));
152 return;
153 }
154
155 if (!EnumCatalogManager::Get().IsValidConditionType(node->conditionType))
156 {
157 errors.push_back(ValidationError(node->id, node->name,
158 "Invalid or deprecated ConditionType: " + node->conditionType,
159 ErrorSeverity::Error, "Type"));
160 }
161 }
162 // Validate Decorator nodes
163 else if (node->type == NodeType::BT_Decorator)
164 {
165 if (node->decoratorType.empty())
166 {
167 errors.push_back(ValidationError(node->id, node->name,
168 "Decorator node has no decorator type specified", ErrorSeverity::Error, "Type"));
169 return;
170 }
171
172 if (!EnumCatalogManager::Get().IsValidDecoratorType(node->decoratorType))
173 {
174 errors.push_back(ValidationError(node->id, node->name,
175 "Invalid or deprecated DecoratorType: " + node->decoratorType,
176 ErrorSeverity::Error, "Type"));
177 }
178 }
179 }
180
182 std::vector<ValidationError>& errors)
183 {
184 // Validate Action parameters
185 if (node->type == NodeType::BT_Action && !node->actionType.empty())
186 {
188 if (actionDef)
189 {
190 // Check for required parameters
191 for (const auto& paramDef : actionDef->parameters)
192 {
193 if (paramDef.required)
194 {
195 auto it = node->parameters.find(paramDef.name);
196 if (it == node->parameters.end() || it->second.empty())
197 {
198 errors.push_back(ValidationError(node->id, node->name,
199 "Missing required parameter: " + paramDef.name,
200 ErrorSeverity::Error, "Parameter"));
201 }
202 }
203 }
204 }
205 }
206 // Validate Condition parameters
207 else if (node->type == NodeType::BT_Condition && !node->conditionType.empty())
208 {
210 if (conditionDef)
211 {
212 for (const auto& paramDef : conditionDef->parameters)
213 {
214 if (paramDef.required)
215 {
216 auto it = node->parameters.find(paramDef.name);
217 if (it == node->parameters.end() || it->second.empty())
218 {
219 errors.push_back(ValidationError(node->id, node->name,
220 "Missing required parameter: " + paramDef.name,
221 ErrorSeverity::Error, "Parameter"));
222 }
223 }
224 }
225 }
226 }
227 // Validate Decorator parameters
228 else if (node->type == NodeType::BT_Decorator && !node->decoratorType.empty())
229 {
231 if (decoratorDef)
232 {
233 for (const auto& paramDef : decoratorDef->parameters)
234 {
235 if (paramDef.required)
236 {
237 auto it = node->parameters.find(paramDef.name);
238 if (it == node->parameters.end() || it->second.empty())
239 {
240 errors.push_back(ValidationError(node->id, node->name,
241 "Missing required parameter: " + paramDef.name,
242 ErrorSeverity::Error, "Parameter"));
243 }
244 }
245 }
246 }
247 }
248 }
249
251 std::vector<ValidationError>& errors)
252 {
253 // Validate composite nodes have children
254 if (node->type == NodeType::BT_Sequence || node->type == NodeType::BT_Selector)
255 {
256 if (node->childIds.empty())
257 {
258 errors.push_back(ValidationError(node->id, node->name,
259 "Composite node has no children", ErrorSeverity::Warning, "Link"));
260 }
261 }
262
263 // Validate decorator nodes have exactly one child
264 if (node->type == NodeType::BT_Decorator)
265 {
266 if (node->decoratorChildId < 0)
267 {
268 errors.push_back(ValidationError(node->id, node->name,
269 "Decorator node has no child", ErrorSeverity::Error, "Link"));
270 }
271 }
272
273 // Validate that child IDs actually exist
274 for (int childId : node->childIds)
275 {
276 if (!graph->GetNode(childId))
277 {
278 errors.push_back(ValidationError(node->id, node->name,
279 "Child node " + std::to_string(childId) + " does not exist",
280 ErrorSeverity::Error, "Link"));
281 }
282 }
283
284 if (node->decoratorChildId >= 0 && !graph->GetNode(node->decoratorChildId))
285 {
286 errors.push_back(ValidationError(node->id, node->name,
287 "Decorator child node " + std::to_string(node->decoratorChildId) + " does not exist",
288 ErrorSeverity::Error, "Link"));
289 }
290 }
291
292 // ========== JSON Schema Validation and Normalization ==========
293
295 {
296 // Check if type is already specified
297 if (blueprint.contains("type") && blueprint["type"].is_string())
298 {
299 return blueprint["type"].get<std::string>();
300 }
301
302 // Use heuristics to detect type
303 // BehaviorTree: has rootNodeId + nodes array
304 if (blueprint.contains("rootNodeId") && blueprint.contains("nodes"))
305 {
306 return "BehaviorTree";
307 }
308
309 // HFSM: has states or initialState
310 if (blueprint.contains("states") || blueprint.contains("initialState"))
311 {
312 return "HFSM";
313 }
314
315 // EntityBlueprint: has components array at root
316 if (blueprint.contains("components") && blueprint["components"].is_array())
317 {
318 return "EntityBlueprint";
319 }
320
321 // EntityPrefab: has data.prefabName or data.components
322 if (blueprint.contains("data"))
323 {
324 const auto& data = blueprint["data"];
325 if (data.contains("prefabName") || data.contains("components"))
326 {
327 return "EntityPrefab";
328 }
329 }
330
331 // UI Blueprint: has elements array
332 if (blueprint.contains("elements") && blueprint["elements"].is_array())
333 {
334 return "UIBlueprint";
335 }
336
337 // Level: has worldSize or entities
338 if (blueprint.contains("worldSize") || blueprint.contains("entities"))
339 {
340 return "Level";
341 }
342
343 // Catalog types
344 if (blueprint.contains("catalogType"))
345 {
346 return "Catalog";
347 }
348
349 // Template
350 if (blueprint.contains("blueprintData"))
351 {
352 return "Template";
353 }
354
355 // Default to Generic
356 return "Generic";
357 }
358
360 {
361 bool modified = false;
362
363 // Ensure schema_version exists
364 if (!blueprint.contains("schema_version"))
365 {
366 blueprint["schema_version"] = 2;
367 modified = true;
368 }
369
370 // Detect and add type if missing
371 std::string detectedType = DetectType(blueprint);
372 if (!blueprint.contains("type"))
373 {
374 blueprint["type"] = detectedType;
375 modified = true;
376 }
377
378 // Add blueprintType if missing (should match type)
379 if (!blueprint.contains("blueprintType"))
380 {
381 std::string type = blueprint.contains("type") ?
382 blueprint["type"].get<std::string>() : detectedType;
383 blueprint["blueprintType"] = type;
384 modified = true;
385 }
386
387 // Ensure metadata exists
388 if (!blueprint.contains("metadata"))
389 {
390 blueprint["metadata"] = nlohmann::json::object();
391 blueprint["metadata"]["author"] = "Unknown";
392 blueprint["metadata"]["created"] = "";
393 blueprint["metadata"]["lastModified"] = "";
394 blueprint["metadata"]["tags"] = nlohmann::json::array();
395 modified = true;
396 }
397
398 // Ensure editorState exists
399 if (!blueprint.contains("editorState"))
400 {
401 blueprint["editorState"] = nlohmann::json::object();
402 blueprint["editorState"]["zoom"] = 1.0f;
403 blueprint["editorState"]["scrollOffset"] = nlohmann::json::object();
404 blueprint["editorState"]["scrollOffset"]["x"] = 0.0f;
405 blueprint["editorState"]["scrollOffset"]["y"] = 0.0f;
406 modified = true;
407 }
408
409 return modified;
410 }
411
413 {
414 errors.clear();
415
416 // Check if type exists
417 if (!blueprint.contains("type"))
418 {
419 errors = "Missing 'type' field";
420 return false;
421 }
422
423 std::string type = blueprint["type"].get<std::string>();
424
425 // Validate based on type
426 if (type == "BehaviorTree")
427 {
428 return ValidateBehaviorTree(blueprint, errors);
429 }
430 else if (type == "HFSM")
431 {
432 return ValidateHFSM(blueprint, errors);
433 }
434 else if (type == "EntityPrefab" || type == "EntityBlueprint")
435 {
436 return ValidateEntityPrefab(blueprint, errors);
437 }
438 else if (type == "UIBlueprint")
439 {
440 return ValidateUIBlueprint(blueprint, errors);
441 }
442 else if (type == "Level")
443 {
444 return ValidateLevel(blueprint, errors);
445 }
446
447 // Generic or unknown types are considered valid
448 return true;
449 }
450
452 {
453 // Check for data wrapper (v2 format)
454 const nlohmann::json* data = &blueprint;
455 if (blueprint.contains("data") && blueprint["data"].is_object())
456 {
457 data = &blueprint["data"];
458 }
459
460 // Required fields for BehaviorTree
461 if (!data->contains("nodes") || !(*data)["nodes"].is_array())
462 {
463 errors = "BehaviorTree missing 'nodes' array";
464 return false;
465 }
466
467 if (!data->contains("rootNodeId"))
468 {
469 errors = "BehaviorTree missing 'rootNodeId'";
470 return false;
471 }
472
473 return true;
474 }
475
477 {
478 // Check for data wrapper (v2 format)
479 const nlohmann::json* data = &blueprint;
480 if (blueprint.contains("data") && blueprint["data"].is_object())
481 {
482 data = &blueprint["data"];
483 }
484
485 // Required fields for HFSM
486 if (!data->contains("states") || !(*data)["states"].is_array())
487 {
488 errors = "HFSM missing 'states' array";
489 return false;
490 }
491
492 if (!data->contains("initialState"))
493 {
494 errors = "HFSM missing 'initialState'";
495 return false;
496 }
497
498 return true;
499 }
500
502 {
503 // Check for data wrapper (v2 format)
504 const nlohmann::json* data = &blueprint;
505 if (blueprint.contains("data") && blueprint["data"].is_object())
506 {
507 data = &blueprint["data"];
508 }
509
510 // Required fields for EntityPrefab
511 if (!data->contains("components") || !(*data)["components"].is_array())
512 {
513 errors = "EntityPrefab missing 'components' array";
514 return false;
515 }
516
517 return true;
518 }
519
521 {
522 // Required fields for UIBlueprint
523 if (!blueprint.contains("elements") || !blueprint["elements"].is_array())
524 {
525 errors = "UIBlueprint missing 'elements' array";
526 return false;
527 }
528
529 return true;
530 }
531
533 {
534 // Required fields for Level
535 if (!blueprint.contains("worldSize") && !blueprint.contains("entities"))
536 {
537 errors = "Level missing 'worldSize' or 'entities'";
538 return false;
539 }
540
541 return true;
542 }
543}
ComponentTypeID GetComponentTypeID_Static()
Definition ECS_Entity.h:56
int GetErrorCount(const std::vector< ValidationError > &errors, ErrorSeverity severity) const
static ImVec4 SeverityToColor(ErrorSeverity severity)
std::vector< ValidationError > ValidateNode(const NodeGraph *graph, int nodeId)
bool ValidateUIBlueprint(const nlohmann::json &blueprint, std::string &errors)
std::string DetectType(const nlohmann::json &blueprint)
bool Normalize(nlohmann::json &blueprint)
void ValidateNodeType(const NodeGraph *graph, const GraphNode *node, std::vector< ValidationError > &errors)
bool ValidateHFSM(const nlohmann::json &blueprint, std::string &errors)
bool ValidateEntityPrefab(const nlohmann::json &blueprint, std::string &errors)
bool IsGraphValid(const NodeGraph *graph)
void ValidateNodeLinks(const NodeGraph *graph, const GraphNode *node, std::vector< ValidationError > &errors)
bool ValidateJSON(const nlohmann::json &blueprint, std::string &errors)
void ValidateNodeParameters(const NodeGraph *graph, const GraphNode *node, std::vector< ValidationError > &errors)
bool ValidateLevel(const nlohmann::json &blueprint, std::string &errors)
static const char * SeverityToString(ErrorSeverity severity)
bool ValidateBehaviorTree(const nlohmann::json &blueprint, std::string &errors)
std::vector< ValidationError > ValidateGraph(const NodeGraph *graph)
const CatalogType * FindActionType(const std::string &id) const
static EnumCatalogManager & Get()
const CatalogType * FindDecoratorType(const std::string &id) const
const CatalogType * FindConditionType(const std::string &id) const
nlohmann::json json
std::vector< CatalogParameter > parameters