UI Subsystem UniqueId Guard: Learnings and Design¶
Problem¶
Remote PlayerState entries on a listen server could reach the UI subsystem before the UniqueId is valid. This caused WidgetControllerReady to run too early, resulting in null/invalid UniqueId usage.
Root Cause¶
- Listen server never cached a v2 UniqueNetId for the local player, so
PlayerState->UniqueIdstayed invalid for both self and remote players. OnRep_UniqueIdonly fires on clients when the replicated field updates. It does not run on the listen server for remote PlayerStates.- The UI subsystem previously only gated on PostNetInit, which is not enough when UniqueId arrives later or is never set.
Solution¶
- Key fix for validity (not just timing):
CacheLocalPlayerUniqueNetId()seeds the v2 account id from EOS PUID, caches it on the LocalPlayer, and (when authority) sets it on the PlayerState.- This makes
PlayerState->GetUniqueId()valid on listen servers (even without a NetConnection) and ensures the login packet carries a valid UniqueId for remote clients. - The UI timing fixes (
OnSetUniqueId) only affect readiness signals; they do not make the UniqueId valid on their own. - Gate remote PlayerState UI initialization on both:
PostNetInit(replication readiness), andUniqueIdreadiness (identity assigned).- Use two hooks to trigger the UniqueId readiness:
OnRep_UniqueIdfor clients (replication path).OnSetUniqueIdfor listen server (server-only path).- When UniqueId is not ready, queue the PlayerState in the UI subsystem and trigger WidgetControllerReady only after the UniqueId-ready delegate fires.
- Ensure the UniqueId is actually assigned on listen servers by caching the local
player’s v2 UniqueNetId from the EOS PUID and setting it on the PlayerState
(done after auth login, on world init, and right before
ClientTravel).
Where Implemented¶
Source/Metal_terra/Private/Core/Subsystems/OnlineManagerSubsystem.cpp- Builds
FUniqueNetIdReplfrom PUID, callsLocalPlayer->SetCachedUniqueNetId, andPlayerState->SetUniqueIdon authority. - Called after auth login and on world init.
Source/Metal_terra/Private/Core/Subsystems/Online/OnlineNakamaHouseService.cpp- Calls
CacheLocalPlayerUniqueNetId()right beforeClientTravelto ensure the login message includes the UniqueId.
Pitfalls and Timing Notes¶
- Handshake logs can show a PUID, but
PlayerState->UniqueIdcan remain invalid if the local UniqueNetId is never cached/assigned. - Reading UniqueId too early will return null/invalid even if EOS P2P is connected.
- Listen server nuance:
- Server-side
PlayerStatenever receivesOnRep_UniqueId, soOnSetUniqueIdis required to notify local UI systems. - Dedicated server nuance:
- UniqueId assignment happens server-side, but the UI runs on clients where
OnRep_UniqueIdfires as expected.
Client vs Listen vs Dedicated¶
- Client:
- Receives
OnRep_UniqueIdfor remote PlayerStates. - UI can rely on the replication callback for readiness.
- Listen server:
- Hosts authority and local UI in the same process.
- Remote PlayerState UniqueId is set on the server, but no
OnRep_UniqueIdis fired. - Must use
OnSetUniqueIdto mark readiness for local UI. - Dedicated server:
- No local UI.
- Client UIs get
OnRep_UniqueIdnormally for remote PlayerStates.
Design Guidance¶
- Treat UniqueId readiness as a separate lifecycle event from PostNetInit.
- Keep a queue of PlayerStates awaiting UniqueId for UI binding.
- Trigger WidgetControllerReady only after both PostNetInit and UniqueId readiness.
- Log or trace UniqueId readiness when investigating login or UI timing issues.