Olympe Engine 2.0
2D Game Engine with ECS Architecture
Loading...
Searching...
No Matches
blueprinteditor.cpp
Go to the documentation of this file.
1/*
2 * Olympe Blueprint Editor - Backend Implementation
3 *
4 * Singleton backend managing business logic, state, and data
5 * Completely separated from UI rendering (handled by BlueprintEditorGUI)
6 */
7
8#include "BlueprintEditor.h"
9#include "EntityBlueprint.h"
10#include "EditorContext.h"
11#include "EnumCatalogManager.h"
12#include "NodeGraphManager.h"
14#include "TemplateManager.h"
15#include "CommandSystem.h"
16#include "BlueprintMigrator.h"
17#include "BlueprintValidator.h"
21#include "../json_helper.h"
22#include <algorithm>
23#include <iostream>
24#include <fstream>
25#ifndef _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING
26#define _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING
27#endif
28#include <experimental/filesystem>
29
30namespace fs = std::experimental::filesystem;
31
32using namespace Olympe::Blueprint;
33
34namespace Olympe
35{
36 // ========================================================================
37 // BlueprintEditor Singleton Backend Implementation
38 // ========================================================================
39
45
47 : m_IsActive(false)
48 , m_HasUnsavedChanges(false)
49 , m_AssetRootPath("Blueprints")
50 , m_SelectedEntity(0) // 0 = INVALID_ENTITY_ID
51 , m_CommandStack(nullptr)
52 , m_ShowMigrationDialog(false)
53 {
54 }
55
57 {
59 {
60 delete m_CommandStack;
61 m_CommandStack = nullptr;
62 }
63 }
64
66 {
67 // Initialize backend state
68 m_IsActive = false;
69 m_HasUnsavedChanges = false;
72
73 // Initialize asset management
74 m_AssetTreeRoot = nullptr;
75 m_LastError.clear();
76
77 // Initialize catalog manager
79
80 // Initialize node graph manager
82
83 // Initialize entity inspector manager
85
86 // Initialize template manager
88
89 // Initialize command stack
91
92 // Initialize plugin system
94
95 // Scan assets on initialization
97 }
98
100 {
101 std::cout << "[BlueprintEditor] Initializing Runtime Editor mode\n";
102 // Initialize the EditorContext in Runtime mode
104 }
105
107 {
108 std::cout << "[BlueprintEditor] Initializing Standalone Editor mode\n";
109 // Initialize the EditorContext in Standalone mode
111 }
112
114 {
115 // Shutdown managers in reverse order
116 if (m_CommandStack)
117 {
118 delete m_CommandStack;
119 m_CommandStack = nullptr;
120 }
121
126
127 // Clean up backend resources
130 m_HasUnsavedChanges = false;
131 }
132
133 void BlueprintEditor::Update(float deltaTime)
134 {
135 // Backend update logic (non-UI)
136 // This is called by GameEngine when the editor is active
137
138 // Update entity inspector (sync with World)
140
141 // Can be used for background tasks, auto-save, etc.
142 // For now, this is a placeholder for future backend logic
143 // such as:
144 // - Auto-save timer
145 // - Asset watching/hot-reload
146 // - Background compilation
147 // - Validation
148 }
149
150 // Blueprint operations
151 void BlueprintEditor::NewBlueprint(const std::string& name, const std::string& description)
152 {
154 m_CurrentBlueprint.description = description;
156 m_HasUnsavedChanges = true;
157 }
158
159 bool BlueprintEditor::LoadBlueprint(const std::string& filepath)
160 {
162
163 if (loaded.name.empty())
164 {
165 return false;
166 }
167
169 m_CurrentFilepath = filepath;
170 m_HasUnsavedChanges = false;
171 return true;
172 }
173
175 {
176 if (m_CurrentBlueprint.name.empty())
177 {
178 return false;
179 }
180
181 if (m_CurrentFilepath.empty())
182 {
183 // No filepath set - caller should use SaveBlueprintAs
184 return false;
185 }
186
188
189 if (success)
190 {
191 m_HasUnsavedChanges = false;
192 }
193
194 return success;
195 }
196
197 bool BlueprintEditor::SaveBlueprintAs(const std::string& filepath)
198 {
199 if (m_CurrentBlueprint.name.empty())
200 {
201 return false;
202 }
203
204 bool success = m_CurrentBlueprint.SaveToFile(filepath);
205
206 if (success)
207 {
208 m_CurrentFilepath = filepath;
209 m_HasUnsavedChanges = false;
210 }
211
212 return success;
213 }
214
215 // ========================================================================
216 // Asset Management Implementation
217 // ========================================================================
218
219 void BlueprintEditor::SetAssetRootPath(const std::string& path)
220 {
221 m_AssetRootPath = path;
223 }
224
226 {
227 m_LastError.clear();
228
229 if (m_AssetRootPath.empty())
230 {
231 m_LastError = "Asset root path is not set";
232 std::cerr << "BlueprintEditor: " << m_LastError << std::endl;
233 m_AssetTreeRoot = nullptr;
234 return;
235 }
236
237 std::cout << "BlueprintEditor: Scanning assets directory: " << m_AssetRootPath << std::endl;
238
239 try
240 {
241 if (fs::exists(m_AssetRootPath) && fs::is_directory(m_AssetRootPath))
242 {
244 std::cout << "BlueprintEditor: Asset scan complete" << std::endl;
245 }
246 else
247 {
248 m_LastError = "Asset directory not found: " + m_AssetRootPath;
249 std::cerr << "BlueprintEditor: " << m_LastError << std::endl;
250 m_AssetTreeRoot = nullptr;
251 }
252 }
253 catch (const std::exception& e)
254 {
255 m_LastError = std::string("Error scanning assets: ") + e.what();
256 std::cerr << "BlueprintEditor: " << m_LastError << std::endl;
257 m_AssetTreeRoot = nullptr;
258 }
259 }
260
261 std::shared_ptr<AssetNode> BlueprintEditor::ScanDirectory(const std::string& path)
262 {
263 auto node = std::make_shared<AssetNode>(
264 fs::path(path).filename().string(),
265 path,
266 true
267 );
268
269 try
270 {
271 for (const auto& entry : fs::directory_iterator(path))
272 {
273 std::string entryPath = entry.path().string();
274 std::string filename = entry.path().filename().string();
275
276 // Skip hidden files and directories
277 if (filename[0] == '.')
278 continue;
279
280 if (fs::is_directory(entry.path()))
281 {
282 // Recursively scan subdirectories
284 node->children.push_back(childNode);
285 }
286 else if (fs::is_regular_file(entry.path()))
287 {
288 // Check if it's a JSON file
289 if (entry.path().extension() == ".json")
290 {
291 auto fileNode = std::make_shared<AssetNode>(
292 filename,
293 entryPath,
294 false
295 );
296
297 // Detect asset type
299
300 node->children.push_back(fileNode);
301 }
302 }
303 }
304
305 // Sort children: directories first, then files alphabetically
306 std::sort(node->children.begin(), node->children.end(),
307 [](const std::shared_ptr<AssetNode>& a, const std::shared_ptr<AssetNode>& b)
308 {
309 if (a->isDirectory != b->isDirectory)
310 return a->isDirectory > b->isDirectory;
311 return a->name < b->name;
312 });
313 }
314 catch (const std::exception& e)
315 {
316 std::cerr << "BlueprintEditor: Error scanning directory " << path << ": " << e.what() << std::endl;
317 }
318
319 return node;
320 }
321
322 std::string BlueprintEditor::DetectAssetType(const std::string& filepath)
323 {
324 try
325 {
326 json j;
327 if (!JsonHelper::LoadJsonFromFile(filepath, j))
328 return "Unknown";
329
330 // Extract filename once for warning messages
331 std::string filename = fs::path(filepath).filename().string();
332
333 // Priority 1: Check explicit "type" field (v1 + v2 standardized)
334 if (j.contains("type"))
335 {
336 std::string type = j["type"].get<std::string>();
337
338 // Validate against blueprintType if present
339 if (j.contains("blueprintType"))
340 {
341 std::string blueprintType = j["blueprintType"].get<std::string>();
342 if (type != blueprintType)
343 {
344 std::cerr << "[BlueprintEditor] WARNING: type (" << type
345 << ") != blueprintType (" << blueprintType << ") in " << filename << std::endl;
346 }
347 }
348
349 return type;
350 }
351
352 // Priority 2: FALLBACK - Check "blueprintType" for old v2 files
353 if (j.contains("blueprintType"))
354 {
355 std::string type = j["blueprintType"].get<std::string>();
356 std::cerr << "[BlueprintEditor] WARNING: Using 'blueprintType' field (missing 'type') in "
357 << filename << std::endl;
358 return type;
359 }
360
361 // Helper lambda for logging structural detection warnings
362 auto logStructuralDetection = [&filename](const std::string& detectedType) {
363 std::cerr << "[BlueprintEditor] WARNING: No type information found in " << filename
364 << ", using structural detection (detected: " << detectedType << ")" << std::endl;
365 };
366
367 // Priority 3: Structural detection for schema v2 (data wrapper)
368 if (j.contains("data"))
369 {
370 const json& data = j["data"];
371 if (data.contains("rootNodeId") && data.contains("nodes"))
372 {
373 logStructuralDetection("BehaviorTree");
374 return "BehaviorTree";
375 }
376 if (data.contains("components"))
377 {
378 logStructuralDetection("EntityPrefab");
379 return "EntityPrefab";
380 }
381 }
382
383 // Priority 4: Structural detection for schema v1 (direct fields)
384 if (j.contains("rootNodeId") && j.contains("nodes"))
385 {
386 logStructuralDetection("BehaviorTree");
387 return "BehaviorTree";
388 }
389
390 if (j.contains("states") || j.contains("initialState"))
391 {
393 return "HFSM";
394 }
395
396 if (j.contains("components"))
397 {
398 logStructuralDetection("EntityBlueprint");
399 return "EntityBlueprint";
400 }
401
402 std::cerr << "[BlueprintEditor] WARNING: Could not determine type for " << filename
403 << ", defaulting to Generic" << std::endl;
404 return "Generic";
405 }
406 catch (const std::exception& e)
407 {
408 std::string filename = fs::path(filepath).filename().string();
409 std::cerr << "Error detecting asset type in " << filename << ": " << e.what() << std::endl;
410 return "Unknown";
411 }
412 }
413
414 std::vector<AssetMetadata> BlueprintEditor::GetAllAssets() const
415 {
416 std::vector<AssetMetadata> assets;
417 if (m_AssetTreeRoot)
418 {
420 }
421 return assets;
422 }
423
424 void BlueprintEditor::CollectAllAssets(const std::shared_ptr<AssetNode>& node, std::vector<AssetMetadata>& assets) const
425 {
426 if (!node)
427 return;
428
429 // Add files only, not directories
430 if (!node->isDirectory)
431 {
432 AssetMetadata metadata;
433 metadata.filepath = node->fullPath;
434 metadata.name = node->name;
435 metadata.type = node->type;
436 metadata.isDirectory = false;
437
438 // Parse full metadata (const_cast to call non-const method)
439 const_cast<BlueprintEditor*>(this)->ParseAssetMetadata(node->fullPath, metadata);
440
441 assets.push_back(metadata);
442 }
443
444 // Recursively process children
445 for (const auto& child : node->children)
446 {
448 }
449 }
450
451 std::vector<AssetMetadata> BlueprintEditor::GetAssetsByType(const std::string& type) const
452 {
453 std::vector<AssetMetadata> allAssets = GetAllAssets();
454 std::vector<AssetMetadata> filtered;
455
456 for (const auto& asset : allAssets)
457 {
458 if (asset.type == type)
459 {
460 filtered.push_back(asset);
461 }
462 }
463
464 return filtered;
465 }
466
467 std::vector<AssetMetadata> BlueprintEditor::SearchAssets(const std::string& query) const
468 {
469 std::vector<AssetMetadata> allAssets = GetAllAssets();
470 std::vector<AssetMetadata> results;
471
472 if (query.empty())
473 return allAssets;
474
475 // Case-insensitive search
476 std::string lowerQuery = query;
477 std::transform(lowerQuery.begin(), lowerQuery.end(), lowerQuery.begin(), ::tolower);
478
479 for (const auto& asset : allAssets)
480 {
481 std::string lowerName = asset.name;
482 std::transform(lowerName.begin(), lowerName.end(), lowerName.begin(), ::tolower);
483
484 if (lowerName.find(lowerQuery) != std::string::npos)
485 {
486 results.push_back(asset);
487 }
488 }
489
490 return results;
491 }
492
494 {
495 AssetMetadata metadata;
496 metadata.filepath = filepath;
497
498 try
499 {
500 // Get filename
501 metadata.name = fs::path(filepath).filename().string();
502
503 // Detect type
504 metadata.type = DetectAssetType(filepath);
505
506 // Parse detailed metadata
507 ParseAssetMetadata(filepath, metadata);
508 }
509 catch (const std::exception& e)
510 {
511 metadata.isValid = false;
512 metadata.errorMessage = std::string("Error loading asset: ") + e.what();
513 std::cerr << "BlueprintEditor: " << metadata.errorMessage << std::endl;
514 }
515
516 return metadata;
517 }
518
519 void BlueprintEditor::ParseAssetMetadata(const std::string& filepath, AssetMetadata& metadata)
520 {
521 try
522 {
523 json j;
524 if (!JsonHelper::LoadJsonFromFile(filepath, j))
525 {
526 metadata.isValid = false;
527 metadata.errorMessage = "Failed to load JSON file";
528 return;
529 }
530
531 // Priority 1: Check explicit "type" field
532 if (j.contains("type"))
533 {
534 std::string type = JsonHelper::GetString(j, "type", "");
535 metadata.type = type;
536
537 if (type == "EntityBlueprint" || type == "EntityPrefab")
538 {
539 ParseEntityBlueprint(j, metadata);
540 }
541 else if (type == "BehaviorTree")
542 {
543 ParseBehaviorTree(j, metadata);
544 }
545 else if (type == "HFSM")
546 {
547 ParseHFSM(j, metadata);
548 }
549 else
550 {
551 metadata.name = JsonHelper::GetString(j, "name", metadata.name);
552 metadata.description = JsonHelper::GetString(j, "description", "");
553 }
554 }
555 // Priority 2: FALLBACK - Check "blueprintType" for old v2 files
556 else if (j.contains("blueprintType"))
557 {
558 std::string type = JsonHelper::GetString(j, "blueprintType", "");
559 metadata.type = type;
560
561 std::string filename = fs::path(filepath).filename().string();
562 std::cerr << "[ParseAssetMetadata] Warning: Using deprecated 'blueprintType' field in " << filename << std::endl;
563
564 if (type == "BehaviorTree")
565 {
566 ParseBehaviorTree(j, metadata);
567 }
568 else if (type == "HFSM")
569 {
570 ParseHFSM(j, metadata);
571 }
572 else if (type == "EntityBlueprint" || type == "EntityPrefab")
573 {
574 ParseEntityBlueprint(j, metadata);
575 }
576 else
577 {
578 metadata.name = JsonHelper::GetString(j, "name", metadata.name);
579 metadata.description = JsonHelper::GetString(j, "description", "");
580 }
581 }
582 // Priority 3: Structural detection for schema v2 (data wrapper)
583 else if (j.contains("data"))
584 {
585 const json& data = j["data"];
586 if (data.contains("rootNodeId") && data.contains("nodes"))
587 {
588 metadata.type = "BehaviorTree";
589 ParseBehaviorTree(j, metadata);
590 }
591 else if (data.contains("components"))
592 {
593 metadata.type = "EntityPrefab";
594 ParseEntityBlueprint(j, metadata);
595 }
596 else
597 {
598 metadata.type = "Generic";
599 metadata.name = JsonHelper::GetString(j, "name", metadata.name);
600 metadata.description = JsonHelper::GetString(j, "description", "");
601 }
602 }
603 // Priority 4: Structural detection for schema v1 (direct fields)
604 else if (j.contains("rootNodeId") && j.contains("nodes"))
605 {
606 metadata.type = "BehaviorTree";
607 ParseBehaviorTree(j, metadata);
608 }
609 else if (j.contains("states") || j.contains("initialState"))
610 {
611 metadata.type = "HFSM";
612 ParseHFSM(j, metadata);
613 }
614 else if (j.contains("components"))
615 {
616 metadata.type = "EntityBlueprint";
617 ParseEntityBlueprint(j, metadata);
618 }
619 else
620 {
621 metadata.type = "Generic";
622 metadata.name = JsonHelper::GetString(j, "name", metadata.name);
623 metadata.description = JsonHelper::GetString(j, "description", "");
624 }
625
626 metadata.isValid = true;
627 }
628 catch (const std::exception& e)
629 {
630 metadata.isValid = false;
631 metadata.errorMessage = std::string("JSON Parse Error: ") + e.what();
632 std::cerr << "BlueprintEditor: " << metadata.errorMessage << std::endl;
633 }
634 }
635
637 {
638 metadata.name = JsonHelper::GetString(j, "name", "Unnamed Entity");
639 metadata.description = JsonHelper::GetString(j, "description", "");
640
641 // Schema v2: Check for components in "data" wrapper
642 if (j.contains("data") && j["data"].contains("components") && j["data"]["components"].is_array())
643 {
644 const auto& components = j["data"]["components"];
645 metadata.componentCount = (int)components.size();
646
647 // Extract component types
648 for (size_t i = 0; i < components.size(); ++i)
649 {
650 const auto& comp = components[i];
651 if (comp.contains("type") && comp["type"].is_string())
652 {
653 std::string compType = JsonHelper::GetString(comp, "type", "Unknown");
654 metadata.components.push_back(compType);
655 }
656 }
657 }
658 // Schema v1: Check for components at top level
659 else if (j.contains("components") && j["components"].is_array())
660 {
661 const auto& components = j["components"];
662 metadata.componentCount = (int)components.size();
663
664 // Extract component types
665 for (size_t i = 0; i < components.size(); ++i)
666 {
667 const auto& comp = components[i];
668 if (comp.contains("type") && comp["type"].is_string())
669 {
670 std::string compType = JsonHelper::GetString(comp, "type", "Unknown");
671 metadata.components.push_back(compType);
672 }
673 }
674 }
675 }
676
678 {
679 metadata.name = JsonHelper::GetString(j, "name", "Unnamed Behavior Tree");
680 metadata.description = "Behavior Tree AI Definition";
681
682 // Schema v2: Check for nodes in "data" wrapper
683 if (j.contains("data"))
684 {
685 const json& data = j["data"];
686 if (data.contains("nodes") && data["nodes"].is_array())
687 {
688 const auto& nodes = data["nodes"];
689 metadata.nodeCount = (int)nodes.size();
690
691 // Extract node types
692 for (size_t i = 0; i < nodes.size(); ++i)
693 {
694 const auto& node = nodes[i];
695 if (node.contains("type") && node["type"].is_string())
696 {
697 std::string nodeType = JsonHelper::GetString(node, "type", "Unknown");
698 if (node.contains("name") && node["name"].is_string())
699 {
700 std::string nodeName = JsonHelper::GetString(node, "name", "");
701 metadata.nodes.push_back(nodeName + " (" + nodeType + ")");
702 }
703 else
704 {
705 metadata.nodes.push_back(nodeType);
706 }
707 }
708 }
709 }
710
711 if (data.contains("rootNodeId"))
712 {
713 int rootId = data["rootNodeId"].get<int>();
714 metadata.description += " - Root Node ID: " + std::to_string(rootId);
715 }
716 }
717 // Schema v1: Check for nodes at top level
718 else if (j.contains("nodes") && j["nodes"].is_array())
719 {
720 const auto& nodes = j["nodes"];
721 metadata.nodeCount = (int)nodes.size();
722
723 // Extract node types
724 for (size_t i = 0; i < nodes.size(); ++i)
725 {
726 const auto& node = nodes[i];
727 if (node.contains("type") && node["type"].is_string())
728 {
729 std::string nodeType = JsonHelper::GetString(node, "type", "Unknown");
730 if (node.contains("name") && node["name"].is_string())
731 {
732 std::string nodeName = JsonHelper::GetString(node, "name", "");
733 metadata.nodes.push_back(nodeName + " (" + nodeType + ")");
734 }
735 else
736 {
737 metadata.nodes.push_back(nodeType);
738 }
739 }
740 }
741
742 if (j.contains("rootNodeId"))
743 {
744 int rootId = j["rootNodeId"].get<int>();
745 metadata.description += " - Root Node ID: " + std::to_string(rootId);
746 }
747 }
748 }
749
751 {
752 metadata.name = JsonHelper::GetString(j, "name", "Unnamed HFSM");
753 metadata.description = "Hierarchical Finite State Machine";
754
755 // Count states
756 if (j.contains("states") && j["states"].is_array())
757 {
758 const auto& states = j["states"];
759 metadata.nodeCount = (int)states.size();
760
761 // Extract state names
762 for (size_t i = 0; i < states.size(); ++i)
763 {
764 const auto& state = states[i];
765 if (state.contains("name") && state["name"].is_string())
766 {
767 std::string stateName = JsonHelper::GetString(state, "name", "");
768 std::string stateType = JsonHelper::GetString(state, "type", "State");
769 metadata.nodes.push_back(stateName + " (" + stateType + ")");
770 }
771 }
772 }
773
774 // Add initial state info
775 if (j.contains("initialState"))
776 {
777 std::string initialState = JsonHelper::GetString(j, "initialState", "");
778 if (!initialState.empty())
779 {
780 metadata.description += " - Initial State: " + initialState;
781 }
782 }
783 }
784
785 bool BlueprintEditor::IsAssetValid(const std::string& filepath) const
786 {
787 try
788 {
789 json j;
790 return JsonHelper::LoadJsonFromFile(filepath, j);
791 }
792 catch (const std::exception&)
793 {
794 return false;
795 }
796 }
797
798 // ========================================================================
799 // B) Runtime Entity Management Implementation
800 // ========================================================================
801
803 {
804 // Add to runtime entities list if not already present
805 auto it = std::find(m_RuntimeEntities.begin(), m_RuntimeEntities.end(), entityId);
806 if (it == m_RuntimeEntities.end())
807 {
808 m_RuntimeEntities.push_back(entityId);
809 std::cout << "BlueprintEditor: Entity " << entityId << " created (total: "
810 << m_RuntimeEntities.size() << ")" << std::endl;
811 }
812 }
813
815 {
816 // Remove from runtime entities list
817 auto it = std::find(m_RuntimeEntities.begin(), m_RuntimeEntities.end(), entityId);
818 if (it != m_RuntimeEntities.end())
819 {
820 m_RuntimeEntities.erase(it);
821 std::cout << "BlueprintEditor: Entity " << entityId << " destroyed (total: "
822 << m_RuntimeEntities.size() << ")" << std::endl;
823
824 // If this was the selected entity, clear selection
825 if (m_SelectedEntity == entityId)
826 {
827 m_SelectedEntity = 0; // INVALID_ENTITY_ID
828 }
829 }
830 }
831
832 // ========================================================================
833 // C) Entity Selection Implementation
834 // ========================================================================
835
837 {
838 if (m_SelectedEntity != entityId)
839 {
840 m_SelectedEntity = entityId;
841 std::cout << "BlueprintEditor: Selected entity " << entityId << std::endl;
842
843 // All panels will automatically read this selection on next Render()
844 // No explicit notification needed - reactive update pattern
845 }
846 }
847
848 // ========================================================================
849 // Asset Selection Implementation
850 // ========================================================================
851
853 {
855 {
857 std::cout << "BlueprintEditor: Selected asset " << assetPath << std::endl;
858 }
859 }
860
861 // ========================================================================
862 // Graph Loading in Node Graph Editor
863 // ========================================================================
864
866 {
867 std::cout << "BlueprintEditor: Opening graph " << assetPath << " in Node Graph Editor" << std::endl;
868
869 // Detect asset type
870 std::string assetType = DetectAssetType(assetPath);
871
872 // Only open BehaviorTree and HFSM types
873 if (assetType != "BehaviorTree" && assetType != "HFSM")
874 {
875 std::cerr << "BlueprintEditor: Cannot open asset type '" << assetType
876 << "' in Node Graph Editor (only BehaviorTree and HFSM supported)" << std::endl;
877 m_LastError = "Asset type '" + assetType + "' cannot be opened in Node Graph Editor";
878 return;
879 }
880
881 // Use NodeGraphManager to load the graph
883
884 if (graphId < 0)
885 {
886 std::cerr << "BlueprintEditor: Failed to load graph from " << assetPath << std::endl;
887 m_LastError = "Failed to load graph file: " + assetPath;
888 return;
889 }
890
891 // Graph is now loaded and active in NodeGraphManager
892 std::cout << "BlueprintEditor: Graph loaded with ID " << graphId << std::endl;
893 }
894
895 // ========================================================================
896 // Phase 5: Template Management Implementation
897 // ========================================================================
898
899 bool BlueprintEditor::SaveCurrentAsTemplate(const std::string& name,
900 const std::string& description,
901 const std::string& category)
902 {
903 if (!HasBlueprint())
904 {
905 m_LastError = "No blueprint loaded to save as template";
906 return false;
907 }
908
909 // Convert current blueprint to JSON
911
912 // Create template from current blueprint
915 name,
916 description,
917 category,
918 "User"
919 );
920
921 // Save template
922 if (!TemplateManager::Get().SaveTemplate(tpl))
923 {
924 m_LastError = "Failed to save template: " + TemplateManager::Get().GetLastError();
925 return false;
926 }
927
928 std::cout << "Template saved: " << name << " (" << tpl.id << ")" << std::endl;
929 return true;
930 }
931
933 {
935
936 if (!TemplateManager::Get().ApplyTemplateToBlueprint(templateId, blueprintJson))
937 {
938 m_LastError = "Failed to apply template: " + TemplateManager::Get().GetLastError();
939 return false;
940 }
941
942 // Load the blueprint from JSON
944 m_CurrentFilepath = ""; // Clear filepath since this is a new blueprint from template
945 m_HasUnsavedChanges = true;
946
947 std::cout << "Template applied: " << templateId << std::endl;
948 return true;
949 }
950
952 {
954 {
955 m_LastError = "Failed to delete template: " + TemplateManager::Get().GetLastError();
956 return false;
957 }
958
959 std::cout << "Template deleted: " << templateId << std::endl;
960 return true;
961 }
962
964 {
966 std::cout << "Templates reloaded" << std::endl;
967 }
968
969 // ========================================================================
970 // Phase 6: Undo/Redo System Implementation
971 // ========================================================================
972
974 {
975 if (m_CommandStack)
976 {
978 m_HasUnsavedChanges = true;
979 }
980 }
981
983 {
984 if (m_CommandStack)
985 {
987 m_HasUnsavedChanges = true;
988 }
989 }
990
992 {
994 }
995
997 {
999 }
1000
1002 {
1003 if (m_CommandStack)
1004 {
1006 }
1007 return "";
1008 }
1009
1011 {
1012 if (m_CommandStack)
1013 {
1015 }
1016 return "";
1017 }
1018
1023
1024 // ========================================================================
1025 // Plugin System Implementation
1026 // ========================================================================
1027
1029 {
1030 std::cout << "BlueprintEditor: Initializing plugins..." << std::endl;
1031
1032 // Register all plugins
1033 RegisterPlugin(std::make_unique<BehaviorTreeEditorPlugin>());
1034 RegisterPlugin(std::make_unique<HFSMEditorPlugin>());
1035 RegisterPlugin(std::make_unique<EntityPrefabEditorPlugin>());
1036 RegisterPlugin(std::make_unique<AnimationGraphEditorPlugin>());
1037 RegisterPlugin(std::make_unique<ScriptedEventEditorPlugin>());
1038 RegisterPlugin(std::make_unique<LevelDefinitionEditorPlugin>());
1039 RegisterPlugin(std::make_unique<UIMenuEditorPlugin>());
1040
1041 std::cout << "BlueprintEditor: " << m_Plugins.size() << " plugins registered" << std::endl;
1042 }
1043
1044 void BlueprintEditor::RegisterPlugin(std::unique_ptr<BlueprintEditorPlugin> plugin)
1045 {
1046 std::string type = plugin->GetBlueprintType();
1047 m_Plugins[type] = std::move(plugin);
1048 std::cout << "BlueprintEditor: Registered plugin: " << type << std::endl;
1049 }
1050
1052 {
1053 auto it = m_Plugins.find(type);
1054 if (it != m_Plugins.end())
1055 {
1056 return it->second.get();
1057 }
1058 return nullptr;
1059 }
1060
1062 {
1063 // V2 format: read blueprintType directly
1064 if (blueprint.contains("blueprintType"))
1065 {
1066 std::string type = blueprint["blueprintType"].get<std::string>();
1067 return GetPlugin(type);
1068 }
1069
1070 // V1 format: use heuristic detection
1071 for (auto& pluginPair : m_Plugins)
1072 {
1073 auto& type = pluginPair.first;
1074 auto& plugin = pluginPair.second;
1075 if (plugin->CanHandle(blueprint))
1076 {
1077 return plugin.get();
1078 }
1079 }
1080
1081 return nullptr;
1082 }
1083
1084 // ========================================================================
1085 // Migration System Implementation
1086 // ========================================================================
1087
1088 std::vector<std::string> BlueprintEditor::ScanBlueprintFiles(const std::string& directory)
1089 {
1090 std::vector<std::string> blueprintFiles;
1091
1092 try
1093 {
1094 if (!fs::exists(directory) || !fs::is_directory(directory))
1095 {
1096 std::cerr << "BlueprintEditor: Directory not found: " << directory << std::endl;
1097 return blueprintFiles;
1098 }
1099
1100 for (const auto& entry : fs::recursive_directory_iterator(directory))
1101 {
1102 if (fs::is_regular_file(entry.path()) && entry.path().extension() == ".json")
1103 {
1104 blueprintFiles.push_back(entry.path().string());
1105 }
1106 }
1107 }
1108 catch (const std::exception& e)
1109 {
1110 std::cerr << "BlueprintEditor: Error scanning directory: " << e.what() << std::endl;
1111 }
1112
1113 return blueprintFiles;
1114 }
1115
1117 {
1118 std::cout << "BlueprintEditor: Starting migration..." << std::endl;
1119
1121 int successCount = 0;
1122 int failCount = 0;
1123 int skippedCount = 0;
1124
1125 std::vector<std::string> files = ScanBlueprintFiles(m_AssetRootPath);
1126
1127 for (const auto& path : files)
1128 {
1129 try
1130 {
1131 json v1;
1132 if (!JsonHelper::LoadJsonFromFile(path, v1))
1133 {
1134 std::cerr << "Failed to load: " << path << std::endl;
1135 failCount++;
1136 continue;
1137 }
1138
1139 // Check if already v2
1140 if (migrator.IsV2(v1))
1141 {
1142 std::cout << "Skipping (already v2): " << path << std::endl;
1143 skippedCount++;
1144 continue;
1145 }
1146
1147 // Create backup
1148 std::string backupPath = path + ".v1.backup";
1149 try
1150 {
1151 fs::copy_file(path, backupPath, fs::copy_options::overwrite_existing);
1152 }
1153 catch (const std::exception& e)
1154 {
1155 std::cerr << "Failed to create backup for " << path << ": " << e.what() << std::endl;
1156 failCount++;
1157 continue;
1158 }
1159
1160 // Migrate
1161 json v2 = migrator.MigrateToV2(v1);
1162
1163 // Save
1164 std::ofstream file(path);
1165 if (!file.is_open())
1166 {
1167 std::cerr << "Failed to open file for writing: " << path << std::endl;
1168 failCount++;
1169 continue;
1170 }
1171
1172 file << v2.dump(2);
1173 file.close();
1174
1175 std::cout << "Migrated: " << path << std::endl;
1176 successCount++;
1177 }
1178 catch (const std::exception& e)
1179 {
1180 std::cerr << "Migration failed for " << path << ": " << e.what() << std::endl;
1181 failCount++;
1182 }
1183 }
1184
1185 std::cout << "Migration complete: " << successCount << " success, "
1186 << skippedCount << " skipped, " << failCount << " failed" << std::endl;
1187
1188 // Refresh assets after migration
1189 RefreshAssets();
1190 }
1191
1192} // namespace Olympe
ComponentTypeID GetComponentTypeID_Static()
Definition ECS_Entity.h:56
BlueprintEditorPlugin - Base interface for blueprint type plugins Each plugin handles a specific blue...
BlueprintEditor Singleton Backend Manages all business logic, state, and data for the Blueprint Edito...
std::vector< AssetMetadata > SearchAssets(const std::string &query) const
void RegisterPlugin(std::unique_ptr< class BlueprintEditorPlugin > plugin)
void ParseEntityBlueprint(const json &j, AssetMetadata &metadata)
std::vector< std::string > ScanBlueprintFiles(const std::string &directory)
static BlueprintEditor & Instance()
std::map< std::string, std::unique_ptr< class BlueprintEditorPlugin > > m_Plugins
bool LoadBlueprint(const std::string &filepath)
std::string DetectAssetType(const std::string &filepath)
std::shared_ptr< AssetNode > ScanDirectory(const std::string &path)
std::vector< AssetMetadata > GetAssetsByType(const std::string &type) const
std::vector< uint64_t > m_RuntimeEntities
void NewBlueprint(const std::string &name, const std::string &description="")
void NotifyEntityDestroyed(uint64_t entityId)
std::string GetNextRedoDescription() const
void SetAssetRootPath(const std::string &path)
class CommandStack * m_CommandStack
std::string GetLastCommandDescription() const
void NotifyEntityCreated(uint64_t entityId)
void ParseHFSM(const json &j, AssetMetadata &metadata)
bool DeleteTemplate(const std::string &templateId)
AssetMetadata GetAssetMetadata(const std::string &filepath)
bool IsAssetValid(const std::string &filepath) const
void SelectAsset(const std::string &assetPath)
void SetSelectedEntity(uint64_t entityId)
class CommandStack * GetCommandStack()
bool SaveBlueprintAs(const std::string &filepath)
class BlueprintEditorPlugin * GetPlugin(const std::string &type)
void OpenGraphInEditor(const std::string &assetPath)
bool ApplyTemplate(const std::string &templateId)
std::shared_ptr< AssetNode > m_AssetTreeRoot
void ParseAssetMetadata(const std::string &filepath, AssetMetadata &metadata)
bool SaveCurrentAsTemplate(const std::string &name, const std::string &description, const std::string &category)
class BlueprintEditorPlugin * DetectPlugin(const json &blueprint)
std::vector< AssetMetadata > GetAllAssets() const
void CollectAllAssets(const std::shared_ptr< AssetNode > &node, std::vector< AssetMetadata > &assets) const
void ParseBehaviorTree(const json &j, AssetMetadata &metadata)
Blueprint::EntityBlueprint m_CurrentBlueprint
void Update(float deltaTime)
BlueprintMigrator - Converts v1 blueprints to v2 format Handles automatic position calculation and st...
CommandStack - Manages undo/redo command history Maintains two stacks for undo and redo operations.
std::string GetNextRedoDescription() const
std::string GetLastCommandDescription() const
static EditorContext & Get()
static EntityInspectorManager & Get()
static EnumCatalogManager & Get()
int LoadGraph(const std::string &filepath)
static NodeGraphManager & Get()
void Initialize(const std::string &templatesPath="Blueprints/Templates")
std::string GetLastError() const
static TemplateManager & Get()
BlueprintTemplate CreateTemplateFromBlueprint(const json &blueprint, const std::string &name, const std::string &description, const std::string &category, const std::string &author="User")
std::string GetString(const json &j, const std::string &key, const std::string &defaultValue="")
Safely get a string value from JSON.
bool LoadJsonFromFile(const std::string &filepath, json &j)
Load and parse a JSON file.
Definition json_helper.h:42
nlohmann::json json
std::vector< std::string > components
std::vector< std::string > nodes
BlueprintTemplate - Template metadata and data Stores a complete blueprint that can be reused as a te...
static EntityBlueprint LoadFromFile(const std::string &filepath)
bool SaveToFile(const std::string &filepath) const
static EntityBlueprint FromJson(const json &j)