Service locator - is it worth to use?

Inside course on Zenva Academy is mentioned Service locator as

This design keeps callers independent from concrete scene paths and from the specific manager currently providing a service.

Systems do not need chains of conditions to determine which regional manager or other implementation is active.

They only need the stable service identifier, while registration determines which instance will answer the request.

func register_service (service_name : String, instance : Node):
services[service_name] = instance

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.

1 Like

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.

func _init(creature_node: Node2D) -> void:
	super(creature_node)

	# COLLISION HANDLERS
	register_collision_handler("Player", _on_bonding_collision)
	register_collision_handler("GoodWisp", _on_bonding_collision)
	register_collision_handler("GoodOrb", _on_bounce_collision)
	register_collision_handler("BadOrb", _on_being_attacked_collision)

	# DETECTED HANDLERS
	register_detected_handler("Player", _on_chase_target_found)
	register_detected_handler("GoodWisp", _on_chase_target_found)
	register_detected_handler("BadOrb", _on_evasion_target_found)

	# LOST HANDLERS
	register_lost_handler("Player", _on_chase_target_lost)
	register_lost_handler("GoodWisp", _on_chase_target_lost)
	register_lost_handler("BadOrb", _on_evasion_target_lost)

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!

2 Likes

Yes absolutely, easy to abuse.

I was suggested in other topic to use it store variable for purpose of naming nodes on decal nodes.

Maybe it could find use in this sort of case.

1 Like

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.

1 Like