# [Service locator pattern](https://gameprogrammingpatterns.com/service-locator.html) generic implementation
This is a, thread safe, generic implementation of the service locator pattern, that can be used with any trait object.
## Description
This pattern is used to provide a global access to a service, safely, without
coupling the service to the code that uses it. Read the Robert Nyström's book [Game Programming Patterns](https://gameprogrammingpatterns.com/service-locator.html) for more information about this pattern.
The key concepts of this pattern are:
- Service - An interface that defines the service (a trait).
- Service provider - An implementation of the service (implements the trait).
- Service locator - An object that locates the service provider.
## Usage
The service locator is a static object that can be used to provide and request the service. Internally, it uses a [`RwLock`](https://doc.rust-lang.org/std/sync/struct.RwLock.html) to be thread safe. The service locator is generic over the service trait object.
## Example
```rust
use service_locator::ServiceLocator;
enum Sound {
Beep,
Laser,
}
// The service.
trait Audio {
fn play(&mut self, sound: Sound) {
// Some stuff.
}
fn stop(&mut self, sound: Sound) {
// Some stuff.
}
fn stop_all(&mut self) {
// Some stuff.
}
fn is_playing(&self, sound: Sound) -> bool {
false
}
}
// The service providers.
#[derive(Default)]
struct SDLAudio {
// Some stuff.
}
impl Audio for SDLAudio {
// Use the default implementation as it is an example.
}
#[derive(Default)]
struct OpenALAudio {
// Some stuff.
}
impl Audio for OpenALAudio {
// Use the default implementation as it is an example.
}
static AUDIO_SERVICE_LOCATOR: ServiceLocator<dyn Audio + Send + Sync> = ServiceLocator::new();
// The service is not provided yet.
assert!(AUDIO_SERVICE_LOCATOR.service().is_err());
// Provide the service.
AUDIO_SERVICE_LOCATOR.provide(Box::new(SDLAudio::default()));
// The service is now provided.
assert_eq!(AUDIO_SERVICE_LOCATOR.service().unwrap().is_playing(Sound::Beep), false);
// The service can be mutably borrowed.
let mut service = AUDIO_SERVICE_LOCATOR.service_mut().unwrap();
service.stop_all();
// IMPORTANT: Drop the service, as it's a guard, before to request it again to avoid a deadlock.
// Before we didn't drop the service because we didn't bind the service to a variable.
drop(service);
// Change the service provider.
AUDIO_SERVICE_LOCATOR.provide(Box::new(OpenALAudio::default()));
// The service is now from the new provider.
let is_playing_laser = AUDIO_SERVICE_LOCATOR.service()
// The closure is executed only if the service is provided,
// and ensures that the service is dropped after the closure execution.
.map(|service| service.is_playing(Sound::Laser))
.unwrap();
assert_eq!(is_playing_laser, false);
```
Remember to handle the errors instead of using `unwrap()`. It's used in the example for simplicity reasons, and because it's run as a test.
## Logging
The crate uses the [`log`](https://crates.io/crates/log) crate to log. With default features to false it can be disabled.