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. } }