Ideal way to (fast) serialize UScriptStruct cross network

I am working on a multiplayer game in which I have need to send custom and often a layer nested UScriptStruct data over RPC call parameters. In many case, this UScriptStruct is a FBlackboard struct which is similar to Blackboard in Mover but support replication. FBlackboard roughly looks like:

USTRUCT(BlueprintType)
struct FBlackboard
{
  UPROPERTY()
  FBlackboardValues Values;
  UPROPERTY()
  TArray<FBlackboardStruct> Structs;
}
 
USTRUCT()
struct FBlackboardValues
{
...
    UPROPERTY(NotReplicated)
    TMap<FName, FBlackboardObject> Objects;
    UPROPERTY()
    TArray<FName> Objects_Key;    
    UPROPERTY()
    TArray<FBlackboardObject> Objects_Value;
    bool Serialize(FArchive& Ar);    
    bool NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess);
};
 
USTRUCT()
struct FBlackboardStruct
{
    ...
    bool Serialize(FArchive& Ar);	
    bool NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess);
    ...
    UPROPERTY()
    FName StructKey = NAME_None;	
    UPROPERTY()
    TArray<uint8> StructData;
    UPROPERTY(NotReplicated)
    const UScriptStruct* StructType;	
    UPROPERTY()
    FString StructPathName;
    UPROPERTY(NotReplicated)
    TMap<FName, FBlackboardObject> Objects;
    UPROPERTY()
    TArray<FName> Objects_Key;	
    UPROPERTY()
    TArray<FBlackboardObject> Objects_Value;
};
 
USTRUCT()
struct FBlackboardObject
{
...
    UObject* ResolveObject() const;	
    bool Serialize(FArchive& Ar);	
    bool NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess);
    friend FArchive& operator<<(FArchive& Ar, FBlackboardObject& Object);
 
#if WITH_EDITORONLY_DATA
    UPROPERTY()
    int32 PIEInstanceID = INDEX_NONE;
#endif	
    UPROPERTY()
    bool bObjectNull = true;
    UPROPERTY()
    bool bObjectStatic = false;
    UPROPERTY()
    bool bObjectNetworked = false;
    UPROPERTY()
    UClass* ObjectClass = nullptr;
    UPROPERTY()
    FString ObjectPathName;
    UPROPERTY()
    FString ObjectName;
    UPROPERTY()
    UObject* Object = nullptr;
    FNetworkGUID ObjectNewGUID; 
};

What I have issues with is how to correctly serialize FBlackboardStruct, which should work with any custom UScriptStruct declared in scripts (AngelScript). I used FObjectReader / FObjectWriter and StructData -> TArray<uint8>, mostly in following code FBlackboardStruct::CopyScriptStructTo / FBlackboardStruct::CopyScriptStructFrom. I also record NetGUID / Actor Path etc for UObject store them in FBlackboardObject so that I could resolve UObject cross network (not shown in code sample). I am quite sure this is NOT ideal way to send nested struct over network, but it works. I used to try FInstancedStruct, UE complains my UObject is not implementing IsSupportedForNetworking etc, it is not quite working as intended (and I stopped following this path). Until recently, my code still works. But recently in cooked game, InStructType->SerializeBin reliably crash at FBlackboardStruct::CopyScriptStructTo (this is RPC receiver call site) -> SerializeBin -> FObjectProperty::PostSerializeObjectItem, where ObjectValue points to a unallocated address. In this crash case, I send a struct contains a should-be replicated AActor pointer reference.

I also tried ScriptStruct->SerializeItem (after studying a bit from FInstancedStruct::Serialize), it needs more data size and it introduces even more unknown property correctness issues which I don’t quite understand yet.

Before I dive into the crash, I’d like some advice. Is my previous approach actually doable, or will it just not work?

void FBlackboardStruct::CopyScriptStructTo(const UScriptStruct* InStructType, void* OutStructMemory) const
{
    FMemory::Memzero(OutStructMemory, InStructType->GetStructureSize());
    
    FObjectReader ObjectReader(StructData);
    TObjectPtr<const UScriptStruct> ScriptStruct = InStructType;
    InStructType->SerializeBin(ObjectReader, OutStructMemory);
    // ConstCast(ScriptStruct)->SerializeItem(ObjectReader, OutStructMemory, ScriptStruct);
    // Search object property to resolve separately
...
}
 
