Skip to content

Repository files navigation

A flexible & efficient way to manage and access services in Unity

ServiceLocator is based on a ScriptableObject implementation, offering various methods for registering, unregistering, and retrieving services.

License: MITPullRequestsReleasesUnity


Installation

Add Service Locator to your Unity project via Package Manager:

  1. Open Window > Package Manager
  2. Click + > Add package from git URL
  3. Enter:
https://www.pkglnk.dev/service-locator.git

pkglnk

Key Features

  • ScriptableObject-based implementation for seamless Unity integration
  • Synchronous, asynchronous, and promise-based service retrieval
  • Coroutine support for service access
  • Multi-service retrieval in a single call
  • Cancellation support with CancellationToken, including MonoBehaviour.destroyCancellationToken
  • Robust error handling and service rejection mechanisms
  • Automatic scene tracking for services with a fallback cleanup method when scenes are unloaded
  • Service validation for checking if MonoBehaviour services are valid or destroyed
  • Configurable feature toggles via the Service Locator Window settings tab
  • Fluent API options

Support

If you like my work then please consider showing your support by buying me a brew

Buy Me A Coffee

Usage

Creating a ServiceLocator Asset

A DefaultServiceLocator asset is included in the package for immediate use. The ServiceLocator property drawer automatically populates with the first ServiceLocator asset found in the project. To create a custom instance:

  1. Right-click in the Unity Editor's Project panel.
  2. Navigate to Create -> ServiceLocator to create a new ServiceLocator asset.

Registering Services

Register services using the Register<T> method:

publicinterfaceIMyService{voidDoSomething();}publicclassMyService:IMyService{publicvoidDoSomething(){Debug.Log("Doing something!");}}// Obtain your ServiceLocator instanceServiceLocatorlocator;// ... reference to your ServiceLocator assetlocator.Register<IMyService>(newMyService());

Accessing Services

ServiceLocator offers multiple retrieval methods:

  1. Async/Await - Single Service:
IMyServicemyService=await_serviceLocator.GetServiceAsync<IMyService>();myService.DoSomething();
  1. Promise-based - Single Service:
_serviceLocator.GetService<IMyService>().Then(service =>service.DoSomething()).Catch(ex =>Debug.LogError($"Failed to get service: {ex.Message}"));
  1. Coroutine-based - Single Service:
StartCoroutine(_serviceLocator.GetServiceCoroutine<IMyService>(service =>{service?.DoSomething();}));
  1. Immediate (Try-Get) - Single Service
if(_serviceLocator.TryGetService<IMyService>(outvarmyService)){myService.DoSomething();}

Retrieving Multiple Services

Retrieve multiple services in one call (up to 6 supported):

  1. Async/Await - Two Services
var(myService,anotherService)=await_serviceLocator.GetServicesAsync<IMyService,IAnotherService>();myService.DoSomething();anotherService.DoAnotherThing();

Promise-based - Three Services

_serviceLocator.GetService<IMyService,IAnotherService,IThirdService>().Then(services =>{services.Item1.DoSomething();services.Item2.DoAnotherThing();services.Item3.DoThirdThing();}).Catch(ex =>Debug.LogError($"Failed to get services: {ex.Message}"));

Fluent API for Service Retrieval

ServiceLocator now includes a fluent API for cleaner, more readable service retrieval, particularly when working with multiple services.

Async/Await with Fluent API:

// Single servicevarmyService=await_serviceLocator.GetAsync<IMyService>().WithCancellation(destroyCancellationToken);// Multiple servicesvar(myService,anotherService,thirdService)=await_serviceLocator.GetAsync<IMyService>().AndAsync<IAnotherService>().AndAsync<IThirdService>().WithCancellation(destroyCancellationToken);

Promise-based with Fluent API:

// Single service_serviceLocator.Get<IMyService>().WithCancellation(destroyCancellationToken).Then(service =>service.DoSomething()).Catch(ex =>Debug.LogError($"Failed to get service: {ex.Message}"));// Multiple services_serviceLocator.Get<IMyService>().And<IAnotherService>().And<IThirdService>().WithCancellation(destroyCancellationToken).Then(services =>{var(service1,service2,service3)=services;service1.DoSomething();service2.DoAnotherThing();service3.DoThirdThing();}).Catch(ex =>Debug.LogError($"Failed to get services: {ex.Message}"));

Coroutine-based with Fluent API:

