Object ownership between C++ and script: free() vs leaking

Godot Version

v4.8.dev.custom_build [12c17c187]

Question

Hi, I’m looking into integrating my custom scripting language with Godot. Memory management of RefCounted objects looks straightforward, and management of Node objects also seems pretty simple because I think they’ll just be destroyed when the scene is, so they can more or less be assumed to be owned by C++. But I’m unsure about other objects that inherit from Object. It seems like gdscript can just call free() on these at any time, and if C++ has a pointer to them, that could crash the game. Object class — Godot Engine (stable) documentation in English does say that C++ should use an ObjectID rather than a pointer in cases like these, so that it can defend against the object being destroyed elsewhere. However, it doesn’t seem like that’s guaranteed.

For instance, RenderingServer::create_local_rendering_device returns a RenderingDevice* that it seems to expect gdscript to free(), or else it will leak. On the other hand, RenderingServer::get_rendering_device() returns a RenderingDevice* that is a singleton that seems to just be stored as a pointer in C++, not an ObjectID, so if gdscript free()s it the engine will crash (confirmed in a local test).

I’d really like for users of my language to not have to free objects manually, but for (non-refcounted non-Node) objects to instead be automatically freed when my language has no remaining references to them. But it seems like if I implement that, it’ll free things like the RenderingDevice singleton which mustn’t be freed. So I have to instead just let them go, and rely on my users to know which ones they must free themselves. But since my language typically doesn’t require that, I’m worried it will lead to many leaks in practice.

Is there some automatic way I can know which objects should or shouldn’t be freed from script? Or alternately, is it considered a bug whenever C++ keeps a raw pointer to an object that could be freed from script? If neither, is there some guidance I could give to my users?

I’m super new to Godot, so my apologies if I’m misunderstanding something.

Just freeing a local RenderingDevice shouldn’t crash anything unless you use the freed reference/pointer afterwards. Same for any other object that inherits Object.

You should probably respect engine’s distinction between managed and non managed objects and not force your own reference counting system on stuff that inherits Object. There are good reasons why some things are not managed. For example, nodes are made to live in the scene tree. The tree needs to keep references to its nodes and nodes may keep references to their children/parent. Nodes need to be kept alive even if there are zero “client” reference to them.

Some Godot functions return borrowed pointers (must not be freed) and others return owning pointers (must be freed after use), and it’s up to you (not your users) to know which is which and to act accordingly. Modern C++ APIs often express the difference by returning raw pointers or references for borrowed pointers, std::unique_ptr for owning pointers, and std::shared_ptr for shared (reference-counted) pointers - but Godot doesn’t seem to use those conventions, so you’ll have to rely on the documentation or the actual code to see which is which.