void FBlackboardStruct::CopyScriptStructFrom(const UScriptStruct* InStructType, void* InStructMemory)
{
    TArray<uint8> ObjectBuffer;
    FObjectWriter ObjectWriter(ObjectBuffer);
    TObjectPtr<const UScriptStruct> ScriptStruct = InStructType;
    InStructType->SerializeBin(ObjectWriter, InStructMemory);
    // ConstCast(ScriptStruct)->SerializeItem(ObjectWriter, InStructMemory, ScriptStruct);
    StructData = TArray<uint8>(ObjectBuffer.GetData(), ObjectBuffer.Num());
    StructPathName = InStructType->GetPathName();
}

[Attachment Removed]

Steps to Reproduce

I used hazelight’s angelscript modded UE 5.6.0, used Replication Graph (similar to Lyra Sample), Steam Socket for networking.

[/Script/Engine.Engine]

!NetDriverDefinitions=ClearArray

+NetDriverDefinitions=(DefName=“BeaconNetDriver”,DriverClassName=“/Script/SteamSockets.SteamSocketsNetDriver”,DriverClassNameFallback=“/Script/SocketSubsystemSteamIP.SteamNetDriver”)

+NetDriverDefinitions=(DefName=“GameNetDriver”,DriverClassName=“/Script/SteamSockets.SteamSocketsNetDriver”,DriverClassNameFallback=“/Script/OnlineSubsystemUtils.IpNetDriver”)

[/Script/OnlineSubsystemUtils.IpNetDriver]

NetServerMaxTickRate=60

[/Script/SteamSockets.SteamSocketsNetDriver]

NetServerMaxTickRate=60

[Attachment Removed]

managed to track down the source of crash. It seems to be UObject pointer get serialize as address in FObjectReader / FObjectWriter, this address would serialize to pointer to unallocated address in remote side (which cause the crash). So I copied the script struct memory and allocate a temp script struct in FBlackboardStruct::CopyScriptStructFrom, later I nullify all UObject property before serilization, this fixed my issues.

void FBlackboardStruct::CopyScriptStructFrom(const UScriptStruct* InStructType, void* InStructMemory)
{
    TArray<uint8> ObjectBuffer;
    FObjectWriter ObjectWriter(ObjectBuffer);
    ObjectWriter.SetWantBinaryPropertySerialization(true);
    
    void* CopiedStructMemory = CreateScriptStructCopy(InStructType, InStructMemory);
    if (CopiedStructMemory)
    {
        for (FProperty* Property = InStructType->PropertyLink; Property != NULL; Property = Property->PropertyLinkNext)
        {
            if (const FObjectProperty* ObjectProperty = CastField<FObjectProperty>(Property))
            {
                void* ObjectDataPtr = ObjectProperty->ContainerPtrToValuePtr<void>(CopiedStructMemory);
            
                // Nullify object pointer so that remote side could not serialize from random address.
                ObjectProperty->SetObjectPtrPropertyValue(ObjectDataPtr, TObjectPtr<UObject>(nullptr));
            }
        }
    }
    
    if (CopiedStructMemory)
    {
        TObjectPtr<const UScriptStruct> ScriptStruct = InStructType;
        ConstCast(ScriptStruct)->SerializeItem(ObjectWriter, CopiedStructMemory, nullptr);
        StructData = TArray<uint8>(ObjectBuffer.GetData(), ObjectBuffer.Num());
    }
    
    StructPathName = InStructType->GetPathName();
 
    if (CopiedStructMemory)
    {
        FMemory::Free(CopiedStructMemory);
    }
}

Anyway, my serialization got patched, it still bother me that whether this method is the correct approach …

[Attachment Removed]

Only NetSerialize is supported for replication. You may have a custom struct whose Serialize is usable by its NetSerialize implementation but that would be up to that struct. For FInstancedStruct in particular you cannot call its Serialize for replication and that is the likely cause of the serialization error you get in your most recent experiment. It uses operations, Seek and Tell, that aren’t supported by NetSerialize. You can use FInstancedStruct as a shortcut not having to retrieve a FRepLayout for your struct but it’s quite expensive as it will new the memory for each instance and serialize the type of it, which you’ll always know is exactly a FBlackboardObject, in addition to the struct data itself. You could instead retrieve the FRepLayout for the struct and use it to serialize each value in a TArray<FBlackboardObject>. Something like:

