Asking for opinion in this pattern for sharing data across nodes

Question

Hi guys, I want to ask for opinions about this programming pattern.

So I want to be able to share some data across nodes that are far from each other in scene hierarchy.

And I heard about this shiny thing called Dependency Injection. So I try create it with signals…

dependency_injector.gd (autoload)

class_name DependencyInjector
extends Node

signal dependency_required(type: String)
signal dependency_provided(type: String, value: Variant)

consumer.gd

var injected_data: Data

func _ready() -> void:
	DependencyInjector.dependency_provided.connect(_on_dependency_provided)
	dependency_required.emit(Data.DEPENDENCY_ID)
	await DependencyInjector.dependency_provided

func _on_dependency_provided(type: String, value: Variant) -> void:
	if type != Data.DEPENDENCY_TYPE: return
	injected_data = value as Data

provider.gd

@onready var data: Data = Data.new()

func _ready() -> void:
	DependencyInjector.dependency_required.connect(_on_dependency_required)
	dependency_required.emit(Data.DEPENDENCY_ID)

func _on_dependency_required(type: String) -> void:
	if type != Data.DEPENDENCY_TYPE: return
	DependencyInjector.dependency_provided.emit(Data.DEPENDENCY_ID, data)
	
class Data:
	const DEPENDENCY_TYPE := "DATA_TYPE"
	var something: int = 0

Although it’s not really 100% dependency injection, How do you think about using this pattern on medium-large scale games? Do you see any pros/cons with it?

pros I can think of are

  1. can share data up/down the node hierarchy easily
  2. remove coupling between provider/consumer node

and cons…

  1. need to await for data on consumer
  2. there can be multiple provider causing unpredictable outcome
  3. there’s no guarantee that provider have the correct data

Why not just use the Server-Client pattern that godot itself uses? You have a singleton like DataManager with static methods and any other script can provide or request data from it just by using the static calls, no need for extra checks or waiting for signals.

Yeah admittedly this just seems like an unneccessary complication. IMO anyway I find things go a bit smoother when you try to go with how the engine is already doing things, your own code feels more consistent with what you write to interface with other parts of the engine, etc.

Plus you are already making a new singletone for the dependency injector, so… making an application-specific one to the actual goal you are trying to achieve seems like it’d be more productive than adding these extra layers