Object references in Blueprints

Morva Kristóf
Unreal Engine Tech Shreds
15 min readApr 28, 2020

--

Object references are one of the building bricks of every project. However, when you try to manage them in blueprints, there are many pitfalls and issues you can encounter if you are not familiar with some lower-level behavior. This post aims to shed some light on the reference handling in blueprints, so (hopefully) you can avoid sweating blood when you are nearing project completion and you have to redo a massive amount of your work because of some mysterious bug or memory consumption issue.

Please note, to keep this entry compact, I’m going to assume that you already know what object (class) references are and how to work with them. If not, please familiarize yourself with the concept before following on.

Dependency Handling

It’s really important to understand when and how objects are loaded into (and out of) memory for any slightly-bigger blueprint project.

In general, when an asset is requested to be loaded, Unreal will first synchronously load all references (dependencies) of said asset, the references of those references, etc, and only lastly the asset itself, when the whole tree of its references is already loaded. As you can imagine, it can really get out of hand. There are some assets which are loaded immediately when you start your level / game / editor: the root assets. These will include:

  • The loaded level (including the Level Blueprint and every placed actor)
  • The Game Mode and all its classes (Player Controller, Player State, etc)
  • The Game Instance

If you don’t see the weight of this yet, I’m going to show you an example. UE4 tools are really helpful to see what references are going to be loaded for an asset, so let’s see the Player Controller of a small project. Press right click on your asset, and choose Reference Viewer.... On the right side of your asset, you’ll see all the direct references.

Top-level references

Well, not the best, but, well, still manageable. Sadly though, as mentioned earlier, not only the top-level references are loaded, but also all the references of these referenced assets. Let’s change the depth limit to 3 (so that we’ll see the first-, second-, and third-level references).

Three level of references

While looking at it, let’s consider these:

  • It doesn’t even fit my screen
  • It’s only 3 levels; there is a 4th, and who knows how many
  • It’s a small one-person project
  • It’s only the Player Controller, this graph does not show the references of the other root assets

So whenever our Player Controller is loaded (which is probably when you open the editor or start the game), 147 other assets will be loaded automatically too. It’s still not so bad, but when you have larger projects and/or you are targeting lower-end hardware (like Nintendo Switch), loading thousands of assets on start will be a bottleneck in your game, and if you only consider this at the end of your project, it’s going be a huge amount of work to fix at that stage.

Later in this post we’re going to look at ways to avoid it, but first, there is some prerequisite knowledge we have to go over.

Object references in Blueprints

Naively, you’d think that when I was talking about references in the previous section, I was only talking about variables. Well, that’s not exactly the case. Let’s see what holds references in a Blueprint:

  • The types and default values of variables
  • The parent class of the asset
  • The types of function inputs and outputs
  • All the literal node inputs
  • Graph literals
  • Cast nodes (yes, if you’re casting to BP_Weapon anywhere in your file, it’s gonna be loaded)
  • Class interfaces
  • Actor components
  • Probably some others I have missed…

So, simply put, if your class has anything to do with a specific asset, it is going to be loaded.

Hard Object References

It is the “default” type of object reference inside blueprints, all bullet-points from the previous section are going to be hard references. In a Blueprint, you can create a hard reference variable by using either Object Reference or Class Reference.

Object and Class Reference from the variable type picker

This type of variable will reference both its type and value, therefore both of these will be loaded into (and kept in) memory upon loading its container blueprint.

A class variable with Weapon type, with Sword as its default value

What it means is that if you have a Weapon variable like illustrated above in a Blueprint, whenever the Blueprint is referenced, we’re going to load BP_Weapon and BP_Sword(and everything they reference).

Soft Object References

Now we’re going to look at something, which is often overlooked in Blueprints (but should not be for non-micro projects): soft object references.

The most important difference between this and the hard reference is that this one does NOT load its value into the memory. Okay, that’s great, so why don’t we always use this one, one could ask? Well, simply because whenever we need the referenced asset, we still need to load it; just this time, we decide when we load it. It can be during a loading screen, the main menu, in-game, or whenever you need it, but the point is, you’ll have to manage the loading yourself. An important positive side is that now, that you control the asset loading, you can also load it asynchronously, so the process can be completely transparent to the player, no more frozen screen.

Beware though, the type is still loaded when using soft references, so if the type is BP_Weapon, it’s still going to be loaded with all its dependencies; therefore, it’s best if you use a C++ base class for variable types (you can still have a BP base class inherited from the C++ one), or a minimalist BP parent class.

Let’s see how to work with these kind of references in practice.

Resolving

The easiest method is using a direct resolve node. Either look for Resolve Soft Reference, or just link your soft reference into a hard reference, and the editor will automatically create this converter node for you.

Use soft reference resolving

The thing you have to pay attention for here is, that it only works, if the asset is already loaded — the resolving node won’t try to load the asset. Hence, only use this function if either you are sure that the asset is loaded already, or if you want to handle the other case yourself.

Sync Loading

If you need the asset immediately without any delay, use this method.

Sync loading a soft reference