// Single serviceStartCoroutine(_serviceLocator.GetCoroutine<IMyService>().WithCallback(service =>service?.DoSomething()));// Multiple servicesStartCoroutine(_serviceLocator.GetCoroutine<IMyService>().And<IAnotherService>().And<IThirdService>().WithCallback((service1,service2,service3)=>{service1?.DoSomething();service2?.DoAnotherThing();service3?.DoThirdThing();}));

This fluent approach improves code readability significantly over the traditional approach, especially when retrieving multiple services. It maintains all the same functionality, including cancellation support, while providing a more intuitive and chainable API.

The fluent API supports up to 6 services in a single chain, just like the traditional API methods.

Simplified Error Handling with Extensions

ServiceLocator now includes extension methods that provide standardized error handling for async operations, reducing the need for repetitive try/catch blocks.

Basic Error Handling

// Without extension - requires try/catch blockprivateasyncvoidStart(){try{varservice=await_serviceLocator.GetServiceAsync<IMyService>();service.DoSomething();}catch(OperationCanceledException){// Handle cancellation}catch(Exceptionex){Debug.LogError($"Failed to retrieve service: {ex.Message}");}}// With extension - cleaner code with default error loggingprivateasyncvoidStart(){varservice=await_serviceLocator.GetServiceAsync<IMyService>().WithErrorHandling(defaultValue:null);service?.DoSomething();}

Advanced Error Handling Options

The error handling extensions provide several features:

