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.
This commit is contained in:
scawful
2025-09-28 22:37:13 -04:00
parent f210cd85b4
commit b911256687

View File

@@ -2,9 +2,14 @@
#include <algorithm>
#include <map>
#include <future>
#include <thread>
#include <mutex>
#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<zelda3::Room, 0x128>& 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<int>(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<std::pair<int, zelda3::RoomSize>> room_size_results;
std::vector<std::pair<int, ImVec4>> room_palette_results;
// Process rooms in parallel batches
std::vector<std::future<absl::Status>> 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<std::mutex> 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();
}