What are the hooks for creating custom replication of a struct?

I have a struct that has a very large set of data (myVoxels) that I want to compress and replicate (myCompression). Below is my best attempt at doing this but when running in the visual studio debugger, neither Serialize or NetSerialize are being hit. My approach is based on the documentation in Runtime/Engine/Classes/Engine/NetSerialization.h.

FVoxelOctree: Should replicate myCompression when it is changed and update myVoxels by expanding myCompression when replication occurs.



USTRUCT()
struct MYCORE_API FVoxelOctree
{
	GENERATED_USTRUCT_BODY()

	Voxel Get(const FIntVector& voxelIndex) const;

	void Set(const FIntVector& voxelIndex, Voxel voxel);

	void SetMany(const TMap<FIntVector, Voxel>& voxelMap);
		
	bool Serialize(FArchive& archive);

	bool NetSerialize(FArchive& archive, class UPackageMap* packageMap, bool& isSuccess);

private:

	int32 GetArrayIndex(const FIntVector& voxelIndex) const;

	void Compress();

	bool Expand();

	Voxel myVoxels[kChunkSize * kChunkSize * kChunkSize];

	TArray<uint32> myCompression;

public:

	friend FArchive& operator<<(FArchive& archive, FVoxelOctree& voxelOctree)
	{
		voxelOctree.Serialize(archive);
		return archive;
	}
};

template<>
struct TStructOpsTypeTraits<FVoxelOctree> : public TStructOpsTypeTraitsBase
{
	enum
	{
		WithNetSerializer = true,
		WithSerializer = true,
	};
};


UChunkModel: Contains a FVoxelOctree



UCLASS()
class MYGAME_API UChunkModel : public UActorComponent
{
	GENERATED_BODY()

public:

	...

	void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
	{
		Super::GetLifetimeReplicatedProps(OutLifetimeProps);

		DOREPLIFETIME(UChunkModel, myVoxels);
	}

	...

private:

	UPROPERTY(Transient, Replicated)
	FVoxelOctree myVoxels;

};


AChunk: Contains a pointer to UChunkModel



UCLASS()
class MYGAME_API AChunk : public AActor
{
	GENERATED_BODY()

public:

	AChunk()
		: Super()
	{
		bReplicates = true;
	}

	void AChunk::SetModel(UChunkModel* model)
	{
		myModel = model;
		myModel->SetIsReplicated(true);
	}

	void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
	{
		Super::GetLifetimeReplicatedProps(OutLifetimeProps);

		DOREPLIFETIME(AChunk, myModel);
	}

	...

private:

	UPROPERTY(Transient, Replicated)
	UChunkModel* myModel;

};


No idea about the struct replication side of things, interesting if you can customize replication like that though.

But are you sure your UChunkModel component is itself being replicated? How do you create the model you pass to SetModel? It looks like you’re not creating it as a default subobject in the constructor, in which case I suspect you’d need to override ReplicateSubobjects in order to manually replicate it.

If you can just create it using CreateDefaultSubobject within the actor’s constructor, it would be easier.

Quick Edit: In all my typing of the answer, I forgot to say that kamrann was right. The root issue was CreateSubobject needed to be called from the constructor.

I redesigned it to create FVoxelOctree in the constructor for UChunkModel and to create UChunkModel in the constructor for AChunk. In the UChunkModel constructor I also call “SetIsReplicated(true)”. Now everything seems to be working.

The way it was setup before, I was creating each subobject using NewObject but was passing in the “Outer” pointer at some point during the programs execution (after BeginPlay was called on the spawning actor). I think NewObject is my worst enemy, it seems every time I try to use it, it causes headaches for me.

I’ve recreated (to the best of my memory & freehand) the changes I made to get this working. I also set “WithIdenticalViaEquality” on the FVoxelOctree but I think that was unrelated. Just putting this all out there in case someone else hits a similar issue.

UChunkModel Before:




...

UChunkModel::UChunkModel()
	: Super()
{
}

UChunkModel* UChunkModel::Create(UObject* outer, const FIntVector& chunkIndex, const TMap<FIntVector, Voxel>& voxels) // this is static
{
	UChunkModel* newModel = NewObject<UChunkModel>(outer);
	newModel->SetIsReplicated(true);
	newModel->myIndex = chunkIndex;
	newModel->myVoxels.SetMany(voxels)
	return newModel;
}

...



UChunkModel After:




...

UChunkModel::UChunkModel()
	: Super()
{
	SetIsReplicated(true);
}

UChunkModel::Initialize(const FIntVector& chunkIndex, const TMap<FIntVector, Voxel>& voxels)
{
	myChunkIndex = chunkIndex;
	myVoxels.SetMany(voxels)
}

...



AChunk Before:




...

AChunk::AChunk()
	: Super()
{
	bReplicates = true;

	RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("SceneComponent"));
}

AChunk::SetModel(UChunkModel* chunkModel)
{
	myModel = chunkModel;
}

...



AChunk After:




...

AChunk::AChunk()
	: Super()
{
	bReplicates = true;

	RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("SceneComponent"));

	myModel = CreateDefaultSubobject<UChunkModel>(TEXT("Model"));
}

void AChunk::Initialize(const FIntVector& chunkIndex, const TMap<FIntVector, Voxel>& voxels)
{
	myModel->Initialize(chunkIndex, voxels);
}

...