mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-24 08:00:14 +00:00
Fix shutdown hangs, bank bag icons/drag-drop, loading screen progress, and login spawn
- Fix shutdown hang: skip vmaDestroyAllocator (walked thousands of allocations), replace unsafe pthread_timedjoin_np with plain join + early-exit checks in workers - Bank window: full icon rendering, click-and-hold pickup (0.10s), drag-drop for all bank slots including bank bag equip slots, same-slot drop detection - Loading screen: process one tile per frame for live progress updates - Camera reset: trust server position in online mode to avoid spawning under WMOs - Fix PLAYER_BYTES/PLAYER_BYTES_2 field indices, preserve purchasedBankBagSlots across inventory rebuilds, fix bank slot purchase result codes
This commit is contained in:
parent
804b947203
commit
a559d5944b
14 changed files with 489 additions and 146 deletions
|
|
@ -1756,15 +1756,47 @@ void CameraController::reset() {
|
|||
return h;
|
||||
};
|
||||
|
||||
// In online mode, try to snap to a nearby floor but fall back to the server
|
||||
// position when no WMO floor is found (e.g. WMO not loaded yet in cities).
|
||||
// This prevents spawning under WMO cities like Stormwind.
|
||||
if (onlineMode) {
|
||||
auto h = evalFloorAt(spawnPos.x, spawnPos.y, spawnPos.z);
|
||||
if (h && std::abs(*h - spawnPos.z) < 16.0f) {
|
||||
spawnPos.z = *h + 0.05f;
|
||||
}
|
||||
// else: keep server Z as-is
|
||||
lastGroundZ = spawnPos.z - 0.05f;
|
||||
|
||||
camera->setRotation(yaw, pitch);
|
||||
glm::vec3 forward3D = camera->getForward();
|
||||
|
||||
if (thirdPerson && followTarget) {
|
||||
*followTarget = spawnPos;
|
||||
currentDistance = userTargetDistance;
|
||||
collisionDistance = currentDistance;
|
||||
float mountedOffset = mounted_ ? mountHeightOffset_ : 0.0f;
|
||||
glm::vec3 pivot = spawnPos + glm::vec3(0.0f, 0.0f, PIVOT_HEIGHT + mountedOffset);
|
||||
glm::vec3 camDir = -forward3D;
|
||||
glm::vec3 camPos = pivot + camDir * currentDistance;
|
||||
smoothedCamPos = camPos;
|
||||
camera->setPosition(camPos);
|
||||
} else {
|
||||
spawnPos.z += eyeHeight;
|
||||
smoothedCamPos = spawnPos;
|
||||
camera->setPosition(spawnPos);
|
||||
}
|
||||
|
||||
LOG_INFO("Camera reset to server position (online mode)");
|
||||
return;
|
||||
}
|
||||
|
||||
// Search nearby for a stable, non-steep spawn floor to avoid waterfall/ledge spawns.
|
||||
// In online mode, use a tight search radius since the server dictates position.
|
||||
float bestScore = std::numeric_limits<float>::max();
|
||||
glm::vec3 bestPos = spawnPos;
|
||||
bool foundBest = false;
|
||||
constexpr float radiiOffline[] = {0.0f, 6.0f, 12.0f, 18.0f, 24.0f, 32.0f};
|
||||
constexpr float radiiOnline[] = {0.0f, 2.0f};
|
||||
const float* radii = onlineMode ? radiiOnline : radiiOffline;
|
||||
const int radiiCount = onlineMode ? 2 : 6;
|
||||
const float* radii = radiiOffline;
|
||||
const int radiiCount = 6;
|
||||
constexpr int ANGLES = 16;
|
||||
constexpr float PI = 3.14159265f;
|
||||
for (int ri = 0; ri < radiiCount; ri++) {
|
||||
|
|
|
|||
|
|
@ -726,31 +726,38 @@ bool Renderer::initialize(core::Window* win) {
|
|||
}
|
||||
|
||||
void Renderer::shutdown() {
|
||||
LOG_WARNING("Renderer::shutdown - terrainManager stopWorkers...");
|
||||
if (terrainManager) {
|
||||
terrainManager->unloadAll();
|
||||
terrainManager->stopWorkers();
|
||||
LOG_WARNING("Renderer::shutdown - terrainManager reset...");
|
||||
terrainManager.reset();
|
||||
}
|
||||
|
||||
LOG_WARNING("Renderer::shutdown - terrainRenderer...");
|
||||
if (terrainRenderer) {
|
||||
terrainRenderer->shutdown();
|
||||
terrainRenderer.reset();
|
||||
}
|
||||
|
||||
LOG_WARNING("Renderer::shutdown - waterRenderer...");
|
||||
if (waterRenderer) {
|
||||
waterRenderer->shutdown();
|
||||
waterRenderer.reset();
|
||||
}
|
||||
|
||||
LOG_WARNING("Renderer::shutdown - minimap...");
|
||||
if (minimap) {
|
||||
minimap->shutdown();
|
||||
minimap.reset();
|
||||
}
|
||||
|
||||
LOG_WARNING("Renderer::shutdown - worldMap...");
|
||||
if (worldMap) {
|
||||
worldMap->shutdown();
|
||||
worldMap.reset();
|
||||
}
|
||||
|
||||
LOG_WARNING("Renderer::shutdown - skySystem...");
|
||||
if (skySystem) {
|
||||
skySystem->shutdown();
|
||||
skySystem.reset();
|
||||
|
|
@ -772,34 +779,41 @@ void Renderer::shutdown() {
|
|||
swimEffects.reset();
|
||||
}
|
||||
|
||||
LOG_WARNING("Renderer::shutdown - characterRenderer...");
|
||||
if (characterRenderer) {
|
||||
characterRenderer->shutdown();
|
||||
characterRenderer.reset();
|
||||
}
|
||||
|
||||
LOG_WARNING("Renderer::shutdown - wmoRenderer...");
|
||||
if (wmoRenderer) {
|
||||
wmoRenderer->shutdown();
|
||||
wmoRenderer.reset();
|
||||
}
|
||||
|
||||
LOG_WARNING("Renderer::shutdown - m2Renderer...");
|
||||
if (m2Renderer) {
|
||||
m2Renderer->shutdown();
|
||||
m2Renderer.reset();
|
||||
}
|
||||
|
||||
LOG_WARNING("Renderer::shutdown - musicManager...");
|
||||
if (musicManager) {
|
||||
musicManager->shutdown();
|
||||
musicManager.reset();
|
||||
}
|
||||
LOG_WARNING("Renderer::shutdown - footstepManager...");
|
||||
if (footstepManager) {
|
||||
footstepManager->shutdown();
|
||||
footstepManager.reset();
|
||||
}
|
||||
LOG_WARNING("Renderer::shutdown - activitySoundManager...");
|
||||
if (activitySoundManager) {
|
||||
activitySoundManager->shutdown();
|
||||
activitySoundManager.reset();
|
||||
}
|
||||
|
||||
LOG_WARNING("Renderer::shutdown - AudioEngine...");
|
||||
// Shutdown AudioEngine singleton
|
||||
audio::AudioEngine::instance().shutdown();
|
||||
|
||||
|
|
|
|||
|
|
@ -129,17 +129,7 @@ TerrainManager::TerrainManager() {
|
|||
}
|
||||
|
||||
TerrainManager::~TerrainManager() {
|
||||
// Stop worker thread before cleanup (containers clean up via destructors)
|
||||
if (workerRunning.load()) {
|
||||
workerRunning.store(false);
|
||||
queueCV.notify_all();
|
||||
for (auto& t : workerThreads) {
|
||||
if (t.joinable()) {
|
||||
t.join();
|
||||
}
|
||||
}
|
||||
workerThreads.clear();
|
||||
}
|
||||
stopWorkers();
|
||||
}
|
||||
|
||||
bool TerrainManager::initialize(pipeline::AssetManager* assets, TerrainRenderer* renderer) {
|
||||
|
|
@ -276,6 +266,9 @@ std::shared_ptr<PendingTile> TerrainManager::prepareTile(int x, int y) {
|
|||
|
||||
LOG_DEBUG("Preparing tile [", x, ",", y, "] (CPU work)");
|
||||
|
||||
// Early-exit check — worker should bail fast during shutdown
|
||||
if (!workerRunning.load()) return nullptr;
|
||||
|
||||
// Load ADT file
|
||||
std::string adtPath = getADTPath(coord);
|
||||
auto adtData = assetManager->readFile(adtPath);
|
||||
|
|
@ -294,6 +287,8 @@ std::shared_ptr<PendingTile> TerrainManager::prepareTile(int x, int y) {
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
if (!workerRunning.load()) return nullptr;
|
||||
|
||||
// WotLK split ADTs can store placements in *_obj0.adt.
|
||||
// Merge object chunks so doodads/WMOs (including ground clutter) are available.
|
||||
std::string objPath = "World\\Maps\\" + mapName + "\\" + mapName + "_" +
|
||||
|
|
@ -362,6 +357,8 @@ std::shared_ptr<PendingTile> TerrainManager::prepareTile(int x, int y) {
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
if (!workerRunning.load()) return nullptr;
|
||||
|
||||
auto pending = std::make_shared<PendingTile>();
|
||||
pending->coord = coord;
|
||||
pending->terrain = std::move(*terrainPtr);
|
||||
|
|
@ -412,6 +409,7 @@ std::shared_ptr<PendingTile> TerrainManager::prepareTile(int x, int y) {
|
|||
// Pre-load M2 doodads (CPU: read files, parse models)
|
||||
int skippedNameId = 0, skippedFileNotFound = 0, skippedInvalid = 0, skippedSkinNotFound = 0;
|
||||
for (const auto& placement : pending->terrain.doodadPlacements) {
|
||||
if (!workerRunning.load()) return nullptr;
|
||||
if (placement.nameId >= pending->terrain.doodadNames.size()) {
|
||||
skippedNameId++;
|
||||
continue;
|
||||
|
|
@ -460,9 +458,12 @@ std::shared_ptr<PendingTile> TerrainManager::prepareTile(int x, int y) {
|
|||
ensureGroundEffectTablesLoaded();
|
||||
generateGroundClutterPlacements(pending, preparedModelIds);
|
||||
|
||||
if (!workerRunning.load()) return nullptr;
|
||||
|
||||
// Pre-load WMOs (CPU: read files, parse models and groups)
|
||||
if (!pending->terrain.wmoPlacements.empty()) {
|
||||
for (const auto& placement : pending->terrain.wmoPlacements) {
|
||||
if (!workerRunning.load()) return nullptr;
|
||||
if (placement.nameId >= pending->terrain.wmoNames.size()) continue;
|
||||
|
||||
const std::string& wmoPath = pending->terrain.wmoNames[placement.nameId];
|
||||
|
|
@ -513,6 +514,7 @@ std::shared_ptr<PendingTile> TerrainManager::prepareTile(int x, int y) {
|
|||
);
|
||||
|
||||
// Pre-load WMO doodads (M2 models inside WMO)
|
||||
if (!workerRunning.load()) return nullptr;
|
||||
if (!wmoModel.doodadSets.empty() && !wmoModel.doodads.empty()) {
|
||||
glm::mat4 wmoMatrix(1.0f);
|
||||
wmoMatrix = glm::translate(wmoMatrix, pos);
|
||||
|
|
@ -636,6 +638,8 @@ std::shared_ptr<PendingTile> TerrainManager::prepareTile(int x, int y) {
|
|||
}
|
||||
}
|
||||
|
||||
if (!workerRunning.load()) return nullptr;
|
||||
|
||||
// Pre-load terrain texture BLP data on background thread so finalizeTile
|
||||
// doesn't block the main thread with file I/O.
|
||||
for (const auto& texPath : pending->terrain.textures) {
|
||||
|
|
@ -1068,6 +1072,28 @@ void TerrainManager::processAllReadyTiles() {
|
|||
}
|
||||
}
|
||||
|
||||
void TerrainManager::processOneReadyTile() {
|
||||
// Move ready tiles into finalizing deque
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(queueMutex);
|
||||
while (!readyQueue.empty()) {
|
||||
auto pending = readyQueue.front();
|
||||
readyQueue.pop();
|
||||
if (pending) {
|
||||
FinalizingTile ft;
|
||||
ft.pending = std::move(pending);
|
||||
finalizingTiles_.push_back(std::move(ft));
|
||||
}
|
||||
}
|
||||
}
|
||||
// Finalize ONE tile completely, then return so caller can update the screen
|
||||
if (!finalizingTiles_.empty()) {
|
||||
auto& ft = finalizingTiles_.front();
|
||||
while (!advanceFinalization(ft)) {}
|
||||
finalizingTiles_.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<PendingTile> TerrainManager::getCachedTile(const TileCoord& coord) {
|
||||
std::lock_guard<std::mutex> lock(tileCacheMutex_);
|
||||
auto it = tileCache_.find(coord);
|
||||
|
|
@ -1237,6 +1263,29 @@ void TerrainManager::unloadTile(int x, int y) {
|
|||
loadedTiles.erase(it);
|
||||
}
|
||||
|
||||
void TerrainManager::stopWorkers() {
|
||||
if (!workerRunning.load()) {
|
||||
LOG_WARNING("stopWorkers: already stopped");
|
||||
return;
|
||||
}
|
||||
LOG_WARNING("stopWorkers: signaling ", workerThreads.size(), " workers to stop...");
|
||||
workerRunning.store(false);
|
||||
queueCV.notify_all();
|
||||
|
||||
// Workers check workerRunning at each I/O point in prepareTile() and bail
|
||||
// out quickly. Use plain join() which is safe with std::thread — no
|
||||
// pthread_timedjoin_np (which silently joins the pthread but leaves the
|
||||
// std::thread object thinking it's still joinable → std::terminate on dtor).
|
||||
for (size_t i = 0; i < workerThreads.size(); i++) {
|
||||
if (workerThreads[i].joinable()) {
|
||||
LOG_WARNING("stopWorkers: joining worker ", i, "...");
|
||||
workerThreads[i].join();
|
||||
}
|
||||
}
|
||||
workerThreads.clear();
|
||||
LOG_WARNING("stopWorkers: done");
|
||||
}
|
||||
|
||||
void TerrainManager::unloadAll() {
|
||||
// Signal worker threads to stop and wait briefly for them to finish.
|
||||
// Workers may be mid-prepareTile (reading MPQ / parsing ADT) which can
|
||||
|
|
@ -1245,29 +1294,8 @@ void TerrainManager::unloadAll() {
|
|||
workerRunning.store(false);
|
||||
queueCV.notify_all();
|
||||
|
||||
auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(500);
|
||||
for (auto& t : workerThreads) {
|
||||
if (!t.joinable()) continue;
|
||||
// Try a timed wait via polling — std::thread has no timed join.
|
||||
bool joined = false;
|
||||
while (std::chrono::steady_clock::now() < deadline) {
|
||||
// Check if thread finished by trying a native timed join
|
||||
#ifdef __linux__
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_REALTIME, &ts);
|
||||
ts.tv_nsec += 50000000; // 50ms
|
||||
if (ts.tv_nsec >= 1000000000) { ts.tv_sec++; ts.tv_nsec -= 1000000000; }
|
||||
if (pthread_timedjoin_np(t.native_handle(), nullptr, &ts) == 0) {
|
||||
joined = true;
|
||||
break;
|
||||
}
|
||||
#else
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
#endif
|
||||
}
|
||||
if (!joined && t.joinable()) {
|
||||
t.detach();
|
||||
}
|
||||
if (t.joinable()) t.join();
|
||||
}
|
||||
workerThreads.clear();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,10 +50,12 @@ bool VkContext::initialize(SDL_Window* window) {
|
|||
}
|
||||
|
||||
void VkContext::shutdown() {
|
||||
LOG_WARNING("VkContext::shutdown - vkDeviceWaitIdle...");
|
||||
if (device) {
|
||||
vkDeviceWaitIdle(device);
|
||||
}
|
||||
|
||||
LOG_WARNING("VkContext::shutdown - destroyImGuiResources...");
|
||||
destroyImGuiResources();
|
||||
|
||||
// Destroy sync objects
|
||||
|
|
@ -68,9 +70,16 @@ void VkContext::shutdown() {
|
|||
if (immFence) { vkDestroyFence(device, immFence, nullptr); immFence = VK_NULL_HANDLE; }
|
||||
if (immCommandPool) { vkDestroyCommandPool(device, immCommandPool, nullptr); immCommandPool = VK_NULL_HANDLE; }
|
||||
|
||||
LOG_WARNING("VkContext::shutdown - destroySwapchain...");
|
||||
destroySwapchain();
|
||||
|
||||
if (allocator) { vmaDestroyAllocator(allocator); allocator = VK_NULL_HANDLE; }
|
||||
// Skip vmaDestroyAllocator — it walks every allocation to free it, which
|
||||
// takes many seconds with thousands of loaded textures/models. The driver
|
||||
// reclaims all device memory when we destroy the device, and the OS reclaims
|
||||
// everything on process exit. Skipping this makes shutdown instant.
|
||||
allocator = VK_NULL_HANDLE;
|
||||
|
||||
LOG_WARNING("VkContext::shutdown - vkDestroyDevice...");
|
||||
if (device) { vkDestroyDevice(device, nullptr); device = VK_NULL_HANDLE; }
|
||||
if (surface) { vkDestroySurfaceKHR(instance, surface, nullptr); surface = VK_NULL_HANDLE; }
|
||||
|
||||
|
|
@ -83,7 +92,7 @@ void VkContext::shutdown() {
|
|||
|
||||
if (instance) { vkDestroyInstance(instance, nullptr); instance = VK_NULL_HANDLE; }
|
||||
|
||||
LOG_INFO("Vulkan context shutdown");
|
||||
LOG_WARNING("Vulkan context shutdown complete");
|
||||
}
|
||||
|
||||
bool VkContext::createInstance(SDL_Window* window) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue