SkillAgentSearch skills...

AutoInject

Reflection-free node-based dependency injection for C# Godot scripts, including utilities for automatic node-binding, additional lifecycle hooks, and .net-inspired notification callbacks.

Install / Use

/learn @chickensoft-games/AutoInject
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

💉 AutoInject

[![Chickensoft Badge][chickensoft-badge]][chickensoft-website] [![Discord][discord-badge]][discord] ![line coverage][line-coverage] ![branch coverage][branch-coverage]

Reflection-free node-based dependency injection for C# Godot scripts, including utilities for automatic node-binding, additional lifecycle hooks, and .net-inspired notification callbacks.


<p align="center"> <img alt="Chickensoft.AutoInject" src="Chickensoft.AutoInject/icon.png" width="200"> </p>

📘 Background

Game scripts quickly become difficult to maintain when strongly coupled to each other. Various approaches to dependency injection are often used to facilitate weak coupling. For C# scripts in Godot games, AutoInject is provided to allow nodes higher in the scene tree to provide dependencies to their descendant nodes lower in the tree.

AutoInject borrows the concept of a Provider and a Dependent from [other tree-based dependency provisioning systems][provider]. A Provider node provides values to its descendant nodes. A Dependent node requests values from its ancestor nodes.

Because _Ready/OnReady is called on node scripts further down the tree first in Godot (see [Understanding Tree Order][tree-order] for more), nodes lower in the tree often cannot access the values they need since they do not exist until their ancestors have a chance to create them in their own _Ready/OnReady methods. AutoInject solves this problem by temporarily subscribing to each Provider it finds that is still initializing from each Dependent until it knows the dependencies have been resolved.

Providing nodes "top-down" over sections of the game's scene tree has a few advantages:

  • ✅ Dependent nodes can find the nearest ancestor that provides the value they need, allowing provided values to be overridden easily (when desired).
  • ✅ Nodes can be moved around the scene tree without needing to update their dependencies.
  • ✅ Nodes that end up under a different provider will automatically use that new provider's value.
  • ✅ Scripts don't have to know about each other.
  • ✅ The natural flow-of-data mimics the other patterns used throughout the Godot engine.
  • ✅ Dependent scripts can still be run in isolated scenes by providing default fallback values.
  • ✅ Scoping dependencies to the scene tree prevents the existence of values that are invalid above the provider node.
  • ✅ Resolution occurs in O(n), where n is the height of the tree above the requesting dependent node (usually only a handful of nodes to search). For deep trees, "reflecting" dependencies by re-providing them further down the tree speeds things up further.
  • ✅ Dependencies are resolved when the node enters the scene tree, allowing for O(1) access afterwards. Exiting and re-entering the scene tree triggers the dependency resolution process again.
  • ✅ Scripts can be both dependents and providers.

📼 About Mixins

The [Introspection] generator that AutoInject uses allows you to add [mixins] to an existing C# class. Mixins are similar to interfaces, but they allow a node to gain additional instance state, as well as allow the node to know which mixins are applied to it and invoke mixin handler methods — all without reflection.

In addition, AutoInject provides a few extra utilities to make working with node scripts even easier:

  • 🎮 IAutoOn: allow node scripts to implement .NET-style handler methods for Godot notifications: i.e., OnReady, OnProcess, etc.
  • 🪢 IAutoConnect: automatically bind properties marked with [Node] to a node in the scene tree — also provides access to nodes via their interfaces using [GodotNodeInterfaces].
  • 🛠 IAutoInit: adds an additional lifecycle method that is called before _Ready if (and only if) the node's IsTesting property is set to false. The additional lifecycle method for production code enables you to more easily unit test code by separating initialization logic from the engine lifecycle.
  • 🎁 IProvider: a node that provides one or more dependencies to its descendants. Providers must implement IProvide<T> for each type of value they provide, or optionally implement IProvideAny to handle the generic on the method level with IProvideAny.Value<T>.
  • 🔗 IDependent: a node that depends on one or more dependencies from its ancestors. Dependent nodes must mark their dependencies with the [Dependency] attribute and call this.DependOn<T>() to retrieve the value.
  • 🐤 IAutoNode: a mixin that applies all of the above mixins to a node script at once.

Want all the functionality that AutoInject provides? Simply add this to your Godot node:

using Chickensoft.AutoInject;
using Chickensoft.Introspection;
using Godot;

// Apply all of the AutoInject mixins at once:
[Meta(typeof(IAutoNode))]
public partial class MyNode : Node
{
  public override void _Notification(int what) => this.Notify(what);
}

