From b91125668756e698c41255780692eb4bbb67df85 Mon Sep 17 00:00:00 2001 From: scawful Date: Sun, 28 Sep 2025 22:37:13 -0400 Subject: [PATCH] Implement multithreaded room loading in DungeonRoomLoader - Refactored the LoadAllRooms method to utilize multithreading for improved performance during dungeon room loading. - Introduced thread-safe data structures and a task-based approach to process room loading in parallel, optimizing resource usage. - Added performance logging to track the number of threads and rooms processed, enhancing monitoring capabilities. - Ensured thread safety when collecting results for room sizes and palettes, maintaining data integrity. --- src/app/editor/dungeon/dungeon_room_loader.cc | 116 ++++++++++++++---- 1 file changed, 93 insertions(+), 23 deletions(-) diff --git a/src/app/editor/dungeon/dungeon_room_loader.cc b/src/app/editor/dungeon/dungeon_room_loader.cc index b366ed05..a5ceb7d0 100644 --- a/src/app/editor/dungeon/dungeon_room_loader.cc +++ b/src/app/editor/dungeon/dungeon_room_loader.cc @@ -2,9 +2,14 @@ #include #include +#include +#include +#include +#include "app/core/performance_monitor.h" #include "app/gfx/snes_palette.h" #include "app/zelda3/dungeon/room.h" +#include "util/log.h" namespace yaze::editor { @@ -13,30 +18,95 @@ absl::Status DungeonRoomLoader::LoadAllRooms(std::array& ro return absl::FailedPreconditionError("ROM not loaded"); } - auto dungeon_man_pal_group = rom_->palette_group().dungeon_main; - - for (int i = 0; i < 0x100 + 40; i++) { - rooms[i] = zelda3::LoadRoomFromRom(rom_, i); - - auto room_size = zelda3::CalculateRoomSize(rom_, i); - room_size_pointers_.push_back(room_size.room_size_pointer); - room_sizes_.push_back(room_size.room_size); - if (room_size.room_size_pointer != 0x0A8000) { - room_size_addresses_[i] = room_size.room_size_pointer; - } - - rooms[i].LoadObjects(); - - auto dungeon_palette_ptr = rom_->paletteset_ids[rooms[i].palette][0]; - auto palette_id = rom_->ReadWord(0xDEC4B + dungeon_palette_ptr); - if (palette_id.status() != absl::OkStatus()) { - continue; - } - int p_id = palette_id.value() / 180; - auto color = dungeon_man_pal_group[p_id][3]; - room_palette_[rooms[i].palette] = color.rgb(); + constexpr int kTotalRooms = 0x100 + 40; // 296 rooms + constexpr int kMaxConcurrency = 8; // Reasonable thread limit for room loading + + // Determine optimal number of threads + const int max_concurrency = std::min(kMaxConcurrency, + static_cast(std::thread::hardware_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)", + kTotalRooms, max_concurrency, rooms_per_thread); + + // Thread-safe data structures for collecting results + std::mutex results_mutex; + std::vector> room_size_results; + std::vector> room_palette_results; + + // Process rooms in parallel batches + std::vector> futures; + + for (int thread_id = 0; thread_id < max_concurrency; ++thread_id) { + auto task = [this, &rooms, thread_id, rooms_per_thread, &results_mutex, + &room_size_results, &room_palette_results, kTotalRooms]() -> absl::Status { + const int start_room = thread_id * rooms_per_thread; + const int end_room = std::min(start_room + rooms_per_thread, kTotalRooms); + + auto dungeon_man_pal_group = rom_->palette_group().dungeon_main; + + for (int i = start_room; i < end_room; ++i) { + // Load room data (this is the expensive operation) + rooms[i] = zelda3::LoadRoomFromRom(rom_, i); + + // Calculate room size + auto room_size = zelda3::CalculateRoomSize(rom_, i); + + // Load room objects + rooms[i].LoadObjects(); + + // Process palette + auto dungeon_palette_ptr = rom_->paletteset_ids[rooms[i].palette][0]; + auto palette_id = rom_->ReadWord(0xDEC4B + dungeon_palette_ptr); + if (palette_id.status() == absl::OkStatus()) { + int p_id = palette_id.value() / 180; + auto color = dungeon_man_pal_group[p_id][3]; + + // Thread-safe collection of results + { + std::lock_guard lock(results_mutex); + room_size_results.emplace_back(i, room_size); + room_palette_results.emplace_back(rooms[i].palette, color.rgb()); + } + } + } + + return absl::OkStatus(); + }; + + futures.emplace_back(std::async(std::launch::async, task)); } - + + // Wait for all threads to complete + for (auto& future : futures) { + RETURN_IF_ERROR(future.get()); + } + + // Process collected results on main thread + { + core::ScopedTimer postprocess_timer("DungeonRoomLoader::PostProcessResults"); + + // Sort results by room ID for consistent ordering + std::sort(room_size_results.begin(), room_size_results.end(), + [](const auto& a, const auto& b) { return a.first < b.first; }); + std::sort(room_palette_results.begin(), room_palette_results.end(), + [](const auto& a, const auto& b) { return a.first < b.first; }); + + // Process room size results + for (const auto& [room_id, room_size] : room_size_results) { + room_size_pointers_.push_back(room_size.room_size_pointer); + room_sizes_.push_back(room_size.room_size); + if (room_size.room_size_pointer != 0x0A8000) { + room_size_addresses_[room_id] = room_size.room_size_pointer; + } + } + + // Process palette results + for (const auto& [palette_id, color] : room_palette_results) { + room_palette_[palette_id] = color; + } + } + LoadDungeonRoomSize(); return absl::OkStatus(); }