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