I'm hitting a wall with saving and loading persistent data in Unreal Engine, especially with references that become invalid.

So i have been following a ton of tuts, and making it to a T. And im finally being able to implement it fully in C++.
Thanks @Predalien and @Auran13

Could you pls just confirm if im not doing something wrong here, it stil doesnt seem to be able to restore the previous references?
In this case I need the AMyUnitActor that has a AMyUnitActor->CurrentRegion, to restore its reference to the CurrentRegion (AMyRegionActor*)
This is how im saving:



void AUnitManager::SaveData_Implementation(UMySaveGame* SaveGameRef)
{
	if (!IsValid(SaveGameRef))
		return;

    SaveGameRef->SerializedUnitActors.Empty();

    for (AMyUnitActor* Unit : All_UnitActors)
    {
        if (!IsValid(Unit)) continue;

        FSerializedActorData Entry;

        FMemoryWriter Writer(Entry.Data, true);
        FObjectAndNameAsStringProxyArchive Ar(Writer, false);
        Ar.ArIsSaveGame = true;
        Unit->Serialize(Ar);

        SaveGameRef->SerializedUnitActors.Add(Entry);
    }

    SaveGameRef->SerializedRegions.Empty();


    for (AMyRegionActor* Region : All_RegionActors)
    {
        if (!IsValid(Region)) continue;

        FSerializedActorData Entry;

        FMemoryWriter Writer(Entry.Data, true);
        FObjectAndNameAsStringProxyArchive Ar(Writer, false);
        Ar.ArIsSaveGame = true;
        Region->Serialize(Ar);

        SaveGameRef->SerializedRegions.Add(Entry);
    }
}

So here im saving, both the Units and the Regions. It works :flexed_biceps:
Then to load:


void AUnitManager::LoadData_Implementation(UMySaveGame* SaveGameRef)
{
	if (!IsValid(SaveGameRef))
		return;

    All_UnitActors.Empty();

    TMap<FString, AMyUnitActor*> ID_Unit_Map;   
    TMap<FString, AMyRegionActor*> ID_Region_Map;

    for (const FSerializedActorData& Entry : SaveGameRef->SerializedUnitActors)
    {
        AMyUnitActor* Unit = GetWorld()->SpawnActor<AMyUnitActor>(UnitClass);
        if (!Unit) continue;

        FMemoryReader Reader(Entry.Data, true);
        FObjectAndNameAsStringProxyArchive Ar(Reader, true);
        Ar.ArIsSaveGame = true;
        Unit->Serialize(Ar);
        ID_Unit_Map.Add(Unit->GetPathName(), Unit);
        Unit->SetActorTransform(Unit->LastTransform);
        All_UnitActors.Add(Unit);
    }


    All_RegionActors.Empty();

    for (const FSerializedActorData& Entry : SaveGameRef->SerializedRegions)
    {
        AMyRegionActor* Region = GetWorld()->SpawnActor<AMyRegionActor>(RegionClass);
        if (!Region) continue;

        FMemoryReader Reader(Entry.Data, true);
        FObjectAndNameAsStringProxyArchive Ar(Reader, true);
        Ar.ArIsSaveGame = true;
        Region->Serialize(Ar);

        ID_Region_Map.Add(Region->GetPathName(), Region);

        Region->SetActorTransform(Region->LastTransform);
        All_RegionActors.Add(Region);
    }

    for (int32 i = 0; i < SaveGameRef->SerializedUnitActors.Num(); ++i) {
        AMyUnitActor* Unit = All_UnitActors[i];
        FSerializedActorData& Entry = SaveGameRef->SerializedUnitActors[i];

        //here should be able to find the Region by the PathName, though its not that simple.
        Unit->CurrentRegion = *ID_Region_Map.Find();

    }

}

So when loading I respawn the actors, and the regions, then to get reconnect the references, I should GetPathName() that from the serialized Unit->CurrentRegion . Though that appears to be nullptr. Is there something im missing?
When we save/serialize a reference in this case Unit->CurrentRegion (AMyRegionActor*), isn’t there a way to serialize its path, or it needs an extra variable for that?

remember we said this, your current load functionality spawns/loads in the same iteration, this means that if actor 1 has a ref to actor 2 it will fail as actor 2 hasnt been spawned yet.

1 Like

(post deleted by author)

Got it :smiley:
its finally working:

