[Iris] Why are NetSerializers so complex?

Hi,

Serializers are a bit more complicated especially for structs for mainly good reasons. We have separated certain steps in order to minimize CPU time during serialization thanks to the quantization step and also to support delta compression. Serialize/deserialize can really only deal with actual serialization and not game logic. Those functions work on the quantized representation of the data and not the source form of the data. Quantize/Dequantize are also not working on the source/target data. So dequantize will overwrite any property that is replicated. In your case it’s bad for two reasons- you didn’t mark the delegate as NotReplicated and you actually want to call the delegate. I think it’s a good idea to still mark the delegate as NotReplicated but most importantly you should implement Apply so that you can compare the values prior to overwriting the target value and optionally call your delegate. Apply is called at the same time we’re copying the other received replicated data to their respective target so we don’t pollute the instruction cache by calling game logic during deserialization.

IsEqual is used for both delta compression reasons but another reason is the ability to use a custom equality method. In your case because the entire struct is replicated (not having marked the delegate as NotReplicated) equality will happen via the structs Identical function which iterates over all properties and see if they are equal or not. So changing the delegate would force the struct to be considered changed and be replicated. I recommend marking your delegate as NotReplicated but you can also force your serializer’s IsEqual function to be called by setting

constexpr bool bUseSerializerIsEqual = true;true in your NetSerializer.

The dynamic state things are for the case when you have something in the quantized state requiring memory allocations. For baseline duplication we need to deep copy that data and for baseline freeing we obviously need to free the allocations as well. CollectNetReferences is so that we can call the appropriate OnReps when a previously no longer existing object starts replicating and is yet again instantiated and resolvable. You don’t need to implement this for your struct.

TMap/TSet. We’re not going to implement it in Iris unless it’s also implemented it for non-Iris. Iris does not yet support replay recording/playback so the same types need to be supported by both in order to not break that. It’s trivial to implement if just serializing the entire TMap or TSet but if you want to be smart about it similar to how FastArraySerializer tries to minimize what elements it replicates it’s quite complex. I’ve seen people wanting this and simply wrap a TMap or TSet in a struct and implement custom serialization, generating arrays for keys and values and then serializing them. Another way is using arrays, sort them when needed and use binary search algorithms to find the element. If coupled with a FastArray you also get minimal replication.

If you’re using the struct with delegate pattern a lot it does sound like you want to request a feature to have something similar to an OnRep on the struct to avoid all the hassle.

Generally speaking we would like for people to not having to implement serializer at all and cover the most common use cases. For example Iris does not require structs derived from a struct with custom serializer to also implement a custom serializer. With the NetSerialize method/trait not only do you need to know if the base implements a custom serializer you also need to implement it for all derived types. The SupportsStructNetSerializerList is there so you can ideally just marking properties as replicated or not and not have to implement a custom serializer unless it’s needed. It’s definitely good for a few of those existing use cases which forced you to implement a NetSerialize method even if you just added a couple of properties.

Cheers,

Peter