Olympe Engine 2.0
2D Game Engine with ECS Architecture
Loading...
Searching...
No Matches
DataManager.cpp
Go to the documentation of this file.
1/*
2Olympe Engine V2 2025
3Nicolas Chereau
4nchereau@gmail.com
5
6Purpose:
7- Singleton class that manages game resources (textures, sprites, sounds,
8 animations, levels, navigation/collision maps, game object data, etc.)
9- Loads resources on demand and keeps them in memory until released or
10 the engine shuts down. Provides categorized listings and safe unload.
11- All resource metadata files are expected to be JSON (parsing helpers can
12 be added later). This implementation focuses on texture loading and the
13 generic resource lifetime management with placeholders for extended types.
14
15Notes:
16- This implementation uses SDL for texture loading (BMP via SDL_LoadBMP to
17 avoid additional image dependencies). It stores SDL_Texture* in the
18 Resource struct. Extend loading functions to support PNG/JPEG/OGG/etc
19 using the appropriate libraries when available.
20*/
21
23#include "DataManager.h"
24#include "GameEngine.h"
25#include "system/system_utils.h"
26#include <fstream>
27#include <sstream>
28#include <algorithm>
29#include <cerrno>
30#include "sdl3_image/sdl_image.h"
31#include "ECS_Components.h"
32#include "third_party/nlohmann/json.hpp"
33
34#ifdef _WIN32
35#include <direct.h>
36#include <windows.h>
37#else
38#include <sys/stat.h>
39#include <sys/types.h>
40#include <dirent.h>
41#endif
42
44{
45 name = "DataManager";
46 SYSTEM_LOG << "DataManager created\n";
47}
48//-------------------------------------------------------------
50{
51 SYSTEM_LOG << "DataManager destroyed\n";
52 // Ensure resources are freed if shutdown wasn't explicitly called
53 UnloadAll();
54}
55//-------------------------------------------------------------
61//-------------------------------------------------------------
63{
64 // Placeholder for initialization logic (e.g. preload system icons)
65 SYSTEM_LOG << "DataManager Initialized\n";
66}
67//-------------------------------------------------------------
69{
70 SYSTEM_LOG << "DataManager Shutdown - unloading all resources\n";
71 UnloadAll();
72}
73//-------------------------------------------------------------
74bool DataManager::PreloadTexture(const std::string& id, const std::string& path, ResourceCategory category)
75{
76 std::lock_guard<std::mutex> lock(m_mutex_);
77 if (id.empty() || path.empty()) return false;
78 if (m_resources_.find(id) != m_resources_.end())
79 {
80 // already loaded
81 return true;
82 }
83
84 // try to load BMP surface first (no external deps required)
85 SDL_Surface* surf = IMG_Load(path.c_str()); //SDL_LoadBMP(path.c_str());
86
87 if (!surf)
88 {
89 SYSTEM_LOG << "DataManager::PreloadTexture IMG_Load failed for '" << path << "' : " << SDL_GetError() << "\n";
90 return false;
91 }
92
94 SDL_Texture* tex = nullptr;
95 if (renderer)
96 {
98 if (!tex)
99 {
100 SYSTEM_LOG << "DataManager::PreloadTexture SDL_CreateTextureFromSurface failed for '" << path << "' : " << SDL_GetError() << "\n";
102 return false;
103 }
104 }
105 else
106 {
107 // no global renderer available: keep the surface as raw data pointer for future conversion
108 // store surface pointer in Resource::data
109 }
110
111 auto res = std::make_shared<Resource>();
113 res->category = category;
114 res->id = id;
115 res->path = path;
116 res->sprite_texture = tex;
117 if (!tex)
118 {
119 // store surface pointer for deferred texture creation
120 res->data = surf;
121 }
122 else
123 {
124 // we no longer need the surface
126 }
127
128 m_resources_.emplace(id, res);
129 SYSTEM_LOG << "DataManager: Loaded texture '" << id << "' from '" << path << "'\n";
130 return true;
131}
132//-------------------------------------------------------------
133bool DataManager::PreloadSprite(const std::string& id, const std::string& path, ResourceCategory category)
134{
135 return PreloadTexture(id, path, category);
136}
137//-------------------------------------------------------------
138SDL_Texture* DataManager::GetTexture(const std::string& id) const
139{
140 std::lock_guard<std::mutex> lock(m_mutex_);
141 auto it = m_resources_.find(id);
142 if (it == m_resources_.end()) return nullptr;
143 auto res = it->second;
144 if (res->sprite_texture) return res->sprite_texture;
145
146 // If texture not created yet but we have a surface stored, try to create it now
147 if (res->data)
148 {
149 SDL_Surface* surf = reinterpret_cast<SDL_Surface*>(res->data);
151 if (surf && renderer)
152 {
154 if (tex)
155 {
156 res->sprite_texture = tex;
157 // we can free the surface now
159 res->data = nullptr;
160 return res->sprite_texture;
161 }
162 else
163 {
164 SYSTEM_LOG << "DataManager: Failed to create deferred texture for '" << id << "' : " << SDL_GetError() << "\n";
165 }
166 }
167 }
168
169 return nullptr;
170}
171//-------------------------------------------------------------
172Sprite* DataManager::GetSprite(const std::string& id, const std::string& path, ResourceCategory category)
173{
174 // Optimized: Check existence without locking twice
175 {
176 std::lock_guard<std::mutex> lock(m_mutex_);
177 auto it = m_resources_.find(id);
178 if (it != m_resources_.end() && it->second->sprite_texture) {
179 return it->second->sprite_texture;
180 }
181 }
182
183 // Not found or texture not ready, try to load it
184 if (PreloadSprite(id, path, category))
185 {
186 return GetTexture(id);
187 }
188 return nullptr;
189}
190//-------------------------------------------------------------
191bool DataManager::GetSprite_data(const std::string& id, const std::string& path, VisualSprite_data& outData)
192{
194 // set srcRect based on texture size
195 if (outData.sprite)
196 {
197 int w = ((SDL_Texture*)outData.sprite)->w, h = ((SDL_Texture*)outData.sprite)->h;
198 outData.srcRect = { 0.0f, 0.0f, static_cast<float>(w), static_cast<float>(h) };
199 outData.hotSpot = Vector(w / 2.0f, h / 2.0f, 0.0f);
200 return true;
201 }
202
203 SYSTEM_LOG << "DataManager: GetSprite_data failed for '" << id << "' file/path '" << path << "' does not exists or is incorrect\n";
204 return false;
205}
206//-------------------------------------------------------------
207bool DataManager::GetSpriteEditor_data(const std::string& id, const std::string& path, VisualEditor_data& outData)
208{
210 // set srcRect based on texture size
211 if (outData.sprite)
212 {
213 int w = ((SDL_Texture*)outData.sprite)->w, h = ((SDL_Texture*)outData.sprite)->h;
214 outData.srcRect = { 0.0f, 0.0f, static_cast<float>(w), static_cast<float>(h) };
215 outData.hotSpot = Vector(w / 2.0f, h / 2.0f, 0.0f);
216 return true;
217 }
218 SYSTEM_LOG << "DataManager: GetSpriteEditor_data failed for '" << id << "' file/path '" << path << "' does not exists or is incorrect\n";
219 return false;
220}
221//-------------------------------------------------------------
222bool DataManager::ReleaseResource(const std::string& id)
223{
224 std::lock_guard<std::mutex> lock(m_mutex_);
225 auto it = m_resources_.find(id);
226 if (it == m_resources_.end()) return false;
227 auto res = it->second;
228
229 if (res->sprite_texture)
230 {
231 SDL_DestroyTexture(res->sprite_texture);
232 res->sprite_texture = nullptr;
233 }
234 if (res->data)
235 {
236 // if it was a surface store, free it
237 SDL_Surface* surf = reinterpret_cast<SDL_Surface*>(res->data);
239 res->data = nullptr;
240 }
241
242 m_resources_.erase(it);
243 SYSTEM_LOG << "DataManager: Released resource '" << id << "'\n";
244 return true;
245}
246//-------------------------------------------------------------
248{
249 std::lock_guard<std::mutex> lock(m_mutex_);
250 for (auto& kv : m_resources_)
251 {
252 auto res = kv.second;
253 if (res->sprite_texture)
254 {
255 SDL_DestroyTexture(res->sprite_texture);
256 res->sprite_texture = nullptr;
257 }
258 if (res->data)
259 {
260 SDL_Surface* surf = reinterpret_cast<SDL_Surface*>(res->data);
262 res->data = nullptr;
263 }
264 }
265 m_resources_.clear();
266}
267//-------------------------------------------------------------
268bool DataManager::HasResource(const std::string& id) const
269{
270 std::lock_guard<std::mutex> lock(m_mutex_);
271 return m_resources_.find(id) != m_resources_.end();
272}
273//-------------------------------------------------------------
274std::vector<std::string> DataManager::ListResourcesByType(ResourceType type) const
275{
276 std::vector<std::string> out;
277 std::lock_guard<std::mutex> lock(m_mutex_);
278 // Reserve capacity to avoid reallocations
279 out.reserve(m_resources_.size());
280 for (const auto& kv : m_resources_)
281 {
282 if (kv.second->type == type) out.push_back(kv.first);
283 }
284 return out;
285}
286//-------------------------------------------------------------
287std::vector<std::string> DataManager::ListResourcesByCategory(ResourceCategory category) const
288{
289 std::vector<std::string> out;
290 std::lock_guard<std::mutex> lock(m_mutex_);
291 // Reserve capacity to avoid reallocations
292 out.reserve(m_resources_.size());
293 for (const auto& kv : m_resources_)
294 {
295 if (kv.second->category == category) out.push_back(kv.first);
296 }
297 return out;
298}
299//-------------------------------------------------------------
300// Build standard game data path: ./Gamedata/{videogameName}/{objectName}.json
301std::string DataManager::BuildGameDataPath(const std::string& videogameName, const std::string& objectName)
302{
303 std::string game = videogameName.empty() ? std::string("default") : videogameName;
304 std::string obj = objectName.empty() ? std::string("object") : objectName;
305 std::string path = std::string(".") + "/Gamedata/" + game + "/" + obj + ".json";
306 return path;
307}
308//-------------------------------------------------------------
309bool DataManager::SaveTextFile(const std::string& filepath, const std::string& content) const
310{
311 if (filepath.empty()) return false;
312 // ensure directory exists
313 auto pos = filepath.find_last_of("/\\");
314 if (pos != std::string::npos)
315 {
316 std::string dir = filepath.substr(0, pos);
318 {
319 SYSTEM_LOG << "DataManager: Failed to ensure directory exists for '" << dir << "'\n";
320 // continue attempt to write; fallthrough may fail
321 }
322 }
323
324 std::ofstream ofs(filepath.c_str(), std::ios::binary | std::ios::trunc);
325 if (!ofs) return false;
326 ofs.write(content.data(), static_cast<std::streamsize>(content.size()));
327 return ofs.good();
328}
329//-------------------------------------------------------------
330bool DataManager::LoadTextFile(const std::string& filepath, std::string& outContent) const
331{
332 outContent.clear();
333 if (filepath.empty()) return false;
334 std::ifstream ifs(filepath.c_str(), std::ios::binary);
335 if (!ifs) return false;
336 std::ostringstream ss;
337 ss << ifs.rdbuf();
338 outContent = ss.str();
339 return true;
340}
341//-------------------------------------------------------------
342bool DataManager::SaveJSONForObject(const std::string& videogameName, const std::string& objectName, const std::string& jsonContent) const
343{
344 std::string path = BuildGameDataPath(videogameName, objectName);
345 return SaveTextFile(path, jsonContent);
346}
347//-------------------------------------------------------------
348bool DataManager::LoadJSONForObject(const std::string& videogameName, const std::string& objectName, std::string& outJson) const
349{
350 std::string path = BuildGameDataPath(videogameName, objectName);
351 return LoadTextFile(path, outJson);
352}
353//-------------------------------------------------------------
354bool DataManager::EnsureDirectoryExists(const std::string& dirpath) const
355{
356 if (dirpath.empty()) return false;
357
358 std::string path = dirpath;
359 // normalize separators to '/'
360 std::replace(path.begin(), path.end(), '\\', '/');
361 // remove trailing slash if present
362 if (!path.empty() && path.back() == '/') path.pop_back();
363 if (path.empty()) return true;
364
365 // iterate and create each subpath
366 std::string accum;
367 size_t pos = 0;
368 // if path starts with '/', keep it (unix absolute)
369 if (!path.empty() && path[0] == '/') { accum = "/"; pos = 1; }
370
371 while (true)
372 {
373 size_t next = path.find('/', pos);
374 std::string part = (next == std::string::npos) ? path.substr(pos) : path.substr(pos, next - pos);
375 if (!accum.empty() && accum.back() != '/') accum += '/';
376 accum += part;
377
378 // attempt to create directory
379 #ifdef _WIN32
380 int r = _mkdir(accum.c_str());
381 #else
382 int r = mkdir(accum.c_str(), 0755);
383 #endif
384 if (r != 0)
385 {
386 if (errno == EEXIST)
387 {
388 // already exists - ok
389 }
390 else
391 {
392 // failed for other reason
393 SYSTEM_LOG << "DataManager: mkdir failed for '" << accum << "' (errno=" << errno << ")\n";
394 return false;
395 }
396 }
397
398 if (next == std::string::npos) break;
399 pos = next + 1;
400 }
401
402 return true;
403}
404//-------------------------------------------------------------
406{
407 std::string content;
409 {
410 SYSTEM_LOG << "DataManager: PreloadSystemResources failed to read '" << configFilePath << "'\n";
411 return false;
412 }
413
414 try
415 {
416 nlohmann::json root = nlohmann::json::parse(content);
417 if (!root.contains("system_resources")) return true; // nothing to do
418 const auto& arr = root["system_resources"];
419 if (!arr.is_array()) return false;
420 for (size_t i = 0; i < arr.size(); ++i)
421 {
422 const auto& item = arr[i];
423 if (!item.is_object()) continue;
424 std::string id = item.contains("id") ? item["id"].get<std::string>() : std::string();
425 std::string path = item.contains("path") ? item["path"].get<std::string>() : std::string();
426 std::string type = item.contains("type") ? item["type"].get<std::string>() : std::string();
427 if (id.empty() || path.empty()) continue;
428 if (type == "texture")
429 {
431 }
432 else
433 if (type == "sprite" || type == "animation")
434 {
436 }
437 else
438 {
440 }
441 }
442 }
443 catch (const std::exception& e)
444 {
445 SYSTEM_LOG << "DataManager: JSON parse error in PreloadSystemResources: " << e.what() << "\n";
446 return false;
447 }
448
449 return true;
450}
451
452//=============================================================================
453// PHASE 2: Batch Preloading Implementation
454//=============================================================================
455
457 const std::vector<std::string>& paths,
458 ResourceCategory category,
460{
461 PreloadStats stats;
462 stats.totalRequested = static_cast<int>(paths.size());
463
464 for (const auto& path : paths)
465 {
466 if (path.empty()) continue;
467
468 // Generate ID from path
469 std::string id = path;
470 size_t lastSlash = id.find_last_of("/\\");
471 if (lastSlash != std::string::npos)
472 {
473 id = id.substr(lastSlash + 1);
474 }
475
476 // Try direct load
477 if (PreloadTexture(id, path, category))
478 {
479 stats.successfullyLoaded++;
480 SYSTEM_LOG << " -> Loaded texture: " << path << "\n";
481 }
483 {
484 // Try fallback search
485 std::string filename = id;
487
488 if (!foundPath.empty() && PreloadTexture(id, foundPath, category))
489 {
490 stats.failedWithFallback++;
491 stats.fallbackPaths[path] = foundPath;
492 SYSTEM_LOG << " -> Loaded texture (fallback): " << path << " -> " << foundPath << "\n";
493 }
494 else
495 {
496 stats.completelyFailed++;
497 stats.failedPaths.push_back(path);
498 SYSTEM_LOG << " x Failed to load texture: " << path << "\n";
499 }
500 }
501 else
502 {
503 stats.completelyFailed++;
504 stats.failedPaths.push_back(path);
505 SYSTEM_LOG << " x Failed to load texture: " << path << "\n";
506 }
507 }
508
509 return stats;
510}
511
513 const std::vector<std::string>& paths,
514 ResourceCategory category,
516{
517 PreloadStats stats;
518 stats.totalRequested = static_cast<int>(paths.size());
519
520 for (const auto& path : paths)
521 {
522 if (path.empty()) continue;
523
524 // Generate ID from path
525 std::string id = path;
526 size_t lastSlash = id.find_last_of("/\\");
527 if (lastSlash != std::string::npos)
528 {
529 id = id.substr(lastSlash + 1);
530 }
531
532 // Try direct load
533 if (PreloadSprite(id, path, category))
534 {
535 stats.successfullyLoaded++;
536 SYSTEM_LOG << " -> Loaded sprite: " << path << "\n";
537 }
539 {
540 // Try fallback search
541 std::string filename = id;
543
544 if (!foundPath.empty() && PreloadSprite(id, foundPath, category))
545 {
546 stats.failedWithFallback++;
547 stats.fallbackPaths[path] = foundPath;
548 SYSTEM_LOG << " -> Loaded sprite (fallback): " << path << " -> " << foundPath << "\n";
549 }
550 else
551 {
552 stats.completelyFailed++;
553 stats.failedPaths.push_back(path);
554 SYSTEM_LOG << " x Failed to load sprite: " << path << "\n";
555 }
556 }
557 else
558 {
559 stats.completelyFailed++;
560 stats.failedPaths.push_back(path);
561 SYSTEM_LOG << " x Failed to load sprite: " << path << "\n";
562 }
563 }
564
565 return stats;
566}
567
569 const std::vector<std::string>& paths,
571{
572 PreloadStats stats;
573 stats.totalRequested = static_cast<int>(paths.size());
574
575 // Audio loading not implemented yet - just log
576 for (const auto& path : paths)
577 {
578 if (path.empty()) continue;
579 SYSTEM_LOG << " ⊙ Audio loading not yet implemented: " << path << "\n";
580 stats.completelyFailed++;
581 stats.failedPaths.push_back(path);
582 }
583
584 return stats;
585}
586
588 const std::vector<TilesetInfo>& tilesets,
590{
591 PreloadStats stats;
592 stats.totalRequested = static_cast<int>(tilesets.size());
593
594 for (const auto& tileset : tilesets)
595 {
596 bool success = true;
597
598 // Load main tileset image (if not a collection)
599 if (!tileset.isCollection && !tileset.imageFile.empty())
600 {
601 std::string id = tileset.imageFile;
602 size_t lastSlash = id.find_last_of("/\\");
603 if (lastSlash != std::string::npos)
604 {
605 id = id.substr(lastSlash + 1);
606 }
607
608 if (PreloadTexture(id, tileset.imageFile, ResourceCategory::Level))
609 {
610 SYSTEM_LOG << " -> Loaded tileset image: " << tileset.imageFile << "\n";
611 }
613 {
614 std::string foundPath = FindResourceRecursive(id);
616 {
617 stats.fallbackPaths[tileset.imageFile] = foundPath;
618 SYSTEM_LOG << " -> Loaded tileset image (fallback): " << foundPath << "\n";
619 }
620 else
621 {
622 success = false;
623 SYSTEM_LOG << " x Failed to load tileset image: " << tileset.imageFile << "\n";
624 }
625 }
626 else
627 {
628 success = false;
629 }
630 }
631
632 // Load individual tile images (for collection tilesets)
633 for (const auto& imagePath : tileset.individualImages)
634 {
635 std::string id = imagePath;
636 size_t lastSlash = id.find_last_of("/\\");
637 if (lastSlash != std::string::npos)
638 {
639 id = id.substr(lastSlash + 1);
640 }
641
642 std::string foundPath = FindResourceRecursive(id);
643
644 if (PreloadTexture(id, foundPath/*imagePath*/, ResourceCategory::Level))
645 {
646 SYSTEM_LOG << " -> Loaded tile image: " << imagePath << "\n";
647 }
649 {
650 std::string foundPath = FindResourceRecursive(id);
652 {
653 stats.fallbackPaths[imagePath] = foundPath;
654 SYSTEM_LOG << " -> Loaded tile image (fallback): " << foundPath << "\n";
655 }
656 else
657 {
658 success = false;
659 SYSTEM_LOG << " x Failed to load tile image: " << imagePath << "\n";
660 }
661 }
662 else
663 {
664 success = false;
665 }
666 }
667
668 if (success)
669 {
670 stats.successfullyLoaded++;
671 }
672 else if (!stats.fallbackPaths.empty())
673 {
674 stats.failedWithFallback++;
675 }
676 else
677 {
678 stats.completelyFailed++;
679 if (!tileset.sourceFile.empty())
680 {
681 stats.failedPaths.push_back(tileset.sourceFile);
682 }
683 }
684 }
685
686 return stats;
687}
688
689std::string DataManager::FindResourceRecursive(const std::string& filename, const std::string& rootDir) const
690{
691#ifdef _WIN32
693#else
695#endif
696}
697
698#ifdef _WIN32
699std::string DataManager::FindResourceRecursive_Windows(const std::string& filename, const std::string& rootDir) const
700{
702 std::string searchPath = rootDir + "\\*";
704
706 {
707 return "";
708 }
709
710 do
711 {
712 std::string name = findData.cFileName;
713
714 if (name == "." || name == "..")
715 continue;
716
717 std::string fullPath = rootDir + "\\" + name;
718
719 if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
720 {
721 // Recursively search subdirectory
722 std::string found = FindResourceRecursive_Windows(filename, fullPath);
723 if (!found.empty())
724 {
726 return found;
727 }
728 }
729 else
730 {
731 // Check if filename matches
732 if (name == filename)
733 {
735 return fullPath;
736 }
737 }
738 } while (FindNextFileA(hFind, &findData) != 0);
739
741 return "";
742}
743#else
744std::string DataManager::FindResourceRecursive_Unix(const std::string& filename, const std::string& rootDir) const
745{
746 DIR* dir = opendir(rootDir.c_str());
747 if (!dir)
748 {
749 return "";
750 }
751
752 struct dirent* entry;
753 while ((entry = readdir(dir)) != nullptr)
754 {
755 std::string name = entry->d_name;
756
757 if (name == "." || name == "..")
758 continue;
759
760 std::string fullPath = rootDir + "/" + name;
761
762 struct stat statbuf;
763 if (stat(fullPath.c_str(), &statbuf) == 0)
764 {
765 if (S_ISDIR(statbuf.st_mode))
766 {
767 // Recursively search subdirectory
768 std::string found = FindResourceRecursive_Unix(filename, fullPath);
769 if (!found.empty())
770 {
771 closedir(dir);
772 return found;
773 }
774 }
775 else if (S_ISREG(statbuf.st_mode))
776 {
777 // Check if filename matches
778 if (name == filename)
779 {
780 closedir(dir);
781 return fullPath;
782 }
783 }
784 }
785 }
786
787 closedir(dir);
788 return "";
789}
790#endif
791
792// Phase 38: Enhanced path resolution for blueprint graphs
793std::string DataManager::ResolveFilePath(const std::string& relativePath) const
794{
795 // If path already exists as absolute, return it
796 std::ifstream test(relativePath.c_str());
797 if (test.good())
798 {
799 return relativePath;
800 }
801
802 // Normalize path separators to forward slashes for consistency
803 std::string normalizedPath = relativePath;
804 std::replace(normalizedPath.begin(), normalizedPath.end(), '\\', '/');
805
806 // Try Blueprints directory first
807 std::string blueprintPath = "Blueprints/" + normalizedPath;
808 std::replace(blueprintPath.begin(), blueprintPath.end(), '/', '\\'); // Convert back to backslashes for Windows
809
810 std::ifstream blueprintTest(blueprintPath.c_str());
811 if (blueprintTest.good())
812 {
813 SYSTEM_LOG << "[DataManager] Resolved path '" << relativePath
814 << "' -> '" << blueprintPath << "' (Blueprints)\n";
815 return blueprintPath;
816 }
817
818 // Try Gamedata directory
819 std::string gamedataPath = "Gamedata/" + normalizedPath;
820 std::replace(gamedataPath.begin(), gamedataPath.end(), '/', '\\'); // Convert back to backslashes for Windows
821
822 std::ifstream gamedataTest(gamedataPath.c_str());
823 if (gamedataTest.good())
824 {
825 SYSTEM_LOG << "[DataManager] Resolved path '" << relativePath
826 << "' -> '" << gamedataPath << "' (Gamedata)\n";
827 return gamedataPath;
828 }
829
830 // If still not found, try just the filename (in case path contains subdirectories)
831 // Extract just the filename from the path
832 size_t lastSlash = normalizedPath.find_last_of('/');
833 if (lastSlash != std::string::npos)
834 {
835 std::string filename = normalizedPath.substr(lastSlash + 1);
836
837 // Search for just the filename in Blueprints recursively
838 std::string found = FindResourceRecursive(filename, "Blueprints");
839 if (!found.empty())
840 {
841 SYSTEM_LOG << "[DataManager] Resolved filename '" << filename
842 << "' -> '" << found << "' (recursive search in Blueprints)\n";
843 return found;
844 }
845
846 // Search for just the filename in Gamedata recursively
847 found = FindResourceRecursive(filename, "Gamedata");
848 if (!found.empty())
849 {
850 SYSTEM_LOG << "[DataManager] Resolved filename '" << filename
851 << "' -> '" << found << "' (recursive search in Gamedata)\n";
852 return found;
853 }
854 }
855
856 SYSTEM_LOG << "[DataManager] Could not resolve path: " << relativePath << "\n";
857 return ""; // Return empty on failure
858}
859
860// ============================================================================
861// PHASE 39c Step 5: File Browser Service Implementation
862// ============================================================================
863
864std::vector<std::string> DataManager::GetBehaviorTreeFiles(const std::string& directory) const
865{
866 std::vector<std::string> result;
867
868 #ifdef _WIN32
869 {
871 HANDLE findHandle = FindFirstFileA((directory + "\\*.bt.json").c_str(), &findData);
873 {
874 SYSTEM_LOG << "[DataManager] No .bt.json files found in: " << directory << "\n";
875 return result; // Return empty vector
876 }
877
878 do
879 {
880 if (!(findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
881 {
882 result.push_back(findData.cFileName);
883 }
884 } while (FindNextFileA(findHandle, &findData));
885
887 }
888 #else
889 {
890 DIR* dir = opendir(directory.c_str());
891 if (!dir)
892 {
893 SYSTEM_LOG << "[DataManager] Failed to open directory: " << directory << "\n";
894 return result; // Return empty vector
895 }
896
897 struct dirent* entry;
898 while ((entry = readdir(dir)) != nullptr)
899 {
900 std::string filename = entry->d_name;
901 if (filename.length() > 7 && filename.substr(filename.length() - 7) == ".bt.json")
902 {
903 result.push_back(filename);
904 }
905 }
906
907 closedir(dir);
908 }
909 #endif
910
911 if (!result.empty())
912 {
913 SYSTEM_LOG << "[DataManager] Found " << result.size() << " .bt.json files in: " << directory << "\n";
914 }
915
916 return result;
917}
918
919//-------------------------------------------------------------
920
921std::string DataManager::SelectBehaviorTreeFile(const std::string& currentPath) const
922{
923 // This method provides a file browser dialog for selecting behavior tree files
924 // For now, we return empty string. In a full GUI implementation, this would open
925 // a file dialog (OS-native or ImGui-based) to let user select a .bt.json file.
926
927 std::string browseDir = currentPath.empty() ? "./Gamedata" : currentPath;
928
929 // Normalize path separators
930 std::replace(browseDir.begin(), browseDir.end(), '/', '\\');
931
932 // Ensure directory exists
933 if (!browseDir.empty())
934 {
935 std::ifstream test(browseDir);
936 if (!test.good())
937 {
938 SYSTEM_LOG << "[DataManager] Browse directory not found: " << browseDir << "\n";
939 browseDir = "Gamedata"; // Fall back to default
940 }
941 }
942
943 // Get available behavior tree files
944 std::vector<std::string> btFiles = GetBehaviorTreeFiles(browseDir);
945
946 if (btFiles.empty())
947 {
948 SYSTEM_LOG << "[DataManager] No behavior tree files available in: " << browseDir << "\n";
949 return ""; // No files to select
950 }
951
952 // For framework implementation: return first file as default
953 // In full implementation, this would open a modal/dialog for user selection
954 std::string selectedFile = browseDir + "\\" + btFiles[0];
955
956 SYSTEM_LOG << "[DataManager] Selected behavior tree file: " << selectedFile << "\n";
957 return selectedFile;
958}
959
960//-------------------------------------------------------------
961// Phase 40: Centralized File Picker Modal
962//-------------------------------------------------------------
963
964std::string DataManager::OpenFilePickerModal(Olympe::FilePickerType fileType, const std::string& currentPath)
965{
966 // Create or reuse modal instance
968 {
969 m_filePickerModal = std::make_unique<Olympe::FilePickerModal>(fileType);
970 }
971
972 // Open the modal with the specified path
973 m_filePickerModal->Open(currentPath);
974
975 SYSTEM_LOG << "[DataManager] Opened file picker modal for file type: "
976 << static_cast<int>(fileType) << "\n";
977
978 // Return empty for now - caller should check IsFilePickerModalOpen()
979 // and wait for user interaction. The selected file is retrieved after Render().
980 return "";
981}
982
984{
986 {
987 m_filePickerModal->Render();
988 }
989}
990
992{
994 return false;
995
996 return m_filePickerModal->IsOpen();
997}
998
1000{
1002 {
1003 m_filePickerModal->Close();
1004 }
1005}
1006
1008{
1009 if (m_filePickerModal && m_filePickerModal->IsConfirmed())
1010 {
1011 return m_filePickerModal->GetSelectedFile();
1012 }
1013 return "";
1014}
1015
1016// ============================================================================
1017// Phase 40 Enhancement: Save As Modal Management
1018// ============================================================================
1019
1021 const std::string& directory,
1022 const std::string& suggestedFilename)
1023{
1025 {
1026 m_saveFilePickerModal = std::make_unique<Olympe::SaveFilePickerModal>(fileType);
1027 }
1029 SYSTEM_LOG << "[DataManager] Opened Save As modal for file type: " << static_cast<int>(fileType) << "\n";
1030}
1031
1033{
1035 {
1036 m_saveFilePickerModal->Render();
1037 }
1038}
1039
1041{
1043 {
1044 return m_saveFilePickerModal->IsOpen();
1045 }
1046 return false;
1047}
1048
1050{
1052 {
1053 m_saveFilePickerModal->Close();
1054 }
1055}
1056
1058{
1059 if (m_saveFilePickerModal && m_saveFilePickerModal->IsConfirmed())
1060 {
1061 return m_saveFilePickerModal->GetSelectedFile();
1062 }
1063 return "";
1064}
ResourceCategory
Definition DataManager.h:61
ResourceType
Definition DataManager.h:45
SDL_Texture Sprite
Definition DataManager.h:38
Core ECS component definitions.
ComponentTypeID GetComponentTypeID_Static()
Definition ECS_Entity.h:56
Centralized file picker modal for all file selection operations (Phase 40).
Core game engine class.
static SDL_Renderer * renderer
static std::string BuildGameDataPath(const std::string &videogameName, const std::string &objectName)
std::string FindResourceRecursive_Unix(const std::string &filename, const std::string &rootDir) const
bool PreloadSystemResources(const std::string &configFilePath)
Sprite * GetSprite(const std::string &id, const std::string &path, ResourceCategory category=ResourceCategory::GameEntity)
void Initialize()
PreloadStats PreloadSprites(const std::vector< std::string > &paths, ResourceCategory category=ResourceCategory::GameEntity, bool enableFallbackScan=true)
std::string GetSelectedFileFromModal() const
Retrieves the selected file from the file picker modal.
bool IsFilePickerModalOpen() const
Checks if the file picker modal is currently visible.
PreloadStats PreloadTextures(const std::vector< std::string > &paths, ResourceCategory category=ResourceCategory::Level, bool enableFallbackScan=true)
virtual ~DataManager()
std::string OpenFilePickerModal(Olympe::FilePickerType fileType, const std::string &currentPath="")
Opens a centralized file picker modal for the specified file type.
bool m_enableFallbackScan
std::string SelectBehaviorTreeFile(const std::string &currentPath="") const
Opens a file browser dialog for selecting a behavior tree file.
Sprite * GetTexture(const std::string &id) const
bool EnsureDirectoryExists(const std::string &dirpath) const
std::string FindResourceRecursive(const std::string &filename, const std::string &rootDir="GameData") const
bool SaveTextFile(const std::string &filepath, const std::string &content) const
static DataManager & GetInstance()
std::vector< std::string > ListResourcesByType(ResourceType type) const
void CloseSaveFilePickerModal()
Closes the Save As file picker modal.
bool HasResource(const std::string &id) const
bool LoadJSONForObject(const std::string &videogameName, const std::string &objectName, std::string &outJson) const
void CloseFilePickerModal()
Closes the file picker modal without user selection.
std::unordered_map< std::string, std::shared_ptr< Resource > > m_resources_
std::mutex m_mutex_
bool PreloadTexture(const std::string &id, const std::string &path, ResourceCategory category=ResourceCategory::System)
PreloadStats PreloadTilesets(const std::vector< TilesetInfo > &tilesets, bool enableFallbackScan=true)
bool SaveJSONForObject(const std::string &videogameName, const std::string &objectName, const std::string &jsonContent) const
bool ReleaseResource(const std::string &id)
PreloadStats PreloadAudioFiles(const std::vector< std::string > &paths, bool enableFallbackScan=true)
void RenderSaveFilePickerModal()
Renders the Save As file picker modal UI.
std::unique_ptr< Olympe::SaveFilePickerModal > m_saveFilePickerModal
bool LoadTextFile(const std::string &filepath, std::string &outContent) const
bool PreloadSprite(const std::string &id, const std::string &path, ResourceCategory category=ResourceCategory::GameEntity)
bool IsSaveFilePickerModalOpen() const
Checks if the Save As file picker modal is currently visible.
void RenderFilePickerModal()
Renders the file picker modal if one is open.
void Shutdown()
std::string ResolveFilePath(const std::string &relativePath) const
std::vector< std::string > GetBehaviorTreeFiles(const std::string &directory="./Gamedata") const
Lists all .bt.json behavior tree files in a directory.
std::vector< std::string > ListResourcesByCategory(ResourceCategory category) const
std::string name
void OpenSaveFilePickerModal(Olympe::SaveFileType fileType, const std::string &directory, const std::string &suggestedFilename="")
Opens the Save As file picker modal.
std::string GetSelectedSaveFile() const
Retrieves the selected file from the Save As modal.
bool GetSprite_data(const std::string &id, const std::string &path, VisualSprite_data &outData)
bool GetSpriteEditor_data(const std::string &id, const std::string &path, VisualEditor_data &outData)
std::unique_ptr< Olympe::FilePickerModal > m_filePickerModal
static SDL_Renderer * renderer
Main SDL renderer.
Definition GameEngine.h:129
FilePickerType
Supported file types for the centralized file picker modal.
SaveFileType
Supported file types for the centralized save modal.
nlohmann::json json
std::vector< std::string > failedPaths
std::map< std::string, std::string > fallbackPaths
#define SYSTEM_LOG