Olympe Engine 2.0
2D Game Engine with ECS Architecture
Loading...
Searching...
No Matches
PrefabScanner.cpp
Go to the documentation of this file.
1/*
2 * PrefabScanner.cpp - Prefab Directory Scanner Implementation
3 *
4 * Cross-platform directory scanning without std::filesystem.
5 * Uses Windows API on Windows, POSIX on Unix/Linux.
6 */
7
8#include "PrefabScanner.h"
10#include "ParameterSchema.h"
11#include "third_party/nlohmann/json.hpp"
12#include "system/system_utils.h"
13#include <fstream>
14#include <algorithm>
15#include <cstring>
16#include <functional>
17#include <iomanip>
18#include <iostream>
19
20#ifdef _WIN32
21#include <windows.h>
22#else
23#include <dirent.h>
24#include <sys/stat.h>
25#endif
26
28
29//=============================================================================
30// PrefabRegistry Implementation
31//=============================================================================
32
34{
35 if (blueprint.prefabName.empty()) return;
36
37 m_blueprints[blueprint.prefabName] = blueprint;
38 if (!blueprint.prefabType.empty())
39 {
40 m_typeToName[blueprint.prefabType] = blueprint.prefabName;
41 }
42}
43
44const PrefabBlueprint* PrefabRegistry::Find(const std::string& name) const
45{
46 auto it = m_blueprints.find(name);
47 return (it != m_blueprints.end()) ? &it->second : nullptr;
48}
49
50std::vector<const PrefabBlueprint*> PrefabRegistry::FindByType(const std::string& type) const
51{
52 std::vector<const PrefabBlueprint*> results;
53
54 // -> Type already normalized in upstream, use direct comparison
55 for (const auto& pair : m_blueprints)
56 {
57 if (pair.second.prefabType == type)
58 {
59 results.push_back(&pair.second);
60 }
61 }
62
63 return results;
64}
65
66std::vector<std::string> PrefabRegistry::GetAllPrefabNames() const
67{
68 std::vector<std::string> names;
69 names.reserve(m_blueprints.size());
70 for (const auto& pair : m_blueprints)
71 {
72 names.push_back(pair.first);
73 }
74 return names;
75}
76
77//=============================================================================
78// PrefabScanner Implementation
79//=============================================================================
80
84
88
89std::string PrefabScanner::ToUpper(const std::string& str) const
90{
91 std::string result = str;
92 std::transform(result.begin(), result.end(), result.begin(), ::toupper);
93 return result;
94}
95
96std::vector<PrefabBlueprint> PrefabScanner::ScanDirectory(const std::string& rootPath)
97{
98 SYSTEM_LOG << "\n";
99 SYSTEM_LOG << "+======================================================================+\\n";
100 SYSTEM_LOG << "| PREFAB DIRECTORY SCAN |\n";
101 SYSTEM_LOG << "|======================================================================|\n";
102 SYSTEM_LOG << "| Path: " << rootPath << std::string(max(0, 63 - static_cast<int>(rootPath.length())), ' ') << "|" << std::endl;
103 SYSTEM_LOG << "\\======================================================================+\n\n";
104
105 std::vector<PrefabBlueprint> blueprints;
106 std::vector<std::string> prefabFiles;
107
108 // Scan directory recursively
109 SYSTEM_LOG << "-> Scanning for .json files...\n";
110#ifdef _WIN32
112#else
114#endif
115
116 SYSTEM_LOG << "-> Found " << prefabFiles.size() << " file(s)\n\n";
117
118 // Parse each prefab file
119 SYSTEM_LOG << "-> Parsing prefab files...\n";
120 int validCount = 0;
121 int invalidCount = 0;
122 int totalComponents = 0;
123 int totalResources = 0;
124
125 for (const auto& filepath : prefabFiles)
126 {
128
129 if (blueprint.isValid)
130 {
131 blueprints.push_back(blueprint);
132 validCount++;
133 totalComponents += static_cast<int>(blueprint.components.size());
134 totalResources += static_cast<int>(blueprint.resources.spriteRefs.size() +
135 blueprint.resources.audioRefs.size() +
136 blueprint.resources.modelRefs.size());
137
138 SYSTEM_LOG << " -> " << blueprint.prefabName << " [" << blueprint.prefabType << "] "
139 << "(" << blueprint.components.size() << " components, "
140 << (blueprint.resources.spriteRefs.size() + blueprint.resources.audioRefs.size() + blueprint.resources.modelRefs.size())
141 << " resources)\n";
142 }
143 else
144 {
145 invalidCount++;
146 SYSTEM_LOG << " x " << filepath << " (parse failed)";
147 if (!blueprint.errors.empty())
148 {
149 SYSTEM_LOG << " - " << blueprint.errors[0];
150 }
151 SYSTEM_LOG << "\n";
152 }
153 }
154
155 SYSTEM_LOG << "\n";
156 SYSTEM_LOG << "+======================================================================+ \n";
157 SYSTEM_LOG << "| SCAN COMPLETE | \n";
158 SYSTEM_LOG << "|======================================================================| \n";
159 SYSTEM_LOG << "| Valid Prefabs: " << validCount
160 << std::string(51 - std::to_string(validCount).length(), ' ') << "| \n";
161 SYSTEM_LOG << "| Invalid Prefabs: " << invalidCount
162 << std::string(51 - std::to_string(invalidCount).length(), ' ') << "| \n";
163 SYSTEM_LOG << "| Total Components: " << totalComponents
164 << std::string(50 - std::to_string(totalComponents).length(), ' ') << "| \n";
165 SYSTEM_LOG << "| Total Resources: " << totalResources
166 << std::string(50 - std::to_string(totalResources).length(), ' ') << "| \n";
167 SYSTEM_LOG << "\\======================================================================+ \n\n";
168
169 return blueprints;
170}
171
172#ifdef _WIN32
173void PrefabScanner::ScanDirectoryRecursive_Windows(const std::string& path, std::vector<std::string>& outFiles)
174{
176 std::string searchPath = path + "\\*";
178
180 {
181 return;
182 }
183
184 do
185 {
186 std::string filename = findData.cFileName;
187
188 // Skip . and ..
189 if (filename == "." || filename == "..")
190 continue;
191
192 std::string fullPath = path + "\\" + filename;
193
194 if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
195 {
196 // Recursively scan subdirectory
198 }
199 else
200 {
201 // Check if it's a .json file
202 if (filename.length() > 5 &&
203 filename.substr(filename.length() - 5) == ".json")
204 {
205 outFiles.push_back(fullPath);
206 }
207 }
208 } while (FindNextFileA(hFind, &findData) != 0);
209
211}
212#else
213void PrefabScanner::ScanDirectoryRecursive_Unix(const std::string& path, std::vector<std::string>& outFiles)
214{
215 DIR* dir = opendir(path.c_str());
216 if (!dir)
217 {
218 return;
219 }
220
221 struct dirent* entry;
222 while ((entry = readdir(dir)) != nullptr)
223 {
224 std::string filename = entry->d_name;
225
226 // Skip . and ..
227 if (filename == "." || filename == "..")
228 continue;
229
230 std::string fullPath = path + "/" + filename;
231
232 // Check if it's a directory
233 struct stat statbuf;
234 if (stat(fullPath.c_str(), &statbuf) == 0)
235 {
236 if (S_ISDIR(statbuf.st_mode))
237 {
238 // Recursively scan subdirectory
240 }
241 else if (S_ISREG(statbuf.st_mode))
242 {
243 // Check if it's a .json file
244 if (filename.length() > 5 &&
245 filename.substr(filename.length() - 5) == ".json")
246 {
247 outFiles.push_back(fullPath);
248 }
249 }
250 }
251 }
252
253 closedir(dir);
254}
255#endif
256
257PrefabBlueprint PrefabScanner::ParsePrefab(const std::string& filepath)
258{
260 blueprint.filePath = filepath;
261 blueprint.prefabName = RemoveExtension(GetFilename(filepath));
262
263 // Read file
264 std::ifstream file(filepath);
265 if (!file.is_open())
266 {
267 blueprint.errors.push_back("Failed to open file");
268 return blueprint;
269 }
270
271 std::string content((std::istreambuf_iterator<char>(file)),
272 std::istreambuf_iterator<char>());
273 file.close();
274
275 try
276 {
277 json j = json::parse(content);
278
279 // CRITICAL FIX: Extract prefabType from Identity_data::entityType first
280 blueprint.prefabType = ExtractPrefabType(j);
281
282 // Fallback to top-level metadata if extraction failed
283 if (blueprint.prefabType.empty())
284 {
285 if (j.contains("type"))
286 {
287 std::string typeValue = j["type"].get<std::string>();
288 if (typeValue != "EntityPrefab" && typeValue != "Player")
289 {
290 blueprint.prefabType = typeValue;
291 }
292 }
293 else if (j.contains("blueprintType"))
294 {
295 blueprint.prefabType = j["blueprintType"].get<std::string>();
296 }
297 }
298
299 // Final fallback: use prefabName
300 if (blueprint.prefabType.empty())
301 {
302 blueprint.prefabType = blueprint.prefabName;
303 }
304
305 if (j.contains("schema_version"))
306 {
307 blueprint.version = std::to_string(j["schema_version"].get<int>());
308 }
309
310 if (j.contains("description"))
311 {
312 blueprint.description = j["description"].get<std::string>();
313 }
314
315 if (j.contains("name"))
316 {
317 blueprint.prefabName = j["name"].get<std::string>();
318 }
319
320 // Parse the "data" field
321 if (j.contains("data") && j["data"].is_object())
322 {
323 json dataJson = j["data"];
324
325 // Override prefab name from data if available
326 if (dataJson.contains("prefabName"))
327 {
328 blueprint.prefabName = dataJson["prefabName"].get<std::string>();
329 }
330
331 // Parse components array
332 if (dataJson.contains("components") && dataJson["components"].is_array())
333 {
334 json componentsArray = dataJson["components"];
335
336 for (const auto& compJson : componentsArray)
337 {
338 try
339 {
341 blueprint.components.push_back(compDef);
342 }
343 catch (const std::exception& e)
344 {
345 blueprint.errors.push_back(std::string("Component parse error: ") + e.what());
346 }
347 }
348
349 // Extract resource references from components
351 }
352 }
353
354 blueprint.isValid = true;
355 }
356 catch (const std::exception& e)
357 {
358 blueprint.errors.push_back(std::string("JSON parse error: ") + e.what());
359 blueprint.isValid = false;
360 }
361
362 // Auto-discover schemas from this prefab if valid
363 if (blueprint.isValid)
364 {
366 }
367
368 return blueprint;
369}
370
371std::string PrefabScanner::DetectComponentType(const std::string& typeName)
372{
373 // Component type detection heuristics
374 std::string lower = typeName;
375 std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower);
376
377 if (lower.find("identity") != std::string::npos) return "Identity";
378 if (lower.find("position") != std::string::npos) return "Position";
379 if (lower.find("sprite") != std::string::npos || lower.find("visual") != std::string::npos) return "VisualSprite";
380 if (lower.find("boundingbox") != std::string::npos || lower.find("collision") != std::string::npos) return "BoundingBox";
381 if (lower.find("movement") != std::string::npos) return "Movement";
382 if (lower.find("physics") != std::string::npos) return "PhysicsBody";
383 if (lower.find("health") != std::string::npos) return "Health";
384 if (lower.find("player") != std::string::npos && lower.find("binding") != std::string::npos) return "PlayerBinding";
385 if (lower.find("controller") != std::string::npos) return "Controller";
386 if (lower.find("audio") != std::string::npos || lower.find("sound") != std::string::npos) return "Audio";
387
388 return typeName;
389}
390
392{
393 // Common resource field names
394 std::vector<std::string> spriteFields = {
395 "sprite", "spritePath", "texture", "texturePath", "image", "imagePath"
396 };
397
398 std::vector<std::string> audioFields = {
399 "audio", "audioPath", "sound", "soundPath", "music", "musicPath"
400 };
401
402 std::vector<std::string> modelFields = {
403 "model", "modelPath", "mesh", "meshPath"
404 };
405
406 // Recursive search for resource references
407 std::function<void(const json&)> searchJson;
408 searchJson = [&](const json& obj)
409 {
410 if (obj.is_object())
411 {
412 for (auto it = obj.begin(); it != obj.end(); ++it)
413 {
414 std::string key = it.key();
415
416 // Check for sprite references
417 for (const auto& field : spriteFields)
418 {
419 if (key == field && it.value().is_string())
420 {
421 std::string path = it.value().get<std::string>();
422 if (!path.empty())
423 {
424 outResources.spriteRefs.push_back(path);
425 }
426 }
427 }
428
429 // Check for audio references
430 for (const auto& field : audioFields)
431 {
432 if (key == field && it.value().is_string())
433 {
434 std::string path = it.value().get<std::string>();
435 if (!path.empty())
436 {
437 outResources.audioRefs.push_back(path);
438 }
439 }
440 }
441
442 // Check for model references
443 for (const auto& field : modelFields)
444 {
445 if (key == field && it.value().is_string())
446 {
447 std::string path = it.value().get<std::string>();
448 if (!path.empty())
449 {
450 outResources.modelRefs.push_back(path);
451 }
452 }
453 }
454
455 // Recurse into nested objects and arrays
456 if (it.value().is_object() || it.value().is_array())
457 {
458 searchJson(it.value());
459 }
460 }
461 }
462 else if (obj.is_array())
463 {
464 for (const auto& element : obj)
465 {
467 }
468 }
469 };
470
472}
473
474std::string PrefabScanner::GetFilename(const std::string& filepath)
475{
476 size_t pos = filepath.find_last_of("/\\");
477 if (pos != std::string::npos)
478 {
479 return filepath.substr(pos + 1);
480 }
481 return filepath;
482}
483
484std::string PrefabScanner::RemoveExtension(const std::string& filename)
485{
486 size_t pos = filename.find_last_of(".");
487 if (pos != std::string::npos)
488 {
489 return filename.substr(0, pos);
490 }
491 return filename;
492}
493
494//=============================================================================
495// NEW: Synonym System Implementation
496//=============================================================================
497
498int LevenshteinDistance(const std::string& s1, const std::string& s2)
499{
500 const size_t m = s1.size();
501 const size_t n = s2.size();
502
503 if (m == 0) return static_cast<int>(n);
504 if (n == 0) return static_cast<int>(m);
505
506 std::vector<std::vector<int>> costs(m + 1, std::vector<int>(n + 1));
507
510
511 for (size_t i = 1; i <= m; ++i)
512 {
513 for (size_t j = 1; j <= n; ++j)
514 {
515 int cost = (s1[i - 1] == s2[j - 1]) ? 0 : 1;
516 costs[i][j] = min(
517 min(costs[i - 1][j] + 1, // suppression
518 costs[i][j - 1] + 1), // insertion
519 costs[i - 1][j - 1] + cost // substitution
520 );
521 }
522 }
523
524 return costs[m][n];
525}
526
527float PrefabScanner::FuzzyMatch(const std::string& str1, const std::string& str2) const
528{
529 if (str1.empty() || str2.empty()) return 0.0f;
530 if (str1 == str2) return 1.0f;
531
533 int maxLen = (int) max(str1.length(), str2.length());
534
535 if (maxLen == 0) return 1.0f;
536
537 return 1.0f - (static_cast<float>(distance) / static_cast<float>(maxLen));
538}
539
541{
542 std::string filepath = directory + "/EntityPrefabSynonymsRegister.json";
543
544 SYSTEM_LOG << "Step 1/3: Loading synonym registry...\n";
545 SYSTEM_LOG << " Loading: " << filepath << "\n";
546
547 // Check if file exists
548 std::ifstream file(filepath);
549 if (!file.is_open())
550 {
551 SYSTEM_LOG << " /!\\ Synonym registry not found, using default behavior\n";
552 return false;
553 }
554
555 try
556 {
557 json j;
558 file >> j;
559 file.close();
560
561 // Parse fallback behavior
562 if (j.contains("fallbackBehavior") && j["fallbackBehavior"].is_object())
563 {
564 const json& fb = j["fallbackBehavior"];
565 m_caseSensitive = fb.value("caseSensitive", false);
566 m_enableFuzzyMatching = fb.value("enableFuzzyMatching", true);
567 m_fuzzyThreshold = fb.value("fuzzyThreshold", 0.8f);
568 m_logUnmatchedTypes = fb.value("logUnmatchedTypes", true);
569 }
570
571 // Parse categories
572 int totalCategories = 0;
573 if (j.contains("categories") && j["categories"].is_object())
574 {
575 for (const auto& pair : j["categories"].items())
576 {
577 const std::string& category = pair.first;
578 const nlohmann::json& types = *pair.second;
579
580 if (types.is_array())
581 {
582 std::vector<std::string> typeList;
583 for (const auto& type : types)
584 {
585 if (type.is_string())
586 {
587 typeList.push_back(type.get<std::string>());
588 }
589 }
590 m_categoryToTypes[category] = typeList;
592 }
593 }
594 }
595
596 // Parse canonical types + synonyms
597 int totalSynonyms = 0;
598 if (j.contains("canonicalTypes") && j["canonicalTypes"].is_object())
599 {
600 for (const auto& pair : j["canonicalTypes"].items())
601 {
602 const std::string& canonical = pair.first;
603 const nlohmann::json& info = *pair.second;
604
607
608 if (info.contains("description") && info["description"].is_string())
609 {
610 synInfo.description = info["description"].get<std::string>();
611 }
612
613 if (info.contains("prefabFile") && info["prefabFile"].is_string())
614 {
615 synInfo.prefabFile = info["prefabFile"].get<std::string>();
616 }
617
618 // Register canonical type (exact match)
620
621 // Register uppercase version if case-insensitive
622 if (!m_caseSensitive)
623 {
625 }
626
627 // Register synonyms
628 if (info.contains("synonyms") && info["synonyms"].is_array())
629 {
630 for (const auto& synonym : info["synonyms"])
631 {
632 if (synonym.is_string())
633 {
634 std::string synStr = synonym.get<std::string>();
635 synInfo.synonyms.push_back(synStr);
637
638 // Register uppercase version if case-insensitive
639 if (!m_caseSensitive)
640 {
642 }
643
645 }
646 }
647 }
648
650 }
651 }
652
653 SYSTEM_LOG << " -> Loaded " << m_canonicalTypes.size() << " canonical types with "
654 << totalSynonyms << " synonyms\n";
655 if (totalCategories > 0)
656 {
657 SYSTEM_LOG << " -> Loaded " << totalCategories << " categories\n";
658 }
659 SYSTEM_LOG << " Settings: case-sensitive=" << (m_caseSensitive ? "yes" : "no")
660 << ", fuzzy-matching=" << (m_enableFuzzyMatching ? "yes" : "no") << "\n";
661
662 return true;
663 }
664 catch (const std::exception& e)
665 {
666 SYSTEM_LOG << " /!\\ Failed to parse synonym registry: " << e.what() << "\n";
667 return false;
668 }
669}
670
672{
673 // Priority 1: Identity_data::entityType
674 if (prefabJson.contains("data") && prefabJson["data"].is_object())
675 {
676 const json& dataJson = prefabJson["data"];
677 if (dataJson.contains("components") && dataJson["components"].is_array())
678 {
679 for (const auto& comp : dataJson["components"])
680 {
681 if (comp.contains("type") && comp["type"].is_string())
682 {
683 std::string compType = comp["type"].get<std::string>();
684 if (compType == "Identity_data" || compType == "Identity")
685 {
686 if (comp.contains("properties") && comp["properties"].is_object())
687 {
688 const json& props = comp["properties"];
689 if (props.contains("entityType") && props["entityType"].is_string())
690 {
691 return props["entityType"].get<std::string>();
692 }
693 }
694 }
695 }
696 }
697 }
698 }
699
700 // Priority 2: Top-level "type" (if not "EntityPrefab")
701 if (prefabJson.contains("type") && prefabJson["type"].is_string())
702 {
703 std::string type = prefabJson["type"].get<std::string>();
704 if (type != "EntityPrefab")
705 {
706 return type;
707 }
708 }
709
710 return "";
711}
712
713std::string PrefabScanner::NormalizeType(const std::string& type) const
714{
715 if (type.empty()) return type;
716
717 // 1. Direct lookup (exact match)
718 auto it = m_synonymToCanonical.find(type);
719 if (it != m_synonymToCanonical.end())
720 {
721 return it->second;
722 }
723
724 // 2. Case-insensitive lookup (if enabled)
725 if (!m_caseSensitive)
726 {
727 std::string upperType = ToUpper(type);
729 if (it != m_synonymToCanonical.end())
730 {
731 return it->second;
732 }
733 }
734
735 // 3. Fuzzy matching (if enabled)
737 {
738 std::string bestMatch;
739 float bestScore = 0.0f;
740
741 for (const auto& pair : m_canonicalTypes)
742 {
743 const std::string& canonical = pair.first;
744 float score = FuzzyMatch(type, canonical);
745
747 {
750 }
751
752 // Also check synonyms
753 for (const auto& synonym : pair.second.synonyms)
754 {
755 score = FuzzyMatch(type, synonym);
757 {
760 }
761 }
762 }
763
764 if (!bestMatch.empty())
765 {
766 SYSTEM_LOG << " 🔍 Fuzzy match: '" << type << "' -> '" << bestMatch
767 << "' (score: " << bestScore << ")\n";
768 return bestMatch;
769 }
770 }
771
772 // 4. Fallback: return original type
774 {
775 SYSTEM_LOG << " /!\\ Unmatched type: '" << type << "'\n";
776 }
777
778 return type;
779}
780
781bool PrefabScanner::AreTypesEquivalent(const std::string& type1, const std::string& type2) const
782{
783 if (type1 == type2) return true;
784
785 std::string normalized1 = NormalizeType(type1);
786 std::string normalized2 = NormalizeType(type2);
787
788 return normalized1 == normalized2;
789}
790
791bool PrefabScanner::IsTypeRegistered(const std::string& type) const
792{
793 return m_synonymToCanonical.find(type) != m_synonymToCanonical.end() ||
795}
796
797bool PrefabScanner::GetCanonicalInfo(const std::string& type, std::string& outCanonical,
798 std::string& outPrefabFile) const
799{
800 // Try to find the canonical type for the given type
801 std::string canonical = NormalizeType(type);
802
803 // Check if we found a valid canonical type
804 auto it = m_canonicalTypes.find(canonical);
805 if (it != m_canonicalTypes.end())
806 {
807 outCanonical = it->second.canonicalType;
808 outPrefabFile = it->second.prefabFile;
809 return true;
810 }
811
812 // Not found
813 outCanonical.clear();
814 outPrefabFile.clear();
815 return false;
816}
817
819{
820 SYSTEM_LOG << "\n";
821 SYSTEM_LOG << "+===========================================================+\n";
822 SYSTEM_LOG << "| PREFAB SCANNER: INITIALIZATION |\n";
823 SYSTEM_LOG << "+===========================================================+\n";
824 SYSTEM_LOG << "Directory: " << prefabDirectory << "\n\n";
825
827
828 // Step 1: Load parameter schemas from JSON
829 SYSTEM_LOG << "Step 1/4: Loading parameter schemas...\n";
830 std::string schemaPath = prefabDirectory + "/ParameterSchemas.json";
832 {
833 SYSTEM_LOG << " x Failed to load parameter schemas from: " << schemaPath << "\n";
834 SYSTEM_LOG << " -> Using built-in schemas as fallback\n";
835 // Note: Built-in schemas are already initialized via EnsureInitialized()
836 }
837 else
838 {
840 SYSTEM_LOG << " ✓ Loaded " << schemaCount << " parameter schemas from JSON\n";
841 }
842
843 // Step 2: Load synonym registry
844 SYSTEM_LOG << "\nStep 2/4: Loading synonym registry...\n";
846
847 // Step 3: Scan directory for prefab files
848 std::vector<std::string> prefabFiles;
849 SYSTEM_LOG << "\nStep 3/4: Scanning prefab directory...\n";
850
851#ifdef _WIN32
853#else
855#endif
856
857 // Filter out the synonym registry and parameter schemas files
858 prefabFiles.erase(
859 std::remove_if(prefabFiles.begin(), prefabFiles.end(),
860 [](const std::string& file) {
861 return file.find("EntityPrefabSynonymsRegister.json") != std::string::npos ||
862 file.find("ParameterSchemas.json") != std::string::npos;
863 }),
864 prefabFiles.end()
865 );
866
867 SYSTEM_LOG << " -> Found " << prefabFiles.size() << " .json file(s)\n";
868
869 // Step 4: Parse prefabs
870 SYSTEM_LOG << "\nStep 4/4: Parsing prefabs...\n";
871
872 int validCount = 0;
873 int invalidCount = 0;
874
875 for (const auto& filepath : prefabFiles)
876 {
878
879 if (blueprint.isValid)
880 {
881 // Normalize the prefab type
882 if (!blueprint.prefabType.empty())
883 {
884 std::string originalType = blueprint.prefabType;
885 blueprint.prefabType = NormalizeType(blueprint.prefabType);
886
887 if (originalType != blueprint.prefabType)
888 {
889 SYSTEM_LOG << " -> Normalized type: '" << originalType << "' -> '"
890 << blueprint.prefabType << "' for " << blueprint.prefabName << "\n";
891 }
892 }
893
894 // Assign categories based on prefab type
895 for (const auto& categoryPair : m_categoryToTypes)
896 {
897 const std::string& category = categoryPair.first;
898 const std::vector<std::string>& types = categoryPair.second;
899
900 // Check if this blueprint's type is in the category
901 if (std::find(types.begin(), types.end(), blueprint.prefabType) != types.end())
902 {
903 blueprint.AddCategory(category);
904 }
905 }
906
907 registry.Register(blueprint);
908 validCount++;
909
910 std::string categoryStr = "";
911 if (!blueprint.categories.empty())
912 {
913 categoryStr = " [Categories: ";
914 for (size_t i = 0; i < blueprint.categories.size(); i++)
915 {
916 categoryStr += blueprint.categories[i];
917 if (i < blueprint.categories.size() - 1) categoryStr += ", ";
918 }
919 categoryStr += "]";
920 }
921
922 SYSTEM_LOG << " -> " << blueprint.prefabName << " [" << blueprint.prefabType << "] "
923 << "(" << blueprint.components.size() << " components)" << categoryStr << "\n";
924 }
925 else
926 {
927 invalidCount++;
928 SYSTEM_LOG << " X " << filepath << " (parse failed)\n";
929 }
930 }
931
932 SYSTEM_LOG << "\n";
933 SYSTEM_LOG << "+===========================================================+\n";
934 SYSTEM_LOG << "| PREFAB SCANNER: INITIALIZATION COMPLETE |\n";
935 SYSTEM_LOG << "+===========================================================+\n";
936 SYSTEM_LOG << "| Total Files Scanned: " << std::left << std::setw(33) << prefabFiles.size() << "|\n";
937 SYSTEM_LOG << "| Valid Prefabs: " << std::left << std::setw(33) << validCount << "|\n";
938 SYSTEM_LOG << "| Invalid Prefabs: " << std::left << std::setw(33) << invalidCount << "|\n";
939 SYSTEM_LOG << "| Canonical Types: " << std::left << std::setw(33) << m_canonicalTypes.size() << "|\n";
940 SYSTEM_LOG << "| Total Synonyms: " << std::left << std::setw(33) << m_synonymToCanonical.size() << "|\n";
941 SYSTEM_LOG << "+===========================================================+\n\n";
942
943 return registry;
944}
nlohmann::json json
ComponentTypeID GetComponentTypeID_Static()
Definition ECS_Entity.h:56
int LevenshteinDistance(const std::string &s1, const std::string &s2)
static ParameterSchemaRegistry & GetInstance()
void DiscoverSchemasFromPrefab(const PrefabBlueprint &prefab)
const PrefabBlueprint * Find(const std::string &name) const
std::map< std::string, std::string > m_typeToName
std::vector< const PrefabBlueprint * > FindByType(const std::string &type) const
std::vector< std::string > GetAllPrefabNames() const
void Register(const PrefabBlueprint &blueprint)
std::map< std::string, PrefabBlueprint > m_blueprints
std::string NormalizeType(const std::string &type) const
Normalize a type string to canonical form.
PrefabBlueprint ParsePrefab(const std::string &filepath)
std::vector< PrefabBlueprint > ScanDirectory(const std::string &rootPath)
std::map< std::string, std::string > m_synonymToCanonical
std::string DetectComponentType(const std::string &typeName)
std::string ToUpper(const std::string &str) const
std::string ExtractPrefabType(const nlohmann::json &prefabJson)
bool LoadSynonymRegistry(const std::string &directory)
bool GetCanonicalInfo(const std::string &type, std::string &outCanonical, std::string &outPrefabFile) const
Get canonical type info (for debugging)
std::map< std::string, std::vector< std::string > > m_categoryToTypes
PrefabRegistry Initialize(const std::string &prefabDirectory="Gamedata/EntityPrefab")
Initialize the prefab system (call once at startup)
bool IsTypeRegistered(const std::string &type) const
Check if a type is registered.
void ExtractResources(const nlohmann::json &componentsJson, ResourceRefs &outResources)
bool AreTypesEquivalent(const std::string &type1, const std::string &type2) const
Check if two types are equivalent.
bool m_enableFuzzyMatching
std::string RemoveExtension(const std::string &filename)
std::string GetFilename(const std::string &filepath)
float FuzzyMatch(const std::string &str1, const std::string &str2) const
void ScanDirectoryRecursive_Unix(const std::string &path, std::vector< std::string > &outFiles)
std::map< std::string, SynonymInfo > m_canonicalTypes
nlohmann::json json
static ComponentDefinition FromJSON(const nlohmann::json &jsonObj)
std::string filePath
std::string canonicalType
#define SYSTEM_LOG