varservice=await_serviceLocator.GetServiceAsync<IMyService>().WithErrorHandling(defaultValue:null,// Value to return if the operation failserrorHandler: ex =>{// Custom error handlerNotifyUser("Service unavailable");AnalyticsManager.LogError(ex);},rethrowException:false// Whether to rethrow the exception);

Combining with Fluent API

Error handling extensions work seamlessly with the fluent API:

// Single service with fluent API and error handlingvarservice=await_serviceLocator.GetAsync<IMyService>().WithCancellation(destroyCancellationToken).WithErrorHandling(defaultValue:null);// Multiple services with fluent API and error handlingvar(service1,service2)=await_serviceLocator.GetAsync<IMyService>().AndAsync<IAnotherService>().WithCancellation(destroyCancellationToken).WithErrorHandling(defaultValue:(null,null),errorHandler: ex =>LogManager.LogError("Failed to load services",ex));// Process services safely with null checksif(service1!=null){service1.DoSomething();}if(service2!=null){service2.DoAnotherThing();}

Benefits

These error handling extensions provide:

  1. Automatic contextual logging: Each error is logged with file, method, and line number
  2. Default value support: Specify fallback values when services aren't available
  3. Customizable error handling: Provide custom handlers for specific error scenarios
  4. Simplified code: Remove boilerplate try/catch blocks for cleaner service access
  5. Cancellation handling: OperationCanceledException is handled gracefully

Error handling works for any Task-based operation, not just service retrieval, making it useful throughout your codebase.

Unregistering Services

Unregister a service when no longer needed:

_serviceLocator.Unregister<IMyService>();

Simplified Service Registration with MonoService

Use MonoService for automatic registration/unregistration::

publicinterfaceIMyService{voidDoSomething();}publicclassMyMonoService:MonoService<IMyService>,IMyService{publicvoidDoSomething(){Debug.Log("Doing something from MonoService!");}protectedoverridevoidAwake(){base.Awake();// Additional initialization if neededServiceReady();// Registers the service}}

MonoService Inheritance

You can create abstract base classes that inherit from MonoService to establish service patterns:

// Base abstract class for all mini-game score servicespublicabstractclassMiniGameScoreService<T>:MonoService<T>,IMiniGameScoreServicewhereT:class,IMiniGameScoreService{// Common score tracking functionalityprotectedint_score;publicvirtualintGetScore()=>_score;publicvirtualvoidAddScore(intpoints){_score+=points;OnScoreChanged?.Invoke(_score);}publiceventAction<int>OnScoreChanged;// Other shared functionality...}// Concrete implementation for a specific gamepublicclassMyGameScoreService:MiniGameScoreService<IMyGameScoreService>,IMyGameScoreService{// Game-specific scoring logicpublicvoidAddComboBonus(intcomboCount){AddScore(comboCount*10);}protectedoverridevoidAwake(){base.Awake();_score=0;// Initialize scoreServiceReady();// Register with ServiceLocator}}

Usage:

publicclassGameController:MonoBehaviour{[SerializeField]privateServiceLocator_serviceLocator;protectedvirtualasyncvoidAwake(){// Get the specific mini-game score service// This works because MyGameScoreService registers as IMyGameScoreServicevarscoreService=await_serviceLocator.GetServiceAsync<IMyGameScoreService>();scoreService.AddComboBonus(5);// This would get the specific implementation, not a generic IMiniGameScoreService// var genericScoreService = await _serviceLocator.GetServiceAsync<IMiniGameScoreService>(); // Won't work!// You can still access the common interface methodsscoreService.AddScore(100);}}

Service Locator Window

The ServiceLocator package includes a powerful debugging tool to help you monitor and manage your services during development.

Opening the Window

To open the Service Locator Window:

  1. In the Unity Editor, navigate to Tools > Service Locator > Service Locator Window
  2. The window displays all ServiceLocator assets in your project and their registered services

Window Tabs

The Service Locator Window now includes two tabs:

Services Tab: View and manage all registered services Settings Tab: Configure which ServiceLocator features are enabled

Services Tab Features

  • Real-time monitoring: Automatically updates to show all registered services
  • Scene-based organization: Services are grouped by the scenes they belong to
  • Quick navigation: Click on a service to select its GameObject in the hierarchy
  • Script access: Open the script file associated with a service directly from the window
  • Unloaded scene tracking: Identifies services from unloaded scenes
  • Play mode awareness: Updates when entering/exiting play mode

Settings Tab Features

  • Feature toggles: Enable/disable specific ServiceLocator features
  • Preprocessor directives: Automatically adds or removes the corresponding preprocessor directives
  • Reset to defaults: Option to reset all settings to their default values (all enabled)
  • Sync from project: Sync settings with the current project's scripting define symbols

Unity_068925rSXh

Available settings include:

  • Toggle Async Services
  • Toggle Promise Services
  • Toggle Coroutine Services
  • Toggle Scene Tracking
  • Toggle Logging

Unity_NB3PwKaBtr

Usage Tips

  • Quick debugging: Quickly identify which services are registered and in which scenes
  • Missing services: If a service isn't registered when expected, check the window to confirm
  • Scene management: Track which services persist across scene loads
  • Development: Monitor service registration/unregistration during gameplay

Advanced Use Cases

Using Cancellation Token

ServiceLocator supports CancellationToken for canceling service retrieval, particularly useful with MonoBehaviour.destroyCancellationToken to handle cleanup when objects are destroyed.

Example: Canceling Async Service Retrieval

publicclassServiceUser:MonoBehaviour{[SerializeField]privateServiceLocator_serviceLocator;privateasyncvoidStart(){try{varservice=await_serviceLocator.GetServiceAsync<IMyService>(destroyCancellationToken);service.DoSomething();}catch(TaskCanceledException){Debug.Log("Service retrieval canceled due to object destruction.");}}}

Behavior: If the MonoBehaviour is destroyed before the service is retrieved, the task cancels automatically, preventing memory leaks or invalid operations.

Example: Canceling Promise-based Retrieval

publicclassPromiseUser:MonoBehaviour{[SerializeField]privateServiceLocatorlocator;privatevoidStart(){locator.GetService<IMyService>(destroyCancellationToken).Then(service =>service.DoSomething()).Catch(ex =>Debug.Log($"Retrieval failed or canceled: {ex.Message}"));}}

Behavior: The promise rejects with a TaskCanceledException if the object is destroyed, ensuring safe cleanup.

Service Validation for MonoBehaviour Services

The IsServiceValid() method provides a convenient way to check if a service is registered and valid, particularly important for MonoBehaviour-based services that might be destroyed:

publicclassServiceValidator:MonoBehaviour{[SerializeField]privateServiceLocator_serviceLocator;privatevoidUpdate(){// Check if the service is still valid before using itif(_serviceLocator.IsServiceValid<IMyService>()){_serviceLocator.TryGetService<IMyService>(outvarservice);service.DoSomething();}else{// Service is not registered or has been destroyedDebug.Log("Service is not valid, waiting for a new one to register...");}}}

Behavior: This method not only checks if the service is registered but also verifies that Unity MonoBehaviour-based services haven't been destroyed, providing safer service access.

Linked Cancellation Tokens

The ServiceLocator uses CancellationTokenSource.CreateLinkedTokenSource internally to provide robust cancellation behavior for multi-service operations. This offers several benefits:

  • All-or-nothing cancellation: If any service in a multi-service request fails or cancels, all other pending requests are automatically cancelled
  • Single control point: One cancellation token affects all dependent operations
  • Reduced resource leakage: Proper cleanup of all related operations when cancellation occurs
  • Consistent application state: Prevents partial results where some parts of a tuple are filled while others aren't

Example: Cancelling Multi-Service Operations

Utilise MonoBehaviour.destroyCancellationToken to cancel multi-service operations when the object is destroyed with ease.

publicclassMultiServiceUser:MonoBehaviour{[SerializeField]privateServiceLocator_serviceLocator;privateasyncvoidStart(){try{// Request multiple services with the same tokenvar(service1,service2,service3)=await_serviceLocator.GetServicesAsync<IAuthService,IUserService,IDataService>(destroyCancellationToken);// All services successfully retrievedProcessServices(service1,service2,service3);}catch(OperationCanceledException){Debug.Log("Multi-service operation was cancelled");}}}

Error Handling and Service Rejection

ServiceLocator provides mechanisms to handle errors and explicitly reject service promises.

Example: Handling Errors with Async/Await

publicclassErrorHandler:MonoBehaviour{[SerializeField]privateServiceLocatorlocator;privateasyncvoidStart(){try{varservice=awaitlocator.GetServiceAsync<IMyService>();service.DoSomething();}catch(Exceptionex){Debug.LogError($"Failed to retrieve service: {ex.Message}");}}}

Behavior: Catches any exceptions, such as cancellation or rejection, thrown during retrieval.

Example: Rejecting a Service with a Custom Exception

publicclassServiceRejector:MonoBehaviour{[SerializeField]privateServiceLocatorlocator;privatevoidStart(){locator.GetService<IMyService>().Then(service =>service.DoSomething()).Catch(ex =>Debug.LogError($"Service rejected: {ex.Message}"));// Simulate a failure to initialize the servicelocator.RejectService<IMyService>(newInvalidOperationException("Service initialization failed"));}}

Behavior: RejectService triggers the Catch handler with the custom exception, allowing you to handle initialization failures gracefully.

Example: Combining Cancellation and Rejection

publicclassCombinedHandler:MonoBehaviour{[SerializeField]privateServiceLocator_serviceLocator;privatevoidStart(){_serviceLocator.GetService<IMyService>(destroyCancellationToken).Then(service =>service.DoSomething()).Catch(ex =>{if(exisTaskCanceledException){Debug.Log("Retrieval canceled due to destruction.");}else{Debug.LogError($"Service retrieval failed: {ex.Message}");}});// Simulate rejection if initialization failsif(someCondition){_serviceLocator.RejectService<IMyService>(newInvalidOperationException("Initialization failed"));}}}

Behavior: Handles both cancellation (e.g., object destruction) and explicit rejection (e.g., initialization failure) in a single Catch block.

Cleaning Up

Manually clean up services and promises:

_serviceLocator.Cleanup();// Clears services and cancels pending promises

When a service is unregistered (either manually or through scene unloading), any pending promises for that service will be automatically rejected with an ObjectDisposedException, allowing consumers to handle the unavailability gracefully.

Scene Management

ServiceLocator automatically tracks which scene each MonoBehaviour-based service belongs to. When a scene is unloaded, any services from that scene are automatically unregistered to prevent memory leaks. If service clean is handled correctly this fallback should not be needed, but it is a good safety net and a warning will be logged for each service that is not cleaned up via the fallback.

Example: Automatic Scene Cleanup

// When TestScene is unloaded, all services registered from that scene// will be automatically unregistered, and any pending promises will be rejectedBehavior:Thisprevents"zombie"servicesfrom persisting after their scene has been unloaded,which could lead to memory leaks or unexpected behavior.
### Initialization and De-initializationServiceLocator initializes/de-initializes automatically, but you can control it manually:
```csharp
_serviceLocator.Initialize();// Manual initialization_serviceLocator.DeInitialize();// Manual cleanup

Contributing

Contributions are welcome! Please refer to CONTRIBUTING.md for guidelines on how to contribute.

License

ServiceLocator is licensed under the MIT license. See LICENSE for more details.

Alternative Solution

For more complex dependency management, consider Dependency Injection frameworks like Zenject. ServiceLocator is ideal for simpler service management needs in Unity.

About

A Unity C# Scriptable Object based Service Locator. The precursor to ServiceKit.

Resources

Contributing

Stars

11 stars

Watchers

2 watching

Forks

Releases

Packages

Contributors

Languages