Grogger
(Grogger)
June 6, 2016, 9:59pm
4
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.
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)