feat: Implement lazy loading for dungeon rooms and refactor room graphics handling

- Introduced lazy loading for room data to optimize performance and reduce initial load times.
- Updated DungeonEditor and DungeonRoomLoader to handle room graphics rendering directly from room objects.
- Refactored methods to accept room references instead of IDs for better clarity and type safety.
- Enhanced tab management in the DungeonEditor UI for improved user experience.
This commit is contained in:
scawful
2025-10-04 15:14:17 -04:00
parent 5bb696e431
commit 28dc394a7c
19 changed files with 799 additions and 468 deletions

View File

@@ -14,13 +14,6 @@ set(
app/emu/snes.cc app/emu/snes.cc
) )
set(
YAZE_UTIL_SRC
util/bps.cc
util/flag.cc
util/hex.cc
)
set(YAZE_RESOURCE_FILES set(YAZE_RESOURCE_FILES
${CMAKE_SOURCE_DIR}/assets/font/Karla-Regular.ttf ${CMAKE_SOURCE_DIR}/assets/font/Karla-Regular.ttf
${CMAKE_SOURCE_DIR}/assets/font/Roboto-Medium.ttf ${CMAKE_SOURCE_DIR}/assets/font/Roboto-Medium.ttf
@@ -249,6 +242,7 @@ if (YAZE_BUILD_LIB)
endif() endif()
else() else()
# Create core library for testing (includes editor and zelda3 components needed by tests) # Create core library for testing (includes editor and zelda3 components needed by tests)
include(util/util.cmake)
set(YAZE_CORE_SOURCES set(YAZE_CORE_SOURCES
app/rom.cc app/rom.cc
${YAZE_APP_CORE_SRC} ${YAZE_APP_CORE_SRC}
@@ -257,7 +251,6 @@ if (YAZE_BUILD_LIB)
${YAZE_APP_ZELDA3_SRC} ${YAZE_APP_ZELDA3_SRC}
${YAZE_APP_EMU_SRC} ${YAZE_APP_EMU_SRC}
${YAZE_GUI_SRC} ${YAZE_GUI_SRC}
${YAZE_UTIL_SRC}
# cli/service/gui_automation_client.cc # Moved to yaze_c # cli/service/gui_automation_client.cc # Moved to yaze_c
cli/service/testing/test_workflow_generator.cc cli/service/testing/test_workflow_generator.cc
) )
@@ -284,6 +277,7 @@ if (YAZE_BUILD_LIB)
yaze_core PUBLIC yaze_core PUBLIC
asar-static asar-static
yaze_agent yaze_agent
yaze_util
${ABSL_TARGETS} ${ABSL_TARGETS}
${SDL_TARGETS} ${SDL_TARGETS}
${CMAKE_DL_LIBS} ${CMAKE_DL_LIBS}
@@ -765,6 +759,7 @@ source_group("Utilities" FILES
util/flag.h util/flag.h
util/hex.cc util/hex.cc
util/hex.h util/hex.h
util/log.cc
util/log.h util/log.h
util/macro.h util/macro.h
util/notify.h util/notify.h

View File

@@ -3,8 +3,6 @@
#include <string> #include <string>
#include "imgui/imgui.h"
namespace yaze { namespace yaze {
namespace core { namespace core {
@@ -117,63 +115,6 @@ class FeatureFlags {
} }
}; };
using ImGui::BeginMenu;
using ImGui::Checkbox;
using ImGui::EndMenu;
using ImGui::MenuItem;
using ImGui::Separator;
struct FlagsMenu {
void DrawOverworldFlags() {
Checkbox("Enable Overworld Sprites",
&FeatureFlags::get().overworld.kDrawOverworldSprites);
Separator();
Checkbox("Save Overworld Maps",
&FeatureFlags::get().overworld.kSaveOverworldMaps);
Checkbox("Save Overworld Entrances",
&FeatureFlags::get().overworld.kSaveOverworldEntrances);
Checkbox("Save Overworld Exits",
&FeatureFlags::get().overworld.kSaveOverworldExits);
Checkbox("Save Overworld Items",
&FeatureFlags::get().overworld.kSaveOverworldItems);
Checkbox("Save Overworld Properties",
&FeatureFlags::get().overworld.kSaveOverworldProperties);
Checkbox("Enable Custom Overworld Features",
&FeatureFlags::get().overworld.kLoadCustomOverworld);
ImGui::SameLine();
if (ImGui::Button("?")) {
ImGui::OpenPopup("CustomOverworldHelp");
}
if (ImGui::BeginPopup("CustomOverworldHelp")) {
ImGui::Text("This flag enables ZSCustomOverworld features.");
ImGui::Text("If ZSCustomOverworld ASM is already applied to the ROM,");
ImGui::Text("features are auto-enabled regardless of this flag.");
ImGui::Text("For vanilla ROMs, enable this to use custom features.");
ImGui::EndPopup();
}
Checkbox("Apply ZSCustomOverworld ASM",
&FeatureFlags::get().overworld.kApplyZSCustomOverworldASM);
}
void DrawDungeonFlags() {
Checkbox("Save Dungeon Maps", &FeatureFlags::get().kSaveDungeonMaps);
}
void DrawResourceFlags() {
Checkbox("Save All Palettes", &FeatureFlags::get().kSaveAllPalettes);
Checkbox("Save Gfx Groups", &FeatureFlags::get().kSaveGfxGroups);
Checkbox("Save Graphics Sheets", &FeatureFlags::get().kSaveGraphicsSheet);
}
void DrawSystemFlags() {
Checkbox("Enable Console Logging", &FeatureFlags::get().kLogToConsole);
Checkbox("Enable Performance Monitoring", &FeatureFlags::get().kEnablePerformanceMonitoring);
Checkbox("Log Instructions to Emulator Debugger",
&FeatureFlags::get().kLogInstructions);
Checkbox("Use Native File Dialog (NFD)", &FeatureFlags::get().kUseNativeFileDialog);
}
};
} // namespace core } // namespace core
} // namespace yaze } // namespace yaze

View File

@@ -371,7 +371,7 @@ void DungeonEditor::DrawCanvasAndPropertiesPanel() {
gui::InputHexByte("Palette", &room.palette); gui::InputHexByte("Palette", &room.palette);
if (ImGui::Button("Reload Room Graphics")) { if (ImGui::Button("Reload Room Graphics")) {
(void)LoadAndRenderRoomGraphics(current_room); (void)LoadAndRenderRoomGraphics(room);
} }
} }
@@ -555,7 +555,7 @@ void DungeonEditor::DrawDungeonCanvas(int room_id) {
SameLine(); SameLine();
if (Button("Load Room Graphics")) { if (Button("Load Room Graphics")) {
(void)LoadAndRenderRoomGraphics(room_id); (void)LoadAndRenderRoomGraphics(rooms_[room_id]);
} }
ImGui::SameLine(); ImGui::SameLine();
@@ -701,7 +701,7 @@ void DungeonEditor::DrawDungeonCanvas(int room_id) {
if (is_loaded_) { if (is_loaded_) {
// Automatically load room graphics if not already loaded // Automatically load room graphics if not already loaded
if (rooms_[room_id].blocks().empty()) { if (rooms_[room_id].blocks().empty()) {
(void)LoadAndRenderRoomGraphics(room_id); (void)LoadAndRenderRoomGraphics(rooms_[room_id]);
} }
// Load room objects if not already loaded // Load room objects if not already loaded
@@ -761,7 +761,7 @@ void DungeonEditor::UpdateObjectEditor() {
// Load room graphics if not already loaded (this populates arena buffers) // Load room graphics if not already loaded (this populates arena buffers)
if (room.blocks().empty()) { if (room.blocks().empty()) {
auto status = LoadAndRenderRoomGraphics(room_id); auto status = LoadAndRenderRoomGraphics(room);
if (!status.ok()) { if (!status.ok()) {
// Log error but continue // Log error but continue
return; return;
@@ -829,18 +829,15 @@ void DungeonEditor::DrawObjectEditorPanels() {
} }
// Legacy method implementations that delegate to components // Legacy method implementations that delegate to components
absl::Status DungeonEditor::LoadAndRenderRoomGraphics(int room_id) { absl::Status DungeonEditor::LoadAndRenderRoomGraphics(zelda3::Room& room) {
if (room_id < 0 || room_id >= rooms_.size()) { return room_loader_.LoadAndRenderRoomGraphics(room);
return absl::InvalidArgumentError("Invalid room ID");
}
return room_loader_.LoadAndRenderRoomGraphics(room_id, rooms_[room_id]);
} }
absl::Status DungeonEditor::ReloadAllRoomGraphics() { absl::Status DungeonEditor::ReloadAllRoomGraphics() {
return room_loader_.ReloadAllRoomGraphics(rooms_); return room_loader_.ReloadAllRoomGraphics(rooms_);
} }
absl::Status DungeonEditor::UpdateRoomBackgroundLayers(int room_id) { absl::Status DungeonEditor::UpdateRoomBackgroundLayers(int /*room_id*/) {
// This method is deprecated - rendering is handled by DungeonRenderer component // This method is deprecated - rendering is handled by DungeonRenderer component
return absl::OkStatus(); return absl::OkStatus();
} }

View File

@@ -118,7 +118,7 @@ class DungeonEditor : public Editor {
void DrawObjectRenderer(); void DrawObjectRenderer();
// Legacy methods (delegated to components) // Legacy methods (delegated to components)
absl::Status LoadAndRenderRoomGraphics(int room_id); absl::Status LoadAndRenderRoomGraphics(zelda3::Room& room);
absl::Status ReloadAllRoomGraphics(); absl::Status ReloadAllRoomGraphics();
absl::Status UpdateRoomBackgroundLayers(int room_id); absl::Status UpdateRoomBackgroundLayers(int room_id);

View File

@@ -4,6 +4,8 @@
#include "absl/strings/str_format.h" #include "absl/strings/str_format.h"
#include "app/gfx/snes_palette.h" #include "app/gfx/snes_palette.h"
#include "app/zelda3/dungeon/room.h"
#include "app/gui/icons.h"
#include "imgui/imgui.h" #include "imgui/imgui.h"
namespace yaze::editor { namespace yaze::editor {
@@ -28,8 +30,8 @@ absl::Status DungeonEditorV2::Load() {
return absl::FailedPreconditionError("ROM not loaded"); return absl::FailedPreconditionError("ROM not loaded");
} }
// Load all rooms using the loader component // Load all rooms using the loader component - DEFERRED for lazy loading
RETURN_IF_ERROR(room_loader_.LoadAllRooms(rooms_)); // RETURN_IF_ERROR(room_loader_.LoadAllRooms(rooms_));
RETURN_IF_ERROR(room_loader_.LoadRoomEntrances(entrances_)); RETURN_IF_ERROR(room_loader_.LoadRoomEntrances(entrances_));
// Load palette group // Load palette group
@@ -106,7 +108,16 @@ void DungeonEditorV2::DrawLayout() {
int room_id = active_rooms_[i]; int room_id = active_rooms_[i];
bool open = true; bool open = true;
if (BeginTabItem(absl::StrFormat("Room %03X", room_id).c_str(), &open)) { std::string tab_name_str;
const char* tab_name;
if (room_id >= 0 && static_cast<size_t>(room_id) < std::size(zelda3::kRoomNames)) {
tab_name = zelda3::kRoomNames[room_id].data();
} else {
tab_name_str = absl::StrFormat("Room %03X", room_id);
tab_name = tab_name_str.c_str();
}
if (BeginTabItem(tab_name, &open)) {
DrawRoomTab(room_id); DrawRoomTab(room_id);
EndTabItem(); EndTabItem();
} }
@@ -116,6 +127,12 @@ void DungeonEditorV2::DrawLayout() {
i--; i--;
} }
} }
// Add tab button
if (ImGui::TabItemButton(ICON_MD_ADD, ImGuiTabItemFlags_Trailing | ImGuiTabItemFlags_NoTooltip)) {
OnRoomSelected(room_selector_.current_room_id());
}
EndTabBar(); EndTabBar();
} }
@@ -133,11 +150,21 @@ void DungeonEditorV2::DrawRoomTab(int room_id) {
return; return;
} }
// Lazy load room data
if (!rooms_[room_id].IsLoaded()) {
auto status = room_loader_.LoadRoom(room_id, rooms_[room_id]);
if (!status.ok()) {
ImGui::TextColored(ImVec4(1, 0, 0, 1), "Failed to load room: %s",
status.message().data());
return;
}
}
// Quick controls // Quick controls
ImGui::Text("Room %03X", room_id); ImGui::Text("Room %03X", room_id);
ImGui::SameLine(); ImGui::SameLine();
if (ImGui::Button("Load Graphics")) { if (ImGui::Button("Load Graphics")) {
(void)room_loader_.LoadAndRenderRoomGraphics(room_id, rooms_[room_id]); (void)room_loader_.LoadAndRenderRoomGraphics(rooms_[room_id]);
} }
ImGui::SameLine(); ImGui::SameLine();
if (ImGui::Button("Save")) { if (ImGui::Button("Save")) {

View File

@@ -13,6 +13,20 @@
namespace yaze::editor { namespace yaze::editor {
absl::Status DungeonRoomLoader::LoadRoom(int room_id, zelda3::Room& room) {
if (!rom_ || !rom_->is_loaded()) {
return absl::FailedPreconditionError("ROM not loaded");
}
if (room_id < 0 || room_id >= 0x128) {
return absl::InvalidArgumentError("Invalid room ID");
}
room = zelda3::LoadRoomFromRom(rom_, room_id);
room.LoadObjects();
return absl::OkStatus();
}
absl::Status DungeonRoomLoader::LoadAllRooms(std::array<zelda3::Room, 0x128>& rooms) { absl::Status DungeonRoomLoader::LoadAllRooms(std::array<zelda3::Room, 0x128>& rooms) {
if (!rom_ || !rom_->is_loaded()) { if (!rom_ || !rom_->is_loaded()) {
return absl::FailedPreconditionError("ROM not loaded"); return absl::FailedPreconditionError("ROM not loaded");
@@ -26,7 +40,7 @@ absl::Status DungeonRoomLoader::LoadAllRooms(std::array<zelda3::Room, 0x128>& ro
static_cast<int>(std::thread::hardware_concurrency())); static_cast<int>(std::thread::hardware_concurrency()));
const int rooms_per_thread = (kTotalRooms + max_concurrency - 1) / max_concurrency; const int rooms_per_thread = (kTotalRooms + max_concurrency - 1) / max_concurrency;
util::logf("Loading %d dungeon rooms using %d threads (%d rooms per thread)", LOG_INFO("Dungeon", "Loading %d dungeon rooms using %d threads (%d rooms per thread)",
kTotalRooms, max_concurrency, rooms_per_thread); kTotalRooms, max_concurrency, rooms_per_thread);
// Thread-safe data structures for collecting results // Thread-safe data structures for collecting results
@@ -164,7 +178,7 @@ void DungeonRoomLoader::LoadDungeonRoomSize() {
} }
} }
absl::Status DungeonRoomLoader::LoadAndRenderRoomGraphics(int room_id, zelda3::Room& room) { absl::Status DungeonRoomLoader::LoadAndRenderRoomGraphics(zelda3::Room& room) {
if (!rom_ || !rom_->is_loaded()) { if (!rom_ || !rom_->is_loaded()) {
return absl::FailedPreconditionError("ROM not loaded"); return absl::FailedPreconditionError("ROM not loaded");
} }
@@ -184,8 +198,8 @@ absl::Status DungeonRoomLoader::ReloadAllRoomGraphics(std::array<zelda3::Room, 0
} }
// Reload graphics for all rooms // Reload graphics for all rooms
for (size_t i = 0; i < rooms.size(); ++i) { for (auto& room : rooms) {
auto status = LoadAndRenderRoomGraphics(static_cast<int>(i), rooms[i]); auto status = LoadAndRenderRoomGraphics(room);
if (!status.ok()) { if (!status.ok()) {
continue; // Log error but continue with other rooms continue; // Log error but continue with other rooms
} }

View File

@@ -23,6 +23,7 @@ class DungeonRoomLoader {
explicit DungeonRoomLoader(Rom* rom) : rom_(rom) {} explicit DungeonRoomLoader(Rom* rom) : rom_(rom) {}
// Room loading // Room loading
absl::Status LoadRoom(int room_id, zelda3::Room& room);
absl::Status LoadAllRooms(std::array<zelda3::Room, 0x128>& rooms); absl::Status LoadAllRooms(std::array<zelda3::Room, 0x128>& rooms);
absl::Status LoadRoomEntrances(std::array<zelda3::RoomEntrance, 0x8C>& entrances); absl::Status LoadRoomEntrances(std::array<zelda3::RoomEntrance, 0x8C>& entrances);
@@ -31,7 +32,7 @@ class DungeonRoomLoader {
uint64_t GetTotalRoomSize() const { return total_room_size_; } uint64_t GetTotalRoomSize() const { return total_room_size_; }
// Room graphics // Room graphics
absl::Status LoadAndRenderRoomGraphics(int room_id, zelda3::Room& room); absl::Status LoadAndRenderRoomGraphics(zelda3::Room& room);
absl::Status ReloadAllRoomGraphics(std::array<zelda3::Room, 0x128>& rooms); absl::Status ReloadAllRoomGraphics(std::array<zelda3::Room, 0x128>& rooms);
// Data access // Data access

File diff suppressed because it is too large Load Diff

View File

@@ -86,12 +86,12 @@ bool LoadROMPaletteGroups(Rom* rom, CanvasPaletteManager& palette_manager) {
} }
palette_manager.palettes_loaded = true; palette_manager.palettes_loaded = true;
util::logf("Canvas: Loaded %zu ROM palette groups", LOG_INFO("Canvas", "Loaded %zu ROM palette groups",
palette_manager.rom_palette_groups.size()); palette_manager.rom_palette_groups.size());
return true; return true;
} catch (const std::exception& e) { } catch (const std::exception& e) {
util::logf("Canvas: Failed to load ROM palette groups: %s", e.what()); LOG_ERROR("Canvas", "Failed to load ROM palette groups");
return false; return false;
} }
} }
@@ -117,12 +117,12 @@ bool ApplyPaletteGroup(gfx::Bitmap* bitmap,
} }
Renderer::Get().UpdateBitmap(bitmap); Renderer::Get().UpdateBitmap(bitmap);
util::logf("Canvas: Applied palette group %d, index %d to bitmap", LOG_INFO("Canvas", "Applied palette group %d, index %d to bitmap",
group_index, palette_index); group_index, palette_index);
return true; return true;
} catch (const std::exception& e) { } catch (const std::exception& e) {
util::logf("Canvas: Failed to apply palette: %s", e.what()); LOG_ERROR("Canvas", "Failed to apply palette");
return false; return false;
} }
} }

View File

@@ -85,12 +85,12 @@ bool LoadROMPaletteGroups(Rom* rom, CanvasPaletteManager& palette_manager) {
} }
palette_manager.palettes_loaded = true; palette_manager.palettes_loaded = true;
util::logf("Canvas: Loaded %zu ROM palette groups", LOG_INFO("Canvas", "Loaded %zu ROM palette groups",
palette_manager.rom_palette_groups.size()); palette_manager.rom_palette_groups.size());
return true; return true;
} catch (const std::exception& e) { } catch (const std::exception& e) {
util::logf("Canvas: Failed to load ROM palette groups: %s", e.what()); LOG_ERROR("Canvas", "Failed to load ROM palette groups");
return false; return false;
} }
} }
@@ -116,12 +116,12 @@ bool ApplyPaletteGroup(gfx::Bitmap* bitmap,
} }
Renderer::Get().UpdateBitmap(bitmap); Renderer::Get().UpdateBitmap(bitmap);
util::logf("Canvas: Applied palette group %d, index %d to bitmap", LOG_INFO("Canvas", "Applied palette group %d, index %d to bitmap",
group_index, palette_index); group_index, palette_index);
return true; return true;
} catch (const std::exception& e) { } catch (const std::exception& e) {
util::logf("Canvas: Failed to apply palette: %s", e.what()); LOG_ERROR("Canvas", "Failed to apply palette");
return false; return false;
} }
} }