UPackageMapClient* MapClient = Cast<UPackageMapClient>(Map);
UNetConnection* NetConnection = MapClient->GetConnection();
const TSharedPtr<FRepLayout> RepLayout = NetConnection->GetDriver()->GetStructRepLayout(Struct);
 
bool bHasUnmapped = false;
iterate over all values in the array...
RepLayout->SerializePropertiesForStruct(Struct, static_cast<FBitArchive&>(Ar), Map, &Array[Index], bHasUnmapped);

So implement NetSerialize for FBlackboardStruct and make sure to mark it as having such an implementation:

template<>
struct TStructOpsTypeTraits<FBlackboardStruct> : public TStructOpsTypeTraitsBase2<FBlackboardStruct>
{
	enum
	{
		WithNetSerializer = true,
	};
};

Cheers,

Peter

[Attachment Removed]

Hi,

You should be using replication approved means to serialize the data. That means using appropriate NetSerialize methods provided with FNetBitWriter/FNetBitReader. You should be using an FInstancedStruct for the abritrary struct with data serialization, but there’s no need to have CopyScriptStructFrom do any serialization. Just initialize an FInstancedStruct member with the struct data- the FInstancedStruct will make a copy! In CopyScriptStructTo you can use something like the below assuming the InstancedStruct has been initialized

InStructType->CopyScriptStruct(OutStructMemory, InstancedStruct.GetMemory(), 1);Cheers,

Peter

[Attachment Removed]

Hi,

I’m glad you were able to track down the source of your crash!

As for whether this is the correct approach, it’s hard to say what the best way to go about implementing something like this would be, as there isn’t any built-in way to net serialize an arbitrary struct’s properties from an owning struct’s NetSerialize function.

The approach generally used in this situation is to implement a NetSerialize function on the nested struct(s), so the owning struct’s NetSerialize can call into this function. You can see this approach used by FGameplayAbilityTargetDataHandle::NetSerialize.

This related case has some more info that you may find helpful: [Content removed]

Thanks,

Alex

[Attachment Removed]

Thanks for replying. FGameplayAbilityTargetDataHandle::NetSerialize is a very nice example. I think NetSerialize is not available for custom USTRUCT unless they declared HasNetSerializer trait. Unless I prepare to implement NetSerialize for all my struct (which is not likely), I can’t use your suggested standard approach. Is it possible to access default implementation of UHT based implementation of serialization of USTRUCT? Or only other viable approach left is like what I did by using FObjectWriter or FMemoryWriter and serialize TArray<uint8>?

Another things that I like to ask is how should UObject reference generally be handled in serliazation. I currently use FNetworkGUID for non-name-stable objects. I find FNetworkGUID in NetDriver->GuidCache, which is never guaranteed to exist for actors / objects that not finishing replication. In my current code, when FNetworkGUID is not resolved, resolve process would set reference to null in receiving side. It seems that FInstancedStruct is aware of UObject reference (implied from unreal reporting UObject::IsSupportedForNetworking), but I cannot confirm it. Would you recommend sending FInstancedStruct that contains UObject reference over network?

Wuxiang

[Attachment Removed]

Hi,

That is fair, as implementing custom NetSerialize functions on every possible struct you may need might not be feasible. Another approach I’ve heard used is iterating through each of the nested struct’s properties and manually serializing each one. You can see FGameplayAbilityTargetDataHandle::NetSerialize falls back to this approach if a native NetSerialize can’t be found, but like the comments there point out, this approach will fail if the struct has any UStructProperties of its own.

As for serializing UObject references across the network, this is handled by the PackageMapClient (please note that this is a bit of a misnomer, as the PackageMapClient is used on both the server and client). For example, when we serialize a UObject pointer to a FNetBitWriter archive, this calls UPackageMapClient::SerializeObject.

As you pointed out, just checking if a NetGUID exists for an object may not work, as the object may not have one. UPackageMapClient::SerializeObject will handle assigning a NetGUID if needed, as well as sending the object’s path in cases where this is needed for referencing the object across the network (for stably-named objects).

Finally, you can see what kinds of objects can be referenced across the network here: https://dev.epicgames.com/documentation/en\-us/unreal\-engine/replicate\-actor\-properties\-in\-unreal\-engine\#replicateobjectreferences

Thanks,

Alex

[Attachment Removed]