//SAVING:
void AUnitManager::SaveData_Implementation(UMySaveGame* SaveGameRef)
{
	if (!IsValid(SaveGameRef))
		return;

    SaveGameRef->SerializedUnitActors.Empty();

    for (AMyUnitActor* Unit : All_UnitActors)
    {
        if (!IsValid(Unit)) continue;

        FSerializedActorData Entry;
        //Entry.ActorClass = Unit->GetClass();
        Entry.SavedName = Unit->GetPathName();

        Entry.ActorClass = Unit->GetClass();
        Entry.ActorPath = Unit->GetPathName();

        Entry.ReferencePathName1 = Unit->CurrentRegion->GetPathName();

        FMemoryWriter Writer(Entry.Data, true);
        FObjectAndNameAsStringProxyArchive Ar(Writer, false);
        Ar.ArIsSaveGame = true;
        Unit->Serialize(Ar);

        SaveGameRef->SerializedUnitActors.Add(Entry);
    }

    SaveGameRef->SerializedRegions.Empty();


    for (AMyRegionActor* Region : All_RegionActors)
    {
        if (!IsValid(Region)) continue;

        FSerializedActorData Entry;
        //Entry.ActorClass = Unit->GetClass();
        Entry.SavedName = Region->GetPathName();

        Entry.ActorClass = Region->GetClass();
        Entry.ActorPath = Region->GetPathName();

        Entry.ReferencePathName1 = Region->CurrentUnit->GetPathName();

        FMemoryWriter Writer(Entry.Data, true);
        FObjectAndNameAsStringProxyArchive Ar(Writer, false);
        Ar.ArIsSaveGame = true;
        Region->Serialize(Ar);

        SaveGameRef->SerializedRegions.Add(Entry);
    }
}


//LOADING:
   for (const FSerializedActorData& LoadedData : SaveGameRef->SerializedUnitActors)
    {
        AActor* CurrentActor = UGameplayStatics::BeginDeferredActorSpawnFromClass(this, LoadedData.ActorClass, FTransform(), ESpawnActorCollisionHandlingMethod::AlwaysSpawn);

        CurrentActor->FinishSpawning(FTransform());

        FString IDName;// old actor path (old reference)

        LoadedData.ActorPath.Split(".", nullptr, &IDName, ESearchCase::IgnoreCase, ESearchDir::FromEnd);
        CurrentActor->Rename(*IDName);
        All_UnitActors.Add(Cast<AMyUnitActor>(CurrentActor));
    }

    // Spawn Regions
    for (const FSerializedActorData& LoadedData : SaveGameRef->SerializedRegions)
    {
        AActor* CurrentActor = UGameplayStatics::BeginDeferredActorSpawnFromClass(this, LoadedData.ActorClass, FTransform(), ESpawnActorCollisionHandlingMethod::AlwaysSpawn);

        CurrentActor->FinishSpawning(FTransform());

        FString IDName;// old actor path (old reference)

        LoadedData.ActorPath.Split(".", nullptr, &IDName, ESearchCase::IgnoreCase, ESearchDir::FromEnd);
        CurrentActor->Rename(*IDName);
        All_RegionActors.Add(Cast<AMyRegionActor>(CurrentActor));

    }

    // === PASS 2 - DESERIALIZE ===

    // Deserialize Units
    for (int32 i = 0; i < SaveGameRef->SerializedUnitActors.Num(); ++i)
    {
        FMemoryReader Reader(SaveGameRef->SerializedUnitActors[i].Data, true);
        FObjectAndNameAsStringProxyArchive Ar(Reader, true);
        Ar.ArIsSaveGame = true;

        All_UnitActors[i]->Serialize(Ar);
    }

    // Deserialize Regions
    for (int32 i = 0; i < SaveGameRef->SerializedRegions.Num(); ++i)
    {
        FMemoryReader Reader(SaveGameRef->SerializedRegions[i].Data, true);
        FObjectAndNameAsStringProxyArchive Ar(Reader, true);
        Ar.ArIsSaveGame = true;

        All_RegionActors[i]->Serialize(Ar);
    }

This is the best solution. It will save me a ton of work, because now i dont have to reconnect every single actor reference using a FGuid. Just the GetPathName() works :flexed_biceps:
I already debugged it and its fixing the references. Amazing.
Thanks @Auran13 , @PREDALIEN

2 Likes