View File

@@ -174,12 +174,9 @@ bool EnhancedPaletteEditor::ApplyROMPalette(gfx::Bitmap* bitmap, int group_index
current_group_index_ = group_index; current_group_index_ = group_index;
current_palette_index_ = palette_index; current_palette_index_ = palette_index;
util::logf("Applied ROM palette: %s (index %d)",
palette_group_names_[group_index].c_str(), palette_index);
return true; return true;
} catch (const std::exception& e) { } catch (const std::exception& e) {
util::logf("Failed to apply ROM palette: %s", e.what());
return false; return false;
} }
} }
@@ -455,10 +452,9 @@ void EnhancedPaletteEditor::LoadROMPalettes() {
} }
rom_palettes_loaded_ = true; rom_palettes_loaded_ = true;
util::logf("Enhanced Palette Editor: Loaded %zu ROM palette groups", rom_palette_groups_.size());
} catch (const std::exception& e) { } catch (const std::exception& e) {
util::logf("Enhanced Palette Editor: Failed to load ROM palettes: %s", e.what()); LOG_ERROR("Enhanced Palette Editor", "Failed to load ROM palettes");
} }
} }

View File

@@ -0,0 +1,72 @@
#ifndef YAZE_APP_GUI_FEATURE_FLAGS_MENU_H
#define YAZE_APP_GUI_FEATURE_FLAGS_MENU_H
#include "app/core/features.h"
#include "imgui/imgui.h"
namespace yaze {
namespace gui {
using ImGui::BeginMenu;
using ImGui::Checkbox;
using ImGui::EndMenu;
using ImGui::MenuItem;
using ImGui::Separator;
struct FlagsMenu {
void DrawOverworldFlags() {
Checkbox("Enable Overworld Sprites",
&core::FeatureFlags::get().overworld.kDrawOverworldSprites);
Separator();
Checkbox("Save Overworld Maps",
&core::FeatureFlags::get().overworld.kSaveOverworldMaps);
Checkbox("Save Overworld Entrances",
&core::FeatureFlags::get().overworld.kSaveOverworldEntrances);
Checkbox("Save Overworld Exits",
&core::FeatureFlags::get().overworld.kSaveOverworldExits);
Checkbox("Save Overworld Items",
&core::FeatureFlags::get().overworld.kSaveOverworldItems);
Checkbox("Save Overworld Properties",
&core::FeatureFlags::get().overworld.kSaveOverworldProperties);
Checkbox("Enable Custom Overworld Features",
&core::FeatureFlags::get().overworld.kLoadCustomOverworld);
ImGui::SameLine();
if (ImGui::Button("?")) {
ImGui::OpenPopup("CustomOverworldHelp");
}
if (ImGui::BeginPopup("CustomOverworldHelp")) {
ImGui::Text("This flag enables ZSCustomOverworld features.");
ImGui::Text("If ZSCustomOverworld ASM is already applied to the ROM,");
ImGui::Text("features are auto-enabled regardless of this flag.");
ImGui::Text("For vanilla ROMs, enable this to use custom features.");
ImGui::EndPopup();
}
Checkbox("Apply ZSCustomOverworld ASM",
&core::FeatureFlags::get().overworld.kApplyZSCustomOverworldASM);
}
void DrawDungeonFlags() {
Checkbox("Save Dungeon Maps", &core::FeatureFlags::get().kSaveDungeonMaps);
}
void DrawResourceFlags() {
Checkbox("Save All Palettes", &core::FeatureFlags::get().kSaveAllPalettes);
Checkbox("Save Gfx Groups", &core::FeatureFlags::get().kSaveGfxGroups);
Checkbox("Save Graphics Sheets", &core::FeatureFlags::get().kSaveGraphicsSheet);
}
void DrawSystemFlags() {
Checkbox("Enable Console Logging", &core::FeatureFlags::get().kLogToConsole);
Checkbox("Enable Performance Monitoring",
&core::FeatureFlags::get().kEnablePerformanceMonitoring);
Checkbox("Log Instructions to Emulator Debugger",
&core::FeatureFlags::get().kLogInstructions);
Checkbox("Use Native File Dialog (NFD)",
&core::FeatureFlags::get().kUseNativeFileDialog);
}
};
} // namespace gui
} // namespace yaze
#endif // YAZE_APP_GUI_FEATURE_FLAGS_MENU_H

