riversong code showcase

This commit is contained in:
Daniele Marotta
2026-05-21 15:52:18 +02:00
commit 4c9eea1c02
462 changed files with 23406 additions and 0 deletions

View 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);
}
}

View 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;
}
}
}

View 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();
}
}
}

View File

@@ -0,0 +1,9 @@
using System;
namespace DanieleMarotta.RiversongCodeShowcase
{
[AttributeUsage(AttributeTargets.Class)]
public class DisableDiscoveryAttribute : Attribute
{
}
}

View 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; }
}
}

View 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; }
}
}

View 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; }
}
}

View 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)
{
}
}
}

View 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);
}
}
}

View 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;
}
}
}

View 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; }
}
}

View 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();
}
}
}

View File

@@ -0,0 +1,6 @@
namespace DanieleMarotta.RiversongCodeShowcase
{
public class DefaultGameSystemGroup : GameSystemGroup
{
}
}

View File

@@ -0,0 +1,9 @@
namespace DanieleMarotta.RiversongCodeShowcase
{
[InitializeBefore(typeof(DefaultGameSystemGroup))]
[UpdateBefore(typeof(DefaultGameSystemGroup))]
[DisposeBefore(typeof(DefaultGameSystemGroup))]
public class EarlyGameSystemGroup : GameSystemGroup
{
}
}

View File

@@ -0,0 +1,9 @@
namespace DanieleMarotta.RiversongCodeShowcase
{
[InitializeAfter(typeof(DefaultGameSystemGroup))]
[UpdateAfter(typeof(DefaultGameSystemGroup))]
[DisposeAfter(typeof(DefaultGameSystemGroup))]
public class LateGameSystemGroup : GameSystemGroup
{
}
}

View File

@@ -0,0 +1,7 @@
namespace DanieleMarotta.RiversongCodeShowcase
{
[DisableDiscovery]
public class RootGameSystemGroup : GameSystemGroup
{
}
}

View 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);
}
}

View File

@@ -0,0 +1,7 @@
namespace DanieleMarotta.RiversongCodeShowcase
{
public interface IGameSystem
{
public string Name { get; }
}
}

View File

@@ -0,0 +1,9 @@
using Cysharp.Threading.Tasks;
namespace DanieleMarotta.RiversongCodeShowcase
{
public interface IInitializable
{
UniTask InitializeAsync();
}
}

View 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);
}
}

View File

@@ -0,0 +1,7 @@
namespace DanieleMarotta.RiversongCodeShowcase
{
public interface IServiceProvider
{
void RegisterServices(IServiceLocator serviceLocator);
}
}

View File

@@ -0,0 +1,7 @@
namespace DanieleMarotta.RiversongCodeShowcase
{
public interface IUpdatable
{
void Update();
}
}

View File

@@ -0,0 +1,7 @@
namespace DanieleMarotta.RiversongCodeShowcase
{
public interface IUpdateFilter
{
bool CanUpdate(IUpdatable updatable);
}
}

View 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;
}
}
}
}

View 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));
}
}
}

View 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;
}
}
}

View File

@@ -0,0 +1,9 @@
using UnityEngine;
namespace DanieleMarotta.RiversongCodeShowcase
{
public abstract class GameDataAsset : ScriptableObject, IGameDataRuntimeId
{
public int RuntimeId { get; set; }
}
}

View 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;
}
}
}

View 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);
}
}
}

View File

@@ -0,0 +1,7 @@
namespace DanieleMarotta.RiversongCodeShowcase
{
public interface IGameDataRuntimeId
{
int RuntimeId { get; set; }
}
}

View 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;
}
}

View 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;
}
}
}

View 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);
}
}
}