Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New features #9

Merged
merged 11 commits into from
Mar 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Code/Runtime/OlegHcp/Engine/UnityObjectExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -97,12 +97,12 @@ public static Sprite ToSprite(this Texture2D self, float pixelsPerUnit = 100f, u
return Sprite.Create(self, new Rect(0f, 0f, x, y), new Vector2(x * 0.5f, y * 0.5f), pixelsPerUnit, extrude, meshType, border);
}

public static bool IsNullOrDead(this object self)
public static bool IsNullOrDead(this UnityObject self)
{
return UnityObjectUtility.IsNullOrDead(self);
}

public static bool ExistsAndAlive(this object self)
public static bool ExistsAndAlive(this UnityObject self)
{
return !UnityObjectUtility.IsNullOrDead(self);
}
Expand Down
41 changes: 41 additions & 0 deletions Code/Runtime/OlegHcp/Managing/BoldServiceLocator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using System;

#if !UNITY_2021_2_OR_NEWER
using OlegHcp.CSharp.Collections;
#endif

namespace OlegHcp.Managing
{
public class BoldServiceLocator : ServiceLocator
{
public BoldServiceLocator() { }

public BoldServiceLocator(ICommonInitialContext commonContext) : base(commonContext) { }

public bool RemoveInstance<TService>(bool dispose = true) where TService : class, IService
{
if (_serviceCache.Remove(typeof(TService), out IService service))
{
if (dispose && service is IDisposable disposable)
disposable.Dispose();
return true;
}

return false;
}

public void RemoveAllInstances(bool dispose = true)
{
if (dispose)
{
foreach (IService service in _serviceCache.Values)
{
if (service is IDisposable disposable)
disposable.Dispose();
}
}

_serviceCache.Clear();
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 21 additions & 0 deletions Code/Runtime/OlegHcp/Managing/DirtyServiceLocator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
namespace OlegHcp.Managing
{
public class DirtyServiceLocator : BoldServiceLocator
{
public DirtyServiceLocator() { }

public DirtyServiceLocator(ICommonInitialContext commonContext) : base(commonContext) { }

public bool Remove<TService>(bool dispose = true) where TService : class, IService
{
_contextCache.RemoveContext<TService>();
return RemoveInstance<TService>(dispose);
}

public void RemoveAll(bool dispose = true)
{
_contextCache.Clear();
RemoveAllInstances(dispose);
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

61 changes: 61 additions & 0 deletions Code/Runtime/OlegHcp/Managing/InitialContextStorage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
using System;
using System.Collections.Generic;

#if !UNITY_2021_2_OR_NEWER
using OlegHcp.CSharp.Collections;
#endif

namespace OlegHcp.Managing
{
internal class InitialContextStorage
{
private ICommonInitialContext _commonContext;
private protected Dictionary<Type, object> _contextCache = new Dictionary<Type, object>();

public InitialContextStorage()
{
_commonContext = new DefaultInitialContext();
}

public InitialContextStorage(ICommonInitialContext commonContext)
{
_commonContext = commonContext;
}

public bool TryGetOrCreateInstance<TService>(out IService service) where TService : class, IService
{
if (_contextCache.TryGetValue(typeof(TService), out object value))
{
IInitialContext<TService> context = (IInitialContext<TService>)value;
service = context.GetOrCreateInstance();
return true;
}

return _commonContext.TryGetOrCreateInstance(typeof(TService), out service);
}

public bool AddContext<TService>(IInitialContext<TService> context) where TService : class, IService
{
return _contextCache.TryAdd(typeof(TService), context);
}

public bool RemoveContext<TService>() where TService : class, IService
{
return _contextCache.Remove(typeof(TService));
}

public void Clear()
{
_contextCache.Clear();
}

private class DefaultInitialContext : ICommonInitialContext
{
bool ICommonInitialContext.TryGetOrCreateInstance(Type type, out IService service)
{
service = default;
return false;
}
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 0 additions & 9 deletions Code/Runtime/OlegHcp/Managing/Interfaces.cs

This file was deleted.

62 changes: 62 additions & 0 deletions Code/Runtime/OlegHcp/Managing/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
## ServiceLocator

```csharp
using OlegHcp.Managing;

public static class Locator
{
private static DirtyServiceLocator _serviceLocator = new DirtyServiceLocator();

public static ServiceLocator Current => _serviceLocator;

public static void Clear()
{
_serviceLocator.RemoveAll();
}
}
```

Stored services can be IDisposable.

```csharp
using OlegHcp;
using UnityEngine;

namespace Assets.PrivateStuff.Testing
{
[DefaultExecutionOrder(-100)]
public class ExampleSceneConstructor : MonoBehaviour
{
[SerializeField]
private ServiceA _serviceA;

private void Awake()
{
Locator.Current.Add<IServiceA>(() => _serviceA);
Locator.Current.Add(() => new ServiceB());
Locator.Current.Add(() => ComponentUtility.CreateInstance<ServiceC>());
}

private void OnDestroy()
{
Locator.Clear();
}
}
}
```

```csharp
using UnityEngine;

public class ExampleClass : MonoBehaviour
{
private void Start()
{
IServiceA s1 = Locator.Current.Get<IServiceA>();
ServiceB s2 = Locator.Current.Get<ServiceB>();
ServiceC s3 = Locator.Current.Get<ServiceC>();

// Do something
}
}
```
7 changes: 7 additions & 0 deletions Code/Runtime/OlegHcp/Managing/README.md.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

99 changes: 66 additions & 33 deletions Code/Runtime/OlegHcp/Managing/ServiceLocator.cs
Original file line number Diff line number Diff line change
@@ -1,68 +1,101 @@
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using OlegHcp.Tools;

#if !UNITY_2021_2_OR_NEWER
using OlegHcp.CSharp.Collections;
#endif

namespace OlegHcp.Managing
{
public interface IService { }

public interface ICommonInitialContext
{
bool TryGetOrCreateInstance(Type serviceType, out IService service);
}

public interface IInitialContext<TService> where TService : class, IService
{
TService GetOrCreateInstance();
}

public class ServiceLocator
{
private Dictionary<Type, IService> _storage = new Dictionary<Type, IService>();
private protected InitialContextStorage _contextCache;
private protected Dictionary<Type, IService> _serviceCache = new Dictionary<Type, IService>();

public T Get<T>(bool error = true) where T : class, IService
public ServiceLocator()
{
if (_storage.TryGetValue(typeof(T), out IService value))
return (T)value;
_contextCache = new InitialContextStorage();
}

public ServiceLocator(ICommonInitialContext commonContext)
{
_contextCache = new InitialContextStorage(commonContext);
}

public TService Get<TService>(bool error = true) where TService : class, IService
{
Type serviceType = typeof(TService);

if (_serviceCache.TryGetValue(serviceType, out IService service))
return (TService)service;

if (_contextCache.TryGetOrCreateInstance<TService>(out service))
{
_serviceCache.Add(serviceType, service);
return (TService)service;
}

if (error)
throw ThrowErrors.ServiceNotRegistered(typeof(T));
throw ThrowErrors.ServiceNotRegistered(typeof(TService));

return null;
}

public void Register<T>(T service, bool error = true) where T : class, IService
public void AddContext<TService>(IInitialContext<TService> context, bool error = true) where TService : class, IService
{
if (service == null)
throw ThrowErrors.NullParameter(nameof(service));
if (context == null)
throw ThrowErrors.NullParameter(nameof(context));

if (_storage.TryAdd(typeof(T), service))
if (_contextCache.AddContext(context))
return;

if (error)
throw new InvalidOperationException($"Service {typeof(T).Name} already registered.");
throw new InvalidOperationException($"Service {typeof(TService)} already registered.");
}

public bool Unregister<T>(bool dispose = true) where T : class, IService
public void AddContext<TService>(Func<TService> instanceProvider, bool error = true) where TService : class, IService
{
if (!dispose)
return _storage.Remove(typeof(T));
if (instanceProvider == null)
throw ThrowErrors.NullParameter(nameof(instanceProvider));

if (_storage.Remove(typeof(T), out IService value))
{
if (value is IDisposable disposable)
disposable.Dispose();

return true;
}
if (_contextCache.AddContext(new DefaultInitialContext<TService>(instanceProvider)))
return;

return false;
if (error)
throw new InvalidOperationException($"Service {typeof(TService)} already registered.");
}

public void UnregisterAll(bool dispose = true)
private class DefaultInitialContext<TService> : IInitialContext<TService> where TService : class, IService
{
if (dispose)
private Func<TService> _provider;

public DefaultInitialContext(Func<TService> provider)
{
foreach (var value in _storage.Values)
{
if (value is IDisposable disposable)
disposable.Dispose();
}
_provider = provider;
}

_storage.Clear();
TService IInitialContext<TService>.GetOrCreateInstance()
{
return _provider.Invoke();
}
}
}

public class ServiceNotFoundException : Exception
{
public ServiceNotFoundException() : base() { }
public ServiceNotFoundException(string message) : base(message) { }
public ServiceNotFoundException(string message, Exception innerException) : base(message, innerException) { }
public ServiceNotFoundException(SerializationInfo info, StreamingContext context) : base(info, context) { }
}
}
Loading