You don’t have to validate yourself beforehand with a resolver node if the asset it already loaded — this method will not load the asset if it’s already in the memory, it’ll just return the already loaded asset.

Async Loading

And finally, async loading!

Async loading a soft reference

The first output pin is going to run immediately after the async calling, and the Completed will run whenever the asset is successfully resolved or loaded from disk (with all its dependencies).

Official Inside Unreal stream

A few days after this blog post, coincidentally, Epic hosted a stream on using Soft Object References in practice. If you learn more easily from videos, be sure to check it out!

Referencing considerations

By understanding the sections above, you can hopefully keep the reference count small, therefore increasing loading times and decreasing memory consumption, if you pay attention to a few things:

  • Don’t reference game mode-specific data in global assets, i.e. don’t use BP_BattleRoyaleGameMode in Game Instance directly, don’t reference runtime classes in globally used Data Assets, etc.
  • Whenever you need to work with specific assets in global namespaces, use interfaces, components, reference-less base classes, etc.
  • If an asset is not always needed for a game mode, use soft references. For example, if you have seasons to choose from, and a game session is going to take place only on the summer level, you shouldn’t load all 3 other seasons. Instead, store them in soft references, and when the player chooses the season, only load that one.

De-referencing assets

It happens that you already have many references tangled together after prototyping (or reading this post too late). At my full-time workplace, Zen Studios, we had a project with a mixture of hard and soft references, and we have stumbled upon the following issue when we ported the game to Nintendo Switch: The loaded assets were 1.6GB in total and the initial black-screen loading time was 1m20s! After some days of work, I was able to decrease it to 28MB and less than 5 seconds by trying to post-apply the suggestions detailed in the previous section.

My closest friend in this process was the Size Map. Right click on your asset, and select Size Map.... You’re gonna see a beautiful screen like this:

Size Map of Game Instance

On the outer layer, you’re going to see your asset. One step in, you see what references does it contain directly. One more step, you see what references the first-level references contain. Here for example we see that BP_GameInstance references a texture, and BP_SaveGame. BP_SaveGame references BP_MapEditorPlayerController, which references BP_GameMode and BP_MapEditorGameMode, which reference… it goes on and on.

My methodology was to check the size map of all root assets in the main menu, and look for the top-most asset which should not be loaded. For example, here, loading the save game in the Game Instance sounds totally legit, as it’s a global object, and we might enable / disable the Continue button based on it, or do whatever. The concerning assets are the texture and the Map Editor Player Controller — we’ll never ever should need this Player Controller in the Game Instance (or any global object), as it obviously belongs to the map editor. We definitely should get rid of that reference in Save Game. For this, some of the options from the previous section should work: use a base class, interface, or something else instead, which won’t pollute your asset with unnecessary references. If you managed to purge your reference, you should see it gone in the Size Map.

Result Size Map

Now that all that expensive reference-tree is gone, the biggest chunk of the asset is the texture, everything else is is just a super-narrow column on the right side. Normally, it won’t be this easy, you’ll probably have to repeat this task a hundred times for a bigger project, but you should see the references gone one by one, until only the necessary bare bones are left.

Garbage Collection

I don’t want to go too deep into this topic, all you have to know is that in simple terms, whenever a loaded asset is not referenced by anything anymore, it might be cleared from the memory at some point. For example:

  • if you loaded a soft reference, but you don’t use it currently anywhere
  • you switch levels, which is gonna destroy all Actors
  • you removed a widget from its parent and there are no more references to it
  • you destroyed an actor manually
  • etc

then the engine can decide if and when it wants to clear this asset from the memory. In most cases, you don’t have to deal with it at all, as it’s all automatic, and in-use objects are generally not going to be touched by the Garbage Collector (GC). However, in some situations it’s important that you understand the implications of this process.

For a more detailed (mostly C++ focused) technical description, check out the official documentation.

There are two issues I’d like to review here: objects that you expect to disappear and are endlessly in the memory, and objects that you don’t expect to be destroyed and are purged.

Objects stuck in memory

You might not use an object anymore, but for some reason, you can’t get that memory to be freed. It can happen in 3 cases:

  • The GC has not run since. As it’s only executed periodically, not in every tick, almost none of the objects will be removed from the memory immediately.
  • The GC has run, but it decided not to free that memory yet. A reason could be that you simply still have a bunch of free memory, so it’s kept in memory just in case you will need it again. Or, any other logic you can imagine. Maybe wait for more dangling objects so that it can purge them in a bulk? Or it had no mood to do so? Or… whatever?
  • The object is still referenced by something you don’t expect.

You can easily disprove the first two bullet-points by forcing the GC to clear everything, just execute obj gc in the console to do so. It’s going to free all memory (in a synchronous way), which is occupied by your trash objects. If, after this, your object is still alive, you can be sure that you still have something referencing it. Besides the obvious (variables), it can also be kept in memory by (take a deep breath) impure nodes in event graphs.

Of course, if you think about it, if it was not referenced by them, you could lose your references in your graphs at any point unexpectedly, which would be kinda impossible to work with. Let’s see an example.

You variable might look like this:

