|
| 1 | +--- |
| 2 | +title: "Bindito" |
| 3 | +description: Timberborn's dependency injection framework |
| 4 | +date: 2025-06-05 |
| 5 | +tags: mods timberborn guide modding architecture bindito di dependency injection |
| 6 | +--- |
| 7 | + |
| 8 | +Timberborn uses Bindito as its in-house dependency injection framework. |
| 9 | + |
| 10 | +# Declaration & Injection |
| 11 | + |
| 12 | +Since U7, you usually use `Configurator` classes to declare your services: |
| 13 | + |
| 14 | +```cs |
| 15 | +[Context("MainMenu")] |
| 16 | +public class ModMenuConfig : Configurator |
| 17 | +{ |
| 18 | + public override void Configure() |
| 19 | + { |
| 20 | + Bind<MSettings>().AsSingleton(); // Bind a class as a singleton service |
| 21 | + MultiBind<ISomeInterface>().ToExisting<MSettings>(); // Bind an interface to an existing singleton |
| 22 | + } |
| 23 | +} |
| 24 | + |
| 25 | +[Context("Game")] |
| 26 | +public class ModGameConfig : Configurator |
| 27 | +{ |
| 28 | + public override void Configure() |
| 29 | + { |
| 30 | + Bind<ISomeInterface>().As<SomeImplementation>(); // Bind an interface to its implementation |
| 31 | + MultiBind<TemplateModule>().ToProvider(() => GetTemplateModule()); // Bind a multibinding to a provider function |
| 32 | +
|
| 33 | + // Bind a class as a transient service, |
| 34 | + // meaning a new instance will be created every time it is requested |
| 35 | + Bind<MyClass>().AsTransient(); |
| 36 | + } |
| 37 | +} |
| 38 | +``` |
| 39 | + |
| 40 | +You can use multiple `ContextAttribute` on the same class but in my experience, usually it is better to separate them into different classes because sometimes not all services are available in all contexts. For example, `CameraService` is not available in the main menu context. |
| 41 | + |
| 42 | +You can then inject them to your services: |
| 43 | + |
| 44 | +```cs |
| 45 | +public class MyService( |
| 46 | + MSettings settings, // Inject a singleton service |
| 47 | + ISomeInterface someInterface, // Inject an interface |
| 48 | + IEnumerable<TemplateModule> templateModules // Inject a multibinding |
| 49 | +) { } |
| 50 | +``` |
| 51 | + |
| 52 | +> **ℹ️ Note:** |
| 53 | +> In multibindings, you can inject them even if no implementation is bound. You will just get an empty collection. However, if you inject a single service that is not bound, it will throw an exception. Be aware of cyclic dependencies as well, Bindito will throw an exception if it detects a cycle. |
| 54 | +
|
| 55 | +In some cases, you cannot use constructor injection, for example `BaseComponent` or `MonoBehaviour` that must have a parameterless constructor. In that case, you can use `[Inject]` to inject the dependencies manually: |
| 56 | + |
| 57 | +```cs |
| 58 | +public class MyComponent : BaseComponent |
| 59 | +{ |
| 60 | + MSettings settings = null!; |
| 61 | + ISomeInterface someInterface = null!; |
| 62 | + |
| 63 | + [Inject] |
| 64 | + public void InjectDependencies(MSettings settings, ISomeInterface someInterface) |
| 65 | + { |
| 66 | + this.settings = settings; |
| 67 | + this.someInterface = someInterface; |
| 68 | + } |
| 69 | +} |
| 70 | +``` |
| 71 | + |
| 72 | +- The method must be public. The name does not matter. You can have multiple methods with `[Inject]` attribute in the same class. It even works for inherited classes. |
| 73 | + |
| 74 | +# Manually get a service |
| 75 | + |
| 76 | +If you need to get a service manually, get a reference to `IContainer` service. I sometimes use this for UI elements: |
| 77 | + |
| 78 | +```cs |
| 79 | +public class MyDialogController(IContainer container) |
| 80 | +{ |
| 81 | + |
| 82 | + public void Show() |
| 83 | + { |
| 84 | + // MyDialog is usually a transient service but it can be anything |
| 85 | + var diag = container.GetInstance<MyDialog>(); |
| 86 | + } |
| 87 | + |
| 88 | +} |
| 89 | +``` |
| 90 | + |
| 91 | +There is also the `IContainer.Inject` method that can inject dependencies into an existing object. |
| 92 | + |
| 93 | +# Popular Multibindings |
| 94 | + |
| 95 | +These are some popular multibindings I think you will use often: |
| 96 | +- `TemplateModule`: Used to [add a component to a prefab with another component](./timberborn-architecture#base-component). |
| 97 | +- `EntityPanelModule`: Add an info panel (the panel to the right of the screen when you select an entity). You can use [TimberUI](../TimberUI/) to make it easier. |
| 98 | +- `IDevModule`: Add debug commands to the game. |
| 99 | +- `IWaterStrengthModifier`: Used to modify the water strength of a `WaterSource`. |
| 100 | + |
| 101 | +## TemplateModule |
| 102 | + |
| 103 | +The game usually uses `TemplateModule` to add a component to a prefab. For example: |
| 104 | + |
| 105 | +```cs |
| 106 | + protected override void Configure() |
| 107 | + { |
| 108 | + Bind<WaterStrengthService>().AsSingleton(); |
| 109 | + MultiBind<TemplateModule>().ToProvider(ProvideTemplateModule).AsSingleton(); |
| 110 | + } |
| 111 | + |
| 112 | + private static TemplateModule ProvideTemplateModule() |
| 113 | + { |
| 114 | + TemplateModule.Builder builder = new TemplateModule.Builder(); |
| 115 | + builder.AddDecorator<WaterSourceSpec, WaterSource>(); |
| 116 | + builder.AddDecorator<WaterSource, DroughtWaterStrengthModifier>(); |
| 117 | + |
| 118 | + // ... |
| 119 | +
|
| 120 | + return builder.Build(); |
| 121 | + } |
| 122 | +``` |
| 123 | + |
| 124 | +Which means, in the prefab, they only need to add `WaterSourceSpec` with all the required properties. Then, all prefabs with `WaterSourceSpec` will automatically have the `WaterSource` component and the `DroughtWaterStrengthModifier` component added to them. |
| 125 | + |
| 126 | +So for example, in my [Moddable Weather](https://steamcommunity.com/workshop/filedetails/?id=3493039008) mod, I add the Monsoon and SurprisinglyRefreshing like this (using TimberUI's extension method to help with the syntax): |
| 127 | + |
| 128 | +```cs |
| 129 | +configurator.BindTemplateModule(h => h |
| 130 | + .AddDecorator<WaterSource, MonsoonWaterStrengthModifier>() |
| 131 | + .AddDecorator<WaterSourceContamination, SurprisinglyRefreshingController>() |
| 132 | + // ... |
| 133 | +); |
| 134 | +``` |
0 commit comments