Thanks for replying. I tracked how UPackageMapClient::SerializeObject works in code someday ago, now I undertand roughly how NetGUID get generated and cached. As you see, I use custom implemented FBlackboardObject to support UObject reference cross nework (tracks AActor’s level path, FNetworkGUID, PIEInstance, etc).

My actual issue is, is there better option or some battle-hardened official struct that is better for this role? Because my implementation is somehow prone to bugs from a lot of edge cases. Or should I manage to call NetSerialize on all UObjectProperty on nested UScriptStruct, letting unreal handle the rest (cross PIEs, cross network, etc). But from what I recalled, >>operator on UObject would only serialize its address.

UObject* FBlackboardObject::ResolveObject() const
{
    if (bObjectNull)
    {
        return nullptr;
    }
    
    if (bObjectStatic)
    {
        if (ObjectClass->IsChildOf(UClass::StaticClass()))
        {
            if (UClass* FoundClass = FindObject<UClass>(nullptr, *ObjectPathName))
            {
                return FoundClass;
            }
 
            // Load class synchronously if not found
            UClass* LoadedClass = LoadObject<UClass>(nullptr, *ObjectPathName);
            return LoadedClass;
        }
        else if (ObjectClass->IsChildOf(AActor::StaticClass()))
        {
#if WITH_EDITOR
            ensure(PIEInstanceID != INDEX_NONE);
            // return Helper::GetActorFromPathNameInPIE(ObjectPathName, PIEInstanceID, UE::GetPlayInEditorID());
            return Helper::GetActorFromName(ObjectName, UE::GetPlayInEditorID());
#else
            return Helper::GetActorFromName(ObjectName, INDEX_NONE);
#endif
        }
        else
        {
            ensureMsgf(false, TEXT("FBlackboardObject::ResolveObject: Unable to resolve object class %s"), *ObjectClass->GetName());
            return nullptr;
        }
    }
    else
    {
        if (bObjectNetworked)
        {		
#if WITH_EDITOR
            ensure(PIEInstanceID != INDEX_NONE);
            return Helper::GetObjectFromNetGUIDInPIE(ObjectNetGUID, UE::GetPlayInEditorID());
#else
            return Helper::GetObjectFromNetGUIDInPIE(ObjectNetGUID, INDEX_NONE);
#endif	
        }
        else
        {
            return Object;
        }
    }
}
 
bool FBlackboardObject::Serialize(FArchive& Ar)
{
    Ar.SerializeBits(&bObjectNetworked, 1);
    Ar.SerializeBits(&bObjectNull, 1);
    Ar.SerializeBits(&bObjectStatic, 1);
    
#if WITH_EDITORONLY_DATA
    Ar << PIEInstanceID;
#endif
    
    Ar << ObjectClass;
 
    // When stably named, we find object by its name or path name
    if (bObjectStatic)
    {	
        Ar << ObjectPathName;
        Ar << ObjectName;
    }
    // When not stably named, we find object by ID or by address ...
    else
    {
        if (bObjectNetworked)
        {
            Ar << ObjectNetGUID;
        }
        else
        {
            Ar << Object;	
        }
    }
    
    return true;
}

Many thanks,

Wuxiang

[Attachment Removed]

Hi,

FInstancedStruct is supported for replication (see FInstancedStruct::NetSerialize), so this may be an option. However, I’m not sure why you were seeing issues with IsSupportedForNetworking, so I’m going to loop in someone more familiar with FInstancedStruct net serialization to get their input.

Thanks,

Alex

[Attachment Removed]

I just tried to embedded my FBlackboardObject, which containing UObject reference into FInstancedStruct.

USTRUCT()
struct FBlackboardObject
{
    GENERATED_BODY()
    
    FBlackboardObject();
 
    explicit FBlackboardObject(UObject* InObject);
 
    UObject* ResolveObject() const;	
 
    // Object is null
    UPROPERTY()
    bool bObjectNull = true;
 
    // Object is stably named
    UPROPERTY()
    bool bObjectStatic = false;
 
    // Object class
    UPROPERTY()
    UClass* ObjectClass = nullptr;
 
    // Object path name
    UPROPERTY()
    FString ObjectPathName;
 
    // Object stable name
    UPROPERTY()
    FString ObjectName;
 
    // Object reference in the same process
    UPROPERTY()
    UObject* Object = nullptr;
};
USTRUCT()
struct FBlackboardStruct
{
    GENERATED_BODY()
    
