If I destroy and Actor it doesn’t free up its FName, so if i spawn a new Actor with SpawnParams.Name the editor will crash.
I’ve tried forcing garbage collection to free the name.
Can workaround the crash with SpawnParams.NameMode = FActorSpawnParameters::ESpawnActorNameMode::Requested;
but then will get a different name assigned.
The Objective:
Its for a Save/Load system. For reasons its better to destroy the previous Actor rather than ‘load’ it.
Its important to have the same FName as FObjectAndNameAsStringProxyArchive uses it to relink references on DeSerialization
I’ve got a few workarounds but it seems like it should be possible?
For save/load it might be easier to assume the names aren’t stable (since they aren’t) and attack the problem from a different direction.
I was able to leverage the ActorGUID to act as a stable, cross-session, identifier. Technically it’s Editor-only but with a bit of a clever component I was able to keep it around for game ID purposes. I know of at least one other person that was able to make that work after chatting it through on discord.
I had a similar task where I had to deterministically name an object for replication purposes. I did not have any issues spawning the actor with a specific name though.
// Spawn the loot actor in a net addressable fashion.
// This is important because the loot actor itself is NOT replicated but its reference is sent over the network when player interacts with it.
// We can support this by deterministically naming the actor, thus making it net addressable.
// This way we avoid the case where client-server would not interact with the *same* loot actor (due to spawn order difference).
FActorSpawnParameters SpawnParams = FActorSpawnParameters();
SpawnParams.Name = FName(*GetName().Append(TEXT("_Loot"))); // results in LootNodeInstanceName_Loot
SpawnParams.NameMode = FActorSpawnParameters::ESpawnActorNameMode::Required_ErrorAndReturnNull;
SpawnParams.Owner = this;
SpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButAlwaysSpawn;
SpawnParams.bDeferConstruction = true; // code says this is for blueprints only, but its wrong. GetWorld()->SpawnActorDeferred uses this too.
LootActor = GetWorld()->SpawnActor<AFPMLootActor>(AFPMLootActor::StaticClass(), GetActorTransform(), SpawnParams);
if (LootActor)
{
LootActor->SetNetAddressable();
LootActor->FinishSpawning(GetActorTransform(), true);
LootActor->InitFromItem(LootItemId);
LootActor->LinkToLootNode(this);
}
Perhaps your issue is more to do with destroy logic? Don’t forget that actor destruction starts with one frame delay.
It’s not the FNames that are the problem, it’s the specific use of FName as the UObject identifier. Serializing other FName properties as strings is exactly what you want to do (since the index into the name table isn’t stable), it’s only as the unique ID of UObjects where it doesn’t work very well. And even then it’s only for runtime-spawned objects. Objects in a level or assets are pretty stable, but can still get messed up if someone manually renames them.
it should be ok as long as the save matches the load then? since i respawn all objects the name is set from the savedata at runtime.
curious how you did yours? i thought about a save component that has a GUID or even just an interface but then you’d have to resolve ObjectRefs manually?
Maybe? I think there are a lot of details that make thing work or not that are hard to discuss in the abstract.
I wish I had a version in my github to share, the only versions I’ve written are in company depots. It’s on my list of things to write & share if only as an example. But at a high level:
A component that acts as an identifier that the owning actor state should be saved. This has an FGUID as the unique ID. When that component is saved in the Editor, it copies the value from the ActorGuid and sets a flag that indicates that this is a level actor. Otherwise the GUID is generated lazily for spawned objects.
Saving consists of finding all the actors with the persistence component and serializing them into a byte stream. This is done with a custom FArchive. FArchive has a few overload-able functions for dealing with different sorts of object references. I overload those to handle the serialization of the reference.
First I gather the collection of objects that should be saved. This starts with all the Actors with the Persistence component, but is expanded to objects that they reference. I have a list (in developer settings) for the component types that should be saved (since you don’t really need to save mesh or collision components).
As I serialize each object, the overloads gets called for object reference members. If the object being referenced is in the “to-save” collection, I serialize out that index value. If that object is a persistent object not in the list (for reasons not worth discussing now), the GUID is saved. If that object is not-persistent but is a or in an asset, save the object path. Otherwise error/ensure because there’s no good way to persist the reference. (I don’t currently support subobjects of persistent objects but that is a case that could be handled)
When loading, I create all of the object instances from the save data. Then serialize each object. This way when the overloads de-serialize the object reference I can fill it in with the constructed object (if it was in the list), an existing persistent object or the asset object (maybe with a sync load on the soft path).
I had the same problem when I tried to load a deleted actor that was part of a level. I ended up just reloading the map and then loading the save. That way, names don’t become an issue.
I REALLY didn’t want to have to change the path to the actor, because otherwise I would have to replace all the references saved to it. But if the path is not changed - everything works by itself.