refactor: Remove OverworldEditorManager and GraphicsManager for Simplification

- Deleted OverworldEditorManager and OverworldGraphicsManager classes to streamline the codebase and reduce complexity in the overworld editor.
- Updated CMake configuration to remove references to the deleted files, ensuring a clean build environment.
- Adjusted OverworldEditor to handle graphics management directly, improving maintainability and reducing the number of dependencies.
- Enhanced the initialization and update methods in OverworldEditor to accommodate the removal of the manager classes, ensuring continued functionality.
- Cleaned up related header files to reflect the removal of obsolete classes and methods, enhancing clarity and organization.
This commit is contained in:
scawful
2025-10-05 19:25:11 -04:00
parent 405dece70a
commit 995c4e4081
9 changed files with 152 additions and 1545 deletions

View File

@@ -18,7 +18,6 @@ set(
app/editor/dungeon/dungeon_room_loader.cc
app/editor/dungeon/dungeon_usage_tracker.cc
app/editor/overworld/overworld_editor.cc
app/editor/overworld/overworld_editor_manager.cc
app/editor/overworld/scratch_space.cc
app/editor/sprite/sprite_editor.cc
app/editor/music/music_editor.cc
@@ -36,7 +35,6 @@ set(
app/editor/graphics/gfx_group_editor.cc
app/editor/overworld/entity.cc
app/editor/overworld/overworld_entity_renderer.cc
app/editor/overworld/overworld_graphics_manager.cc
app/editor/system/settings_editor.cc
app/editor/system/command_manager.cc
app/editor/system/extension_manager.cc

View File

@@ -59,10 +59,6 @@ void OverworldEditor::Initialize() {
[this](int map_index) { this->ForceRefreshGraphics(map_index); }
);
// Initialize OverworldEditorManager for v3 features
overworld_manager_ =
std::make_unique<OverworldEditorManager>(&overworld_, rom_, this);
// Initialize OverworldEntityRenderer for entity visualization
entity_renderer_ = std::make_unique<OverworldEntityRenderer>(
&overworld_, &ow_map_canvas_, &sprite_previews_);
@@ -220,14 +216,6 @@ absl::Status OverworldEditor::Update() {
usage_stats_card.End();
}
// v3 Settings popup
if (show_v3_settings_ && v3_settings_card.Begin(&show_v3_settings_)) {
if (rom_->is_loaded()) {
status_ = overworld_manager_->DrawV3SettingsPanel();
}
v3_settings_card.End();
}
// Area Configuration Panel (detailed editing)
if (show_map_properties_panel_) {
ImGui::SetNextWindowSize(ImVec2(650, 750), ImGuiCond_FirstUseEver);
@@ -1397,9 +1385,26 @@ absl::Status OverworldEditor::LoadGraphics() {
}
}
// Store remaining maps for lazy texture creation
deferred_map_textures_.assign(maps_to_texture.begin() + initial_texture_count,
maps_to_texture.end());
// Queue remaining maps for progressive loading via Arena
// Priority based on current world (0 = current world, 11+ = other worlds)
for (size_t i = initial_texture_count; i < maps_to_texture.size(); ++i) {
// Determine priority based on which world this map belongs to
int map_index = -1;
for (int j = 0; j < zelda3::kNumOverworldMaps; ++j) {
if (&maps_bmp_[j] == maps_to_texture[i]) {
map_index = j;
break;
}
}
int priority = 15; // Default low priority
if (map_index >= 0) {
int map_world = map_index / 0x40;
priority = (map_world == current_world_) ? 5 : 15; // Current world = priority 5, others = 15
}
gfx::Arena::Get().QueueDeferredTexture(maps_to_texture[i], priority);
}
if (core::FeatureFlags::get().overworld.kDrawOverworldSprites) {
{
@@ -1433,75 +1438,29 @@ absl::Status OverworldEditor::LoadSpriteGraphics() {
}
void OverworldEditor::ProcessDeferredTextures() {
std::lock_guard<std::mutex> lock(deferred_textures_mutex_);
if (deferred_map_textures_.empty()) {
return;
}
// Priority-based loading: process more textures for visible maps
const int textures_per_frame = 8; // Increased from 2 to 8 for faster loading
int processed = 0;
// First pass: prioritize textures for the current world
auto it = deferred_map_textures_.begin();
while (it != deferred_map_textures_.end() && processed < textures_per_frame) {
if (*it && !(*it)->texture()) {
// Check if this texture belongs to the current world
int map_index = -1;
for (int i = 0; i < zelda3::kNumOverworldMaps; ++i) {
if (&maps_bmp_[i] == *it) {
map_index = i;
break;
}
}
bool is_current_world = false;
if (map_index >= 0) {
int map_world = map_index / 0x40; // 64 maps per world
is_current_world = (map_world == current_world_);
}
// Prioritize current world maps, but also process others if we have capacity
if (is_current_world || processed < textures_per_frame / 2) {
Renderer::Get().RenderBitmap(*it);
processed++;
it = deferred_map_textures_.erase(
it); // Remove immediately after processing
} else {
++it;
}
} else {
++it;
// Use Arena's centralized progressive loading system
// This makes progressive loading available to all editors
auto batch = gfx::Arena::Get().GetNextDeferredTextureBatch(4, 2);
for (auto* bitmap : batch) {
if (bitmap && !bitmap->texture()) {
Renderer::Get().RenderBitmap(bitmap);
}
}
// Second pass: process remaining textures if we still have capacity
if (processed < textures_per_frame) {
it = deferred_map_textures_.begin();
while (it != deferred_map_textures_.end() &&
processed < textures_per_frame) {
if (*it && !(*it)->texture()) {
Renderer::Get().RenderBitmap(*it);
processed++;
it = deferred_map_textures_.erase(it);
} else {
++it;
}
}
}
// Third pass: process deferred map refreshes for visible maps
if (processed < textures_per_frame) {
for (int i = 0;
i < zelda3::kNumOverworldMaps && processed < textures_per_frame; ++i) {
if (maps_bmp_[i].modified() && maps_bmp_[i].is_active()) {
// Check if this map is visible
bool is_visible = (i == current_map_) || (i / 0x40 == current_world_);
if (is_visible) {
RefreshOverworldMapOnDemand(i);
processed++;
}
// Also process deferred map refreshes for modified maps
int refresh_count = 0;
const int max_refreshes_per_frame = 2;
for (int i = 0; i < zelda3::kNumOverworldMaps && refresh_count < max_refreshes_per_frame; ++i) {
if (maps_bmp_[i].modified() && maps_bmp_[i].is_active()) {
// Check if this map is in current world (prioritize)
bool is_current_world = (i / 0x40 == current_world_);
bool is_current_map = (i == current_map_);
if (is_current_map || is_current_world) {
RefreshOverworldMapOnDemand(i);
refresh_count++;
}
}
}
@@ -1539,14 +1498,7 @@ void OverworldEditor::EnsureMapTexture(int map_index) {
if (!bitmap.texture() && bitmap.is_active()) {
Renderer::Get().RenderBitmap(&bitmap);
// Remove from deferred list if it was there
std::lock_guard<std::mutex> lock(deferred_textures_mutex_);
auto it = std::find(deferred_map_textures_.begin(),
deferred_map_textures_.end(), &bitmap);
if (it != deferred_map_textures_.end()) {
deferred_map_textures_.erase(it);
}
// Note: Arena automatically removes from deferred queue when textures are created
}
}

View File

@@ -15,7 +15,6 @@
#include "app/gui/input.h"
#include "app/rom.h"
#include "app/zelda3/overworld/overworld.h"
#include "app/editor/overworld/overworld_editor_manager.h"
#include "imgui/imgui.h"
#include <mutex>
@@ -273,7 +272,6 @@ class OverworldEditor : public Editor, public gfx::GfxContext {
// Map properties system for UI organization
std::unique_ptr<MapPropertiesSystem> map_properties_system_;
std::unique_ptr<OverworldEditorManager> overworld_manager_;
std::unique_ptr<OverworldEntityRenderer> entity_renderer_;
// Scratch space for large layouts
@@ -313,8 +311,7 @@ class OverworldEditor : public Editor, public gfx::GfxContext {
std::vector<gfx::Bitmap> sprite_previews_;
// Deferred texture creation for performance optimization
std::vector<gfx::Bitmap*> deferred_map_textures_;
std::mutex deferred_textures_mutex_;
// Deferred texture management now handled by gfx::Arena::Get()
zelda3::Overworld overworld_{rom_};
zelda3::OverworldBlockset refresh_blockset_;

View File

@@ -1,423 +0,0 @@
#include "overworld_editor_manager.h"
#include "app/gfx/snes_color.h"
#include "app/gui/icons.h"
#include "app/gui/input.h"
#include "app/gui/style.h"
#include "app/zelda3/overworld/overworld_map.h"
namespace yaze {
namespace editor {
using namespace ImGui;
absl::Status OverworldEditorManager::DrawV3SettingsPanel() {
if (BeginTabItem("v3 Settings")) {
Text("ZSCustomOverworld v3 Settings");
Separator();
// Check if custom ASM is applied
uint8_t asm_version = GetCustomASMVersion();
if (asm_version >= 3 && asm_version != 0xFF) {
TextColored(ImVec4(0, 1, 0, 1), "Custom Overworld ASM v%d Applied", asm_version);
} else if (asm_version == 0x00) {
TextColored(ImVec4(1, 1, 0, 1), "Vanilla ROM - Custom features available via flag");
} else {
TextColored(ImVec4(1, 0, 0, 1), "Custom ASM v%d - Consider upgrading to v3", asm_version);
}
Separator();
RETURN_IF_ERROR(DrawCustomOverworldSettings());
RETURN_IF_ERROR(DrawAreaSpecificSettings());
RETURN_IF_ERROR(DrawTransitionSettings());
RETURN_IF_ERROR(DrawOverlaySettings());
EndTabItem();
}
return absl::OkStatus();
}
absl::Status OverworldEditorManager::DrawCustomOverworldSettings() {
if (TreeNode("Custom Overworld Features")) {
RETURN_IF_ERROR(DrawBooleanSetting("Enable Area-Specific Background Colors",
&enable_area_specific_bg_,
"Allows each overworld area to have its own background color"));
RETURN_IF_ERROR(DrawBooleanSetting("Enable Main Palette Override",
&enable_main_palette_,
"Allows each area to override the main palette"));
RETURN_IF_ERROR(DrawBooleanSetting("Enable Mosaic Transitions",
&enable_mosaic_,
"Enables mosaic screen transitions between areas"));
RETURN_IF_ERROR(DrawBooleanSetting("Enable Custom GFX Groups",
&enable_gfx_groups_,
"Allows each area to have custom tile GFX groups"));
RETURN_IF_ERROR(DrawBooleanSetting("Enable Subscreen Overlays",
&enable_subscreen_overlay_,
"Enables custom subscreen overlays (fog, sky, etc.)"));
RETURN_IF_ERROR(DrawBooleanSetting("Enable Animated GFX Override",
&enable_animated_gfx_,
"Allows each area to have custom animated tiles"));
Separator();
if (Button("Apply Custom Overworld ASM")) {
RETURN_IF_ERROR(ApplyCustomOverworldASM());
}
SameLine();
HOVER_HINT("Writes the custom overworld settings to ROM");
TreePop();
}
return absl::OkStatus();
}
absl::Status OverworldEditorManager::DrawAreaSpecificSettings() {
if (TreeNode("Area-Specific Settings")) {
// Map selection
int map_count = zelda3::kNumOverworldMaps;
SliderInt("Map Index", &current_map_index_, 0, map_count - 1);
auto* current_map = overworld_->mutable_overworld_map(current_map_index_);
// Area size controls
RETURN_IF_ERROR(DrawAreaSizeControls());
// Background color
if (enable_area_specific_bg_) {
uint16_t bg_color = current_map->area_specific_bg_color();
RETURN_IF_ERROR(DrawColorPicker("Background Color", &bg_color));
current_map->set_area_specific_bg_color(bg_color);
}
// Main palette
if (enable_main_palette_) {
uint8_t main_palette = current_map->main_palette();
SliderInt("Main Palette", (int*)&main_palette, 0, 5);
current_map->set_main_palette(main_palette);
}
// Mosaic settings
if (enable_mosaic_) {
RETURN_IF_ERROR(DrawMosaicControls());
}
// GFX groups
if (enable_gfx_groups_) {
RETURN_IF_ERROR(DrawGfxGroupControls());
}
// Subscreen overlay
if (enable_subscreen_overlay_) {
uint16_t overlay = current_map->subscreen_overlay();
RETURN_IF_ERROR(DrawOverlaySetting("Subscreen Overlay", &overlay));
current_map->set_subscreen_overlay(overlay);
}
// Animated GFX
if (enable_animated_gfx_) {
uint8_t animated_gfx = current_map->animated_gfx();
RETURN_IF_ERROR(DrawGfxGroupSetting("Animated GFX", &animated_gfx));
current_map->set_animated_gfx(animated_gfx);
}
TreePop();
}
return absl::OkStatus();
}
absl::Status OverworldEditorManager::DrawAreaSizeControls() {
auto* current_map = overworld_->mutable_overworld_map(current_map_index_);
const char* area_size_names[] = {"Small", "Large", "Wide", "Tall"};
int current_size = static_cast<int>(current_map->area_size());
if (Combo("Area Size", &current_size, area_size_names, 4)) {
current_map->SetAreaSize(static_cast<zelda3::AreaSizeEnum>(current_size));
}
return absl::OkStatus();
}
absl::Status OverworldEditorManager::DrawMosaicControls() {
auto* current_map = overworld_->mutable_overworld_map(current_map_index_);
const auto& mosaic = current_map->mosaic_expanded();
bool mosaic_up = mosaic[0];
bool mosaic_down = mosaic[1];
bool mosaic_left = mosaic[2];
bool mosaic_right = mosaic[3];
if (Checkbox("Mosaic Up", &mosaic_up)) {
current_map->set_mosaic_expanded(0, mosaic_up);
}
SameLine();
if (Checkbox("Mosaic Down", &mosaic_down)) {
current_map->set_mosaic_expanded(1, mosaic_down);
}
if (Checkbox("Mosaic Left", &mosaic_left)) {
current_map->set_mosaic_expanded(2, mosaic_left);
}
SameLine();
if (Checkbox("Mosaic Right", &mosaic_right)) {
current_map->set_mosaic_expanded(3, mosaic_right);
}
return absl::OkStatus();
}
absl::Status OverworldEditorManager::DrawGfxGroupControls() {
auto* current_map = overworld_->mutable_overworld_map(current_map_index_);
Text("Custom Tile GFX Groups:");
for (int i = 0; i < 8; i++) {
uint8_t gfx_id = current_map->custom_tileset(i);
std::string label = "GFX " + std::to_string(i);
RETURN_IF_ERROR(DrawGfxGroupSetting(label.c_str(), &gfx_id));
current_map->set_custom_tileset(i, gfx_id);
if (i < 7) SameLine();
}
return absl::OkStatus();
}
absl::Status OverworldEditorManager::DrawTransitionSettings() {
if (TreeNode("Transition Settings")) {
Text("Complex area transition calculations are automatically handled");
Text("based on neighboring area sizes (Large, Wide, Tall, Small).");
if (GetCustomASMVersion() >= 3) {
TextColored(ImVec4(0, 1, 0, 1), "Using v3+ enhanced transitions");
} else {
TextColored(ImVec4(1, 1, 0, 1), "Using vanilla/v2 transitions");
}
TreePop();
}
return absl::OkStatus();
}
absl::Status OverworldEditorManager::DrawOverlaySettings() {
if (TreeNode("Interactive Overlay Settings")) {
Text("Interactive overlays reveal holes and change map elements.");
auto* current_map = overworld_->mutable_overworld_map(current_map_index_);
Text("Map %d has %s", current_map_index_,
current_map->has_overlay() ? "interactive overlay" : "no overlay");
if (current_map->has_overlay()) {
Text("Overlay ID: 0x%04X", current_map->overlay_id());
Text("Overlay data size: %zu bytes", current_map->overlay_data().size());
}
TreePop();
}
return absl::OkStatus();
}
absl::Status OverworldEditorManager::ApplyCustomOverworldASM() {
return overworld_->SaveCustomOverworldASM(
enable_area_specific_bg_, enable_main_palette_, enable_mosaic_,
enable_gfx_groups_, enable_subscreen_overlay_, enable_animated_gfx_);
}
bool OverworldEditorManager::ValidateV3Compatibility() {
uint8_t asm_version = GetCustomASMVersion();
return (asm_version >= 3 && asm_version != 0xFF);
}
bool OverworldEditorManager::CheckCustomASMApplied() {
uint8_t asm_version = GetCustomASMVersion();
return (asm_version != 0xFF && asm_version != 0x00);
}
uint8_t OverworldEditorManager::GetCustomASMVersion() {
return (*rom_)[zelda3::OverworldCustomASMHasBeenApplied];
}
absl::Status OverworldEditorManager::DrawBooleanSetting(const char* label, bool* setting,
const char* help_text) {
Checkbox(label, setting);
if (help_text && IsItemHovered()) {
SetTooltip("%s", help_text);
}
return absl::OkStatus();
}
absl::Status OverworldEditorManager::DrawColorPicker(const char* label, uint16_t* color) {
gfx::SnesColor snes_color(*color);
ImVec4 imgui_color = snes_color.rgb();
if (ColorEdit3(label, &imgui_color.x)) {
gfx::SnesColor new_color;
new_color.set_rgb(imgui_color);
*color = new_color.snes();
}
return absl::OkStatus();
}
absl::Status OverworldEditorManager::DrawOverlaySetting(const char* label, uint16_t* overlay) {
int overlay_int = *overlay;
if (InputInt(label, &overlay_int, 1, 16, ImGuiInputTextFlags_CharsHexadecimal)) {
*overlay = static_cast<uint16_t>(overlay_int & 0xFFFF);
}
return absl::OkStatus();
}
absl::Status OverworldEditorManager::DrawGfxGroupSetting(const char* label, uint8_t* gfx_id,
int max_value) {
int gfx_int = *gfx_id;
if (SliderInt(label, &gfx_int, 0, max_value)) {
*gfx_id = static_cast<uint8_t>(gfx_int);
}
return absl::OkStatus();
}
absl::Status OverworldEditorManager::DrawUnifiedSettingsTable() {
// Create a comprehensive settings table that combines toolset and properties
if (BeginTable("##UnifiedOverworldSettings", 6,
ImGuiTableFlags_Resizable | ImGuiTableFlags_BordersOuter |
ImGuiTableFlags_BordersV | ImGuiTableFlags_SizingFixedFit)) {
// Setup columns with proper widths
TableSetupColumn(ICON_MD_BUILD " Tools", ImGuiTableColumnFlags_WidthFixed, 120);
TableSetupColumn(ICON_MD_MAP " World", ImGuiTableColumnFlags_WidthFixed, 100);
TableSetupColumn(ICON_MD_IMAGE " Graphics", ImGuiTableColumnFlags_WidthFixed, 100);
TableSetupColumn(ICON_MD_PALETTE " Palette", ImGuiTableColumnFlags_WidthFixed, 100);
TableSetupColumn(ICON_MD_SETTINGS " Properties", ImGuiTableColumnFlags_WidthStretch);
TableSetupColumn(ICON_MD_EXTENSION " v3 Features", ImGuiTableColumnFlags_WidthFixed, 120);
TableHeadersRow();
TableNextRow();
// Tools column
TableNextColumn();
RETURN_IF_ERROR(DrawToolsetInSettings());
// World column
TableNextColumn();
Text(ICON_MD_PUBLIC " Current World");
SetNextItemWidth(80.f);
// if (Combo("##world", &current_world_, kWorldList.data(), 3)) {
// // World change logic would go here
// }
// Graphics column
TableNextColumn();
Text(ICON_MD_IMAGE " Area Graphics");
// Graphics controls would go here
// Palette column
TableNextColumn();
Text(ICON_MD_PALETTE " Area Palette");
// Palette controls would go here
// Properties column
TableNextColumn();
Text(ICON_MD_SETTINGS " Map Properties");
// Map properties would go here
// v3 Features column
TableNextColumn();
uint8_t asm_version = GetCustomASMVersion();
if (asm_version >= 3 && asm_version != 0xFF) {
TextColored(ImVec4(0, 1, 0, 1), ICON_MD_NEW_RELEASES " v3 Active");
if (Button(ICON_MD_TUNE " Settings")) {
// Open v3 settings
}
} else {
TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1), ICON_MD_UPGRADE " v3 Available");
if (Button(ICON_MD_UPGRADE " Upgrade")) {
// Trigger upgrade
}
}
EndTable();
}
return absl::OkStatus();
}
absl::Status OverworldEditorManager::DrawToolsetInSettings() {
// Compact toolset layout within the settings table
BeginGroup();
// Core editing tools in a compact grid
if (Button(ICON_MD_PAN_TOOL_ALT, ImVec2(25, 25))) {
// Set PAN mode
}
HOVER_HINT("Pan (1)");
SameLine();
if (Button(ICON_MD_DRAW, ImVec2(25, 25))) {
// Set DRAW_TILE mode
}
HOVER_HINT("Draw Tile (2)");
SameLine();
if (Button(ICON_MD_DOOR_FRONT, ImVec2(25, 25))) {
// Set ENTRANCES mode
}
HOVER_HINT("Entrances (3)");
SameLine();
if (Button(ICON_MD_DOOR_BACK, ImVec2(25, 25))) {
// Set EXITS mode
}
HOVER_HINT("Exits (4)");
// Second row
if (Button(ICON_MD_GRASS, ImVec2(25, 25))) {
// Set ITEMS mode
}
HOVER_HINT("Items (5)");
SameLine();
if (Button(ICON_MD_PEST_CONTROL_RODENT, ImVec2(25, 25))) {
// Set SPRITES mode
}
HOVER_HINT("Sprites (6)");
SameLine();
if (Button(ICON_MD_ADD_LOCATION, ImVec2(25, 25))) {
// Set TRANSPORTS mode
}
HOVER_HINT("Transports (7)");
SameLine();
if (Button(ICON_MD_MUSIC_NOTE, ImVec2(25, 25))) {
// Set MUSIC mode
}
HOVER_HINT("Music (8)");
EndGroup();
return absl::OkStatus();
}
absl::Status OverworldEditorManager::HandleCanvasSelectionTransfer() {
// This could be called to manage bidirectional selection transfer
// For now, it's a placeholder for future canvas interaction management
return absl::OkStatus();
}
absl::Status OverworldEditorManager::TransferOverworldSelectionToScratch() {
// Transfer logic would go here to copy selections from overworld to scratch
// This could be integrated with the editor's context system
return absl::OkStatus();
}
absl::Status OverworldEditorManager::TransferScratchSelectionToOverworld() {
// Transfer logic would go here to copy selections from scratch to overworld
// This could be integrated with the editor's context system
return absl::OkStatus();
}
} // namespace editor
} // namespace yaze

View File

@@ -1,108 +0,0 @@
#ifndef YAZE_APP_EDITOR_OVERWORLD_OVERWORLD_EDITOR_MANAGER_H
#define YAZE_APP_EDITOR_OVERWORLD_OVERWORLD_EDITOR_MANAGER_H
#include <memory>
#include "absl/status/status.h"
#include "app/rom.h"
#include "app/zelda3/overworld/overworld.h"
#include "app/gui/canvas.h"
#include "app/gui/input.h"
namespace yaze {
namespace editor {
// Forward declarations
enum class EditingMode;
class OverworldEditor;
/**
* @class OverworldEditorManager
* @brief Manages the complex overworld editor functionality and v3 features
*
* This class separates the complex overworld editing functionality from the main
* OverworldEditor class to improve maintainability and organization, especially
* for ZSCustomOverworld v3 features.
*/
class OverworldEditorManager {
public:
OverworldEditorManager(zelda3::Overworld* overworld, Rom* rom,
OverworldEditor* editor = nullptr)
: overworld_(overworld), rom_(rom), editor_(editor) {}
// Set editor context for mode changes
void SetEditorContext(OverworldEditor* editor) { editor_ = editor; }
// v3 Feature Management
absl::Status DrawV3SettingsPanel();
absl::Status DrawCustomOverworldSettings();
absl::Status DrawAreaSpecificSettings();
absl::Status DrawTransitionSettings();
absl::Status DrawOverlaySettings();
// Map Properties Management
absl::Status DrawMapPropertiesTable();
absl::Status DrawUnifiedSettingsTable();
absl::Status DrawToolsetInSettings();
absl::Status DrawAreaSizeControls();
absl::Status DrawMosaicControls();
absl::Status DrawPaletteControls();
absl::Status DrawGfxGroupControls();
// Save/Load Operations for v3
absl::Status SaveCustomOverworldData();
absl::Status LoadCustomOverworldData();
absl::Status ApplyCustomOverworldASM();
// Canvas Interaction Management
absl::Status HandleCanvasSelectionTransfer();
absl::Status TransferOverworldSelectionToScratch();
absl::Status TransferScratchSelectionToOverworld();
// Validation and Checks
bool ValidateV3Compatibility();
bool CheckCustomASMApplied();
uint8_t GetCustomASMVersion();
// Getters/Setters for v3 settings
auto enable_area_specific_bg() const { return enable_area_specific_bg_; }
auto enable_main_palette() const { return enable_main_palette_; }
auto enable_mosaic() const { return enable_mosaic_; }
auto enable_gfx_groups() const { return enable_gfx_groups_; }
auto enable_subscreen_overlay() const { return enable_subscreen_overlay_; }
auto enable_animated_gfx() const { return enable_animated_gfx_; }
void set_enable_area_specific_bg(bool value) { enable_area_specific_bg_ = value; }
void set_enable_main_palette(bool value) { enable_main_palette_ = value; }
void set_enable_mosaic(bool value) { enable_mosaic_ = value; }
void set_enable_gfx_groups(bool value) { enable_gfx_groups_ = value; }
void set_enable_subscreen_overlay(bool value) { enable_subscreen_overlay_ = value; }
void set_enable_animated_gfx(bool value) { enable_animated_gfx_ = value; }
private:
zelda3::Overworld* overworld_;
Rom* rom_;
OverworldEditor* editor_;
// v3 Feature flags
bool enable_area_specific_bg_ = false;
bool enable_main_palette_ = false;
bool enable_mosaic_ = false;
bool enable_gfx_groups_ = false;
bool enable_subscreen_overlay_ = false;
bool enable_animated_gfx_ = false;
// Current editing state
int current_map_index_ = 0;
// Helper methods
absl::Status DrawBooleanSetting(const char* label, bool* setting, const char* help_text = nullptr);
absl::Status DrawColorPicker(const char* label, uint16_t* color);
absl::Status DrawOverlaySetting(const char* label, uint16_t* overlay);
absl::Status DrawGfxGroupSetting(const char* label, uint8_t* gfx_id, int max_value = 255);
};
} // namespace editor
} // namespace yaze
#endif // YAZE_APP_EDITOR_OVERWORLD_OVERWORLD_EDITOR_MANAGER_H

View File

@@ -1,714 +0,0 @@
#include "overworld_graphics_manager.h"
#include <algorithm>
#include "app/core/features.h"
#include "app/core/window.h"
#include "app/gfx/performance_profiler.h"
#include "util/log.h"
#include "util/macro.h"
namespace yaze {
namespace editor {
using core::Renderer;
using zelda3::kNumOverworldMaps;
using zelda3::kOverworldMapSize;
constexpr int kTile16Size = 16;
// ============================================================================
// Loading Operations
// ============================================================================
absl::Status OverworldGraphicsManager::LoadGraphics() {
gfx::ScopedTimer timer("LoadGraphics");
LOG_INFO("OverworldGraphicsManager", "Loading overworld.");
// Load the Link to the Past overworld.
{
gfx::ScopedTimer load_timer("Overworld::Load");
RETURN_IF_ERROR(overworld_->Load(rom_));
}
*palette_ = overworld_->current_area_palette();
LOG_INFO("OverworldGraphicsManager", "Loading overworld graphics (optimized).");
// Phase 1: Create bitmaps without textures for faster loading
// This avoids blocking the main thread with GPU texture creation
{
gfx::ScopedTimer gfx_timer("CreateBitmapWithoutTexture_Graphics");
Renderer::Get().CreateBitmapWithoutTexture(0x80, kOverworldMapSize, 0x40,
overworld_->current_graphics(),
*current_gfx_bmp_, *palette_);
}
LOG_INFO("OverworldGraphicsManager", "Loading overworld tileset (deferred textures).");
{
gfx::ScopedTimer tileset_timer("CreateBitmapWithoutTexture_Tileset");
Renderer::Get().CreateBitmapWithoutTexture(
0x80, 0x2000, 0x08, overworld_->tile16_blockset_data(),
*tile16_blockset_bmp_, *palette_);
}
map_blockset_loaded_ = true;
// Copy the tile16 data into individual tiles.
auto tile16_blockset_data = overworld_->tile16_blockset_data();
LOG_INFO("OverworldGraphicsManager", "Loading overworld tile16 graphics.");
{
gfx::ScopedTimer tilemap_timer("CreateTilemap");
*tile16_blockset_ =
gfx::CreateTilemap(tile16_blockset_data, 0x80, 0x2000, kTile16Size,
zelda3::kNumTile16Individual, *palette_);
}
// Phase 2: Create bitmaps only for essential maps initially
// Non-essential maps will be created on-demand when accessed
constexpr int kEssentialMapsPerWorld = 8;
constexpr int kLightWorldEssential = kEssentialMapsPerWorld;
constexpr int kDarkWorldEssential =
zelda3::kDarkWorldMapIdStart + kEssentialMapsPerWorld;
constexpr int kSpecialWorldEssential =
zelda3::kSpecialWorldMapIdStart + kEssentialMapsPerWorld;
LOG_INFO("OverworldGraphicsManager",
"Creating bitmaps for essential maps only (first %d maps per world)",
kEssentialMapsPerWorld);
std::vector<gfx::Bitmap*> maps_to_texture;
maps_to_texture.reserve(kEssentialMapsPerWorld *
3); // 8 maps per world * 3 worlds
{
gfx::ScopedTimer maps_timer("CreateEssentialOverworldMaps");
for (int i = 0; i < zelda3::kNumOverworldMaps; ++i) {
bool is_essential = false;
// Check if this is an essential map
if (i < kLightWorldEssential) {
is_essential = true;
} else if (i >= zelda3::kDarkWorldMapIdStart && i < kDarkWorldEssential) {
is_essential = true;
} else if (i >= zelda3::kSpecialWorldMapIdStart &&
i < kSpecialWorldEssential) {
is_essential = true;
}
if (is_essential) {
overworld_->set_current_map(i);
auto palette = overworld_->current_area_palette();
try {
// Create bitmap data and surface but defer texture creation
(*maps_bmp_)[i].Create(kOverworldMapSize, kOverworldMapSize, 0x80,
overworld_->current_map_bitmap_data());
(*maps_bmp_)[i].SetPalette(palette);
maps_to_texture.push_back(&(*maps_bmp_)[i]);
} catch (const std::bad_alloc& e) {
LOG_ERROR("OverworldGraphicsManager", "Error allocating map %d: %s",
i, e.what());
continue;
}
}
// Non-essential maps will be created on-demand when accessed
}
}
// Phase 3: Create textures only for currently visible maps
// Only create textures for the first few maps initially
const int initial_texture_count =
std::min(4, static_cast<int>(maps_to_texture.size()));
{
gfx::ScopedTimer initial_textures_timer("CreateInitialTextures");
for (int i = 0; i < initial_texture_count; ++i) {
Renderer::Get().RenderBitmap(maps_to_texture[i]);
}
}
// Store remaining maps for lazy texture creation
deferred_map_textures_.assign(maps_to_texture.begin() + initial_texture_count,
maps_to_texture.end());
if (core::FeatureFlags::get().overworld.kDrawOverworldSprites) {
{
gfx::ScopedTimer sprites_timer("LoadSpriteGraphics");
RETURN_IF_ERROR(LoadSpriteGraphics());
}
}
all_gfx_loaded_ = true;
return absl::OkStatus();
}
absl::Status OverworldGraphicsManager::LoadSpriteGraphics() {
// Render the sprites for each Overworld map
const int depth = 0x10;
for (int i = 0; i < 3; i++)
for (auto const& sprite : *overworld_->mutable_sprites(i)) {
int width = sprite.width();
int height = sprite.height();
if (width == 0 || height == 0) {
continue;
}
if (sprite_previews_->size() < sprite.id()) {
sprite_previews_->resize(sprite.id() + 1);
}
(*sprite_previews_)[sprite.id()].Create(width, height, depth,
*sprite.preview_graphics());
(*sprite_previews_)[sprite.id()].SetPalette(*palette_);
Renderer::Get().RenderBitmap(&(*sprite_previews_)[sprite.id()]);
}
return absl::OkStatus();
}
// ============================================================================
// Texture Management
// ============================================================================
void OverworldGraphicsManager::ProcessDeferredTextures() {
std::lock_guard<std::mutex> lock(deferred_textures_mutex_);
// Always process deferred textures progressively, even if the list is "empty"
// This allows for continuous background loading
// PHASE 1: Priority loading for current world
const int high_priority_per_frame = 4; // Current world maps
const int low_priority_per_frame = 2; // Other world maps
const int refresh_per_frame = 2; // Modified map refreshes
int processed = 0;
// Process high-priority deferred textures (current world)
if (!deferred_map_textures_.empty()) {
auto it = deferred_map_textures_.begin();
while (it != deferred_map_textures_.end() && processed < high_priority_per_frame) {
if (*it && !(*it)->texture()) {
// Find map index for priority check
int map_index = -1;
for (int i = 0; i < zelda3::kNumOverworldMaps; ++i) {
if (&(*maps_bmp_)[i] == *it) {
map_index = i;
break;
}
}
bool is_current_world = false;
if (map_index >= 0) {
int map_world = map_index / 0x40; // 64 maps per world
is_current_world = (map_world == current_world_);
}
if (is_current_world) {
Renderer::Get().RenderBitmap(*it);
processed++;
it = deferred_map_textures_.erase(it);
} else {
++it;
}
} else {
++it;
}
}
}
// PHASE 2: Background loading for other worlds (lower priority)
if (!deferred_map_textures_.empty() && processed < high_priority_per_frame) {
auto it = deferred_map_textures_.begin();
int low_priority_processed = 0;
while (it != deferred_map_textures_.end() && low_priority_processed < low_priority_per_frame) {
if (*it && !(*it)->texture()) {
Renderer::Get().RenderBitmap(*it);
low_priority_processed++;
processed++;
it = deferred_map_textures_.erase(it);
} else {
++it;
}
}
}
// PHASE 3: Process modified maps that need refresh (highest priority)
int refresh_processed = 0;
for (int i = 0; i < zelda3::kNumOverworldMaps && refresh_processed < refresh_per_frame; ++i) {
if ((*maps_bmp_)[i].modified() && (*maps_bmp_)[i].is_active()) {
// Check if this map is in current world (high priority) or visible
bool is_current_world = (i / 0x40 == current_world_);
bool is_current_map = (i == current_map_);
if (is_current_map || is_current_world) {
RefreshOverworldMapOnDemand(i);
refresh_processed++;
}
}
}
// PHASE 4: Background refresh for modified maps in other worlds (very low priority)
if (refresh_processed == 0) {
for (int i = 0; i < zelda3::kNumOverworldMaps; ++i) {
if ((*maps_bmp_)[i].modified() && (*maps_bmp_)[i].is_active()) {
bool is_current_world = (i / 0x40 == current_world_);
if (!is_current_world) {
// Just mark for later, don't refresh now to avoid lag
// These will be refreshed when the world is switched
break;
}
}
}
}
}
void OverworldGraphicsManager::EnsureMapTexture(int map_index) {
if (map_index < 0 || map_index >= zelda3::kNumOverworldMaps) {
return;
}
// Ensure the map is built first (on-demand loading)
auto status = overworld_->EnsureMapBuilt(map_index);
if (!status.ok()) {
LOG_ERROR("OverworldGraphicsManager", "Failed to build map %d: %s", map_index,
status.message().data());
return;
}
auto& bitmap = (*maps_bmp_)[map_index];
// If bitmap doesn't exist yet (non-essential map), create it now
if (!bitmap.is_active()) {
overworld_->set_current_map(map_index);
auto palette = overworld_->current_area_palette();
try {
bitmap.Create(kOverworldMapSize, kOverworldMapSize, 0x80,
overworld_->current_map_bitmap_data());
bitmap.SetPalette(palette);
} catch (const std::bad_alloc& e) {
LOG_ERROR("OverworldGraphicsManager", "Error allocating bitmap for map %d: %s",
map_index, e.what());
return;
}
}
if (!bitmap.texture() && bitmap.is_active()) {
Renderer::Get().RenderBitmap(&bitmap);
// Remove from deferred list if it was there
std::lock_guard<std::mutex> lock(deferred_textures_mutex_);
auto it = std::find(deferred_map_textures_.begin(),
deferred_map_textures_.end(), &bitmap);
if (it != deferred_map_textures_.end()) {
deferred_map_textures_.erase(it);
}
}
}
// ============================================================================
// Refresh Operations
// ============================================================================
void OverworldGraphicsManager::RefreshOverworldMap() {
// Use the new on-demand refresh system
RefreshOverworldMapOnDemand(current_map_);
}
void OverworldGraphicsManager::RefreshOverworldMapOnDemand(int map_index) {
if (map_index < 0 || map_index >= zelda3::kNumOverworldMaps) {
return;
}
// Check if the map is actually visible or being edited
bool is_current_map = (map_index == current_map_);
bool is_current_world = (map_index / 0x40 == current_world_);
// For non-current maps in non-current worlds, defer the refresh
if (!is_current_map && !is_current_world) {
// Mark for deferred refresh - will be processed when the map becomes visible
(*maps_bmp_)[map_index].set_modified(true);
return;
}
// For visible maps, do immediate refresh
RefreshChildMapOnDemand(map_index);
}
void OverworldGraphicsManager::RefreshChildMap(int map_index) {
overworld_->mutable_overworld_map(map_index)->LoadAreaGraphics();
auto status = overworld_->mutable_overworld_map(map_index)->BuildTileset();
PRINT_IF_ERROR(status);
status = overworld_->mutable_overworld_map(map_index)->BuildTiles16Gfx(
*overworld_->mutable_tiles16(), overworld_->tiles16().size());
PRINT_IF_ERROR(status);
status = overworld_->mutable_overworld_map(map_index)->BuildBitmap(
overworld_->GetMapTiles(current_world_));
(*maps_bmp_)[map_index].set_data(
overworld_->mutable_overworld_map(map_index)->bitmap_data());
(*maps_bmp_)[map_index].set_modified(true);
PRINT_IF_ERROR(status);
}
void OverworldGraphicsManager::RefreshChildMapOnDemand(int map_index) {
auto* map = overworld_->mutable_overworld_map(map_index);
// Check what actually needs to be refreshed
bool needs_graphics_rebuild = (*maps_bmp_)[map_index].modified();
if (needs_graphics_rebuild) {
// Only rebuild what's actually changed
map->LoadAreaGraphics();
// Rebuild tileset only if graphics changed
auto status = map->BuildTileset();
if (!status.ok()) {
LOG_ERROR("OverworldGraphicsManager", "Failed to build tileset for map %d: %s",
map_index, status.message().data());
return;
}
// Rebuild tiles16 graphics
status = map->BuildTiles16Gfx(*overworld_->mutable_tiles16(),
overworld_->tiles16().size());
if (!status.ok()) {
LOG_ERROR("OverworldGraphicsManager", "Failed to build tiles16 graphics for map %d: %s",
map_index, status.message().data());
return;
}
// Rebuild bitmap
status = map->BuildBitmap(overworld_->GetMapTiles(current_world_));
if (!status.ok()) {
LOG_ERROR("OverworldGraphicsManager", "Failed to build bitmap for map %d: %s",
map_index, status.message().data());
return;
}
// Update bitmap data
(*maps_bmp_)[map_index].set_data(map->bitmap_data());
(*maps_bmp_)[map_index].set_modified(false);
// Validate surface synchronization to help debug crashes
if (!(*maps_bmp_)[map_index].ValidateDataSurfaceSync()) {
LOG_WARN("OverworldGraphicsManager", "Warning: Surface synchronization issue detected for map %d",
map_index);
}
// Update texture on main thread
if ((*maps_bmp_)[map_index].texture()) {
Renderer::Get().UpdateBitmap(&(*maps_bmp_)[map_index]);
} else {
// Create texture if it doesn't exist
EnsureMapTexture(map_index);
}
}
// Handle multi-area maps (large, wide, tall) with safe coordination
// Check if ZSCustomOverworld v3 is present
uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied];
bool use_v3_area_sizes = (asm_version >= 3 && asm_version != 0xFF);
if (use_v3_area_sizes) {
// Use v3 multi-area coordination
RefreshMultiAreaMapsSafely(map_index, map);
} else {
// Legacy logic: only handle large maps for vanilla/v2
if (map->is_large_map()) {
RefreshMultiAreaMapsSafely(map_index, map);
}
}
}
void OverworldGraphicsManager::RefreshMultiAreaMapsSafely(
int map_index, zelda3::OverworldMap* map) {
using zelda3::AreaSizeEnum;
// Skip if this is already a processed sibling to avoid double-processing
static std::set<int> currently_processing;
if (currently_processing.count(map_index)) {
return;
}
auto area_size = map->area_size();
if (area_size == AreaSizeEnum::SmallArea) {
return; // No siblings to coordinate
}
LOG_DEBUG("OverworldGraphicsManager",
"RefreshMultiAreaMapsSafely: Processing %s area map %d (parent: %d)",
(area_size == AreaSizeEnum::LargeArea) ? "large"
: (area_size == AreaSizeEnum::WideArea) ? "wide"
: "tall",
map_index, map->parent());
// Determine all maps that are part of this multi-area structure
std::vector<int> sibling_maps;
int parent_id = map->parent();
// Use the same logic as ZScream for area coordination
switch (area_size) {
case AreaSizeEnum::LargeArea: {
// Large Area: 2x2 grid (4 maps total)
sibling_maps = {parent_id, parent_id + 1, parent_id + 8, parent_id + 9};
LOG_DEBUG("OverworldGraphicsManager",
"RefreshMultiAreaMapsSafely: Large area siblings: %d, %d, %d, %d",
parent_id, parent_id + 1, parent_id + 8, parent_id + 9);
break;
}
case AreaSizeEnum::WideArea: {
// Wide Area: 2x1 grid (2 maps total, horizontally adjacent)
sibling_maps = {parent_id, parent_id + 1};
LOG_DEBUG("OverworldGraphicsManager",
"RefreshMultiAreaMapsSafely: Wide area siblings: %d, %d",
parent_id, parent_id + 1);
break;
}
case AreaSizeEnum::TallArea: {
// Tall Area: 1x2 grid (2 maps total, vertically adjacent)
sibling_maps = {parent_id, parent_id + 8};
LOG_DEBUG("OverworldGraphicsManager",
"RefreshMultiAreaMapsSafely: Tall area siblings: %d, %d",
parent_id, parent_id + 8);
break;
}
default:
LOG_WARN("OverworldGraphicsManager",
"RefreshMultiAreaMapsSafely: Unknown area size %d for map %d",
static_cast<int>(area_size), map_index);
return;
}
// Mark all siblings as being processed to prevent recursion
for (int sibling : sibling_maps) {
currently_processing.insert(sibling);
}
// Only refresh siblings that are visible/current and need updating
for (int sibling : sibling_maps) {
if (sibling == map_index) {
continue; // Skip self (already processed above)
}
// Bounds check
if (sibling < 0 || sibling >= zelda3::kNumOverworldMaps) {
continue;
}
// Only refresh if it's visible or current
bool is_current_map = (sibling == current_map_);
bool is_current_world = (sibling / 0x40 == current_world_);
bool needs_refresh = (*maps_bmp_)[sibling].modified();
if ((is_current_map || is_current_world) && needs_refresh) {
LOG_DEBUG("OverworldGraphicsManager",
"RefreshMultiAreaMapsSafely: Refreshing %s area sibling map %d "
"(parent: %d)",
(area_size == AreaSizeEnum::LargeArea) ? "large"
: (area_size == AreaSizeEnum::WideArea) ? "wide"
: "tall",
sibling, parent_id);
// Direct refresh without calling RefreshChildMapOnDemand to avoid recursion
auto* sibling_map = overworld_->mutable_overworld_map(sibling);
if (sibling_map && (*maps_bmp_)[sibling].modified()) {
sibling_map->LoadAreaGraphics();
if (auto status = sibling_map->BuildTileset(); !status.ok()) {
LOG_ERROR("OverworldGraphicsManager",
"RefreshMultiAreaMapsSafely: Failed to refresh sibling map %d: %s",
sibling, status.message().data());
continue;
}
if (auto status = sibling_map->BuildTiles16Gfx(*overworld_->mutable_tiles16(),
overworld_->tiles16().size()); !status.ok()) {
LOG_ERROR("OverworldGraphicsManager",
"RefreshMultiAreaMapsSafely: Failed to build tiles16 graphics for sibling map %d: %s",
sibling, status.message().data());
continue;
}
if (auto status = sibling_map->LoadPalette(); !status.ok()) {
LOG_ERROR("OverworldGraphicsManager",
"RefreshMultiAreaMapsSafely: Failed to load palette for sibling map %d: %s",
sibling, status.message().data());
continue;
}
if (auto status = sibling_map->BuildBitmap(overworld_->GetMapTiles(current_world_)); status.ok()) {
(*maps_bmp_)[sibling].set_data(sibling_map->bitmap_data());
(*maps_bmp_)[sibling].SetPalette(overworld_->current_area_palette());
(*maps_bmp_)[sibling].set_modified(false);
// Update texture if it exists
if ((*maps_bmp_)[sibling].texture()) {
Renderer::Get().UpdateBitmap(&(*maps_bmp_)[sibling]);
} else {
EnsureMapTexture(sibling);
}
} else {
LOG_ERROR("OverworldGraphicsManager",
"RefreshMultiAreaMapsSafely: Failed to build bitmap for sibling map %d: %s",
sibling, status.message().data());
}
}
} else if (!is_current_map && !is_current_world) {
// Mark non-visible siblings for deferred refresh
(*maps_bmp_)[sibling].set_modified(true);
}
}
// Clear processing set after completion
for (int sibling : sibling_maps) {
currently_processing.erase(sibling);
}
}
absl::Status OverworldGraphicsManager::RefreshMapPalette() {
RETURN_IF_ERROR(
overworld_->mutable_overworld_map(current_map_)->LoadPalette());
const auto current_map_palette = overworld_->current_area_palette();
// Check if ZSCustomOverworld v3 is present
uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied];
bool use_v3_area_sizes = (asm_version >= 3 && asm_version != 0xFF);
if (use_v3_area_sizes) {
// Use v3 area size system
using zelda3::AreaSizeEnum;
auto area_size = overworld_->overworld_map(current_map_)->area_size();
if (area_size != AreaSizeEnum::SmallArea) {
// Get all sibling maps that need palette updates
std::vector<int> sibling_maps;
int parent_id = overworld_->overworld_map(current_map_)->parent();
switch (area_size) {
case AreaSizeEnum::LargeArea:
// 2x2 grid: parent, parent+1, parent+8, parent+9
sibling_maps = {parent_id, parent_id + 1, parent_id + 8,
parent_id + 9};
break;
case AreaSizeEnum::WideArea:
// 2x1 grid: parent, parent+1
sibling_maps = {parent_id, parent_id + 1};
break;
case AreaSizeEnum::TallArea:
// 1x2 grid: parent, parent+8
sibling_maps = {parent_id, parent_id + 8};
break;
default:
break;
}
// Update palette for all siblings
for (int sibling_index : sibling_maps) {
if (sibling_index < 0 || sibling_index >= zelda3::kNumOverworldMaps) {
continue;
}
RETURN_IF_ERROR(
overworld_->mutable_overworld_map(sibling_index)->LoadPalette());
(*maps_bmp_)[sibling_index].SetPalette(current_map_palette);
}
} else {
// Small area - only update current map
(*maps_bmp_)[current_map_].SetPalette(current_map_palette);
}
} else {
// Legacy logic for vanilla and v2 ROMs
if (overworld_->overworld_map(current_map_)->is_large_map()) {
// We need to update the map and its siblings if it's a large map
for (int i = 1; i < 4; i++) {
int sibling_index =
overworld_->overworld_map(current_map_)->parent() + i;
if (i >= 2)
sibling_index += 6;
RETURN_IF_ERROR(
overworld_->mutable_overworld_map(sibling_index)->LoadPalette());
(*maps_bmp_)[sibling_index].SetPalette(current_map_palette);
}
}
(*maps_bmp_)[current_map_].SetPalette(current_map_palette);
}
return absl::OkStatus();
}
absl::Status OverworldGraphicsManager::RefreshTile16Blockset() {
LOG_DEBUG("OverworldGraphicsManager", "RefreshTile16Blockset called");
if (current_blockset_ ==
overworld_->overworld_map(current_map_)->area_graphics()) {
return absl::OkStatus();
}
current_blockset_ = overworld_->overworld_map(current_map_)->area_graphics();
overworld_->set_current_map(current_map_);
*palette_ = overworld_->current_area_palette();
const auto tile16_data = overworld_->tile16_blockset_data();
gfx::UpdateTilemap(*tile16_blockset_, tile16_data);
tile16_blockset_->atlas.SetPalette(*palette_);
return absl::OkStatus();
}
void OverworldGraphicsManager::ForceRefreshGraphics(int map_index) {
if (map_index < 0 || map_index >= zelda3::kNumOverworldMaps) {
return;
}
LOG_INFO("OverworldGraphicsManager",
"ForceRefreshGraphics: Forcing graphics reload for map %d", map_index);
// Mark bitmap as modified to force refresh
(*maps_bmp_)[map_index].set_modified(true);
// Clear the blockset cache to force tile16 reload
current_blockset_ = 0xFF;
// If this is the current map, also ensure sibling maps are refreshed for multi-area maps
uint8_t asm_version = (*rom_)[zelda3::OverworldCustomASMHasBeenApplied];
bool use_v3_area_sizes = (asm_version >= 3 && asm_version != 0xFF);
auto* map = overworld_->mutable_overworld_map(map_index);
if (use_v3_area_sizes) {
using zelda3::AreaSizeEnum;
auto area_size = map->area_size();
if (area_size != AreaSizeEnum::SmallArea) {
std::vector<int> sibling_maps;
int parent_id = map->parent();
switch (area_size) {
case AreaSizeEnum::LargeArea:
sibling_maps = {parent_id, parent_id + 1, parent_id + 8, parent_id + 9};
break;
case AreaSizeEnum::WideArea:
sibling_maps = {parent_id, parent_id + 1};
break;
case AreaSizeEnum::TallArea:
sibling_maps = {parent_id, parent_id + 8};
break;
default:
break;
}
// Mark all sibling maps as needing refresh
for (int sibling : sibling_maps) {
if (sibling >= 0 && sibling < zelda3::kNumOverworldMaps) {
(*maps_bmp_)[sibling].set_modified(true);
}
}
}
} else if (map->is_large_map()) {
// Legacy large map handling
int parent_id = map->parent();
for (int i = 0; i < 4; ++i) {
int sibling = parent_id + (i < 2 ? i : i + 6);
if (sibling >= 0 && sibling < zelda3::kNumOverworldMaps) {
(*maps_bmp_)[sibling].set_modified(true);
}
}
}
}
} // namespace editor
} // namespace yaze

View File

@@ -1,204 +0,0 @@
#ifndef YAZE_APP_EDITOR_OVERWORLD_OVERWORLD_GRAPHICS_MANAGER_H
#define YAZE_APP_EDITOR_OVERWORLD_OVERWORLD_GRAPHICS_MANAGER_H
#include <array>
#include <mutex>
#include <vector>
#include "absl/status/status.h"
#include "app/gfx/bitmap.h"
#include "app/gfx/snes_palette.h"
#include "app/gfx/tilemap.h"
#include "app/rom.h"
#include "app/zelda3/overworld/overworld.h"
#include "app/zelda3/overworld/overworld_map.h"
namespace yaze {
namespace editor {
using Tilemap = gfx::Tilemap;
/**
* @class OverworldGraphicsManager
* @brief Manages all graphics-related operations for the Overworld Editor
*
* This class handles:
* - Graphics loading and initialization
* - Sprite graphics loading
* - Deferred texture processing for smooth loading
* - Map texture management
* - Map refreshing (full and on-demand)
* - Palette refreshing
* - Tile16 blockset refreshing
* - Multi-area map coordination
*
* Separating graphics management from the main OverworldEditor improves:
* - Code organization and maintainability
* - Performance optimization opportunities
* - Testing and debugging
* - Clear separation of concerns
*/
class OverworldGraphicsManager {
public:
OverworldGraphicsManager(
zelda3::Overworld* overworld, Rom* rom,
std::array<gfx::Bitmap, zelda3::kNumOverworldMaps>* maps_bmp,
gfx::Bitmap* tile16_blockset_bmp, gfx::Bitmap* current_gfx_bmp,
std::vector<gfx::Bitmap>* sprite_previews, gfx::SnesPalette* palette,
Tilemap* tile16_blockset)
: overworld_(overworld),
rom_(rom),
maps_bmp_(maps_bmp),
tile16_blockset_bmp_(tile16_blockset_bmp),
current_gfx_bmp_(current_gfx_bmp),
sprite_previews_(sprite_previews),
palette_(palette),
tile16_blockset_(tile16_blockset) {}
// ============================================================================
// Loading Operations
// ============================================================================
/**
* @brief Load all overworld graphics (maps, tilesets, sprites)
*
* This uses a multi-phase loading strategy:
* - Phase 1: Create bitmaps without textures
* - Phase 2: Create bitmaps for essential maps only
* - Phase 3: Create textures for visible maps
* - Deferred loading for remaining maps
*/
absl::Status LoadGraphics();
/**
* @brief Load sprite graphics for all overworld maps
*/
absl::Status LoadSpriteGraphics();
// ============================================================================
// Texture Management
// ============================================================================
/**
* @brief Process deferred texture creation (called per frame)
*
* Creates textures gradually to avoid frame drops.
* Prioritizes textures for the current world and visible maps.
*/
void ProcessDeferredTextures();
/**
* @brief Ensure a specific map has a texture created
*
* @param map_index Index of the map to ensure texture for
*/
void EnsureMapTexture(int map_index);
// ============================================================================
// Refresh Operations
// ============================================================================
/**
* @brief Refresh the current overworld map
*/
void RefreshOverworldMap();
/**
* @brief Refresh a specific map on-demand (only if visible)
*
* @param map_index Index of the map to refresh
*/
void RefreshOverworldMapOnDemand(int map_index);
/**
* @brief Refresh a child map (legacy method)
*
* @param map_index Index of the map to refresh
*/
void RefreshChildMap(int map_index);
/**
* @brief Refresh a child map with selective updates
*
* @param map_index Index of the map to refresh
*/
void RefreshChildMapOnDemand(int map_index);
/**
* @brief Safely refresh multi-area maps (large, wide, tall)
*
* Handles coordination of multi-area maps without recursion.
*
* @param map_index Index of the map to refresh
* @param map Pointer to the OverworldMap object
*/
void RefreshMultiAreaMapsSafely(int map_index, zelda3::OverworldMap* map);
/**
* @brief Refresh the palette for the current map
*
* Also handles palette updates for multi-area maps.
*/
absl::Status RefreshMapPalette();
/**
* @brief Refresh the tile16 blockset
*
* This should be called whenever area graphics change.
*/
absl::Status RefreshTile16Blockset();
/**
* @brief Force a graphics refresh for a specific map
*
* Marks the map's bitmap as modified and clears the blockset cache
* to force a full reload on next refresh. Use this when graphics
* properties change (area_graphics, animated_gfx, custom tilesets).
*
* @param map_index Index of the map to force refresh
*/
void ForceRefreshGraphics(int map_index);
// ============================================================================
// State Management
// ============================================================================
void set_current_map(int map_index) { current_map_ = map_index; }
void set_current_world(int world_index) { current_world_ = world_index; }
void set_current_blockset(uint8_t blockset) { current_blockset_ = blockset; }
int current_map() const { return current_map_; }
int current_world() const { return current_world_; }
uint8_t current_blockset() const { return current_blockset_; }
bool all_gfx_loaded() const { return all_gfx_loaded_; }
bool map_blockset_loaded() const { return map_blockset_loaded_; }
private:
// Core dependencies
zelda3::Overworld* overworld_;
Rom* rom_;
std::array<gfx::Bitmap, zelda3::kNumOverworldMaps>* maps_bmp_;
gfx::Bitmap* tile16_blockset_bmp_;
gfx::Bitmap* current_gfx_bmp_;
std::vector<gfx::Bitmap>* sprite_previews_;
gfx::SnesPalette* palette_;
Tilemap* tile16_blockset_;
// State tracking
int current_map_ = 0;
int current_world_ = 0;
uint8_t current_blockset_ = 0;
bool all_gfx_loaded_ = false;
bool map_blockset_loaded_ = false;
// Deferred texture loading
std::vector<gfx::Bitmap*> deferred_map_textures_;
std::mutex deferred_textures_mutex_;
};
} // namespace editor
} // namespace yaze
#endif // YAZE_APP_EDITOR_OVERWORLD_OVERWORLD_GRAPHICS_MANAGER_H