Get All Actors Of Class with Level Streaming broken after update to 4.9

In 4.8.3 it worked perfectly. Level streamed, actors placed, event beginplay starts. Now beginplay starts before an actors from streamed level presents at scene and Get All Actors Of Class returns an empty array.

It works fine with single level or with Always Loaded method but once same levels switched to blueprint streaming methot it stops working. Though little delay helps but thats just silly.

Steps to reproduce:

1 - create blank project with a persistent and streamed levels (blueprint method).

2 - create new actor bp with code (also tried placing it at beginplay of streamed level bp, same result) and place it at streamed level:

2 - Stream level at persistent level bp:

56579-stream2.png

3 - Result:

56580-stream3.png

I have exact same issue! Worked fine in 4.8.2, but in 4.9, GetAllActorsOfClass doesn’t find anything.
It seems it is related to, that outer object is now persistent level and not streamed level. If you in C++ use GetObjectsOfClass(), you retrieve them all.

This seems to only be a problem in BeginPlay(). Maybe BeginPlay is called prior to streamed level has finished loading?

I’m having exact same issue as well and your description is spot on. It works fine in single level, but when streamed from another level it’s broken, but worked fine in 4.8. only difference for me is I’m calling GetAllActorsOfClass in C++ during BeginPlay for certain actors (instead of blueprint approach), but result is same.

Hi Arida,

Thanks for report! I was able to reproduce this in 4.9.0 as well as our internal builds, and I’ve entered a bug report for issue (UE-20736). I’ll post here when I see any update.

I spoke with developers about it, and problem is not that Event Begin Play is being fired before streaming level is loaded. Instead, problem is likely a timing issue with Get All Actors of Class. I’ll let you know if I find out more. For now, using delay is your best bet.

For me GetAllActorsOfClass is completely broken regardless of timing (For test purposes I added it to gamemode’s tick, and then entered there with a debugger long after streamed level was loaded. actor from streamed level was not there). streamed level does have actors as I get their beginplay events just fine. Also iterator seems to be broken as well - only returns actors from peristent level. I posted this here, before I found this thread:

And for completeness sake, here’s test code I added in my GameMode::Tick event :

auto loadedlevels = GetWorld()->GetLevels();
TArray<AActor*> actorstest;
for (auto level : loadedlevels)
{
for (auto actor : level->Actors)
{
actorstest.Add(actor);
}
}
TArray<AActor*> actorstest2;
for (TActorIterator ActorItr(GetWorld()); ActorItr; ++ActorItr)
{
actorstest2.Add(*ActorItr);
}

So, I’ll wait for about 10 seconds after I load level. Then I’ll go here and add a breakpoint. result - actorstest : 34 actors, actorstest2: 31 actors.

And for completeness sake, here’s test code I added in my GameMode::Tick event :

auto loadedlevels = GetWorld()->GetLevels();
	TArray<AActor*> actorstest;
	for (auto level : loadedlevels)
	{
		for (auto actor : level->Actors)
		{
			actorstest.Add(actor);
		}
	}
	TArray<AActor*> actorstest2;
	for (TActorIterator<AActor> ActorItr(GetWorld()); ActorItr; ++ActorItr)
	{
		actorstest2.Add(*ActorItr);
	}

So, I’ll wait for about 10 seconds after I load level. Then I’ll go here and add a breakpoint. result - actorstest : 34 actors, actorstest2: 31 actors.

Nevermind my previous posts. That was my screwup. So yeah, it is delay issue. In my beginplay of level if I put:
TArray<AActor*> actorstest;
UGameplayStatics::GetAllActorsOfClass(GetWorld(), AMyCharacter::StaticClass(), actorstest);
It will return empty. But if I put same in gamemode’s Tick event eventually it will start returning 1 (after a few update cycles)

Could you make this a priority? Just tested master branch tonight and bug still exists.

All we can do is bump community interest level on it, which I have done. Is there a reason delay workaround doesn’t work for you in meantime?

This bug looks like it cropped up when Actor Iterator was refactored. previous actor iterator made use of a Level Filter Class, but notice that templated Actor Iterator doesn’t specify a Level Filter. That means it was using Default Level Filter:

template<typename FILTER_CLASS,typename LEVEL_FILTER_CLASS = FDefaultLevelFilter>
class TActorIteratorBase :
	public FActorIteratorBase

template <typename ActorType>
class TActorIterator : 
	public TActorIteratorBase< FActorFilter >

Which wasn’t culling iteration results based on whether level was visible:

struct FDefaultLevelFilter
{
	bool CanIterateLevel(ULevel* Level) const
	{
		return true;
	}
};

However now iterator itself implements result culling and both Actor Iterator implementations perform culling:

template <typename ActorType>
class TActorIterator : public TActorIteratorBase<TActorIterator<ActorType>>
{

static bool CanIterateLevel(ULevel* Level)
{
	return Level->bIsVisible;
}

This is a problem for this particular use case of iterator where streaming level calls BeginPlay() on Actors it contains prior to marking level visible.
See:

void UWorld::AddToWorld( ULevel* Level, const FTransform& LevelTransform )

	// Ultimately calls BeginPlay on Actors somewhere up here...

	// We're done.
	if( bPerformedLastStep )
	{
		Level->bIsVisible = true;

suggested fix of a Delay works around problem by using Actor Iterator outside of streaming level initialization step. An easy workaround fix in any C++ code with this problem is to implement a custom actor iterator with previous behavior.

Any update on this ? Seems to still be an issue in 4.10

I’m sorry, no update at this time.

Has this been addressed in 4.10.1?

No, and we do not expect it to be fixed for any hotfixes. As mentioned above, there has been no update on report itself, including a fix, but we will post here once there is. Thank you.

Four years later. Any fix?

Hallo all,

I just stumbled across this issue and solved it by setting up a timer to check whether streamed level is actually visible or not, to eventually perform whatever depends on that visibility (ie. counting actors).

In my function where I turn level visibility on (currentLevel is a pointer to loaded level whose visibility we want to turn on; FTcheckForLevelState a global timer handle):

 // Visualize Level
	ULevelStreaming* levelProxy = UGameplayStatics::GetStreamingLevel(GetWorld(), currentLevel);
	levelProxy->SetShouldBeVisible(true);

// Check for visibility, then initializes level
FTimerDelegate TimerDelegate;
TimerDelegate.BindUFunction(this, FName("InitializeLevelWhenVisible"), levelProxy);
GetWorldTimerManager().SetTimer(FTcheckForLevelState, TimerDelegate, .1f, true);

And here’s InitializeLevelWhenVisible:

void InitializeLevelWhenVisible(ULevelStreaming* levelToInitialize)
{
	if (levelToInitialize->IsLevelVisible())
	{
		// Count Actors here (...and/or call other functions whose result depends on your levelToInitialize being visible)
                [...]
		// Reset Timer
		GetWorldTimerManager().ClearTimer(FTcheckForLevelState);
	}
}

Hope that helps!
f

1 Like