Acting on replication of an array

Please bare with me, I don’t have access to UE4 at the minute so class the poor code below as pseudocode as it probably isn’t valid :slight_smile:

I have some code which currently does something like this:

private:
	TArray<SomeThing> ArrayOfThings;
	
public:
	void AddThing(const SomeThing& Thing)
	{
		ArrayOfThings.Add(Thing);
		OnThingAdded(Thing);
	}
	
	void RemoveThing(const SomeThing& Thing)
	{
		ArrayOfThings.Remove(Thing);
		OnThingRemoved(Thing);
	}

To network this I can replicate the array and be notified when it changes but I can’t know whether something was added or removed to call the relevant OnThingAdded/Removed() on the client.

I could change the OnThingAdded/Removed() functions to be multicast but there’s no guarantee that the array will be replicated before the clients get the function call, which the function call could depend on.

So my current idea is to have the Add/RemoveThing() functions as multicast so that the server and clients will all do things in the right order (assume for the purposes of this that the clients won’t be allowed to call these functions).

I gather that all replicated functions will happen in the correct order so if I AddThing() and then RemoveThing() on the server they’ll play out in the same order on the client. However, let’s say I had a third function called ClearThings() which clears the array, but isn’t set to multicast… If I call AddThing() immediately followed by ClearThings() on the server, isn’t it possible that by the time the AddThing() function is played out on the clients, they could already have received the new cleared array, causing the same issue as above?

Any thoughts or suggestions are much appreciated.

1 Like

Interesting question. Was wondering that myself after reading that, so I did some digging. Found this

Particularly DennyR has what seems to be the solution of comparing the previous value before replication. By so

/** declaration of a replciated actor array*/
UPROPERTY(Transient, BlueprintReadOnly, ReplicatedUsing = OnRep_Array)
TArray<class AActor*> ReplicatedActors;

void AYourActor::OnRep_Array(TArray<class AActor*> PreReplicationArray)
{
	//ReplicatedActors has already been replicated as this point.
        //PreReplicationArray has the values pre-replication.
}

Did a further check into the Engine’s source code to see if I can find more examples of being able to check old values.
In SceneComponent.cpp there is this

void USceneComponent::OnRep_Visibility(bool OldValue)
{
	bool ReppedValue = bVisible;
	bVisible = OldValue;
	SetVisibility(ReppedValue);
}

I’m sure this is one of many actual use cases. So it seems like this is a legit way of checking/comparing what changed after the replication.

Yes, I actually do something similar to this elsewhere, storing off two version of the item so I can compare. I didn’t realise you could pass an argument to get the previous state, though. That’s very handy!

However, the problem with this approach with my above example is if I change the state multiple times in an update, only the very latest state is sent to the clients. But I want to be able to call OnThingAdded/Removed() each time something is added/removed.

In my case the replicated functions are reliable so we can ignore the unreliable case. That just opens up a whole new world of issues :slight_smile:

So, let’s say that the OnThingAdded/Remove functions just print out the size of the array (my actual ones are far more complicated but that’s not required to address this issue).

This is what I think could happen:

Server:
Array count is 1.
Calls AddThing().
Array count is 2.
OnThingAdded prints “New Thing Added, size now 2!”.
Calls ClearThings().
Array count is 0.

Client:
Array count is 1.
Gets replicated state of array (from server’s ClearThings() call).
Array count is 0.
Gets RPC call to AddThing().
Array count is 1.
OnThingAdded prints “New Thing Added, size now 1!”.

Does that make sense?

Oh huh. That’s an interesting issue. I’ll dig through the source code to see if I find anything that gives any indication of the behavior of that. The behavior as in what happens when OnRep_ gets called out of order.

Lets take the ClearThings scenario.

Array count is 1

Server performs AddThing() → Array count is 2

Server performs ClearThings() → Array count is 0

For the sake of argument, lets say the data for AddThing() was lost and that change was never replicated to the client.

Then the client would just get the changes for ClearThings, which becomes a problem.

Now for the sake of argument lets say it’s always reliable, but the order could be different due to networking issues.

If client gets the change for ClearThings, and it would show from 1 to 0. (?)
When client gets the change for AddThing, does it then show from 0 to 2?

EDIT: For anyone interested in looking into how the Engine does replication, the related code seems to be in DataReplication.cpp

Yeah. Makes sense, I think that’s what I was imagining too.

I would make the assumption that the Engine must have some way to avoid or detect that, because otherwise that problem becomes a lot more common when it comes to movement.

So I also found this in DataReplication.cpp within the FObjectReplicator::QueuePropertyRepNotify method(version 4.12.2, d727689444e4fbd689f5e7d924cd0c5b869fccea)

	// @todo UE4 - not checking if replicated value is changed from old.  Either fix or document, as may get multiple repnotifies of unacked properties.

That seems to indicate multiple OnReps would be made, but then the properties are unacknowledged? Meaning, you would get the OnRep call, but the actual value wouldn’t be changed to 1. It will still be 0 because the Engine ignores setting the array to 1 since its an unacknowledged property.