Dynamically Loaded Assemblies
Init(args) supports creating global services registered using the [Service] attribute from assemblies that are loaded at runtime.
If the assembly is loaded before Init(args) has finished initializing global services, then global services registered in the assembly using the [Service] attribute will get initialized as part of the initial service registration process. This occurs during the [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] event.
If the assembly is loaded after initial global service initialization has finished, then non-lazy global services will found in the assembly be created when the assembly is loaded, and lazy global services will be created after the first client requests them after the assembly has been loaded.
Replacing Global Services
Using Service.Register
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
static void InitServices() => ServiceUtility.ServiceInitializationStarting += OnServiceInitializationStarting;
static void OnServiceInitializationStarting()
{
Service.Create<SomeServiceReplacement, SomeService>();
}
class SomeServiceReplacement : SomeService
{
public override void DoSomething() => Debug.Log("Hello from mod!");
}
Using Local Services
Because clients prioritize local services registered using a Service Tag, a Services component and Service.AddFor over global services registered using the [Service] attribute, it is possible for a mod to effectively replace a global service using them.
var newService = new SomeServiceReplacement();
Service.AddFor<SomeService>(Clients.Everywhere, newService, registerer: this);
...
class SomeServiceReplacement : SomeService
{
public override void DoSomething() => Debug.Log("Hello from mod!");
}
Decorating Global Services
Using Local Services
A local service could also be used to “decorate” or “wrap” an existing global service, to layer some additional functionality on top of it, instead of completely replacing all its functionality:
var oldService = Service.Get<ISomeService>();
var newService = new SomeServiceDecorator(oldService);
Service.AddFor<ISomeService>(Clients.Everywhere, newService, registerer: this);
...
class SomeServiceDecorator : ISomeService
{
ISomeService decoratedService;
public SomeServiceDecorator(ISomeService decoratedService)
=> this.decoratedService = decoratedService;
public void DoSomething()
{
Debug.Log("Doing something...");
decoratedService.DoSomething();
Debug.Log("Did something.");
}
}
Reacting To Changing Services
If a new mod DLL is loaded at runtime, and that mod makes changes to existing services after some clients have already been initialized, those existing clients will not automatically get re-initialized with the new services.
To do this you can subscribe to receive callbacks when a service that a client depends on changes to a different one using the Service.AddInstanceChangedListener method:
class Player : MonoBehaviour<IInputManager>
{
IInputManager inputManager;
protected override void Init(IInputManager inputManager) => this.inputManager = inputManager;
void OnEnable() => Service.AddInstanceChangedListener(OnInputManagerChanged);
void OnDisable() => Service.RemoveInstanceChangedListener(OnInputManagerChanged);
void OnInputManagerChanged(Clients clients, IInputManager oldInstance, IInputManager newInstance) => inputManager = newInstance;
}
Alternatively you can also use Service.AddChangedListener to execute a method with no parameters in reaction to a service changing:
class Player : MonoBehaviour<A, B, C>
{
A a;
B b;
C c;
protected override void Init(A a, B b, C c)
{
this.a = a;
this.b = b;
this.c = c;
}
void OnEnable()
{
Service.AddChangedListener<A>(OnServiceChanged);
Service.AddChangedListener<B>(OnServiceChanged);
Service.AddChangedListener<C>(OnServiceChanged);
}
void OnDisable()
{
Service.RemoveChangedListener<A>(OnServiceChanged);
Service.RemoveChangedListener<B>(OnServiceChanged);
Service.RemoveChangedListener<C>(OnServiceChanged);
}
void OnServiceChanged() => Init(Context.MainThread | Context.Runtime);
}