Unload preloaded objects from memory

Hello!

I have a card game where every card is a class_name Card instance that has @export var cardData = Card where i fill cardData with whatever properties the card is going to have.
When starting the game i have an array which holds the scripts for the cards ex.

func _ready():
	$"..".cardData = preload("res://CardResources/Example.tres")
	$"..".name = $"..".cardData.Name
	$"../CardImg".texture = $"..".cardData.Img

func cardEffect():
	#whatever this card does

In the loading screen, this array is filled with preloaded scripts which also preload the objects.

My issue is that when you go back to main menu and start a new game without closing and reopening the game, the cardData resource stays the same as it did in the previous session ex. Cards keep the buffs they gained.

How do i free these preloaded objects when returning to main menu ?

Might be something you’ve already seen, but here’s how freeing resources works.

Since your use cases seems to be having a menu and a level where card data is loaded, I’d recommend having a card manager script that isn’t persistent between scenes (I doubt you need it’s functionality in the main menu anyways). That way you load your card data in the main level, and then when you quit to main menu the card manager is freed and therefore all its resources.

I should also note that with preload you are loading the resource into memory whenever that node is instantiated. Load could give you a little more control of when the cards are loaded into memory during gameplay.

How would that card manager script work? I have no idea how thats going to solve it.

Is there a way to see the references for an object so i can remove them? Because even if i queue_free() every node that cardData is attached to then the object should be deleted along with references but the same thing still happens.

Well you could be getting into some more complicated memory management topics. I’d recommend looking at refcounted and the profiler if you really want to see what’s being referenced/still in memory.

Although I think your problem is just with making sure you have a fresh instance of your resource every time your scene is loaded, right? In that case we might be overthinking this. We need to make sure whatever is loading these card resources is a part of the game scene that is unloaded whenever we switch to the menu.

How do you transition between scenes? Are you using change_scene_to_file?

Im using change_scene_to_packed

by also using var somescene = load(res://somescene.tscn)

Figured it out! In your initial code above you are editing the Name and Image of the Card Resource Itself. This editing changes the actual serialized resource, and therefore persists between scenes. I’d agree this is a little counterintuitive, as usually loading a serialized object just creates an instance and will not write any changes back to the serialized object.

Quick fix could be to duplicate the loaded resource, breaking the association with the serialized object.

$"..".cardData = preload("res://CardResources/Example.tres").duplicate()

But you could also separate the deserialized card object from the resource as its own class. That way you’d know when it’s safe to modify the values of a card during runtime.

Hope that helps!

Alternatively, ResourceLoader.Load should also give you the flexibility you need.

I think the problem is that the Resource Cache keeps your loaded resource between scenes. By default, load and preload check the resource cache in order to not load it multiple times. You can override this by providing the property “cache_mode” in ResourceLoader.Load.

ResourceLoader.load("res://new_resource.tres","",ResourceLoader.CACHE_MODE_IGNORE)

Duplicating seems like really a quick fix that i’ll use as a last resort.

I didn’t know about CACHE_MODE up until now because the docs for load() doesnt redirect me to ResourceLoader.
I replaced all the load()'s with this but there’s no change because the scripts are the ones that preload() the “Resource”. This however is EXACTLY what i want but for “preload”.

The way the loading done is:
1 - User selects a deck which appends all cardScript’s in the chosen decks’ path to chosenCards ex. res://CardScripts/Example.gd
2 - Loading screen executes load_threaded_request(cardScript) for every cardScript in chosenCards.
3 - Each card script has a preload(res://CardResources/Example.tres") inside their own _ready() that loads their cardData resource which holds the stats of the card
4 - All the cardScript’s are appended to CardsInDeck by load_threaded_get(cardScript)

After loading screen in game, this is what print(CardsInDeck) looks like with 3 cards in the deck:
[<GDScript#-9223372007645641519>, <GDScript#-9223372007964408625>, <GDScript#-9223372008283175731>]

When returning to main menu, the Monitor in the editor shows that “Resources” under “Object” are still present.

What i would like to happen:
• When returning to main menu, i want all the cardData a.k.a “Resource” in the cache to be reset as if the game was reopened.

Theres “ResourcePreLoader” node but i don’t know if it helps me or not.

This should’ve been very simple yet its becoming so frustrating, isn’t there just a simple “flush all resources and objects” or “view all references that are tied to objects” to atleast see what’s referencing them and causing them not to be deleted?

Happened to stumble upon this from this docs page.

“Keep in mind that loading a resource fetches the cached resource instance maintained by the engine. To get a new object, one must duplicate an existing reference or instantiate one from scratch with new().”

Fun fact, new() isn’t really documented in Object? It’s a pain.

But anyways, I think the general idea is that

  • Loading and Preloading tries to use a resource cache that avoids redundant resource loads. It’s annoying for our use case here.
  • If you want to instance a Resource you have to use duplicate, which seems klugey but is the intended strat.

But for your general problem, let me try and think through this. You talk about cardScript and cardData. It’s clear to me that cardData is a resource that contains base stats that can change, like power, toughness, hp, etc. This is the resource that you need to have instanced, and is causing problems when it is not.

However, you are also loading each cardScript. I’m assuming that this maybe contains other data like the image for the card, alongside functionality that the card has.

If this is true then you need to be loading the cardScripts while ignoring the cache (so you get a fresh instance), and then inside that cardScript ALSO loading the cardData while ignoring the cache. Having to do that nested loading might suck, especially if you have a loading screen that keeps track of that loading progress.

One way I might recommend making this simpler is by having ALL data that the card needs live in the cardData resource. That way when you load a “deck”, it uses the ResourceLoader to make instances of all the cardData resources, and then plugs them into brand new cardScript nodes. In this case the cardScript just has a slot for a cardData and any functions that will allow it to do what it needs to do in the game.

That should fix your persistence issues I think

# For each cardData in the deck
var card_data_path = "res://resources/card_data/boolia_the_true.tres"
# I think this resource will still live in the cache, but haven't checked
# Shouldn't matter either way
load_threaded_request(card_data_path, ResourceLoader.CACHE_MODE_IGNORE)

# eventually, after polling to see if the resource has finished loading
var loaded_resource = load_threaded_get(card_data_path)

# We do NOT want to edit this resource, it is our template. Instead we instance it first
var output_card_data = loaded_resource.duplicate()
# And if you want to follow my suggestions from above :)
var new_card_script = cardScript.new()
new_card_script.Initialize(output_card_data)

But let me know if this is making sense, this is new territory for me!

Yeah this is a solution but wouldn’t this cause a “memory leak” since the other objects are not unloaded so every time you return back to main menu you get even more instances of the objects in memory?

It wouldnt cause a memory leak as Godot has Garbage collection built in (as far as I know for GD script, unless its just C#). However if there is a lot to remove then it will hurt performance (probably not an issue for a card game though)

Personally I wouldn’t unload anything if you have already loaded it. You have an array of cards, so just go through each card and reset their attributes back to a ‘default’ state. Not sue how you do that in GD Script as I use C#, but I imagine its not that dissimilar.

Resource inherits from RefCounted, so it will be Garbage Collected automatically :slight_smile:. When you leave to the main menu, the instanced data has no more references and is freed.