Replication behavior in PIE vs Standalone

Hello,

I’m working on a small personal game and currently handling Lobby functionality.

I used RepNotify on an Array of Struct in order to synchronize the Lobby UI for all clients.

The variable is of type Array of Struct and is set to RepNotify in the Player Controller with the following update (SET) function:

SetWNotify

The corresponding OnRep function is as follows:

Where the Update function resides in the created widgets BP and sets the contents accordingly followed by the AddChild node which adds the newly created widget to a Scroll box.

In the GameMode I use OnPostLogin to add the newly connected players to an array of PCs and then traverse it in order to Update the UI for the joining Client as well as for the Clients that are already in session using UpdateLayerLobbyInfo Function.

UpdatePlayerLobbyInfo:

Problem is that if I run the game in Listen Server configuration (PIE) the server player list is showing as intended but on clients it gets multiplied. (Debugged it and it seems like the OnRep function fires multiple times even if the RepNotify variable is set only once) like so:

But if I run the game in Listen Server configuration (Standalone) everything runs as intended like so:
Standalone.PNG

I’m trying to understand what could cause this behavior as it’s my first time working on a game and I want to avoid going deeper into the project before I fully understand what’s going on with replication.

Thanks!

when you add widgets in the OnRep you need to either clear the existing widgets, or you can check if it already exists by saving a reference somewhere.

the difference in pie and standalone is things load differently so it gets called multple times as players load, you’d want this to be handled anyway because your game could have late joiners

1 Like

Hello, Thanks for your reply but I don’t think it has to do with Widgets as I’ve run some more tests:

Instead of creating/adding the widget I’ve just printed self(PlayerController)->PlayerName in the OnRep Function and I get the following results in PIE.

When the server hosts the game and loads the lobby map:

Server: playername-DB123…

Which is fine because it ran when the server should have added itself to it’s player list.

When the Client joins:

Client 0: Playername-XB721…
Client 0:
Server: playername-DB123…

Which ain’t fine cause it runs once when the server in supposed to add the new client to it’s player list then the client runs twice for some unknown reason and for the 1’st print the PC doesn’t even have a name assigned.
Moreover I added a new replicated variable to the PC, this time a simple bool that has the same print function (in OnRep function) and with the bool it seems to run fine as in:

When the server hosts the game and loads the lobby map:

Server: playername-DB123…

When the Client joins:

Client 0: Playername-XB721…
Server: playername-DB123…

which leads me to believe the functionality is somehow related to the fact that my RepNotify variable is of tipe Array of Struct.

And again, I’m not trying to find a workaround or add a mindless delay (I’ve sworn not to use simple delays when something doesn’t work, not here, not in C++ not in Java not anywhere :rofl: ) but I’m trying to understand what could cause this behavior/what I did wrong so I can go on to more complicated replication later on in the development.

no that looks as intended,

there are 2 clients because there is the server version and the client version, this is how replication works.

your RepNotify will only fire on the client because you have IsLocalPlayer, this is correct, however it will fire every time the Array changes which means it can fire multiple times and you don’t have any logic to handle that

the bool only runs once because the value hasnt changed, true is still true, your array has changed because a new element was added

I’m using a Local Variable in the GM as a dummy to build the array before I use it to set the replicated variable. I’ve traced the execution and the SET function on the replicated variable runs exactly as it should.
ex: 2 times when the first client connects to the session - one time on the server, one time on the client.
The OnRep function runs:
-one time on Host/Servers PC,
-one time on Client’s PC (Server side - returns on IsLocalController)
-two (or sometimes three) times on clients PC (Client side). (even though the replicated SET function runs only once)

Also the bool is not just living there by it’s own, i’m switching true/false every time otherwise you are correct and it won’t fire the OnRep.

P.S. Switched the functionality to a replicated event (RunOnClient) and setting the PlayerInfo struct for each PC on BeginPlay. Replication seems to run fine this way but I still have to wait for the base PC->PlayerName variable (Engine default variable) to be set which I thought would be since the PC called it’s BeginPlay event.

it could be all those loops which seem unnecessary but its hard to follow the code so i’ll ask some questions.

what are you waiting for on UInitDone? and could it be an event rather than loop

what is the FOR loop even for, just add the lobby info directly to an array.

all of this should be handled in the gamemode or gamestate, no need to go into the controllers at all, you already have the Ref from casting.

Event OnPostLogin by definition says “Notification that a player has successfully logged in, and has been given a player controller” and I figured out it won’t guarantee that the BeginPlay event fully executes before you can acces anything from the newly assigned PC. ( And sometimes even engine defined and handled variables won’t be set properly such as PC->PlayerName )
UIInitDone is a replicated boolean in PC, used in order to avoid adding a simple delay, that would give the PC time to run it’s BeginPlay event and it’s set in the PC via ExecuteOnServer RPC in order to notify the server in (GameMode) that the newly assigned PC has finished initializing input modes and UI elements.


This is where I call the RPC and it’s on the execution sequence of the BeginPlay event in PC, with and without authority.

I understand where you’re going with the PlayerInfo array, I could have just multicast an updated array of PlayerInfo and refresh the UI accordingly but I went for a different aproach:

In the GM, when a new player connects, I loop over all the connected players and run the function UpdatePlayerLobbyInfo which gets as input:

  • The PC of the newly connected player - as ReqPC (Requesting PC)
  • The list of all connected PCs - as ConnectedPlayers
  • The PC on which it sets the RepNotify variable and runs the OnRep function - as RunOnPC.

UpdatePlayerLobbyInfo then checks if ReqPC is the same as RunOnPC

  • If true, this is the newly connected PC and has no lobby information. I add all the connected players info to a Local Variable Array of PlayerInfo and call UpdateLobbyInfo which sets the RepNotify variable in the PC → triggers OnRep function → Creates and adds all player cards to the clients ScrollBox.
  • If false, this is an already connected player which has lobby info about previous players connected, so it just needs to add the PlayerCard for the newly connected player so it sets the RepNotify variable to an array of size 1 containing the newly connected PlayerInfo → triggers OnRep function → Creates and adds the new PlayerCard to the clients ScrollBox.

Like so:

Now upon further testing if I add a long enough delay in the PC BeginPlay event ( Worked with 1 sec, didn’t work with 0.2 sec ) everything runs fine. The only reason I can come up with is that PC sets the RepNotify varaible and runs OnRep function, but the engine didn’t finish initializing the PC so it somehow triggers again.

Note that by tracing the execution, the RepNotify variable gets set only once on the server instance of the PC and calls OnRep but exits on IsLocalController then the corresponding Client side PC calls OnRep multiple times.

P.S. I’m sorry to bother you with this but I would really like to understand what goes under the hood or what I did wrong.

you could do it as simple as this,

in the player controller BeginPlay (or whenever you’re initialized) call an event when ready passing through the player data to the GameState

In the GameState the event updates the Array and calls RepNotify
UIs can bind to the RepNotify Event and update their widget list