Custom Net Serialization with editable UProperties and RPCs

Hello guys,

suppose I have a USTRUCT with a custom net serialize function and some (not serialized) uproperties for customizing the replication process within Blueprint (like quantization level for example) and I use these properties to branch within the net serialize function (similar to how it is done for FRepMovement in the engine). If I send that struct as argument of an RPC and changed the property values in the editor from the defaults, I will get a mismatch while reading the serialized values on the receiving machine. It makes sense why this happens (because the RPC argument on the receiving machine cannot be a member field and therefore always has the default C++ constructor values set on the uproperties when created), but I wanted to know if there is a way to make this work like it does with the server to client replication process without RPCs? Obviously without also sending the changed properties that is.

Yeah you would have to change the technique slightly for RPC’s. The receiver needs to be able to unpack the struct with the same parameters it was packed with. This is why for FRepMovement you can’t change the properties at runtime, they are defaults only (it also can’t be sent through RPC’s unless you send the whole struct).

If the struct has additional packing parameters that can change at runtime, or it has to be sent via RPC and has variable packing params, you have no choice but to send the packing params too. You could do this by using a different struct, or by manually flagging it so that it serializes differently before you call the RPC, e.g:



StructData.MarkSerializeForRPC();
Server_DoSomething(StructData);


AFAIK there’s no built-in way to define a different version of NetSerialize() for RPC vs UPROPERTY, but you can mimic it this way.

When you talk about runtime changes, do you mean the runtime of the editor or runtime of the game (in-editor play)? I realize that it wouldn’t be possible to change them at the game runtime without sending them, but I am talking about properties that you would change in the Blueprint archetype i.e. properties with the EditDefaultsOnly specifier. An example:



USTRUCT(BlueprintType)
struct FClientData
{
  GENERATED_BODY()

  // Replicated property.
  UPROPERTY(Transient)
  FVector InputVector{FVector::ZeroVector};

  // Editor properties, local only.
  UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
  bool bSerializeInputVectorX{true};
  UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
  bool bSerializeInputVectorY{true};
  UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
  bool bSerializeInputVectorZ{true};

  bool NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess);
};


bool FClientData::NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess)
{
  if (Ar.IsSaving()) {
    if (bSerializeInputVectorX) bOutSuccess &= WriteFixedCompressedFloat<2, 16>(InputVector.X, Ar);
    if (bSerializeInputVectorY) bOutSuccess &= WriteFixedCompressedFloat<2, 16>(InputVector.Y, Ar);
    if (bSerializeInputVectorZ) bOutSuccess &= WriteFixedCompressedFloat<2, 16>(InputVector.Z, Ar);
  }
  else {
    if (bSerializeInputVectorX) bOutSuccess &= ReadFixedCompressedFloat<2, 16>(InputVector.X, Ar);
    if (bSerializeInputVectorY) bOutSuccess &= ReadFixedCompressedFloat<2, 16>(InputVector.Y, Ar);
    if (bSerializeInputVectorZ) bOutSuccess &= ReadFixedCompressedFloat<2, 16>(InputVector.Z, Ar);
  }
  return true;
}

// Somewhere in my actor or component class:
UFUNCTION(Server, Reliable, WithValidation)
void Server_SendData(FClientData Data);
UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
FClientData MyClientData;

// someplace in the code with with role == autonomous proxy:
Server_SendData(MyClientData);


If I change any of the properties bSerializeInputVectorX, bSerializeInputVectorY, bSerializeInputVectorZ from the default value “true” in the editor (in the archetype before starting a game session), this won’t work anymore because on the server the RPC argument is constructed with only the C++ constructor filling in the values (everything true) and you get a mismatch on the server when deserializing. If it’s not possible to do this, this seems more like a missing feature than a technical limitation because they are already overriding the constructor values when replicating without RPCs.

When a member variable is replicated it is serialized in-place, which is why this works only when the struct is a replicated member because the Client already has the same values the Server has from the class. RPC’s can’t do that, the data passed by RPC is completely new data and has no reference to that member variable.

To make this work, you have no choice but to send the other params required to do serialize/deserialize properly. In this case in particular though, you only need three-bits (one for each bool), so it’s not worth breaking a sweat over. You can handle it pretty easily using Ar.SerializeBits().

Ok so it’s not possible to do this without sending the properties, thanks, that’s what I wanted to know.