[SaveGame] How to handle Dynamic ActorComponents

I’ve been struggling with saving dynamically spawned actor components for a few days, and I’m hoping someone here might have dealt with this problem.
I’ve posted this on AnswerHub (here), but I figured it would be a better place to discuss on the forums.

I’m aiming to save and load an actor who has dynamic actor components added at runtime.
I haven’t been able to find examples of saving and loading dynamic components.

The actor is saving in my implementation, and it seems to properly save and load static components (eg. it’s static mesh is set properly on load),
but I’m having trouble with dynamic components added at runtime - they are not populated on load.

I’m using the SaveGame object method of saving and loading (UGameplayStatics::{Load/Save}GameFromSlot), and the actor is being serialized like this:



FActorRecord record;
record.Name = Actor->GetFName();
record.Class = Actor->GetClass();
record.AttachSocketName = Actor->GetAttachParentSocketName();
record.Transform = Actor->GetTransform();
     
FMemoryWriter MemoryWriter(record.ActorData, false);
FH2SaveGameArchive Ar(MemoryWriter, false);
Actor->Serialize(Ar);


After this, I loop through all the components, serialize them, and store them on the Actor record.

The records look like this:



USTRUCT()
struct FComponentRecord
{
	GENERATED_BODY()
public:
	UPROPERTY(SaveGame)
	FName Name;

	UPROPERTY(SaveGame)
	UClass* Class;

	UPROPERTY(SaveGame)
	FTransform RelativeTransform;

	UPROPERTY(SaveGame)
	TArray<uint8> ComponentData;
};

USTRUCT()
struct FActorRecord 
{
	GENERATED_BODY()
public:
	UPROPERTY(SaveGame)
	FName Name;

	UPROPERTY(SaveGame)
	USceneComponent* AttachParent;

	UPROPERTY(SaveGame)
	FName AttachSocketName;

	UPROPERTY(SaveGame)
	UClass* Class;

	UPROPERTY(SaveGame)
	FTransform Transform;

	UPROPERTY(SaveGame)
	FVector_NetQuantize100 LinearVelocity;

	UPROPERTY(SaveGame)
	FVector_NetQuantize100 AngularVelocity;
	
	UPROPERTY(SaveGame)
	TArray<uint8> ActorData;

	UPROPERTY(SaveGame)
	TArray<FComponentRecord> Components;
};


The save portion SEEMS to work fine.

On Load, I do a similar chore as above with the actor (except with a MemoryReader instead of a MemoryWriter).
I then loop through the component records stored along with it, find the related component or spawn a new one if it doesn’t exist, and then deserialize the component (using the UObject::Serialize(Ar) method).

Some of the components work fine on reload, but the dynamically spawned ones end up in an infinite loop in Class.cpp::SerializeTaggedProperties.
EDIT: and eventually crashes specifically on this line of FName::InitInternal called by FNameAsStringProxyArchive::operator<<



check(TCString<TCharType>::Strlen(InName)<=NAME_SIZE);


Has anyone dealt with dynamic components while reloading? How did you handle them?

Thanks in advance!

Your “static” components (default subobjects) are being saved/loaded because they are part of the CDO and therefore are instantiated for free. In order to handle “dynamic” components (non-default subobjects), you’ll have to handle instantiation yourself.

There’s a few ways to go about it. The simplest (but not the most robust) would be to just serialize your dynamic components inline as you encounter them. This is what we did for a while, here’s the code responsible for it:


