An issue with runtime saving/loading of Blueprint Classes

Dear Epic,

I’m engineering a save system for UE4 where I reload actors from a FString of their class path during runtime.

The issue I am having is that if all instances of a particular BP are destroyed using AActor::Destroy during runtime, after GC runs, then BP class itself seems to get unloaded, and then calls to StaticLoadClass dont work.

However, the odd thing is that if I run my serialization logic a second time, reloading the file in the same tick, then the BP class always gets found on the second try!

So again the flow of events

1 save the level, saving as a string all of the paths to all BP classes of all actors

I use this code:

//Get Path
static FORCEINLINE FString GetClassPath(UClass* ClassPtr)
{
	return FStringClassReference(ClassPtr).ToString();
}
	
template <typename T>
static FORCEINLINE T* LoadClassFromPath(const FString& Path)
{ 
	if(Path == "") return NULL;
		 
	return Cast<T>(StaticLoadClass(T::StaticClass(), NULL, *Path, NULL, LOAD_None, NULL));
}

2 All instances of the actor BP are Destroyed out of the level, during runtime.

3 Garbage Collection has a chance to run.

4 When I load the class string paths the first time I get this error:

LogUObjectGlobals:Warning: BlueprintGeneratedClass /Game/PowerUps/RedGem/RedGemBP_Med.RedGemBP_Med_C is not a child class of Class /Script/CoreUObject.Class

This is a message produced by StaticLoadClass.

Then I Run Static Class Again, Same Tick, And It Works!

5 If I then load the file a second time, exactly as before, without waiting even a single tick, then StaticLoadClass finds the BP class just fine!


Do you have any idea why:

1. If **all instances** of the BP class are deleted from the level, during runtime, the BP itself becomes unloaded / StaticLoadClass cannot find it

2. Why is StaticLoadClass saying that the BlueprintGeneratedClass is not a child class of CoreUObject.Class?

3. Why does StaticLoadClass work flawlessly the second time I call it, even in the same tick?

# What Works

Just for clarity, my code works perfectly for actors who are never deleted from the level during runtime, or for actors where at least 1 instance is still in the level.

It's only when ALL actor instances are removed and then GC runs that the BP class cannot be loaded instantly from StaticLoadClass.

#Serialization Not The Issue

I do not think it is an issue with my serialization process because all I do is re-run my load process over again exactly the same way. **I can verify that I am getting the same string path both times that I am then passing along to StaticLoadClass.**

The biggest clue seems to be this odd message:

    LogUObjectGlobals:Warning: BlueprintGeneratedClass /Game/PowerUps/RedGem/RedGemBP_Med.RedGemBP_Med_C is not a child class of Class /Script/CoreUObject.Class

#Runtime Commandline Game

Again all of this is in a commandline version of the game, editor is closed.

Thanks!

#♥

Rama
1 Like

Hi Rama!

So the error message would seem to imply that you’re calling your function like this (with a UClass in the template param): LoadClassFromPath<UClass>(…).

I believe the UClass’s class is not what StaticLoadClass() expects. I think it expects the super class for the class you are loading. For example: StaticLoadClass(UObject::StaticClass(), …); should always work.

It is odd that you see this error only once in the scenario you described. I’d expect that calling StaticLoadClass(UClass::StaticClass(), …); would always result in this error.

I hope what I’ve said is clear. It can be confusing. The concept that UClass has its own class type is a little meta.

To give one more example, say I am wanting to load a Pawn Blueprint (“MyPawnBP”). I would use StaticLoadClass like this: StaticLoadClass(APawn::StatisClass(), nullptr, “…MyPawnBP_C”, …); or even: StaticLoadClass(AActor…).

Hope this helps!
Cheers - MikeB

1 Like

#Hi There Mike Beach!

Hi there Mike Beach, I am happy we are getting the chance to interact! Yay!

Thanks for your time and help!

#Thank You Mike Beach!

Based on your input I tried changing my load class function to this:

//Get Path
static FORCEINLINE FString GetClassPath(UClass* ClassPtr)
{
	return FStringClassReference(ClassPtr).ToString();
}
	  
static FORCEINLINE UClass* LoadClassFromPath(const FString& Path)
{ 
	if(Path == "") return NULL;
		  
	return StaticLoadClass(UObject::StaticClass(), NULL, *Path, NULL, LOAD_None, NULL);
}

As you can see I am now side-stepping the whole issue of UClass being used directly vs super class of the object , and I am just using UObject::StaticClass() as the base class, with the proper path.

#And It works!

Now when the class fails to load the first time, using the standard UE4 save system method (FindClass/LoadClass), now I successfully find the class using the long asset path!

Here’s my code, now working thanks to you! (and for others to enjoy)

1 Like

//Get Path
static FORCEINLINE FString GetClassPath(UClass* ClassPtr)
{
return FStringClassReference(ClassPtr).ToString();
}

static FORCEINLINE UClass* LoadClassFromPath(const FString& Path)
{ 
	if(Path == "") return NULL;
	  
	return StaticLoadClass(UObject::StaticClass(), NULL, *Path, NULL, LOAD_None, NULL);
}

#Loading

//! #4 String Actor Class Path
FString ActorClassFullPath;
Ar << ActorClassFullPath;	

//Get the Class Name!
UClass* LoadedActorOwnerClass = FindObject<UClass>(ANY_PACKAGE, *ActorClassFromFile);
if(LoadedActorOwnerClass == NULL)
{
	LoadedActorOwnerClass = LoadObject<UClass>(NULL, *ActorClassFromFile);
}

//Check Class
if(LoadedActorOwnerClass == NULL)
{ 
	UE_LOG(RamaSave, Error,TEXT("Actor Class not found, using extra measures! %s"), *ActorClassFromFile );
	   
	//Load Static Class
	//		This works where the FindObject/LoadObject code pattern fails!
	//			Thanks for the help Mike Beach!
	LoadedActorOwnerClass = LoadClassFromPath(ActorClassFullPath);  
	//LoadedActorOwnerClass = LoadClassFromPath<UClass>(ActorClassFullPath);	//<~~~ Mike was right!
		
	UE_LOG(RamaSave, Error,TEXT("Actor Class not found, loading from full class path... %s"), *ActorClassFullPath);
	   
	if(LoadedActorOwnerClass == NULL)
	{
		UE_LOG(RamaSave, Error,TEXT("Actor Class not found, was it removed? %s"), *ActorClassFullPath );
		
		//Skip! Essential to maintain integrity of load process!
		Ar.Seek(ActorArchiveEndPos);
	
		return false;
	}
	else
	{ 
		UE_LOG(RamaSave, Warning,TEXT(">>>> Actor Class not found, EXTRA MEASURES SUCCESSFUL FOR ~ %s"), *ActorClassFullPath);
	}
}

#Conclusion

I am now seeing “Actor Class not found, EXTRA MEASURES SUCCESSFUL FOR” on the first load attempt, and my load cycle is not needing to run a second time!

As you intuited, I was passing in UCLASS as the template parameter (see commented out code).

By switching to UObject::StaticClass() as the base class and using StaticLoadClass from long path, now everything works!

#Thank You Again Mike Beach!

You really helped me out with that one!

Rainbow for you Mike!

40534-rainbow.jpg

Rama