View File

@@ -2,6 +2,7 @@ set(
YAZE_GUI_SRC YAZE_GUI_SRC
app/gui/modules/asset_browser.cc app/gui/modules/asset_browser.cc
app/gui/modules/text_editor.cc app/gui/modules/text_editor.cc
app/gui/widgets/agent_chat_widget.cc
app/gui/canvas.cc app/gui/canvas.cc
app/gui/canvas_utils.cc app/gui/canvas_utils.cc
app/gui/enhanced_palette_editor.cc app/gui/enhanced_palette_editor.cc
@@ -12,6 +13,7 @@ set(
app/gui/background_renderer.cc app/gui/background_renderer.cc
app/gui/bpp_format_ui.cc app/gui/bpp_format_ui.cc
app/gui/widget_id_registry.cc app/gui/widget_id_registry.cc
app/gui/widget_auto_register.cc
# Canvas system components # Canvas system components
app/gui/canvas/canvas_modals.cc app/gui/canvas/canvas_modals.cc
app/gui/canvas/canvas_context_menu.cc app/gui/canvas/canvas_context_menu.cc

View File

@@ -125,7 +125,7 @@ void ThemeManager::InitializeBuiltInThemes() {
// Load all available theme files dynamically // Load all available theme files dynamically
auto status = LoadAllAvailableThemes(); auto status = LoadAllAvailableThemes();
if (!status.ok()) { if (!status.ok()) {
util::logf("Warning: Failed to load some theme files: %s", status.message().data()); LOG_ERROR("Theme Manager", "Failed to load some theme files");
} }
// Ensure we have a valid current theme (Classic is already set above) // Ensure we have a valid current theme (Classic is already set above)
@@ -326,7 +326,7 @@ void ThemeManager::ApplyTheme(const std::string& theme_name) {
// Fallback to YAZE Tre if theme not found // Fallback to YAZE Tre if theme not found
auto fallback_status = LoadTheme("YAZE Tre"); auto fallback_status = LoadTheme("YAZE Tre");
if (!fallback_status.ok()) { if (!fallback_status.ok()) {
util::logf("Failed to load fallback theme: %s", fallback_status.message().data()); LOG_ERROR("Theme Manager", "Failed to load fallback theme");
} }
} }
} }
@@ -426,7 +426,7 @@ void ThemeManager::ShowThemeSelector(bool* p_open) {
name.c_str()).c_str(), ImVec2(-1, 40))) { name.c_str()).c_str(), ImVec2(-1, 40))) {
auto status = LoadTheme(name); // Use LoadTheme instead of ApplyTheme to ensure correct tracking auto status = LoadTheme(name); // Use LoadTheme instead of ApplyTheme to ensure correct tracking
if (!status.ok()) { if (!status.ok()) {
util::logf("Failed to load theme %s: %s", name.c_str(), status.message().data()); LOG_ERROR("Theme Manager", "Failed to load theme %s", name.c_str());
} }
} }
@@ -460,7 +460,7 @@ void ThemeManager::ShowThemeSelector(bool* p_open) {
if (ImGui::Button(absl::StrFormat("%s Refresh Themes", ICON_MD_REFRESH).c_str())) { if (ImGui::Button(absl::StrFormat("%s Refresh Themes", ICON_MD_REFRESH).c_str())) {
auto status = RefreshAvailableThemes(); auto status = RefreshAvailableThemes();
if (!status.ok()) { if (!status.ok()) {
util::logf("Failed to refresh themes: %s", status.message().data()); LOG_ERROR("Theme Manager", "Failed to refresh themes");
} }
} }
@@ -1019,7 +1019,7 @@ void ThemeManager::ShowSimpleThemeEditor(bool* p_open) {
if (!current_file_path.empty()) { if (!current_file_path.empty()) {
auto status = SaveThemeToFile(current_theme_, current_file_path); auto status = SaveThemeToFile(current_theme_, current_file_path);
if (!status.ok()) { if (!status.ok()) {
util::logf("Failed to save theme: %s", status.message().data()); LOG_ERROR("Theme Manager", "Failed to save theme");
} }
} else { } else {
// No existing file, prompt for new location // No existing file, prompt for new location
@@ -1027,7 +1027,7 @@ void ThemeManager::ShowSimpleThemeEditor(bool* p_open) {
if (!file_path.empty()) { if (!file_path.empty()) {
auto status = SaveThemeToFile(current_theme_, file_path); auto status = SaveThemeToFile(current_theme_, file_path);
if (!status.ok()) { if (!status.ok()) {
util::logf("Failed to save theme: %s", status.message().data()); LOG_ERROR("Theme Manager", "Failed to save theme");
} }
} }
} }
@@ -1038,7 +1038,7 @@ void ThemeManager::ShowSimpleThemeEditor(bool* p_open) {
if (!file_path.empty()) { if (!file_path.empty()) {
auto status = SaveThemeToFile(current_theme_, file_path); auto status = SaveThemeToFile(current_theme_, file_path);
if (!status.ok()) { if (!status.ok()) {
util::logf("Failed to save theme: %s", status.message().data()); LOG_ERROR("Theme Manager", "Failed to save theme");
} }
} }
} }
@@ -1831,7 +1831,7 @@ void ThemeManager::ShowSimpleThemeEditor(bool* p_open) {
ApplyTheme(edit_theme); ApplyTheme(edit_theme);
theme_backup_made = false; // Reset backup state since theme is now applied theme_backup_made = false; // Reset backup state since theme is now applied
} else { } else {
util::logf("Failed to save over current theme: %s", status.message().data()); LOG_ERROR("Theme Manager", "Failed to save over current theme");
} }
} }
@@ -1873,7 +1873,7 @@ void ThemeManager::ShowSimpleThemeEditor(bool* p_open) {
themes_[edit_theme.name] = edit_theme; themes_[edit_theme.name] = edit_theme;
ApplyTheme(edit_theme); ApplyTheme(edit_theme);
} else { } else {
util::logf("Failed to save theme: %s", status.message().data()); LOG_ERROR("Theme Manager", "Failed to save theme");
} }
} }
} }
@@ -1981,7 +1981,7 @@ std::vector<std::string> ThemeManager::DiscoverAvailableThemeFiles() const {
} }
#endif #endif
} catch (const std::exception& e) { } catch (const std::exception& e) {
util::logf("Error scanning directory %s: %s", search_path.c_str(), e.what()); LOG_ERROR("Theme Manager", "Error scanning directory %s", search_path.c_str());
} }
} }

