I ran into an issue that I’ll describe as best I can, hoping a replication system owner can tell me if what I saw is expected.
We have an actor component on our character called UModifierComponent. It has a property:
The modifiers in the array are themselves replicated subobjects, managed by this UModifierComponent. Although m_ActiveModifiers is replicated, the client UModifierComponent can choose to remove expired modifiers from the m_ActiveModifiers array when it ticks as well.
I noticed today that the client m_ActiveModifiers array was “garbled” in some cases, in the PostNetReceive. I saw duplicate entries of the same UModifier, and convinced myself 100% that the server was not sending duplicate entries. I also saw null entries, and it was missing UModifiers that it should’ve had, based on the server m_ActiveModifiers. Basically it looked like the replication update of the array was just wrong, and it did not match what the server sent.
On a hunch, I disabled the code that had the client remove modifiers from the m_ActiveModifiers array and instead just relied on replication to update the state. That made the problem go away, but it was also surprising, I thought that array updates from the server would just stomp the client array, the state of the array on the client shouldn’t matter.
Is it expected behavior that removing entries on a client from a replicated array would break replication?
Yes, this is expected behavior. To minimize bandwidth usage, the server will keep track of the state of replicated properties on the client - this allows the server to only send deltas when properties change. Modifying replicated properties directly on the client will cause this state to get out of sync.
Ah ok, I didn’t realize TArray did delta replication by default. What then is the difference/advantage of using FFastArraySerializer? I thought the Fast Array was THE delta array replication feature, which is why I assumed basic TArrays did not delta replicate.
Is it feasible to detect and throw asserts if a client modifies a replicated array?
The advantage of FFastArraySerializer is that it does faster delta replication :). The disadvantage is that you have to explicitly mark items as dirty - but that means the replication system doesn’t have to compare as much data as it does with default array replication.
Great, thanks for the quick reply. If it’s feasible, an assert to catch this issue would be ideal as the behavior right now can be subtly evil. We’ve had this bug for months I think, it only occurred once the length of the array grew long enough.
I agree that it would be useful to have some way to protect against this issue in all cases. We do have FObjectReplicator::ValidateAgainstState, which we use to verify that properties of dormant actors don’t change on the server, and it might be possible to do something similar for non-dormant actors on clients, but it would probably be non-trivial to implement. We’ll keep this in mind as a possible feature for a future version.