Alternatively, you can use just the mixins you need from this project.

[Meta(
  typeof(IAutoOn),
  typeof(IAutoConnect),
  typeof(IAutoInit),
  typeof(IProvider),
  typeof(IDependent)
)]
public partial class MyNode : Node
{
  public override void _Notification(int what) => this.Notify(what);
}

[!IMPORTANT] For the mixins to work, you must override _Notification in your node script and call this.Notify(what) from it. This is necessary for the mixins to know when to invoke their handler methods. Unfortunately, there is no way around this since Godot must see the _Notification method in your script to generate handlers for it.

public override void _Notification(int what) => this.Notify(what);

📦 Installation

AutoInject is a source-only package that uses the [Introspection] source generator. AutoInject provides two mixins: IDependent and IProvider that must be applied with the Introspection generator's [Meta].

You'll need to include Chickensoft.GodotNodeInterfaces, Chickensoft.Introspection, Chickensoft.Introspection.Generator, and Chickensoft.AutoInject in your project. All of the packages are extremely lightweight.

Simply add the following to your project's .csproj file. Be sure to specify the appropriate versions for each package by checking on Nuget.

<ItemGroup>
    <PackageReference Include="Chickensoft.GodotNodeInterfaces" Version="..." />
    <PackageReference Include="Chickensoft.Introspection" Version="..." />
    <PackageReference Include="Chickensoft.Introspection.Generator" Version="..." PrivateAssets="all" OutputItemType="analyzer" />
    <PackageReference Include="Chickensoft.AutoInject" Version="..." PrivateAssets="all" />
</ItemGroup>

You can also add Chickensoft.AutoInject.Analyzers to your project to get additional checks and code fixes for AutoInject, such as ensuring that you override _Notification and call this.Provide() from your provider nodes.

<ItemGroup>
    <PackageReference Include="Chickensoft.AutoInject.Analyzers" Version="..." PrivateAssets="all" OutputItemType="analyzer" />
</ItemGroup>

[!WARNING] We strongly recommend treating warning CS9057 as an error to catch possible compiler-mismatch issues with the Introspection generator. (See the [Introspection] README for more details.) To do so, add a WarningsAsErrors line to your .csproj file's PropertyGroup:

<PropertyGroup>
  <TargetFramework>net8.0</TargetFramework>
  ...
  <!-- Catch compiler-mismatch issues with the Introspection generator -->
  <WarningsAsErrors>CS9057</WarningsAsErrors>
  ...
</PropertyGroup>

[!TIP] Want to see AutoInject in action? Check out the Chickensoft [Game Demo].

🎁 Providers

To provide values to descendant nodes, add the IProvider mixin to your node script and implement IProvide<T> for each value you'd like to make available, or IProvideAny if you want to handle the generic on the method level.

Once providers have initialized the values they provide, they must call the this.Provide() extension method to inform AutoInject that the provided values are now available.

The example below shows a node script that provides a string value to its descendants. Values are always provided by their type.

[!NOTE] The IProvideAny interface will stop signals from propagating to the node's parent. This could at best mute any errors from unresolved dependencies when implemented on the root node, and at worst stop a dependency from being resolved if it was supposed to be fetched from an ancestor.

namespace MyGameProject;

using Chickensoft.AutoInject;
using Chickensoft.Introspection;
using Godot;

[Meta(typeof(IAutoNode))]
public partial class MyProvider : Node, IProvide<string>
{
  public override void _Notification(int what) => this.Notify(what);

  string IProvide<string>.Value() => "Value"

  // Call the this.Provide() method once your dependencies have been initialized.
  public void OnReady() => this.Provide();

  public void OnProvided()
  {
    // You can optionally implement this method. It gets called once you call
    // this.Provide() to inform AutoInject that the provided values are now
    // available.
  }
}

🐣 Dependents

To use a provided value in a descendant node somewhere, add the IDependent mixin to your descendent node script and mark each dependency with the [Dependency] attribute. The notification method override is used to automatically tell the mixins when your node is ready and begin the dependency resolution process.

Once all of the dependencies in your dependent node are resolved, the OnResolved() method of your dependent node will be called (if overridden).

namespace MyGameProject;

using Chickensoft.Introspection;
using Godot;

[Meta(typeof(IAutoNode))]
public partial class StringDependent : Node
{
  public override void _Notification(int what) => this.Notify(what);

  [Dependency]
  public string MyDependency => this.DependOn<string>
View on GitHub
GitHub Stars198
CategoryDevelopment
Updated2d ago
Forks12

Languages

C#

Security Score

100/100

Audited on Mar 27, 2026

No findings