Is this worth to use or is it a bit over complication for multiple singletons ?
extends Node
var services : Dictionary[String, Node]
func register_service (service_name : String, instance : Node):
services[service_name] = instance
func unregister_service (service_name : String, instance : Node):
# Cannot unregister a service that doesn't exist
if not has_service(service_name):
return
# Cannot unregister a service that is linked to another node
if services[service_name] != instance:
return
services.erase(service_name)
func has_service (service_name : String) -> bool:
# Return false if service is not in dictionary
if not services.has(service_name):
return false
# Try to access the service
var service_node : Node = services[service_name]
# If service is null, remove it then return false
if service_node == null:
services.erase(service_name)
return false
return true
func get_service (service_name : String) -> Node:
if has_service(service_name):
return services[service_name]
printerr(str(service_name, " service not found."))
return null
That looks like a clean pattern to me to express multiple singletons. I don’t see why not to use it.
If it is overly complicated depends on project and preferences, I would say.
When using Project Settings > Globals > Autoloads that’s the standard way everyone knows where to look. This also solves that these scripts get loaded before the rest of the project, which otherwise still has to be solved (by managing services from an autoload or in another way).
I usually prefer in-code solutions, because they are more flexible, but the standard way has the main advantage that everyone knows where to look and how to deal with it.
I currently have about seven singletons in use in my current project, and despite my dislike of singletons the Godot globals are really useful. I have trackers, settings, config, signals, world, turbulance and global_noise. I cannot see any benefit to using that pattern for singletons.
Although the registry pattern is something that I use a fair amount. For instance in my current project I use it so my creatures can register responses to collisions. It makes parent classes really efficient and child classes really clean. Here for instance is my sensor controller for a particular creature.
So the pattern is a good one. But the Godot globals (autoloads) inbuilt system means doing this for singletons is pointless. And as @kraash pointed out, the guaranteed load order and availability at onready, as well as the universal accessibility, is invaluable for global singletons too.
PS I try to keep my autoloads to a minimum in both number and size, but they are just so handy it is easy to start abusing them!
Yep, that would be using globals to get around a deeper problem. The kind of abuse that seems like a good idea until the underlying problem rears its head again somewhere else. This is how code gets spaghettified.
Even my trackers singleton is a bit of an abuse. It now only has about four vars in it as I refactor and get rid of the need for it. Won’t be long before I can drop it entirely. I think I got caught out using them for data storage. They are so much more than that. Now I think of them as persistent global managers.