A Sword soft reference

And your graph might be like this:

Adding a Sword weapon after loading it from a soft reference

Okay, this is great, until the player a uses a sword, it’s never going to be loaded. When they get a sword, we load it, and give it to them. Great! Now let’s say the player’s sword wears out (like in Witcher 3), and when it reaches 0%, we take it from them. Sad player, but happy memory: no more sword needed! Or is it? The reality is, the sword class is going to stay in memory forever, because the LoadClassAssetBlocking node is going to hold onto its output pin until the end of days (or, well, until the player is destroyed). The most obvious thing you can do with it is to move it into a function.

Adding the sword from a function

Problem solved! Since nodes in functions don’t retain their values after the function is terminated, we have the same result, but now the sword can be freed from the memory if we take it from the player. However… (you knew it was coming, right?) let’s consider the following graph:

Adding a sword to the player after an async load

Since we want to load asynchronously now, we have to use a latent function, and as you (probably) know, functions can’t contain latent calls. Okay, so what now, do we just have to accept that it’s going to stay in memory forever? Not at all!

Setting the output pin of the async loading to none

If you ask, “what the hell is that?”… I hear you. Let me explain: when the loading is done, we’re going to want to make sure that the async loading node does not keep the sword in the memory. In this graph I achieve it by setting the variable, returned by the output pin of the node, to none. It does not remove the asset from the memory itself, it’s still going to stay loaded until the player has the weapon (or something references it). All we do here is resetting the pin of the node, so it doesn’t hold a reference to anything anymore.

You didn’t hear about this hacky solution from me *Obi-wan mind trick*.

Objects disappearing from memory

There are some cases, when objects might be destroyed, even if they are actively referenced. It can happen if:

  • The owner (outer) of the object is deleted.
  • The object is not accessible from any root object. Meaning, if object A holds a reference to object B, and object B holds a reference to object A, they both will be removed if there isn’t a chain of references, which leads to one of the root objects (like Game Instance, Player State, etc).

However, there is another issue, when an object is not going to be actively referenced, even if you’d think it is. I’ll describe an example to give you some context as to when might this issue happen, if you don’t care about it, only the take of the story, scroll to the bottom of this section.

Let’s say we want to handle menus for a player. We’ll have a WBP_Stackable base widget with an OnClosed delegate, and a WBP_MainMenu which derives from it. Latter has a button, with the following event:

CloseButton’s OnClicked event

We have a BP_MenuStack object as the central manager. You can add menus to it, and when all the menus are closed, it’s going to notify the PlayerController about it. It has a MenuCount variable and an OnCloseAll delegate. Its AddMenu function looks like this:

AddMenu function

And its MenuClosed is the following:

MenuClosed function

And that’s all the system this manager will have. The PlayerController will now simply create the manager, add a menu, and wait for the manager to tell us when all the menus are closed so we can continue the game.

PlayerController’s OpenMainMenu function

It doesn’t really matter how the MenusClosed event looks like, it can have a simple string print in it, so you know when all the menus are closed. Now just call OpenMainMenu somewhere (on a key press for example).

Start a Standalone Game (not a PIE!), open the menu and press the close button. You’ll see that it works as expected, MenusClosed is run properly. Now, open the main menu again, but this time, go for a coffee, or to dinner, or whatever (or just run obj gc if you are not that patient). Now when you come back and press the close button… nothing will happen, your string print won’t run. What happened?

The answer is simple, but not easy at all to debug: the GC has eaten your menu manager. Why? Because nothing kept a reference to it. It’s not necessarily easy to see, but after you construct and use this manager, you bind an event on it, and then you let it go. One might assume that this event binding will keep it referenced, as you have run it in the Player Controller, but that’s not the case. The binding happens on the menu manager, and when the OnClosedAll is called, it calls the MenusClosed of the Player Controller. Meaning, no one knows that this manager is waiting for an event to relay to the Player Controller, besides the manager itself. Sadly, that’s not enough to be worthy enough for the GC, so it’s purged, and you’ll never receive the event. You’ll see that as soon as you store the manager after its creation in the Player Controller, you’ll receive the event even if you wait 70 years (legal notice: don’t wait 70 years).

To make it even worse: the editor doesn’t run GC like the game does (whatever asset it has loaded once, it’s going to stay loaded forever), so this problem won’t appear during development, only if the game is run in standalone or packaged mode (which makes debugging much harder).

The take from this is: if you want to execute something when an event happens, the listening object has to be kept in the memory with a variable or some other means, otherwise the object could be trashed, and you’ll never receive the event (since an event binding is not going to save it from GC).

Debugging runtime references

There is a great write-up on Unreal Engine blog which describes the available console commands, with which you can understand what assets are loaded, what holds references to specific objects, etc. They do help tremendously when debugging, so be sure to check this post out too!

I hope the post wasn’t too dry and you have acquired some new knowledge. Please let me know if you find any mistakes or confusing parts, so that I can correct them. Cheers!

--

--

Morva Kristóf
Unreal Engine Tech Shreds

Game Developer, lover of open source projects, freedom, sustainability and strawberries.