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