    FBlackboardStruct() = default;
    
    FBlackboardStruct(FName StructKey, const UScriptStruct* StructType, void* StructMemory);
 
    void CopyScriptStructTo(const UScriptStruct* InStructType, void* OutStructMemory) const;
    
    void CopyScriptStructFrom(const UScriptStruct* InStructType, void* InStructMemory);
 
    void CopyScriptObjectFrom(const UScriptStruct* InStructType, void* InStructMemory);
 
    bool Serialize(FArchive& Ar);
    
    bool NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess);
 
    friend FArchive& operator<<(FArchive& Ar, FBlackboardStruct& Struct);
 
    ...
    
    // Object data stored inside struct. Only allow referencing object in StructType rather than nested StructType
    UPROPERTY(NotReplicated)
    TMap<FName, FBlackboardObject> Objects;
 
    UPROPERTY()
    TArray<FName> Objects_Key;
    
    UPROPERTY()
    TArray<FInstancedStruct> Objects_Struct;
};
bool FBlackboardStruct::Serialize(FArchive& Ar)
{
    // Objects
    int32 Objects_Num = 0;
    if (Ar.IsSaving())
    {	
        Objects.GenerateKeyArray(Objects_Key);
        
        TArray<FBlackboardObject> Objects_Value;
        Objects.GenerateValueArray(Objects_Value);
        Objects_Num = Objects.Num();
        Objects_Struct.SetNumZeroed(Objects_Num);		
        for (int KeyIndex = 0; KeyIndex < Objects_Key.Num(); ++KeyIndex)
        {
            Objects_Struct[KeyIndex] = FInstancedStruct::Make(Objects_Value[KeyIndex]);
        }
    }
    
    Ar << Objects_Key;
    Ar << Objects_Num;
 
    // Objects_Struct.	
    if (Ar.IsLoading())
    {
        Objects_Struct.SetNumZeroed(Objects_Num);
    }
    
    for (int32 ObjectIndex = 0; ObjectIndex < Objects_Num && !Ar.IsError(); ++ObjectIndex)
    {
        Objects_Struct[ObjectIndex].Serialize(Ar);
    }
 
    if (Ar.IsLoading())
    {
        Objects.Reset();
        for (int KeyIndex = 0; KeyIndex < Objects_Key.Num(); ++KeyIndex)
        {
            FBlackboardObject Object = Objects_Struct[KeyIndex].Get<FBlackboardObject>();			
            Objects.Emplace(Objects_Key[KeyIndex], Object);
        }
    }
 
    ...
}

My attempt is not very successful. I got in PIE with Listener Server setup:

[2026.01.28-20.33.44:970][281]LogRep: Error: ReceiveProperties_r: Failed to receive property, BunchIsError - Property=SiteBlackboard, Parent=12, Cmd=17, ReadHandle=18

[2026.01.28-20.33.44:970][281]LogRep: Error: RepLayout->ReceiveProperties FAILED: BP_SiteGrow_C /Game/Private/Level/Feature/Performance/UEDPIE_1_L_Site_Tiny_OP.L_Site_Tiny_OP:PersistentLevel.BP_SiteGrow_C_0

I actually don’t know how to effectively debug ReceiveProperties_r error.

[Attachment Removed]

Hi Alex. I did manage to track down my ReceiveProperties_r issue here. I incorrectly mix Serialize and NetSerialize together, which cause ReceiveProperties_r showing up on NetSerialize FInstancedStruct. Using Serialize as NetSerialize also cause network reference is serialized as pointer, which is root of all issues … After fixing that, most serialization now work out alright. Some other related issues surely be iron out shortly!

Thanks,

Wuxiang

[Attachment Removed]

Hi Peter, your suggestion was very helpful. I did re-implement all NetSerialize with FRepLayout.

Regarding FInstancedStruct, if I can ensure all UObject pointers are nullified, is it safe to serialize it with FObjectWriter / FObjectReader to byte array and send it over network? As I mentioned earlier in FBlackboardStruct::CopyScriptStructFrom and FBlackboardStruct::CopyScriptStructTo, which I relist them here. I still struggle with FInstancedStruct::Serialize crash triggered in FBlackboardStruct::CopyScriptStructTo.