FArchive& FGlimpseSaveGameArchive::operator<<( class UObject*& Obj )
{	
	uint32 ObjectTag;

	if( IsSaving() )
	{
		if( Obj != nullptr && Obj->GetOuter() == SubObjectStack.Top() )
		{
			const bool bIsDefaultSubobject = ( Obj->HasAnyFlags( RF_DefaultSubObject ) );
			ObjectTag = ( bIsDefaultSubobject ) ? DefaultSubObjectTag : InstancedSubObjectTag;
			*this << ObjectTag;

			if( !bIsDefaultSubobject )
			{
				UClass* SubObjectClass = Obj->GetClass();
				*this << SubObjectClass;

				FString SubObjectName = Obj->GetName();
				*this << SubObjectName;
			}

			SubObjectStack.Push( Obj );
			Obj->Serialize( *this );
			SubObjectStack.Pop();
		}
		else
		{
			ObjectTag = ReferencedObjectTag;
			*this << ObjectTag;

			FObjectAndNameAsStringProxyArchive::operator<<( Obj );
		}
	}
	else if( IsLoading() )
	{
		*this << ObjectTag;

		switch( ObjectTag )
		{
		case InstancedSubObjectTag:
			{
				UE_CLOG( Obj != nullptr, LogGlimpseSave, Warning, TEXT("Overwriting instanced object %s"), *GetNameSafe( Obj ) );
				UClass* SubObjectClass;
				*this << SubObjectClass;

				FString SubObjectName;
				*this << SubObjectName;

				Obj = NewObject<UObject>( SubObjectStack.Top(), SubObjectClass, FName( *SubObjectName ) );
			}

		case DefaultSubObjectTag: // fall-through
			SubObjectStack.Push( Obj );
			Obj->Serialize( *this );
			SubObjectStack.Pop();
			break;

		case ReferencedObjectTag:
			FObjectAndNameAsStringProxyArchive::operator<<( Obj );
			break;
		}
	}

But this will potentially break save games if you ever change components. The tags are there to recover from such changes, but it’s not perfect. It’s simpler, however, and might be enough for your purposes.

The most complete solution would be to serialize all objects, making sure they are sorted innermost to outermost in your archive (we just walk up the outer chain until finding the level and use that as a “depth” to sort against). That way, whenever you serialize a new object, you’ll be certain that any dependent subobjects already exist.

Another approach is to keep some state for what you WANT the instance to be. For example, if the dynamic instances are some kind of firearm, then you save member variables that say “Walther PPK” or “M249 SAW” or whatever. Then, the way that the instance gets created in your code, is that your code notices that the “right” sub-actor isn’t attached, and removes the wrong one (if present) and creates/attaches the right one. Run this code when entering the world, and it will be in the right configuration once the player can see it.

If the sub-actors in turn have their own save data, this won’t work straight up, unless they store their sub-data (ammo count, etc?) in the player/playercontroller.

Thanks for the detailed response !

For our purposes, we do need the control of adding/removing components after launch, so I would prefer to take the complete solution route.
When you say “The most complete solution would be to serialize all objects”, do you mean to cycle through all of the game world’s objects (eg. with a TObjectIterator)? That seems like a good way to go, so long as there’s a good filter to remove any unnecessary scene actors/objects. Are there any caveats to this route in your experience? (eg. performance or file size)

Thanks for the suggestion jwatte, though this won’t work for us. We have a data driven setup where an actor can have any amount of data components at any given time, so the solution we’re looking for would ideally be non actor specific

Inline serialization would still work for this – the information for creating dynamic components is serialized after the “InstancedSubObjectTag”. It’s changing the order of default subobjects that can cause issues since they don’t have the data to create or destroy themselves if encountering unexpected data in the save stream.

Honestly, I can’t think of why it wouldn’t work to simply destroy old default subobjects and reinstance them as they are encountered in the save stream.

As for saving all objects – since the data we want to save is contained within actors, we iterate on those rather than all UObjects. The actor and subobjects is iterated for any CPF_SaveGame flags and those who fail that check get excluded. Build a “tree” of all the subobjects in the hierarchy, save them going from deepest to the root.

:slight_smile:

Using a similar approach to what you mentioned, I’ve got something working pretty well. I didn’t end up sorting anything by depth, because the references were circular in our setup, rather than purely hierarchical; instead, I kept track of what was serialized already and stored simple references if it was encountered again (instead of all the data needed to store if it didn’t exist).

Thanks one last time for the help cmartel, I wasn’t going to solve that one very well by myself!