Closed andra961 closed 1 year ago
Yeah SpudPostRestore
only happens when data has actually been restored, so for new cells it won't won't occur. While I could add a function that gets called for every actor in the level whether it's a load case or not, that's an extra overhead that I'm not sure a lot of people would want.
What I usually do is let init happen in BeginPlay
anyway, and then if then subsequently new data is restored after then I run the init again with the new data (so both would call the same function) which overrides whatever the previous init did and you can't tell that both happened. It requires that it's safe to call this function more than once, which sometimes means dealing with things that should never happen twice (like subbing to an event - either have a flag which prevents it happening again, or use the 'unique' variants of subscribe so it's safe to do).
In general it's a good idea to code defensively like this anyway rather than relying on an init only ever getting called once. BeginPlay
is notoriously unreliable in terms of timing e.g. what gets called first, when exactly it's called.
i see, thanks!one last doubt though. I have an actor init that depends on the loaded data of an other actor (a GameState
object for instance, that gathers informations and saves them) first. Where should i place the init actor of this actor in a way that when it's executed i'm sure the actor it depends on has already been loaded correctly first?
It's the same as with BeginPlay: you can't know the ordering that actors will be initialised by BeginPlay, and you can't know the order that actors are restored by SPUD either. So the only reliable answer is not to rely on ordering between actors, either by not having cross-dependencies, or resolving them at the end of all actors having been loaded. What you can do instead is use the event that occurs after a level is fully loaded, which is the PostLevelRestore
event on the subsystem. When that is fired, all actors in that level have had their state restored so you can resolve any cross-dependencies then.
Hi @sinbad , Thank you! (I'm in team with @andra961 )
I tried using PostLevelRestore
, but to check if the restored level is the actor specific level(a world partition cell,data layer or just the persistent level) I must know the actor specific level name.
I managed to get it this way, but it looks a bit messy, do you know if there is a better way to get the actor specific level name?
FString UMyBlueprintFunctionLibrary::GetActorLevelName(const UObject* WorldContextObject, const AActor* actor)
{
const UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull);
//Discover if the actor is currently in an active DataLayer
bool bActorInDataLayer = false;
if (actor->GetDataLayerInstances().Num() > 0)
{
TArray<const UDataLayerInstance*> LayerInstances = actor->GetDataLayerInstances();
for (const UDataLayerInstance* LayerI : LayerInstances)
{
if (LayerI->IsRuntime() && LayerI->IsVisible())
{
bActorInDataLayer = true;
}
}
}
FString ActorLevelName;
//if the actor is currently in a cell of world partition or in an active data layer
if (actor->GetIsSpatiallyLoaded() || bActorInDataLayer)
{
//get the specific name of cell or data layer
ActorLevelName = actor->GetLevel()->GetOuter()->GetOuter()->GetName();
int Index = INDEX_NONE;
ActorLevelName.FindLastChar('/', Index);
if (Index != INDEX_NONE)
{
ActorLevelName = ActorLevelName.RightChop(Index + 1);
}
ActorLevelName = ActorLevelName.Mid(World->StreamingLevelsPrefix.Len());
}
//the actor is currently just in the persistent level
else
{
//get the name of the persistent level
ActorLevelName = actor->GetLevel()->GetOuter()->GetName();
}
return ActorLevelName;
}
Have you considered only subscribing to the PostLevelRestore
event inside the SpudPostRestore
for the actor that depends on other actors? Then unsubscribing after PostLevelRestore
is fired. Because the PostLevelRestore
gets fired just after doing all the actors for that level, it should be the same level that the event gets fired for. Then you wouldn't need to check the level, you're just asking for a final mop-up callback at the end of this specific restored level.
Thank you! I wasn't sure if PostLevelRestore
of the levels was sequential so I hadn't thought about using it with SpudPostRestore
like this! I'll go with this approach instead of brutally comparing level names every time its possible!
Unfortunately it is not so simple when it comes to dependencies between actors of different levels,
In our case between a persistent manager and observers in the specific levels:
It would be very convenient if observers could know if the manager is ready and get the data, or wait for him to be ready before asking him for the data. The manager would trigger his PostLevelRestore
to notify observers that he's ready with the method you mentioned earlier.
However, to avoid observers waiting indefinitely (for example, in the case of a new game the manager is not loading so it will never be ready since its PostLevelRestore
will never be called) it would be very convenient check IsLoadingGame()
in the manager BeginPlay
and if not, set that the manager is ready and notify the observers immediately.
Basically I would like to know:
When we load a game, does a persistent level actor that calls IsLoadingGame()
in BeginPlay
always returns true? Is this a good practice? Do you think there is a better solution for this specific case?
When we load a game, does a persistent level actor that calls IsLoadingGame() in BeginPlay always returns true?
Yes. The reason is the way that UE loads its maps, which calls BeginPlay and then calls PostLoadMapWithWorld callback, the latter of which is what triggers SUDS to know that the persistent level has finished loading, and the state of the system changes to return false from IsLoading(). Of course, streaming / world partition levels can load after this.
Also, you can rely on global objects and actors in the persistent level will always load before streaming levels.
Life saving information right there, thank you again for your exhaustive answers!
Hi, awesome plugin!I'm using World Partition and i noticed that when a cell loads,
SpudPostRestore
WON'T be called always (if it's a new game for instance it doesn't it seems). I have some initialization logic that is executed in my actors and i'm faced with a dilemma. Since this init logic depends on the loaded actor data, i can't just execute it inBeginPlay
(data would be incorrect), but i can't execute it onSpudPostRestore
neither because it's not granted it will fire and i risk skipping initialization altogether. Is there a way, insideBeginPlay
, to know for sure thatSpudPostRestore
will fire, so that i can skip the init logic and execute it (only once) inSpudPostRestore
?