From e3aab64c8cf306f218ffcc45f03a8afd0fb11da7 Mon Sep 17 00:00:00 2001 From: Mateus Rodrigues Date: Mon, 1 Jun 2026 16:26:55 -0300 Subject: [PATCH] fix(world): destroy proxy on DESPAWN + handle stale weak ptr on SPAWN MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ZMMOWorldSubsystem mantinha proxies no mapa RemoteEntities apos DESPAWN apenas marcando SetActorHiddenInGame(true) via SetEntityRelevant(false). Entry ficava la apontando pra actor escondido — SPAWN subsequente do mesmo entityId caia no fast-path "reactivar" e tudo bem... ate' o TWeakObjectPtr ficar stale (GC, World Partition stream-out, etc): a branch Contains(EntityId) saia com return incondicional sem spawnar nada. Resultado: SPAWN silenciosamente ignorado, pawn permanente invisivel ate' algo limpar a key. Fixes: 1. HandlePlayerDespawned: Actor->Destroy() + RemoteEntities.Remove (destroi de verdade em vez de so esconder). 2. HandlePlayerSpawned: quando Contains mas weak ptr stale, remove a entry orfa e cai pro caminho de spawn novo — em vez de return. Confirmado pelos logs de cliente durante teste de oscilacao do AOI (servidor enviando DESPAWN+SPAWN ao oscilar ZI/ZD). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.7 --- .../ZMMO/Game/Network/ZMMOWorldSubsystem.cpp | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/Source/ZMMO/Game/Network/ZMMOWorldSubsystem.cpp b/Source/ZMMO/Game/Network/ZMMOWorldSubsystem.cpp index cf72d08..3850726 100644 --- a/Source/ZMMO/Game/Network/ZMMOWorldSubsystem.cpp +++ b/Source/ZMMO/Game/Network/ZMMOWorldSubsystem.cpp @@ -104,7 +104,10 @@ void UZMMOWorldSubsystem::HandlePlayerSpawned(const int32 EntityId, const bool b if (RemoteEntities.Contains(EntityId)) { - // Ja existe um proxy: reactivar e re-snap. + // Ja existe um proxy: tenta reactivar e re-snap. Se o weak ptr ficou + // stale (actor coletado pelo GC, stream-out do World Partition, etc.), + // remove a entry orfa e cai no caminho de spawn novo — evita SPAWN + // silenciosamente ignorado depois de oscilacoes na AOI. if (AActor* Existing = RemoteEntities[EntityId].Get()) { if (IZMMOEntityInterface* AsEntity = Cast(Existing)) @@ -112,8 +115,12 @@ void UZMMOWorldSubsystem::HandlePlayerSpawned(const int32 EntityId, const bool b AsEntity->SetEntityRelevant(true); } Existing->SetActorLocationAndRotation(PosCm, FRotator(0.0f, YawDeg, 0.0f)); + UE_LOG(LogZMMO, Verbose, TEXT("ZMMOWorldSubsystem: re-snap EntityId=%d"), EntityId); + return; } - return; + // weak ptr stale — limpa entry e cai pro spawn novo abaixo. + RemoteEntities.Remove(EntityId); + UE_LOG(LogZMMO, Log, TEXT("ZMMOWorldSubsystem: stale entry para EntityId=%d, respawnando"), EntityId); } UWorld* World = GetWorld(); @@ -166,15 +173,23 @@ void UZMMOWorldSubsystem::HandlePlayerDespawned(const int32 EntityId) return; } + // Destruir o actor pra valer + remover do mapa. Antes, despawn apenas + // fazia SetActorHiddenInGame(true) via SetEntityRelevant(false), o que + // deixava o entry no mapa apontando pra um actor escondido. SPAWN + // subsequente do mesmo entityId era detectado como "duplicate" e nada + // acontecia — pawn ficava invisivel ate' o weak ptr ser GC'd. Bug + // confirmado nos logs do AOI ao oscilar entrada/saida da ZI/ZD. if (AActor* Actor = Entry->Get()) { if (IZMMOEntityInterface* AsEntity = Cast(Actor)) { AsEntity->SetEntityRelevant(false); } + Actor->Destroy(); } + RemoteEntities.Remove(EntityId); - UE_LOG(LogZMMO, Log, TEXT("ZMMOWorldSubsystem: despawn EntityId=%d"), EntityId); + UE_LOG(LogZMMO, Log, TEXT("ZMMOWorldSubsystem: despawn EntityId=%d (destroyed)"), EntityId); } void UZMMOWorldSubsystem::HandlePlayerStateUpdate(const int32 EntityId, const int32 InputSeq,