using System;
using System.Collections.Generic;
namespace LobbyRelaySample
{
///
/// Anything which provides itself to a Locator can then be globally accessed. This should be a single access point for things that *want* to be singleton (that is,
/// when they want to be available for use by arbitrary, unknown clients) but might not always be available or might need alternate flavors for tests, logging, etc.
/// (See http://gameprogrammingpatterns.com/service-locator.html to learn more.)
///
public class Locator : LocatorBase
{
private static Locator s_instance;
public static Locator Get
{
get
{
if (s_instance == null)
s_instance = new Locator();
return s_instance;
}
}
protected override void FinishConstruction()
{
s_instance = this;
}
}
///
/// Allows Located services to transfer data to their replacements if needed.
///
/// The base interface type you want to Provide.
public interface IProvidable
{
void OnReProvided(T previousProvider);
}
///
/// Base Locator behavior, without static access.
///
public class LocatorBase
{
private Dictionary m_provided = new Dictionary();
///
/// On construction, we can prepare default implementations of any services we expect to be required. This way, if for some reason the actual implementations
/// are never Provided (e.g. for tests), nothing will break.
///
public LocatorBase()
{
Provide(new Messenger());
Provide(new UpdateSlowNoop());
Provide(new ngo.InGameInputHandlerNoop());
FinishConstruction();
}
protected virtual void FinishConstruction() { }
///
/// Call this to indicate that something is available for global access.
///
private void ProvideAny(T instance) where T : IProvidable
{
Type type = typeof(T);
if (m_provided.ContainsKey(type))
{
var previousProvision = (T)m_provided[type];
instance.OnReProvided(previousProvision);
m_provided.Remove(type);
}
m_provided.Add(type, instance);
}
///
/// If a T has previously been Provided, this will retrieve it. Else, null is returned.
///
private T Locate() where T : class
{
Type type = typeof(T);
if (!m_provided.ContainsKey(type))
return null;
return m_provided[type] as T;
}
// To limit global access to only components that should have it, and to reduce programmer error, we'll declare explicit flavors of Provide and getters for them.
public IMessenger Messenger => Locate();
public void Provide(IMessenger messenger) { ProvideAny(messenger); }
public IUpdateSlow UpdateSlow => Locate();
public void Provide(IUpdateSlow updateSlow) { ProvideAny(updateSlow); }
public ngo.IInGameInputHandler InGameInputHandler => Locate();
public void Provide(ngo.IInGameInputHandler inputHandler) { ProvideAny(inputHandler); }
// As you add more Provided types, be sure their default implementations are included in the constructor.
}
}