View File

@@ -14,7 +14,6 @@
#include "app/zelda3/dungeon/room_diagnostic.h" #include "app/zelda3/dungeon/room_diagnostic.h"
#include "app/zelda3/dungeon/room_object.h" #include "app/zelda3/dungeon/room_object.h"
#include "app/zelda3/sprite/sprite.h" #include "app/zelda3/sprite/sprite.h"
#include "util/log.h"
namespace yaze { namespace yaze {
namespace zelda3 { namespace zelda3 {
@@ -198,6 +197,7 @@ Room LoadRoomFromRom(Rom *rom, int room_id) {
room.LoadBlocks(); room.LoadBlocks();
room.LoadPits(); room.LoadPits();
room.SetLoaded(true);
return room; return room;
} }
@@ -318,9 +318,7 @@ void Room::RenderRoomGraphics() {
// Validate palette ID and fall back to palette 0 if invalid // Validate palette ID and fall back to palette 0 if invalid
if (palette_id < 0 || palette_id >= num_palettes) { if (palette_id < 0 || palette_id >= num_palettes) {
std::printf("WARNING: Room %d has invalid palette_id=%d (max=%d), falling back to palette 0\n", //palette_id = 0;
room_id_, palette_id, num_palettes - 1);
palette_id = 0;
} }
// Load the 90-color dungeon palette directly // Load the 90-color dungeon palette directly
@@ -573,12 +571,6 @@ void Room::ParseObjectsFromLocation(int objects_location) {
uint8_t b1 = 0; uint8_t b1 = 0;
uint8_t b2 = 0; uint8_t b2 = 0;
uint8_t b3 = 0; uint8_t b3 = 0;
uint8_t posX = 0;
uint8_t posY = 0;
uint8_t sizeX = 0;
uint8_t sizeY = 0;
uint8_t sizeXY = 0;
short oid = 0;
int layer = 0; int layer = 0;
bool door = false; bool door = false;
bool end_read = false; bool end_read = false;
@@ -852,7 +844,9 @@ void Room::LoadSprites() {
rom_data[sprite_pointer + (room_id_ * 2)]; rom_data[sprite_pointer + (room_id_ * 2)];
int sprite_address = SnesToPc(sprite_address_snes); int sprite_address = SnesToPc(sprite_address_snes);
bool sortsprites = rom_data[sprite_address] == 1; if (rom_data[sprite_address] == 1) {
// sortsprites is unused
}
sprite_address += 1; sprite_address += 1;
while (true) { while (true) {
@@ -897,7 +891,7 @@ void Room::LoadChests() {
size_t clength = (rom_data[chests_length_pointer + 1] << 8) + size_t clength = (rom_data[chests_length_pointer + 1] << 8) +
(rom_data[chests_length_pointer]); (rom_data[chests_length_pointer]);
for (int i = 0; i < clength; i++) { for (size_t i = 0; i < clength; i++) {
if ((((rom_data[cpos + (i * 3) + 1] << 8) + (rom_data[cpos + (i * 3)])) & if ((((rom_data[cpos + (i * 3) + 1] << 8) + (rom_data[cpos + (i * 3)])) &
0x7FFF) == room_id_) { 0x7FFF) == room_id_) {
// There's a chest in that room ! // There's a chest in that room !
@@ -945,7 +939,7 @@ void Room::LoadTorches() {
auto rom_data = rom()->vector(); auto rom_data = rom()->vector();
// Load torch data from torch_data address // Load torch data from torch_data address
int torch_count = rom_data[torches_length_pointer + 1] << 8 | rom_data[torches_length_pointer]; // int torch_count = rom_data[torches_length_pointer + 1] << 8 | rom_data[torches_length_pointer];
// For now, create placeholder torch objects // For now, create placeholder torch objects
// TODO: Implement full torch loading from ROM data // TODO: Implement full torch loading from ROM data
@@ -955,7 +949,7 @@ void Room::LoadBlocks() {
auto rom_data = rom()->vector(); auto rom_data = rom()->vector();
// Load block data from blocks_* addresses // Load block data from blocks_* addresses
int block_count = rom_data[blocks_length + 1] << 8 | rom_data[blocks_length]; // int block_count = rom_data[blocks_length + 1] << 8 | rom_data[blocks_length];
// For now, create placeholder block objects // For now, create placeholder block objects
// TODO: Implement full block loading from ROM data // TODO: Implement full block loading from ROM data

View File

@@ -312,6 +312,10 @@ class Room {
void SetStair2Target(uint8_t target) { stair2_.target = target; } void SetStair2Target(uint8_t target) { stair2_.target = target; }
void SetStair3Target(uint8_t target) { stair3_.target = target; } void SetStair3Target(uint8_t target) { stair3_.target = target; }
void SetStair4Target(uint8_t target) { stair4_.target = target; } void SetStair4Target(uint8_t target) { stair4_.target = target; }
// Loaded state
bool IsLoaded() const { return is_loaded_; }
void SetLoaded(bool loaded) { is_loaded_ = loaded; }
// Read-only accessors for metadata // Read-only accessors for metadata
EffectKey effect() const { return effect_; } EffectKey effect() const { return effect_; }
@@ -350,7 +354,7 @@ class Room {
std::array<uint8_t, 0x4000> current_gfx16_; std::array<uint8_t, 0x4000> current_gfx16_;
bool is_light_; bool is_light_;
bool is_loaded_; bool is_loaded_ = false;
bool is_dark_; bool is_dark_;
bool is_floor_ = true; bool is_floor_ = true;

109
src/util/log.cc Normal file
View File

@@ -0,0 +1,109 @@
#include "util/log.h"
#include <chrono>
#include <iomanip>
#include <iostream>
namespace yaze {
namespace util {
// Helper function to convert LogLevel enum to a string representation.
static const char* LogLevelToString(LogLevel level) {
switch (level) {
case LogLevel::YAZE_DEBUG:
return "YAZE_DEBUG";
case LogLevel::INFO:
return "INFO";
case LogLevel::WARNING:
return "WARN";
case LogLevel::ERROR:
return "ERROR";
case LogLevel::FATAL:
return "FATAL";
}
return "UNKN";
}
// --- LogManager Implementation ---
LogManager& LogManager::instance() {
static LogManager instance;
return instance;
}
LogManager::LogManager()
: min_level_(LogLevel::INFO), all_categories_enabled_(true) {}
LogManager::~LogManager() {
if (log_stream_.is_open()) {
log_stream_.close();
}
}
void LogManager::configure(LogLevel level, const std::string& file_path,
const std::set<std::string>& categories) {
min_level_.store(level);
if (categories.empty()) {
all_categories_enabled_.store(true);
enabled_categories_.clear();
} else {
all_categories_enabled_.store(false);
enabled_categories_ = categories;
}
// If a file path is provided, close any existing stream and open the new file.
if (!file_path.empty() && file_path != log_file_path_) {
if (log_stream_.is_open()) {
log_stream_.close();
}
// Open in append mode to preserve history.
log_stream_.open(file_path, std::ios::out | std::ios::app);
log_file_path_ = file_path;
}
}
void LogManager::log(LogLevel level, absl::string_view category,
absl::string_view message) {
// 1. Filter by log level.
if (level < min_level_.load()) {
return;
}
// 2. Filter by category.
if (!all_categories_enabled_.load()) {
if (enabled_categories_.find(std::string(category)) ==
enabled_categories_.end()) {
return;
}
}
// 3. Format the complete log message.
// [HH:MM:SS.ms] [LEVEL] [category] message
auto now = std::chrono::system_clock::now();
auto now_tt = std::chrono::system_clock::to_time_t(now);
auto now_tm = *std::localtime(&now_tt);
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
now.time_since_epoch()) %
1000;
std::string final_message = absl::StrFormat(
"[%02d:%02d:%02d.%03d] [%-5s] [%s] %s\n", now_tm.tm_hour, now_tm.tm_min,
now_tm.tm_sec, ms.count(), LogLevelToString(level), category, message);
// 4. Write to the configured sink (file or stderr).
if (log_stream_.is_open()) {
log_stream_ << final_message;
log_stream_.flush(); // Ensure immediate write for debugging.
} else {
std::cerr << final_message;
}
// 5. Abort on FATAL error.
if (level == LogLevel::FATAL) {
std::abort();
}
}
} // namespace util
} // namespace yaze

View File

@@ -1,20 +1,26 @@
#ifndef YAZE_UTIL_LOG_H #ifndef YAZE_UTIL_LOG_H
#define YAZE_UTIL_LOG_H #define YAZE_UTIL_LOG_H
#include <atomic>
#include <chrono> #include <chrono>
#include <cstdint>
#include <fstream> #include <fstream>
#include <iostream> #include <iostream>
#include <memory>
#include <set>
#include <string> #include <string>
#include <vector>
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h" #include "absl/strings/str_format.h"
#include "absl/strings/str_cat.h"
#include "app/core/features.h" #include "app/core/features.h"
#include "absl/strings/string_view.h"
namespace yaze { namespace yaze {
namespace util { namespace util {
static std::string g_log_file_path = "yaze_log.txt"; // Static variables for library state
static std::string g_log_file_path = "";
// Set custom log file path // Set custom log file path
inline void SetLogFile(const std::string& filepath) { inline void SetLogFile(const std::string& filepath) {
@@ -50,7 +56,91 @@ static void logf(const absl::FormatSpec<Args...> &format, const Args &...args) {
fout.flush(); // Ensure immediate write for debugging fout.flush(); // Ensure immediate write for debugging
} }
/**
* @enum LogLevel
* @brief Defines the severity levels for log messages.
* This allows for filtering messages based on their importance.
*/
enum class LogLevel { YAZE_DEBUG, INFO, WARNING, ERROR, FATAL };
/**
* @class LogManager
* @brief A singleton that manages all logging configuration and output.
*
* It is designed to be configured once at application startup, typically from
* command-line arguments. It supports filtering by level and category, and can
* direct output to stderr (default) or a specified file.
*/
class LogManager {
public:
// Singleton access
static LogManager& instance();
// Deleted constructors for singleton pattern
LogManager(const LogManager&) = delete;
void operator=(const LogManager&) = delete;
/**
* @brief Configures the logging system.
* @param level The minimum log level to record.
* @param file_path The path to the log file. If empty, logs to stderr.
* @param categories A set of specific categories to enable. If empty, all
* categories are enabled.
*/
void configure(LogLevel level, const std::string& file_path,
const std::set<std::string>& categories);
/**
* @brief The primary logging function.
* @param level The severity level of the message.
* @param category The category of the message (e.g., "Graphics", "Agent").
* @param message The formatted log message.
*/
void log(LogLevel level, absl::string_view category,
absl::string_view message);
private:
LogManager();
~LogManager();
// Configuration state
std::atomic<LogLevel> min_level_;
std::set<std::string> enabled_categories_;
std::atomic<bool> all_categories_enabled_;
// Output sink
std::ofstream log_stream_;
std::string log_file_path_;
};
// logf mapping
#define logf LOG_INFO
// --- Public Logging Macros ---
// These macros provide a convenient and efficient API for logging.
// The `do-while(0)` loop ensures they behave like a single statement.
// The level check avoids the cost of string formatting if the message won't be
// logged.
#define LOG(level, category, format, ...) \
do { \
yaze::util::LogManager::instance().log( \
level, category, absl::StrFormat(format, ##__VA_ARGS__)); \
} while (0)
#define LOG_DEBUG(category, format, ...) \
LOG(yaze::util::LogLevel::YAZE_DEBUG, category, format, ##__VA_ARGS__)
#define LOG_INFO(category, format, ...) \
LOG(yaze::util::LogLevel::INFO, category, format, ##__VA_ARGS__)
#define LOG_WARN(category, format, ...) \
LOG(yaze::util::LogLevel::WARNING, category, format, ##__VA_ARGS__)
#define LOG_ERROR(category, format, ...) \
LOG(yaze::util::LogLevel::ERROR, category, format, ##__VA_ARGS__)
#define LOG_FATAL(category, format, ...) \
LOG(yaze::util::LogLevel::FATAL, category, format, ##__VA_ARGS__)
} // namespace util } // namespace util
} // namespace yaze } // namespace yaze
#endif // YAZE_UTIL_LOG_H #endif // YAZE_UTIL_LOG_H

View File

@@ -14,6 +14,7 @@ set(YAZE_UTIL_SRC
util/bps.cc util/bps.cc
util/flag.cc util/flag.cc
util/hex.cc util/hex.cc
util/log.cc
) )
add_library(yaze_util STATIC ${YAZE_UTIL_SRC}) add_library(yaze_util STATIC ${YAZE_UTIL_SRC})