void* CreateScriptStructCopy(const UScriptStruct* ScriptStruct, const void* InStructMemory)
{
    if (!ensure(ScriptStruct))
    {
        return nullptr;
    }
 
    void* StructMemory = FMemory::Malloc(ScriptStruct->GetStructureSize(), ScriptStruct->GetMinAlignment());
    ScriptStruct->InitializeStruct(StructMemory);
    ScriptStruct->CopyScriptStruct(StructMemory, InStructMemory);
 
    // Avoid this because it won't work with FString, TArray, TMap, FInstancedStruct!
    // FMemory::Memcpy(StructMemory, InStructMemory, ScriptStruct->GetStructureSize());
 
    return StructMemory;
}
 
void DestroyScriptStruct(const UScriptStruct* ScriptStruct, void* OutStructMemory)
{
    if (ScriptStruct && OutStructMemory)
    {
        ScriptStruct->DestroyStruct(OutStructMemory);
        FMemory::Free(OutStructMemory);
    }
}
 
void FBlackboardStruct::CopyScriptStructTo(const UScriptStruct* InStructType, void* OutStructMemory) const
{
    if (StructData.Num() > 0)
    {
        FObjectReader ObjectReader(StructData);
        ObjectReader.SetWantBinaryPropertySerialization(true);
        TObjectPtr<const UScriptStruct> ScriptStruct = InStructType;	
        ConstCast(ScriptStruct)->SerializeItem(ObjectReader, OutStructMemory, nullptr);
    }
 
    // Search object property to resolve separately
    for (FProperty* Property = InStructType->PropertyLink; Property != NULL; Property = Property->PropertyLinkNext)
    {
        if (const FObjectProperty* ObjectProperty = CastField<FObjectProperty>(Property))
        {
            void* ObjectDataPtr = ObjectProperty->ContainerPtrToValuePtr<void>(OutStructMemory);
            if (ensure(Objects.Contains(ObjectProperty->GetFName())))
            {
                TObjectPtr<UObject> ObjectValue = Objects[ObjectProperty->GetFName()].ResolveObject();
                
                // For some unknown reason, much more reliable and safer than SetValue_InContainer	
                ObjectProperty->SetObjectPtrPropertyValue(ObjectDataPtr, TObjectPtr<UObject>(ObjectValue));	
            }
        }
    }
}
 
void FBlackboardStruct::CopyScriptStructFrom(const UScriptStruct* InStructType, void* InStructMemory)
{
    TArray<uint8> ObjectBuffer;
    FObjectWriter ObjectWriter(ObjectBuffer);
    ObjectWriter.SetWantBinaryPropertySerialization(true);
    
    void* CopiedStructMemory = CreateScriptStructCopy(InStructType, InStructMemory);
    if (CopiedStructMemory)
    {
        for (FProperty* Property = InStructType->PropertyLink; Property != NULL; Property = Property->PropertyLinkNext)
        {
            if (const FObjectProperty* ObjectProperty = CastField<FObjectProperty>(Property))
            {
                void* ObjectDataPtr = ObjectProperty->ContainerPtrToValuePtr<void>(CopiedStructMemory);            
                // Nullify object pointer so that remote side could not serialize from random address.
                ObjectProperty->SetObjectPtrPropertyValue(ObjectDataPtr, TObjectPtr<UObject>(nullptr));
            }
        }
    }
    
    if (CopiedStructMemory)
    {
        TObjectPtr<const UScriptStruct> ScriptStruct = InStructType;
        ConstCast(ScriptStruct)->SerializeItem(ObjectWriter, CopiedStructMemory, nullptr);
        StructData = TArray<uint8>(ObjectBuffer.GetData(), ObjectBuffer.Num());
    }
    
    StructPathName = InStructType->GetPathName();
 
    if (CopiedStructMemory)
    {
        DestroyScriptStruct(InStructType, CopiedStructMemory);
    }
}

In my use case, I would enclose a lot of different struct (mostly implemented in AngelScript) with FInstancedStruct and serialize to byte array and send over network. Receiving side would know exactly what UScriptStruct to expect in FInstancedStruct and use InstancedStruct.Get<FXXX>() to fetch corresponding struct. It is used to work. But in cooked game, I continue to hit this crash.

[2026.01.29-16.04.12:780][144]LogWindows: Error: === Critical error: ===

[2026.01.29-16.04.12:780][144]LogWindows: Error:

[2026.01.29-16.04.12:780][144]LogWindows: Error: Fatal error!

[2026.01.29-16.04.12:780][144]LogWindows: Error:

