riversong code showcase
This commit is contained in:
35
Source/Engine/Collections/IMultiDictionary.cs
Normal file
35
Source/Engine/Collections/IMultiDictionary.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public interface IMultiDictionary<K, V, C> : IEnumerable<KeyValuePair<K, V>> where C : ICollection<V>
|
||||
{
|
||||
C this[K key] { get; }
|
||||
|
||||
Dictionary<K, C>.KeyCollection Keys { get; }
|
||||
|
||||
int KeyCount { get; }
|
||||
|
||||
int ValueCount { get; }
|
||||
|
||||
int Count(K key);
|
||||
|
||||
bool Add(K key, V value);
|
||||
|
||||
bool Remove(K key, V value);
|
||||
|
||||
bool Remove(V value);
|
||||
|
||||
void Clear(K key);
|
||||
|
||||
void Clear();
|
||||
|
||||
bool ContainsKey(K key);
|
||||
|
||||
bool ContainsValue(K key, V value);
|
||||
|
||||
bool ContainsValue(V value);
|
||||
|
||||
bool TryGetValues(K key, out C values);
|
||||
}
|
||||
}
|
||||
22
Source/Engine/Collections/ListMultiDictionary.cs
Normal file
22
Source/Engine/Collections/ListMultiDictionary.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public class ListMultiDictionary<K, V> : MultiDictionary<K, V, List<V>>
|
||||
{
|
||||
public ListMultiDictionary(IEqualityComparer<K> keyComparer = null) : base(keyComparer)
|
||||
{
|
||||
}
|
||||
|
||||
protected override List<V> CreateCollection()
|
||||
{
|
||||
return new List<V>();
|
||||
}
|
||||
|
||||
protected override bool AddToCollection(V value, List<V> collection)
|
||||
{
|
||||
collection.Add(value);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
137
Source/Engine/Collections/MultiDictionary.cs
Normal file
137
Source/Engine/Collections/MultiDictionary.cs
Normal file
@@ -0,0 +1,137 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public abstract class MultiDictionary<K, V, C> : IMultiDictionary<K, V, C> where C : ICollection<V>
|
||||
{
|
||||
private readonly Dictionary<K, C> _collections;
|
||||
|
||||
protected MultiDictionary(IEqualityComparer<K> keyComparer = null)
|
||||
{
|
||||
_collections = keyComparer != null ? new Dictionary<K, C>(keyComparer) : new Dictionary<K, C>();
|
||||
}
|
||||
|
||||
public C this[K key] => _collections[key];
|
||||
|
||||
public Dictionary<K, C>.KeyCollection Keys => _collections.Keys;
|
||||
|
||||
public int KeyCount => _collections.Count;
|
||||
|
||||
public int ValueCount { get; private set; }
|
||||
|
||||
protected abstract C CreateCollection();
|
||||
|
||||
protected abstract bool AddToCollection(V value, C collection);
|
||||
|
||||
public int Count(K key)
|
||||
{
|
||||
return _collections.TryGetValue(key, out var collection) ? collection.Count : 0;
|
||||
}
|
||||
|
||||
public bool Add(K key, V value)
|
||||
{
|
||||
if (!_collections.TryGetValue(key, out var collection))
|
||||
{
|
||||
collection = CreateCollection();
|
||||
_collections.Add(key, collection);
|
||||
}
|
||||
|
||||
if (AddToCollection(value, collection))
|
||||
{
|
||||
ValueCount++;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool Remove(K key, V value)
|
||||
{
|
||||
if (_collections.TryGetValue(key, out var collection) && collection.Remove(value))
|
||||
{
|
||||
ValueCount--;
|
||||
if (collection.Count == 0) _collections.Remove(key);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool Remove(V value)
|
||||
{
|
||||
var valueRemoved = false;
|
||||
var removeCollection = false;
|
||||
K collectionKeyToRemove = default;
|
||||
foreach (var kvp in _collections)
|
||||
{
|
||||
var collection = kvp.Value;
|
||||
if (collection.Remove(value))
|
||||
{
|
||||
valueRemoved = true;
|
||||
ValueCount--;
|
||||
if (collection.Count == 0)
|
||||
{
|
||||
removeCollection = true;
|
||||
collectionKeyToRemove = kvp.Key;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (removeCollection) _collections.Remove(collectionKeyToRemove);
|
||||
return valueRemoved;
|
||||
}
|
||||
|
||||
public void Clear(K key)
|
||||
{
|
||||
if (_collections.TryGetValue(key, out var collection))
|
||||
{
|
||||
ValueCount -= collection.Count;
|
||||
collection.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_collections.Clear();
|
||||
ValueCount = 0;
|
||||
}
|
||||
|
||||
public bool ContainsKey(K key)
|
||||
{
|
||||
return _collections.ContainsKey(key);
|
||||
}
|
||||
|
||||
public bool ContainsValue(K key, V value)
|
||||
{
|
||||
return _collections.TryGetValue(key, out var collection) && collection.Contains(value);
|
||||
}
|
||||
|
||||
public bool ContainsValue(V value)
|
||||
{
|
||||
foreach (var collection in _collections.Values)
|
||||
if (collection.Contains(value))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool TryGetValues(K key, out C values)
|
||||
{
|
||||
return _collections.TryGetValue(key, out values);
|
||||
}
|
||||
|
||||
public IEnumerator<KeyValuePair<K, V>> GetEnumerator()
|
||||
{
|
||||
foreach (var kvp in _collections)
|
||||
{
|
||||
var key = kvp.Key;
|
||||
var collection = kvp.Value;
|
||||
foreach (var value in collection) yield return new KeyValuePair<K, V>(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
9
Source/Engine/GameData/GameDataAsset.cs
Normal file
9
Source/Engine/GameData/GameDataAsset.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public abstract class GameDataAsset : ScriptableObject, IGameDataRuntimeId
|
||||
{
|
||||
public int RuntimeId { get; set; }
|
||||
}
|
||||
}
|
||||
61
Source/Engine/GameData/GameDatabase.cs
Normal file
61
Source/Engine/GameData/GameDatabase.cs
Normal file
@@ -0,0 +1,61 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public class GameDatabase : IGameDatabase
|
||||
{
|
||||
private Dictionary<int, object> _idLookup = new();
|
||||
|
||||
private Dictionary<Type, object> _typeLookup = new();
|
||||
|
||||
public void Add<T>(int id, T asset) where T : class
|
||||
{
|
||||
_idLookup.Add(id, asset);
|
||||
|
||||
InvokeAddToTypeLookupWithType(asset, asset.GetType());
|
||||
|
||||
if (asset is IGameDataRuntimeId runtimeId) runtimeId.RuntimeId = id;
|
||||
}
|
||||
|
||||
private void InvokeAddToTypeLookupWithType(object asset, Type type)
|
||||
{
|
||||
var bindingAttr = BindingFlags.Instance | BindingFlags.NonPublic;
|
||||
var method = GetType().GetMethod(nameof(AddToTypeLookup), bindingAttr)!.MakeGenericMethod(type);
|
||||
method.Invoke(this, new[] { asset });
|
||||
}
|
||||
|
||||
private void AddToTypeLookup<T>(T asset)
|
||||
{
|
||||
var type = typeof(T);
|
||||
|
||||
if (!_typeLookup.TryGetValue(type, out var list))
|
||||
{
|
||||
list = new List<T>();
|
||||
_typeLookup.Add(type, list);
|
||||
}
|
||||
((List<T>)list).Add(asset);
|
||||
|
||||
var nextType = type.BaseType;
|
||||
if (nextType == typeof(object)) return;
|
||||
|
||||
InvokeAddToTypeLookupWithType(asset, nextType);
|
||||
}
|
||||
|
||||
public T WithId<T>(int id) where T : class
|
||||
{
|
||||
return _idLookup.TryGetValue(id, out var asset) ? (T)asset : null;
|
||||
}
|
||||
|
||||
public List<T> OfType<T>() where T : class
|
||||
{
|
||||
if (!_typeLookup.TryGetValue(typeof(T), out var assets))
|
||||
{
|
||||
assets = new List<T>();
|
||||
_typeLookup.Add(typeof(T), assets);
|
||||
}
|
||||
return (List<T>)assets;
|
||||
}
|
||||
}
|
||||
}
|
||||
37
Source/Engine/GameData/GameDatabaseSystem.cs
Normal file
37
Source/Engine/GameData/GameDatabaseSystem.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using Cysharp.Threading.Tasks;
|
||||
using UnityEngine.AddressableAssets;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
[GameSystemGroup(typeof(EarlyGameSystemGroup))]
|
||||
public class GameDatabaseSystem : GameSystem, IInitializable, IServiceProvider
|
||||
{
|
||||
public const string GameDataAddressablesKey = "GameData";
|
||||
|
||||
private GameDatabase _gameDatabase;
|
||||
|
||||
public GameDatabaseSystem(IServiceLocator serviceLocator) : base(serviceLocator)
|
||||
{
|
||||
}
|
||||
|
||||
public void RegisterServices(IServiceLocator serviceLocator)
|
||||
{
|
||||
_gameDatabase = new GameDatabase();
|
||||
ServiceLocator.RegisterService<IGameDatabase>(_gameDatabase);
|
||||
}
|
||||
|
||||
public async UniTask InitializeAsync()
|
||||
{
|
||||
await Addressables.LoadAssetsAsync<Object>(GameDataAddressablesKey, OnAssetLoaded);
|
||||
}
|
||||
|
||||
private void OnAssetLoaded(Object asset)
|
||||
{
|
||||
var id = asset.GetInstanceID();
|
||||
id = unchecked(id + 0x40000000);
|
||||
|
||||
_gameDatabase.Add(id, asset);
|
||||
}
|
||||
}
|
||||
}
|
||||
7
Source/Engine/GameData/IGameDataRuntimeId.cs
Normal file
7
Source/Engine/GameData/IGameDataRuntimeId.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public interface IGameDataRuntimeId
|
||||
{
|
||||
int RuntimeId { get; set; }
|
||||
}
|
||||
}
|
||||
13
Source/Engine/GameData/IGameDatabase.cs
Normal file
13
Source/Engine/GameData/IGameDatabase.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public interface IGameDatabase
|
||||
{
|
||||
void Add<T>(int id, T asset) where T : class;
|
||||
|
||||
T WithId<T>(int id) where T : class;
|
||||
|
||||
List<T> OfType<T>() where T : class;
|
||||
}
|
||||
}
|
||||
32
Source/Engine/Helpers/AsyncBudget.cs
Normal file
32
Source/Engine/Helpers/AsyncBudget.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using Cysharp.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public class AsyncBudget
|
||||
{
|
||||
private int _max;
|
||||
|
||||
private int _counter;
|
||||
|
||||
public AsyncBudget(int max)
|
||||
{
|
||||
_max = max;
|
||||
}
|
||||
|
||||
public async UniTask TickAsync()
|
||||
{
|
||||
if (++_counter > _max)
|
||||
{
|
||||
_counter = 0;
|
||||
await UniTask.NextFrame();
|
||||
}
|
||||
}
|
||||
|
||||
public void Reset(int max)
|
||||
{
|
||||
_max = max;
|
||||
_counter = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
63
Source/Engine/Helpers/TopologicalSort.cs
Normal file
63
Source/Engine/Helpers/TopologicalSort.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public class TopologicalSort<T>
|
||||
{
|
||||
private HashSet<T> _currentDependencies;
|
||||
|
||||
private HashSet<T> _closed;
|
||||
|
||||
public TopologicalSort(IEqualityComparer<T> comparer)
|
||||
{
|
||||
_currentDependencies = new HashSet<T>(comparer);
|
||||
_closed = new HashSet<T>(comparer);
|
||||
}
|
||||
|
||||
public TopologicalSort() : this(EqualityComparer<T>.Default)
|
||||
{
|
||||
}
|
||||
|
||||
public static TopologicalSort<T> Default { get; } = new();
|
||||
|
||||
public IEnumerable<T> Sort(IEnumerable<T> source, Func<T, List<T>> dependenciesGetter, ICollection<T> sorted = null)
|
||||
{
|
||||
sorted ??= new List<T>();
|
||||
|
||||
try
|
||||
{
|
||||
foreach (var item in source) Visit(item, dependenciesGetter, sorted);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_currentDependencies.Clear();
|
||||
_closed.Clear();
|
||||
}
|
||||
|
||||
return sorted;
|
||||
}
|
||||
|
||||
private void Visit(T item, Func<T, List<T>> dependenciesGetter, ICollection<T> sorted)
|
||||
{
|
||||
if (!_currentDependencies.Add(item))
|
||||
{
|
||||
var ex = new StringBuilder();
|
||||
ex.AppendLine(item.ToString());
|
||||
foreach (var dependency in _currentDependencies) ex.AppendLine(dependency.ToString());
|
||||
|
||||
throw new InvalidOperationException(ex.ToString());
|
||||
}
|
||||
|
||||
var dependencies = dependenciesGetter.Invoke(item);
|
||||
if (dependencies != null)
|
||||
foreach (var dependency in dependencies)
|
||||
Visit(dependency, dependenciesGetter, sorted);
|
||||
|
||||
_currentDependencies.Remove(item);
|
||||
|
||||
if (_closed.Add(item)) sorted.Add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user