yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
dungeon_room_loader.cc
Go to the documentation of this file.
2
3#include <algorithm>
4#include <future>
5#include <map>
6#include <mutex>
7#include <thread>
8
9#ifdef __EMSCRIPTEN__
11#endif
12
15#include "util/log.h"
16#include "zelda3/dungeon/room.h"
17
18namespace yaze::editor {
19
20absl::Status DungeonRoomLoader::LoadRoom(int room_id, zelda3::Room& room) {
21 if (!rom_ || !rom_->is_loaded()) {
22 return absl::FailedPreconditionError("ROM not loaded");
23 }
24 if (room_id < 0 || room_id >= 0x128) {
25 return absl::InvalidArgumentError("Invalid room ID");
26 }
27
28 room = zelda3::LoadRoomFromRom(rom_, room_id);
29 room.SetGameData(game_data_); // Ensure room has access to GameData
30 room.LoadObjects();
31
32 return absl::OkStatus();
33}
34
36 std::array<zelda3::Room, 0x128>& rooms) {
37 if (!rom_ || !rom_->is_loaded()) {
38 return absl::FailedPreconditionError("ROM not loaded");
39 }
40
41 constexpr int kTotalRooms = 0x100 + 40; // 296 rooms
42
43 // Data structures for collecting results
44 std::vector<std::pair<int, zelda3::RoomSize>> room_size_results;
45 std::vector<std::pair<int, ImVec4>> room_palette_results;
46
47#ifdef __EMSCRIPTEN__
48 // WASM: Sequential loading to avoid Web Worker explosion
49 // std::async creates pthreads which become Web Workers in browsers,
50 // causing excessive worker spawning and main thread blocking.
51 LOG_DEBUG("Dungeon", "Loading %d dungeon rooms sequentially (WASM build)",
52 kTotalRooms);
53
54 if (!game_data_) {
55 return absl::FailedPreconditionError("GameData not available");
56 }
57 auto dungeon_man_pal_group = game_data_->palette_groups.dungeon_main;
58
59 // Create loading indicator for progress feedback
60 auto loading_handle =
61 app::platform::WasmLoadingManager::BeginLoading("Loading Dungeon Rooms");
62
63 for (int i = 0; i < kTotalRooms; ++i) {
64 // Update progress every 10 rooms to reduce overhead
65 if (i % 10 == 0) {
66 float progress = static_cast<float>(i) / static_cast<float>(kTotalRooms);
67 app::platform::WasmLoadingManager::UpdateProgress(loading_handle,
68 progress);
69
70 // Check for cancellation
71 if (app::platform::WasmLoadingManager::IsCancelled(loading_handle)) {
72 app::platform::WasmLoadingManager::EndLoading(loading_handle);
73 return absl::CancelledError("Dungeon room loading cancelled by user");
74 }
75 }
76
77 // Lazy load: Only load header/metadata, not objects/graphics
79 rooms[i].SetGameData(game_data_); // Ensure room has access to GameData
80 auto room_size = zelda3::CalculateRoomSize(rom_, i);
81 // rooms[i].LoadObjects(); // DEFERRED: Load on demand
82
83 auto dungeon_palette_ptr = game_data_->paletteset_ids[rooms[i].palette][0];
84 auto palette_id = rom_->ReadWord(0xDEC4B + dungeon_palette_ptr);
85 if (palette_id.status() == absl::OkStatus()) {
86 int p_id = palette_id.value() / 180;
87 auto color = dungeon_man_pal_group[p_id][3];
88 room_size_results.emplace_back(i, room_size);
89 room_palette_results.emplace_back(rooms[i].palette, color.rgb());
90 }
91 }
92
93 app::platform::WasmLoadingManager::EndLoading(loading_handle);
94#else
95 // Native: Parallel loading for performance
96 constexpr int kMaxConcurrency =
97 8; // Reasonable thread limit for room loading
98
99 // Determine optimal number of threads
100 const int max_concurrency = std::min(
101 kMaxConcurrency, static_cast<int>(std::thread::hardware_concurrency()));
102 const int rooms_per_thread =
103 (kTotalRooms + max_concurrency - 1) / max_concurrency;
104
105 LOG_DEBUG("Dungeon",
106 "Loading %d dungeon rooms using %d threads (%d rooms per thread)",
107 kTotalRooms, max_concurrency, rooms_per_thread);
108
109 // Thread-safe data structures for collecting results
110 std::mutex results_mutex;
111
112 // Process rooms in parallel batches
113 std::vector<std::future<absl::Status>> futures;
114
115 for (int thread_id = 0; thread_id < max_concurrency; ++thread_id) {
116 auto task = [this, &rooms, thread_id, rooms_per_thread, &results_mutex,
117 &room_size_results, &room_palette_results,
118 kTotalRooms]() -> absl::Status {
119 const int start_room = thread_id * rooms_per_thread;
120 const int end_room = std::min(start_room + rooms_per_thread, kTotalRooms);
121
122 if (!game_data_) {
123 return absl::FailedPreconditionError("GameData not available");
124 }
125 auto dungeon_man_pal_group = game_data_->palette_groups.dungeon_main;
126
127 for (int i = start_room; i < end_room; ++i) {
128 // Lazy load: Only load header/metadata
129 rooms[i] = zelda3::LoadRoomHeaderFromRom(rom_, i);
130 rooms[i].SetGameData(game_data_); // Ensure room has access to GameData
131
132 // Calculate room size
133 auto room_size = zelda3::CalculateRoomSize(rom_, i);
134
135 // Load room objects - DEFERRED
136 // rooms[i].LoadObjects();
137
138 // Process palette
139 auto dungeon_palette_ptr = game_data_->paletteset_ids[rooms[i].palette][0];
140 auto palette_id = rom_->ReadWord(0xDEC4B + dungeon_palette_ptr);
141 if (palette_id.status() == absl::OkStatus()) {
142 int p_id = palette_id.value() / 180;
143 auto color = dungeon_man_pal_group[p_id][3];
144
145 // Thread-safe collection of results
146 {
147 std::lock_guard<std::mutex> lock(results_mutex);
148 room_size_results.emplace_back(i, room_size);
149 room_palette_results.emplace_back(rooms[i].palette, color.rgb());
150 }
151 }
152 }
153
154 return absl::OkStatus();
155 };
156
157 futures.emplace_back(std::async(std::launch::async, task));
158 }
159
160 // Wait for all threads to complete
161 for (auto& future : futures) {
162 RETURN_IF_ERROR(future.get());
163 }
164#endif
165
166 // Process collected results on main thread
167 {
168 gfx::ScopedTimer postprocess_timer("DungeonRoomLoader::PostProcessResults");
169
170 // Sort results by room ID for consistent ordering
171 std::sort(room_size_results.begin(), room_size_results.end(),
172 [](const auto& a, const auto& b) { return a.first < b.first; });
173 std::sort(room_palette_results.begin(), room_palette_results.end(),
174 [](const auto& a, const auto& b) { return a.first < b.first; });
175
176 // Process room size results
177 for (const auto& [room_id, room_size] : room_size_results) {
178 room_size_pointers_.push_back(room_size.room_size_pointer);
179 room_sizes_.push_back(room_size.room_size);
180 if (room_size.room_size_pointer != 0x0A8000) {
181 room_size_addresses_[room_id] = room_size.room_size_pointer;
182 }
183 }
184
185 // Process palette results
186 for (const auto& [palette_id, color] : room_palette_results) {
187 room_palette_[palette_id] = color;
188 }
189 }
190
192 return absl::OkStatus();
193}
194
196 std::array<zelda3::RoomEntrance, 0x8C>& entrances) {
197 if (!rom_ || !rom_->is_loaded()) {
198 return absl::FailedPreconditionError("ROM not loaded");
199 }
200
201 // Load entrances
202 for (int i = 0; i < 0x07; ++i) {
203 entrances[i] = zelda3::RoomEntrance(rom_, i, true);
204 }
205
206 for (int i = 0; i < 0x85; ++i) {
207 entrances[i + 0x07] = zelda3::RoomEntrance(rom_, i, false);
208 }
209
210 return absl::OkStatus();
211}
212
214 std::map<int, std::vector<int>> rooms_by_bank;
215 for (const auto& room : room_size_addresses_) {
216 int bank = room.second >> 16;
217 rooms_by_bank[bank].push_back(room.second);
218 }
219
220 // Process and calculate room sizes within each bank
221 for (auto& bank_rooms : rooms_by_bank) {
222 std::ranges::sort(bank_rooms.second);
223
224 for (size_t i = 0; i < bank_rooms.second.size(); ++i) {
225 int room_ptr = bank_rooms.second[i];
226
227 // Identify the room ID for the current room pointer
228 int room_id =
229 std::ranges::find_if(room_size_addresses_, [room_ptr](
230 const auto& entry) {
231 return entry.second == room_ptr;
232 })->first;
233
234 if (room_ptr != 0x0A8000) {
235 if (i < bank_rooms.second.size() - 1) {
236 room_sizes_[room_id] = bank_rooms.second[i + 1] - room_ptr;
237 } else {
238 int bank_end_address = (bank_rooms.first << 16) | 0xFFFF;
239 room_sizes_[room_id] = bank_end_address - room_ptr + 1;
240 }
241 total_room_size_ += room_sizes_[room_id];
242 } else {
243 room_sizes_[room_id] = 0x00;
244 }
245 }
246 }
247}
248
250 if (!rom_ || !rom_->is_loaded()) {
251 return absl::FailedPreconditionError("ROM not loaded");
252 }
253
254 // Load room graphics with proper blockset
255 room.LoadRoomGraphics(room.blockset);
256
257 // Render the room graphics to the graphics arena
258 room.RenderRoomGraphics();
259
260 return absl::OkStatus();
261}
262
264 std::array<zelda3::Room, 0x128>& rooms) {
265 if (!rom_ || !rom_->is_loaded()) {
266 return absl::FailedPreconditionError("ROM not loaded");
267 }
268
269 // Reload graphics for all rooms
270 for (auto& room : rooms) {
271 auto status = LoadAndRenderRoomGraphics(room);
272 if (!status.ok()) {
273 continue; // Log error but continue with other rooms
274 }
275 }
276
277 return absl::OkStatus();
278}
279
280} // namespace yaze::editor
absl::StatusOr< uint16_t > ReadWord(int offset)
Definition rom.cc:228
bool is_loaded() const
Definition rom.h:128
absl::Status LoadRoomEntrances(std::array< zelda3::RoomEntrance, 0x8C > &entrances)
absl::Status LoadAllRooms(std::array< zelda3::Room, 0x128 > &rooms)
absl::Status LoadRoom(int room_id, zelda3::Room &room)
std::unordered_map< int, int > room_size_addresses_
std::vector< int64_t > room_size_pointers_
absl::Status ReloadAllRoomGraphics(std::array< zelda3::Room, 0x128 > &rooms)
std::unordered_map< int, ImVec4 > room_palette_
absl::Status LoadAndRenderRoomGraphics(zelda3::Room &room)
RAII timer for automatic timing management.
Dungeon Room Entrance or Spawn Point.
void LoadRoomGraphics(uint8_t entrance_blockset=0xFF)
Definition room.cc:370
uint8_t blockset
Definition room.h:490
void RenderRoomGraphics()
Definition room.cc:518
void LoadObjects()
Definition room.cc:1140
void SetGameData(GameData *data)
Definition room.h:531
#define LOG_DEBUG(category, format,...)
Definition log.h:103
Editors are the view controllers for the application.
Definition agent_chat.cc:23
Room LoadRoomHeaderFromRom(Rom *rom, int room_id)
Definition room.cc:206
RoomSize CalculateRoomSize(Rom *rom, int room_id)
Definition room.cc:107
Room LoadRoomFromRom(Rom *rom, int room_id)
Definition room.cc:177
#define RETURN_IF_ERROR(expr)
Definition snes.cc:22
std::array< std::array< uint8_t, 4 >, kNumPalettesets > paletteset_ids
Definition game_data.h:99
gfx::PaletteGroupMap palette_groups
Definition game_data.h:89