[2026.01.29-16.04.12:780][144]LogWindows: Error: Unhandled Exception: EXCEPTION_ACCESS_VIOLATION reading address 0x00007ff3e98aaf8c

[2026.01.29-16.04.12:780][144]LogWindows: Error:

[2026.01.29-16.04.12:780][144]LogWindows: Error: [Callstack] 0x00007ff706bcaa3f Game.exe!FInstancedStruct::Serialize() [B:\UnrealProjects\Game\Engine\Source\Runtime\CoreUObject\Private\StructUtils\InstancedStruct.cpp:161]

[2026.01.29-16.04.12:780][144]LogWindows: Error: [Callstack] 0x00007ff706bd618a Game.exe!UScriptStruct::SerializeItem() [B:\UnrealProjects\Game\Engine\Source\Runtime\CoreUObject\Private\UObject\Class.cpp:3352]

[2026.01.29-16.04.12:780][144]LogWindows: Error: [Callstack] 0x00007ff706eb3f38 Game.exe!FStructProperty::SerializeItem() [B:\UnrealProjects\Game\Engine\Source\Runtime\CoreUObject\Private\UObject\PropertyStruct.cpp:171]

[2026.01.29-16.04.12:780][144]LogWindows: Error: [Callstack] 0x00007ff706bce22a Game.exe!UStruct::SerializeBin() [B:\UnrealProjects\Game\Engine\Source\Runtime\CoreUObject\Private\UObject\Class.cpp:1276]

[2026.01.29-16.04.12:780][144]LogWindows: Error: [Callstack] 0x00007ff706bd61b4 Game.exe!UScriptStruct::SerializeItem() [B:\UnrealProjects\Game\Engine\Source\Runtime\CoreUObject\Private\UObject\Class.cpp:3374]

[2026.01.29-16.04.12:780][144]LogWindows: Error: [Callstack] 0x00007ff706bd5f8e Game.exe!UScriptStruct::SerializeItem() [B:\UnrealProjects\Game\Engine\Source\Runtime\CoreUObject\Private\UObject\Class.cpp:3307]

[2026.01.29-16.04.12:780][144]LogWindows: Error: [Callstack] 0x00007ff710f09d32 Game.exe!FBlackboardStruct::CopyScriptStructTo() [B:\UnrealProjects\Game\Game\Plugins\CommonSource\Common\Private\Blackboard.cpp:173]

bool FInstancedStruct::Serialize(FArchive& Ar, UStruct* DefaultsStruct, const void* Defaults)
{
  ...
 
  if (Ar.IsLoading())
  {
    // UScriptStruct type
    UScriptStruct* SerializedScriptStruct = nullptr;
    Ar << SerializedScriptStruct;
    if (SerializedScriptStruct)
    {
      Ar.Preload(SerializedScriptStruct);
    } 
 
    // Initialize only if the type changes.
    if (ScriptStruct != SerializedScriptStruct)
    {
      InitializeAs(SerializedScriptStruct); // <<<<<<<<<<<<<<<<<<<<<<<<<<< Crash Here.
    }
  } 
 
  ...
}

In crash site, SerializedScriptStruct points to some unknown un-allocated memory address. I know UScriptStruct is a UObject. From what I have seen with UClass, it probably won’t work over network with FObjectReader / FObjectWriter. If it doesn’t, what might be my alternative for byte array serialization and recovery over network?

Edit: Consider byte array serialization / deserialization over network isn’t feasible. I decided to use FInstancedStruct as struct container for FBlackboardStruct and forward FBlackboardStruct::Serialize / FBlackboardStruct::NetSerialize to FInstancedStruct::Serialize / FInstancedStruct::NetSerialize and implement IsNameStableForNetworking for my AS script struct so that FInstancedStruct::SerializedScriptStruct can correctly NetSerialize. This use case is tested and confirmed to be working.

Thanks,

Wuxiang

[Attachment Removed]

Yeah, I current working solution just did what you said. I have come to understand just a few FArchive type can reconstruct UObject reliably. These probably include FNetBitWriter / FNetBitReader, FJsonStringifyArchive, FObjectAndNameAsStringProxyArchive, etc. These may still be used for my original purpose to store them and send over network. But I won’t do that after actually understanding NetSerialize and Serialize better.

Thanks a lot for the clarification.

Wuxiang

[Attachment Removed]