RepNotify shadow data is stale when single properties are changed

When replicating a property that is a struct containing multiple properties, the RepNotify “old” or “shadow” value is not being populated correctly. Rough repro steps:

USTRUCT()
struct FMyStruct
{
UPROPERTY(Transient)
float A;
UPROPERTY(Transient)
float B;
};
UPROPERTY(Transient, ReplicatedUsing = OnRep_StructProperty)
FMyStruct theStruct;
UFUNCTION()
void OnRep_StructProperty(FMyStruct const& oldStructVal) {}

On the server do the following set of operations over multiple frames:

  1. theStruct.A = 10.0f; theStruct.B = 0.0f;
  2. theStruct.A = 0.0f; theStruct.B = 5.0f; (these must be done at the same time)
  3. theStruct.B = 4.0f;
  4. theStruct.B = 3.0f;

The result on the client is that you’ll see OnRep_StructProperty called with the following current and “oldStructVal” values:

  1. theStruct{A = 10.0f, B = 0.0f} oldStructVal{A = 0.0f, B = 0.0f}
  2. theStruct{A = 0.0f, B = 5.0f} oldStructVal{A = 10.0f, B = 0.0f}
  3. theStruct{A = 0.0f, B = 4.0f} oldStructVal{A = 10.0f, B = 5.0f}
  4. theStruct{A = 0.0f, B = 3.0f} oldStructVal{A = 10.0f, B = 4.0f}

Note that the “A” value in the 3rd and 4th step are still set to 10.0 even though it was previously set to 0.0
This causes some serious problems if you’re relying on this functionality to fire events when a value passes a particular threshold, like dropping to 0 from a non-zero value.

The problem appears to be in RepLayout.cpp inside FRepLayout::ReceiveProperties and PROCESS_CMD( FReceivedPropertiesStackState ), where each individual property update command does an independent operation of {StorePropertyToShadowData, NetSerializeItem, AddRepNotifyIfModified}, leaving the shadow data in a partially updated state. The RepNotify is called with the whole object shadow copy, not just the modified properties, so the whole object needs to be refreshed before any properties are serialized.