mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 23:30:14 +00:00
Hide post-login world hitch behind loading screen
- keep character-selection state until world entry load/finalize completes - move expensive post-load setup (test transport + creature callback prep) before loading screen shutdown - add bounded world warmup pass under loading screen to drain initial network/spawn backlog - start intro camera pan after warmup so rotation begins when gameplay becomes visible - guard test transport setup so it runs once per session - add per-update world socket parse budget to prevent single-frame packet-drain stalls This reduces visible 3-4s stutter after login by shifting startup work behind the loading screen and time-slicing packet processing.
This commit is contained in:
parent
954edc91b8
commit
acb63d4f6e
3 changed files with 74 additions and 10 deletions
|
|
@ -218,6 +218,7 @@ private:
|
|||
std::unordered_map<uint64_t, PendingTransportMove> pendingTransportMoves_; // guid -> latest pre-registration move
|
||||
uint32_t nextGameObjectModelId_ = 20000;
|
||||
uint32_t nextGameObjectWmoModelId_ = 40000;
|
||||
bool testTransportSetup_ = false;
|
||||
bool gameObjectLookupsBuilt_ = false;
|
||||
|
||||
// Mount model tracking
|
||||
|
|
|
|||
|
|
@ -1181,8 +1181,8 @@ void Application::setupUICallbacks() {
|
|||
if (gameHandler) {
|
||||
gameHandler->setActiveCharacterGuid(characterGuid);
|
||||
}
|
||||
// Online mode - login will be handled by world entry callback
|
||||
setState(AppState::IN_GAME);
|
||||
// Keep CHARACTER_SELECTION active until world entry is fully loaded.
|
||||
// This avoids exposing pre-load hitching before the loading screen/intro.
|
||||
});
|
||||
|
||||
// Character create screen callbacks
|
||||
|
|
@ -2827,7 +2827,6 @@ void Application::loadOnlineWorldTerrain(uint32_t mapId, float x, float y, float
|
|||
renderer->getCameraController()->setOnlineMode(true);
|
||||
renderer->getCameraController()->setDefaultSpawn(spawnRender, 0.0f, -15.0f);
|
||||
renderer->getCameraController()->reset();
|
||||
renderer->getCameraController()->startIntroPan(2.8f, 140.0f);
|
||||
}
|
||||
|
||||
// Set map name for WMO renderer
|
||||
|
|
@ -3021,16 +3020,12 @@ void Application::loadOnlineWorldTerrain(uint32_t mapId, float x, float y, float
|
|||
renderer->getCameraController()->reset();
|
||||
}
|
||||
|
||||
showProgress("Entering world...", 1.0f);
|
||||
|
||||
if (loadingScreenOk) {
|
||||
loadingScreen.shutdown();
|
||||
}
|
||||
|
||||
// Set up test transport (development feature)
|
||||
showProgress("Finalizing world...", 0.94f);
|
||||
setupTestTransport();
|
||||
|
||||
// Set up NPC animation callbacks (for online creatures)
|
||||
showProgress("Preparing creatures...", 0.97f);
|
||||
if (gameHandler && renderer && renderer->getCharacterRenderer()) {
|
||||
auto* cr = renderer->getCharacterRenderer();
|
||||
auto* app = this;
|
||||
|
|
@ -3059,6 +3054,64 @@ void Application::loadOnlineWorldTerrain(uint32_t mapId, float x, float y, float
|
|||
});
|
||||
}
|
||||
|
||||
// Hide first-login hitch by draining initial world packets/spawn queues before
|
||||
// dropping the loading screen. Keep this bounded so we don't stall indefinitely.
|
||||
{
|
||||
const float kWarmupMaxSeconds = 2.5f;
|
||||
const auto warmupStart = std::chrono::high_resolution_clock::now();
|
||||
while (true) {
|
||||
SDL_Event event;
|
||||
while (SDL_PollEvent(&event)) {
|
||||
if (event.type == SDL_QUIT) {
|
||||
window->setShouldClose(true);
|
||||
if (loadingScreenOk) loadingScreen.shutdown();
|
||||
return;
|
||||
}
|
||||
if (event.type == SDL_WINDOWEVENT &&
|
||||
event.window.event == SDL_WINDOWEVENT_RESIZED) {
|
||||
int w = event.window.data1;
|
||||
int h = event.window.data2;
|
||||
window->setSize(w, h);
|
||||
glViewport(0, 0, w, h);
|
||||
if (renderer && renderer->getCamera()) {
|
||||
renderer->getCamera()->setAspectRatio(static_cast<float>(w) / h);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Drain network and process deferred spawn/composite queues while hidden.
|
||||
if (gameHandler) gameHandler->update(1.0f / 60.0f);
|
||||
if (world) world->update(1.0f / 60.0f);
|
||||
processPlayerSpawnQueue();
|
||||
processCreatureSpawnQueue();
|
||||
processDeferredEquipmentQueue();
|
||||
processGameObjectSpawnQueue();
|
||||
processPendingMount();
|
||||
updateQuestMarkers();
|
||||
|
||||
const auto now = std::chrono::high_resolution_clock::now();
|
||||
const float elapsed = std::chrono::duration<float>(now - warmupStart).count();
|
||||
const float t = std::clamp(elapsed / kWarmupMaxSeconds, 0.0f, 1.0f);
|
||||
showProgress("Finalizing world sync...", 0.97f + t * 0.025f);
|
||||
|
||||
if (elapsed >= kWarmupMaxSeconds) {
|
||||
break;
|
||||
}
|
||||
SDL_Delay(16);
|
||||
}
|
||||
}
|
||||
|
||||
// Start intro pan right before entering gameplay so it's visible after loading.
|
||||
if (renderer->getCameraController()) {
|
||||
renderer->getCameraController()->startIntroPan(2.8f, 140.0f);
|
||||
}
|
||||
|
||||
showProgress("Entering world...", 1.0f);
|
||||
|
||||
if (loadingScreenOk) {
|
||||
loadingScreen.shutdown();
|
||||
}
|
||||
|
||||
// Set game state
|
||||
setState(AppState::IN_GAME);
|
||||
}
|
||||
|
|
@ -5458,6 +5511,7 @@ void Application::updateQuestMarkers() {
|
|||
}
|
||||
|
||||
void Application::setupTestTransport() {
|
||||
if (testTransportSetup_) return;
|
||||
if (!gameHandler || !renderer || !assetManager) return;
|
||||
|
||||
auto* transportManager = gameHandler->getTransportManager();
|
||||
|
|
@ -5584,6 +5638,7 @@ void Application::setupTestTransport() {
|
|||
glm::vec3(-15.0f, -30.0f, 0.0f),
|
||||
glm::vec3(15.0f, 30.0f, 10.0f));
|
||||
|
||||
testTransportSetup_ = true;
|
||||
LOG_INFO("========================================");
|
||||
LOG_INFO("Test transport registered:");
|
||||
LOG_INFO(" GUID: 0x", std::hex, transportGuid, std::dec);
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
|
||||
namespace {
|
||||
constexpr size_t kMaxReceiveBufferBytes = 8 * 1024 * 1024;
|
||||
constexpr int kMaxParsedPacketsPerUpdate = 220;
|
||||
|
||||
inline bool isLoginPipelineSmsg(uint16_t opcode) {
|
||||
switch (opcode) {
|
||||
|
|
@ -323,7 +324,8 @@ void WorldSocket::update() {
|
|||
|
||||
void WorldSocket::tryParsePackets() {
|
||||
// World server packets have 4-byte incoming header: size(2) + opcode(2)
|
||||
while (receiveBuffer.size() >= 4) {
|
||||
int parsedThisTick = 0;
|
||||
while (receiveBuffer.size() >= 4 && parsedThisTick < kMaxParsedPacketsPerUpdate) {
|
||||
uint8_t rawHeader[4] = {0, 0, 0, 0};
|
||||
std::memcpy(rawHeader, receiveBuffer.data(), 4);
|
||||
|
||||
|
|
@ -417,6 +419,12 @@ void WorldSocket::tryParsePackets() {
|
|||
if (packetCallback) {
|
||||
packetCallback(packet);
|
||||
}
|
||||
++parsedThisTick;
|
||||
}
|
||||
|
||||
if (parsedThisTick >= kMaxParsedPacketsPerUpdate && receiveBuffer.size() >= 4) {
|
||||
LOG_DEBUG("World socket parse budget reached (", parsedThisTick,
|
||||
" packets); deferring remaining buffered data=", receiveBuffer.size(), " bytes");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue