riversong code showcase
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
using System;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class DisableDiscoveryAttribute : Attribute
|
||||
{
|
||||
}
|
||||
}
|
||||
15
Source/Engine/Core/Attributes/GameSystemGroupAttribute.cs
Normal file
15
Source/Engine/Core/Attributes/GameSystemGroupAttribute.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class GameSystemGroupAttribute : Attribute
|
||||
{
|
||||
public GameSystemGroupAttribute(Type systemGroupType)
|
||||
{
|
||||
SystemGroupType = systemGroupType;
|
||||
}
|
||||
|
||||
public Type SystemGroupType { get; set; }
|
||||
}
|
||||
}
|
||||
15
Source/Engine/Core/Attributes/InjectServiceAttribute.cs
Normal file
15
Source/Engine/Core/Attributes/InjectServiceAttribute.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Field)]
|
||||
public class InjectServiceAttribute : Attribute
|
||||
{
|
||||
public InjectServiceAttribute(Type serviceType = null)
|
||||
{
|
||||
ServiceType = serviceType;
|
||||
}
|
||||
|
||||
public Type ServiceType { get; set; }
|
||||
}
|
||||
}
|
||||
15
Source/Engine/Core/Attributes/ServiceAttribute.cs
Normal file
15
Source/Engine/Core/Attributes/ServiceAttribute.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
|
||||
public class ServiceAttribute : Attribute
|
||||
{
|
||||
public ServiceAttribute(Type serviceType = null)
|
||||
{
|
||||
ServiceType = serviceType;
|
||||
}
|
||||
|
||||
public Type ServiceType { get; set; }
|
||||
}
|
||||
}
|
||||
57
Source/Engine/Core/Attributes/SortingAttributes.cs
Normal file
57
Source/Engine/Core/Attributes/SortingAttributes.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
using System;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
|
||||
public class SortingAttribute : Attribute
|
||||
{
|
||||
protected SortingAttribute(Type systemType)
|
||||
{
|
||||
SystemType = systemType;
|
||||
}
|
||||
|
||||
public Type SystemType { get; set; }
|
||||
}
|
||||
|
||||
public class InitializeAfterAttribute : SortingAttribute
|
||||
{
|
||||
public InitializeAfterAttribute(Type systemType) : base(systemType)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class InitializeBeforeAttribute : SortingAttribute
|
||||
{
|
||||
public InitializeBeforeAttribute(Type systemType) : base(systemType)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class UpdateAfterAttribute : SortingAttribute
|
||||
{
|
||||
public UpdateAfterAttribute(Type systemType) : base(systemType)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class UpdateBeforeAttribute : SortingAttribute
|
||||
{
|
||||
public UpdateBeforeAttribute(Type systemType) : base(systemType)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class DisposeAfterAttribute : SortingAttribute
|
||||
{
|
||||
public DisposeAfterAttribute(Type systemType) : base(systemType)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public class DisposeBeforeAttribute : SortingAttribute
|
||||
{
|
||||
public DisposeBeforeAttribute(Type systemType) : base(systemType)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
155
Source/Engine/Core/EngineRunner.cs
Normal file
155
Source/Engine/Core/EngineRunner.cs
Normal file
@@ -0,0 +1,155 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public class EngineRunner : MonoBehaviour, IEngine
|
||||
{
|
||||
private ServiceLocator _serviceLocator;
|
||||
|
||||
private RootGameSystemGroup _rootSystemGroup;
|
||||
|
||||
private List<GameSystemGroup> _systemGroups = new();
|
||||
|
||||
private Dictionary<Type, GameSystemGroup> _systemGroupsByType = new();
|
||||
|
||||
private EngineUpdateFilter _updateFilter = new();
|
||||
|
||||
public bool IsInitialized { get; private set; }
|
||||
|
||||
public List<IGameSystem> Systems { get; } = new();
|
||||
|
||||
public void Start()
|
||||
{
|
||||
StartAsync().Forget(Debug.LogException);
|
||||
}
|
||||
|
||||
private async UniTask StartAsync()
|
||||
{
|
||||
Debug.Log("Engine started");
|
||||
|
||||
_serviceLocator = new ServiceLocator();
|
||||
|
||||
_serviceLocator.RegisterService(typeof(IEngine), this);
|
||||
|
||||
var serviceProviders = GetComponentsInChildren<IServiceProvider>();
|
||||
foreach (var serviceProvider in serviceProviders) serviceProvider.RegisterServices(_serviceLocator);
|
||||
|
||||
await UniTask.NextFrame();
|
||||
|
||||
_rootSystemGroup = new RootGameSystemGroup();
|
||||
_systemGroups.Add(_rootSystemGroup);
|
||||
_systemGroupsByType.Add(_rootSystemGroup.GetType(), _rootSystemGroup);
|
||||
|
||||
Debug.Log("Systems discovery started");
|
||||
DiscoverSystems();
|
||||
Debug.Log($"Systems discovery completed. Discovered {_rootSystemGroup.Systems.Count} system groups and {Systems.Count} systems");
|
||||
|
||||
await UniTask.NextFrame();
|
||||
|
||||
foreach (var systemGroup in _systemGroups) _serviceLocator.Inject(systemGroup);
|
||||
foreach (var system in Systems) _serviceLocator.Inject(system);
|
||||
|
||||
await UniTask.NextFrame();
|
||||
|
||||
var types = Systems.Select(obj => obj.GetType()).Concat(_systemGroups.Select(obj => obj.GetType())).Distinct().ToList();
|
||||
SystemSorter.InitializeSorters(types);
|
||||
|
||||
await UniTask.NextFrame();
|
||||
|
||||
Debug.Log("Systems initialization started");
|
||||
await InitializeAsync();
|
||||
Debug.Log("Systems initialization completed");
|
||||
|
||||
IsInitialized = true;
|
||||
}
|
||||
|
||||
private void DiscoverSystems()
|
||||
{
|
||||
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
|
||||
|
||||
foreach (var assembly in assemblies)
|
||||
foreach (var type in assembly.GetTypes())
|
||||
{
|
||||
if (type.IsAbstract || !type.IsSubclassOf(typeof(GameSystemGroup)) || type.GetCustomAttribute<DisableDiscoveryAttribute>() != null) continue;
|
||||
|
||||
var systemGroup = (GameSystemGroup)Activator.CreateInstance(type);
|
||||
systemGroup.UpdateFilter = _updateFilter;
|
||||
|
||||
_systemGroups.Add(systemGroup);
|
||||
_systemGroupsByType.Add(type, systemGroup);
|
||||
}
|
||||
|
||||
foreach (var systemGroup in _systemGroups)
|
||||
{
|
||||
if (systemGroup == _rootSystemGroup) continue;
|
||||
|
||||
var parent = GetContainingSystemGroup(systemGroup.GetType(), typeof(RootGameSystemGroup));
|
||||
parent.Add(systemGroup);
|
||||
}
|
||||
|
||||
foreach (var assembly in assemblies)
|
||||
foreach (var type in assembly.GetTypes())
|
||||
{
|
||||
if (type.IsAbstract || !type.IsSubclassOf(typeof(GameSystem)) || type.GetCustomAttribute<DisableDiscoveryAttribute>() != null) continue;
|
||||
|
||||
var system = CreateSystem(type);
|
||||
Systems.Add(system);
|
||||
|
||||
var systemGroup = GetContainingSystemGroup(type, typeof(DefaultGameSystemGroup));
|
||||
systemGroup.Add(system);
|
||||
}
|
||||
}
|
||||
|
||||
private GameSystem CreateSystem(Type systemType)
|
||||
{
|
||||
var system = (GameSystem)Activator.CreateInstance(systemType, _serviceLocator);
|
||||
|
||||
var serviceAttributes = systemType.GetCustomAttributes<ServiceAttribute>();
|
||||
foreach (var serviceAttribute in serviceAttributes)
|
||||
{
|
||||
var serviceType = serviceAttribute?.ServiceType ?? systemType;
|
||||
_serviceLocator.RegisterService(serviceType, system);
|
||||
}
|
||||
|
||||
if (system is IServiceProvider serviceProvider) serviceProvider.RegisterServices(_serviceLocator);
|
||||
|
||||
return system;
|
||||
}
|
||||
|
||||
private GameSystemGroup GetContainingSystemGroup(Type type, Type defaultGroup)
|
||||
{
|
||||
var systemGroupAttribute = type.GetCustomAttribute<GameSystemGroupAttribute>();
|
||||
var systemGroupType = systemGroupAttribute?.SystemGroupType ?? defaultGroup;
|
||||
return _systemGroupsByType[systemGroupType];
|
||||
}
|
||||
|
||||
private async UniTask InitializeAsync()
|
||||
{
|
||||
await _rootSystemGroup.InitializeAsync();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!IsInitialized) return;
|
||||
|
||||
_rootSystemGroup.Update();
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
Debug.Log("Systems disposing started");
|
||||
_rootSystemGroup.Dispose();
|
||||
Debug.Log("Systems disposing completed");
|
||||
}
|
||||
|
||||
public void RegisterUpdateFilter(IUpdateFilter filter)
|
||||
{
|
||||
_updateFilter.Filters.Add(filter);
|
||||
}
|
||||
}
|
||||
}
|
||||
17
Source/Engine/Core/EngineUpdateFilter.cs
Normal file
17
Source/Engine/Core/EngineUpdateFilter.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public class EngineUpdateFilter : IUpdateFilter
|
||||
{
|
||||
public List<IUpdateFilter> Filters { get; } = new();
|
||||
|
||||
public bool CanUpdate(IUpdatable updatable)
|
||||
{
|
||||
foreach (var updateFilter in Filters)
|
||||
if (!updateFilter.CanUpdate(updatable))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
14
Source/Engine/Core/GameSystem.cs
Normal file
14
Source/Engine/Core/GameSystem.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public abstract class GameSystem : IGameSystem
|
||||
{
|
||||
protected GameSystem(IServiceLocator serviceLocator)
|
||||
{
|
||||
ServiceLocator = serviceLocator;
|
||||
}
|
||||
|
||||
public virtual string Name => GetType().Name;
|
||||
|
||||
protected IServiceLocator ServiceLocator { get; }
|
||||
}
|
||||
}
|
||||
83
Source/Engine/Core/GameSystemGroup.cs
Normal file
83
Source/Engine/Core/GameSystemGroup.cs
Normal file
@@ -0,0 +1,83 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using Debug = UnityEngine.Debug;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public abstract class GameSystemGroup : IGameSystem, IInitializable, IUpdatable, IDisposable
|
||||
{
|
||||
private List<IInitializable> _initializables = new();
|
||||
|
||||
private List<IUpdatable> _updatables = new();
|
||||
|
||||
private List<IDisposable> _disposables = new();
|
||||
|
||||
public virtual string Name => GetType().Name;
|
||||
|
||||
public List<IGameSystem> Systems { get; } = new();
|
||||
|
||||
public IUpdateFilter UpdateFilter { get; set; }
|
||||
|
||||
public void Add(IGameSystem system)
|
||||
{
|
||||
Systems.Add(system);
|
||||
if (system is IInitializable initializable) _initializables.Add(initializable);
|
||||
if (system is IUpdatable updatable) _updatables.Add(updatable);
|
||||
if (system is IDisposable disposable) _disposables.Add(disposable);
|
||||
}
|
||||
|
||||
public virtual async UniTask InitializeAsync()
|
||||
{
|
||||
SystemSorter.InitializableSorter.Sort(_initializables);
|
||||
SystemSorter.UpdatableSorter.Sort(_updatables);
|
||||
SystemSorter.DisposableSorter.Sort(_disposables);
|
||||
|
||||
var batches = SystemSorter.InitializableSorter.CreateExecutionBatches(_initializables);
|
||||
foreach (var batch in batches)
|
||||
{
|
||||
var tasks = new UniTask[batch.Count];
|
||||
for (var i = 0; i < batch.Count; i++) tasks[i] = InitializeAndLogAsync(batch[i]);
|
||||
|
||||
await UniTask.WhenAll(tasks);
|
||||
}
|
||||
}
|
||||
|
||||
private static async UniTask InitializeAndLogAsync(IInitializable initializable)
|
||||
{
|
||||
var startTime = Time.unscaledTime;
|
||||
|
||||
await initializable.InitializeAsync();
|
||||
|
||||
LogInitializationTime(initializable, startTime);
|
||||
}
|
||||
|
||||
private static void LogInitializationTime(IInitializable initializable, float startTime)
|
||||
{
|
||||
if (initializable is GameSystemGroup) return;
|
||||
|
||||
var elapsed = Time.unscaledTime - startTime;
|
||||
|
||||
var log = $"Initialized {((IGameSystem)initializable).Name} in {(int)(elapsed * 1000)} ms";
|
||||
if (elapsed > 0.3f) log = $"<color=yellow>{log}</color>";
|
||||
|
||||
Debug.Log(log);
|
||||
}
|
||||
|
||||
public virtual void Update()
|
||||
{
|
||||
foreach (var updatable in _updatables)
|
||||
{
|
||||
if (UpdateFilter != null && !UpdateFilter.CanUpdate(updatable)) continue;
|
||||
|
||||
updatable.Update();
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void Dispose()
|
||||
{
|
||||
foreach (var disposable in _disposables) disposable.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
6
Source/Engine/Core/Groups/DefaultGameSystemGroup.cs
Normal file
6
Source/Engine/Core/Groups/DefaultGameSystemGroup.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public class DefaultGameSystemGroup : GameSystemGroup
|
||||
{
|
||||
}
|
||||
}
|
||||
9
Source/Engine/Core/Groups/EarlyGameSystemGroup.cs
Normal file
9
Source/Engine/Core/Groups/EarlyGameSystemGroup.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
[InitializeBefore(typeof(DefaultGameSystemGroup))]
|
||||
[UpdateBefore(typeof(DefaultGameSystemGroup))]
|
||||
[DisposeBefore(typeof(DefaultGameSystemGroup))]
|
||||
public class EarlyGameSystemGroup : GameSystemGroup
|
||||
{
|
||||
}
|
||||
}
|
||||
9
Source/Engine/Core/Groups/LateGameSystemGroup.cs
Normal file
9
Source/Engine/Core/Groups/LateGameSystemGroup.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
[InitializeAfter(typeof(DefaultGameSystemGroup))]
|
||||
[UpdateAfter(typeof(DefaultGameSystemGroup))]
|
||||
[DisposeAfter(typeof(DefaultGameSystemGroup))]
|
||||
public class LateGameSystemGroup : GameSystemGroup
|
||||
{
|
||||
}
|
||||
}
|
||||
7
Source/Engine/Core/Groups/RootGameSystemGroup.cs
Normal file
7
Source/Engine/Core/Groups/RootGameSystemGroup.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
[DisableDiscovery]
|
||||
public class RootGameSystemGroup : GameSystemGroup
|
||||
{
|
||||
}
|
||||
}
|
||||
13
Source/Engine/Core/IEngine.cs
Normal file
13
Source/Engine/Core/IEngine.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public interface IEngine
|
||||
{
|
||||
public bool IsInitialized { get; }
|
||||
|
||||
public List<IGameSystem> Systems { get; }
|
||||
|
||||
public void RegisterUpdateFilter(IUpdateFilter filter);
|
||||
}
|
||||
}
|
||||
7
Source/Engine/Core/IGameSystem.cs
Normal file
7
Source/Engine/Core/IGameSystem.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public interface IGameSystem
|
||||
{
|
||||
public string Name { get; }
|
||||
}
|
||||
}
|
||||
9
Source/Engine/Core/IInitializable.cs
Normal file
9
Source/Engine/Core/IInitializable.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using Cysharp.Threading.Tasks;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public interface IInitializable
|
||||
{
|
||||
UniTask InitializeAsync();
|
||||
}
|
||||
}
|
||||
13
Source/Engine/Core/IServiceLocator.cs
Normal file
13
Source/Engine/Core/IServiceLocator.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public interface IServiceLocator
|
||||
{
|
||||
void RegisterService(Type serviceType, object service);
|
||||
|
||||
object GetService(Type serviceType);
|
||||
|
||||
void Inject(object target);
|
||||
}
|
||||
}
|
||||
7
Source/Engine/Core/IServiceProvider.cs
Normal file
7
Source/Engine/Core/IServiceProvider.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public interface IServiceProvider
|
||||
{
|
||||
void RegisterServices(IServiceLocator serviceLocator);
|
||||
}
|
||||
}
|
||||
7
Source/Engine/Core/IUpdatable.cs
Normal file
7
Source/Engine/Core/IUpdatable.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public interface IUpdatable
|
||||
{
|
||||
void Update();
|
||||
}
|
||||
}
|
||||
7
Source/Engine/Core/IUpdateFilter.cs
Normal file
7
Source/Engine/Core/IUpdateFilter.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public interface IUpdateFilter
|
||||
{
|
||||
bool CanUpdate(IUpdatable updatable);
|
||||
}
|
||||
}
|
||||
53
Source/Engine/Core/ServiceLocator.cs
Normal file
53
Source/Engine/Core/ServiceLocator.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public class ServiceLocator : IServiceLocator
|
||||
{
|
||||
private Dictionary<Type, object> _services = new();
|
||||
|
||||
public void RegisterService(Type serviceType, object service)
|
||||
{
|
||||
_services.Add(serviceType, service);
|
||||
}
|
||||
|
||||
public object GetService(Type serviceType)
|
||||
{
|
||||
_services.TryGetValue(serviceType, out var service);
|
||||
return service;
|
||||
}
|
||||
|
||||
public void Inject(object target)
|
||||
{
|
||||
var type = target.GetType();
|
||||
|
||||
while (type != typeof(object))
|
||||
{
|
||||
var bindingAttr = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
|
||||
var fields = type.GetFields(bindingAttr);
|
||||
|
||||
foreach (var field in fields)
|
||||
{
|
||||
var serviceAttribute = field.GetCustomAttribute<InjectServiceAttribute>();
|
||||
if (serviceAttribute == null) continue;
|
||||
|
||||
var serviceType = serviceAttribute.ServiceType ?? field.FieldType;
|
||||
|
||||
var service = GetService(serviceType);
|
||||
if (service == null)
|
||||
{
|
||||
Debug.LogError($"Could not resolve service of type {serviceType} when injecting {type}");
|
||||
continue;
|
||||
}
|
||||
|
||||
field.SetValue(target, service);
|
||||
}
|
||||
|
||||
type = type.BaseType;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
15
Source/Engine/Core/ServiceLocatorExtensions.cs
Normal file
15
Source/Engine/Core/ServiceLocatorExtensions.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public static class ServiceLocatorExtensions
|
||||
{
|
||||
public static void RegisterService<T>(this IServiceLocator serviceLocator, T service)
|
||||
{
|
||||
serviceLocator.RegisterService(typeof(T), service);
|
||||
}
|
||||
|
||||
public static T GetService<T>(this IServiceLocator serviceLocator)
|
||||
{
|
||||
return (T)serviceLocator.GetService(typeof(T));
|
||||
}
|
||||
}
|
||||
}
|
||||
93
Source/Engine/Core/SystemSorter.cs
Normal file
93
Source/Engine/Core/SystemSorter.cs
Normal file
@@ -0,0 +1,93 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public class SystemSorter
|
||||
{
|
||||
public static DependencySorter<InitializeBeforeAttribute, InitializeAfterAttribute, IInitializable> InitializableSorter { get; } = new();
|
||||
|
||||
public static DependencySorter<UpdateBeforeAttribute, UpdateAfterAttribute, IUpdatable> UpdatableSorter { get; } = new();
|
||||
|
||||
public static DependencySorter<DisposeBeforeAttribute, DisposeAfterAttribute, IDisposable> DisposableSorter { get; } = new();
|
||||
|
||||
public static void InitializeSorters(List<Type> types)
|
||||
{
|
||||
InitializableSorter.Initialize(types);
|
||||
UpdatableSorter.Initialize(types);
|
||||
DisposableSorter.Initialize(types);
|
||||
}
|
||||
}
|
||||
|
||||
public class DependencySorter<TBeforeAttr, TAfterAttr, TObj> where TBeforeAttr : SortingAttribute where TAfterAttr : SortingAttribute
|
||||
{
|
||||
private Dictionary<Type, int> _indexLookup = new();
|
||||
|
||||
private Dictionary<Type, List<Type>> _dependencyLookup = new();
|
||||
|
||||
private Comparison<TObj> _comparison;
|
||||
|
||||
public DependencySorter()
|
||||
{
|
||||
_comparison = (lhs, rhs) => _indexLookup[lhs.GetType()].CompareTo(_indexLookup[rhs.GetType()]);
|
||||
}
|
||||
|
||||
public void Initialize(List<Type> types)
|
||||
{
|
||||
var dependencies = new ListMultiDictionary<Type, Type>();
|
||||
foreach (var type in types)
|
||||
{
|
||||
var beforeAttributes = type.GetCustomAttributes<TBeforeAttr>(true);
|
||||
foreach (var beforeAttribute in beforeAttributes) dependencies.Add(beforeAttribute.SystemType, type);
|
||||
|
||||
var afterAttributes = type.GetCustomAttributes<TAfterAttr>(true);
|
||||
foreach (var afterAttribute in afterAttributes) dependencies.Add(type, afterAttribute.SystemType);
|
||||
}
|
||||
|
||||
_dependencyLookup = types.ToDictionary(type => type, type => dependencies.TryGetValues(type, out var d) ? d : new List<Type>());
|
||||
_indexLookup.Clear();
|
||||
|
||||
var sortedTypes = TopologicalSort<Type>.Default.Sort(types, t => dependencies.TryGetValues(t, out var d) ? d : null);
|
||||
|
||||
var index = 0;
|
||||
foreach (var type in sortedTypes) _indexLookup.Add(type, index++);
|
||||
}
|
||||
|
||||
public void Sort(List<TObj> list)
|
||||
{
|
||||
list.Sort(_comparison);
|
||||
}
|
||||
|
||||
public List<List<TObj>> CreateExecutionBatches(List<TObj> list)
|
||||
{
|
||||
var batches = new List<List<TObj>>();
|
||||
|
||||
if (list.Count == 0) return batches;
|
||||
|
||||
var remainingTypes = new HashSet<Type>(list.Select(item => item.GetType()));
|
||||
while (remainingTypes.Count > 0)
|
||||
{
|
||||
var batch = new List<TObj>();
|
||||
foreach (var item in list)
|
||||
{
|
||||
var itemType = item.GetType();
|
||||
if (!remainingTypes.Contains(itemType)) continue;
|
||||
|
||||
if (_dependencyLookup.TryGetValue(itemType, out var dependencies) && dependencies.Exists(remainingTypes.Contains)) continue;
|
||||
|
||||
batch.Add(item);
|
||||
}
|
||||
|
||||
if (batch.Count == 0) throw new InvalidOperationException($"Unable to build execution batch for {typeof(TObj).Name}");
|
||||
|
||||
batches.Add(batch);
|
||||
|
||||
foreach (var item in batch) remainingTypes.Remove(item.GetType());
|
||||
}
|
||||
|
||||
return batches;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user