Godot randomly invalidating pointers of nodes stored outside scene tree?

Godot Version

4.3

I have several Nodes that do not always need to be in the scene tree in my game but they do always need to exist. So I made a NodeManager class to store pointers to each of these scenes and then I instantiate them all 1 time at startup. This approach is recommend in the performance section of the docs so I figure it is something a lot of games must be doing. This is working…during the game I can retrieve these nodes however for some reason if I go to the main menu of my game and then back into the game and then back to the main menu all the pointers just become invalid for seemingly no reason and then cause the game to crash :frowning: when trying to access them again.

Here is my NodeManager class:

class NodeManager : public RefCounted {
	GDCLASS(NodeManager, RefCounted)
	GDSINGLETON(NodeManager)

	HashMap<StringName, Node *> nodes;

protected:
	static void _bind_methods() {
		ClassDB::bind_method(D_METHOD("get", "key"), &NodeManager::get);
		ClassDB::bind_method(D_METHOD("set", "key", "node"), &NodeManager::set);
		ClassDB::bind_method(D_METHOD("remove", "key"), &NodeManager::remove);
		ClassDB::bind_method(D_METHOD("has", "key"), &NodeManager::has);
		ClassDB::bind_method(D_METHOD("clear"), &NodeManager::clear);
	}

public:
	Node *get(const String &key) { return nodes[key]; }
	void set(const String &key, Node *node) { nodes[key] = node; }
	void remove(const String &key) { nodes.erase(key); }
	bool has(const String &key) { return nodes.has(key); }

	static Node *init_scene(String path) {
		Ref<PackedScene> scene = ResourceLoader::get_singleton()->load(path);
		if (scene == nullptr) {
			UtilityFunctions::printerr("Error initializing: ", path);
			return nullptr;
		}

		return scene->instantiate();
	}

	void clear() {
		for (const KeyValue<StringName, Node *> &key_value : nodes) {
			if (key_value.value != nullptr) {
				key_value.value->queue_free();
			}
		}
	}
};

Like I said this works perfectly fine, the init() function is only ever called one time at game start so the pointers SHOULD always be valid… I am never calling any kind of free or delete function on them so it seems like somehow the engine is just randomly deciding these pointers don’t need to exist and frees them itself. Am I stupid? How would these pointers possibly get invalidated even though I store references to them and never delete them and also never set them to null?

I’m not that familiar with C++ usage in Godot (or any other place for that matter). However, from what I know about Godot’s memory management in GDScript and C#, most objects make use of RefCounted to automatically free the allocated memory once it is no longer referenced anywhere.

Since you are sure that you’re not freeing/deleting anything from your nodes HashMap anywhere (you do it in clear()), perhaps your nodes are instantiated incorrectly? I’m only questioning this because I read this section in Godot Docs that states:

Declaring [a RefCounted object] must be done using Ref<> template.

Now, it’s highly likely that Node->instantiate() does this already, so maybe that’s not it.

Maybe Godot frees memory when a scene switch occurs which inadvertently violates the reference counting in RefCounted? Honestly, I have no clue – the water is too deep for me here.


Hopefully someone more experienced with development in GDExtension comes along and helps you out.

Would you mind posting a link to the page in the docs about performance that you referred to? I couldn’t find the exact page you were talking about.

I figured out the problem, I was calling change_scene_to_file while one of the Nodes I was storing in the NodeManager was still in the scene tree so it was being freed automatically when the scene transitioned. Reworking where I called change_scene_to_file fixed it.

This is the section of the docs I was referring to:

The idea is to pop the huge scenes out of the tree when they aren’t needed so they don’t need to be processed. Not a big deal most of the time but I found some of my UI scenes in particular get to be huge, like 100+ Control Nodes each with dozens of scenes. So instead of toggling the visibility I use remove_child instead so they don’t have to be processed by the scene tree when they don’t need to be processed.

1 Like

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.