-
Notifications
You must be signed in to change notification settings - Fork 30
Commit
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
*.sln | ||
.vscode | ||
.idea | ||
Library | ||
bin | ||
obj | ||
Temp | ||
.DS_Store |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
Copyright (c) 2012 - 2022 leopotam@yandex.ru | ||
|
||
Данное программное обеспечение и сопутствующая документация (далее - Продукт) | ||
выпускается на условиях двойного лицензирования - под собственнической/коммерческой | ||
и MIT-Red лицензиями. | ||
|
||
Условия использования под собственнической/коммерческой лицензии обсуждаются | ||
индивидуально, для подробностей следует писать на электронную почту. | ||
|
||
MIT-Red регулируется совокупностью следующих правил, если хотя бы | ||
одно из них невыполнимо - использование Продукта запрещено: | ||
|
||
Если вы за применение opensource программного обеспечения в | ||
военной сфере - вы не можете использовать этот Продукт. | ||
|
||
Если вы испытываете ненависть к русским или поддерживаете | ||
любые нападки на них - вы не можете использовать этот Продукт. | ||
|
||
Данная лицензия разрешает лицам, получившим копию данного Продукта, | ||
безвозмездно использовать Программное обеспечение без ограничений, включая | ||
неограниченное право на использование, копирование, изменение, слияние, | ||
публикацию и распространение копий Продукта. | ||
|
||
Указанное выше уведомление об авторском праве и данные условия должны быть | ||
включены во все копии или значимые части данного Продукта. | ||
|
||
ДАННЫЙ ПРОДУКТ ПРЕДОСТАВЛЯЕТСЯ «КАК ЕСТЬ», БЕЗ КАКИХ-ЛИБО ГАРАНТИЙ, ЯВНО | ||
ВЫРАЖЕННЫХ ИЛИ ПОДРАЗУМЕВАЕМЫХ, ВКЛЮЧАЯ ГАРАНТИИ ТОВАРНОЙ ПРИГОДНОСТИ, | ||
СООТВЕТСТВИЯ ПО ЕГО КОНКРЕТНОМУ НАЗНАЧЕНИЮ И ОТСУТСТВИЯ НАРУШЕНИЙ, НО | ||
НЕ ОГРАНИЧИВАЯСЬ ИМИ. НИ В КАКОМ СЛУЧАЕ АВТОРЫ ИЛИ ПРАВООБЛАДАТЕЛИ НЕ НЕСУТ | ||
ОТВЕТСТВЕННОСТИ ПО КАКИМ-ЛИБО ИСКАМ, ЗА УЩЕРБ ИЛИ ПО ИНЫМ ТРЕБОВАНИЯМ, | ||
В ТОМ ЧИСЛЕ, ПРИ ДЕЙСТВИИ КОНТРАКТА, ДЕЛИКТЕ ИЛИ ИНОЙ СИТУАЦИИ, ВОЗНИКШИМ | ||
ИЗ-ЗА ИСПОЛЬЗОВАНИЯ ПРОГРАММНОГО ОБЕСПЕЧЕНИЯ ИЛИ ИНЫХ ДЕЙСТВИЙ С ПРОДУКТОМ. |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
{ | ||
"name": "Leopotam.Ecs", | ||
"references": [], | ||
"includePlatforms": [], | ||
"excludePlatforms": [], | ||
"allowUnsafeCode": false, | ||
"overrideReferences": false, | ||
"precompiledReferences": [], | ||
"autoReferenced": true, | ||
"defineConstraints": [], | ||
"versionDefines": [], | ||
"noEngineReferences": false | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Large diffs are not rendered by default.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
{ | ||
"name": "com.leopotam.ecs", | ||
"author": "Leopotam", | ||
"displayName": "LeoECS", | ||
"description": "LeoECS - легковесный ECS-фреймворк, основанный на структурах. Производительность, нулевые или минимальные аллокации, минимизация использования памяти, отсутствие зависимостей от любого игрового движка - это основные цели данного фреймворка.", | ||
"unity": "2020.3", | ||
"version": "2022.3.22", | ||
"keywords": [ | ||
"leoecs", | ||
"ecs", | ||
"performance" | ||
], | ||
"dependencies": {}, | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/Leopotam/ecs.git" | ||
} | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,245 @@ | ||
// ---------------------------------------------------------------------------- | ||
// The Proprietary or MIT-Red License | ||
// Copyright (c) 2012-2022 Leopotam <leopotam@yandex.ru> | ||
// ---------------------------------------------------------------------------- | ||
|
||
using System; | ||
using System.Runtime.CompilerServices; | ||
using System.Threading; | ||
|
||
// ReSharper disable ClassNeverInstantiated.Global | ||
|
||
namespace Leopotam.Ecs { | ||
/// <summary> | ||
/// Marks component type to be not auto-filled as GetX in filter. | ||
/// </summary> | ||
public interface IEcsIgnoreInFilter { } | ||
|
||
/// <summary> | ||
/// Marks component type for custom reset behaviour. | ||
/// </summary> | ||
/// <typeparam name="T">Type of component, should be the same as main component!</typeparam> | ||
public interface IEcsAutoReset<T> where T : struct { | ||
void AutoReset (ref T c); | ||
} | ||
|
||
/// <summary> | ||
/// Marks field of IEcsSystem class to be ignored during dependency injection. | ||
/// </summary> | ||
public sealed class EcsIgnoreInjectAttribute : Attribute { } | ||
|
||
/// <summary> | ||
/// Global descriptor of used component type. | ||
/// </summary> | ||
/// <typeparam name="T">Component type.</typeparam> | ||
public static class EcsComponentType<T> where T : struct { | ||
// ReSharper disable StaticMemberInGenericType | ||
public static readonly int TypeIndex; | ||
public static readonly Type Type; | ||
public static readonly bool IsIgnoreInFilter; | ||
public static readonly bool IsAutoReset; | ||
// ReSharper restore StaticMemberInGenericType | ||
|
||
static EcsComponentType () { | ||
TypeIndex = Interlocked.Increment (ref EcsComponentPool.ComponentTypesCount); | ||
Type = typeof (T); | ||
IsIgnoreInFilter = typeof (IEcsIgnoreInFilter).IsAssignableFrom (Type); | ||
IsAutoReset = typeof (IEcsAutoReset<T>).IsAssignableFrom (Type); | ||
#if DEBUG | ||
if (!IsAutoReset && Type.GetInterface ("IEcsAutoReset`1") != null) { | ||
throw new Exception ($"IEcsAutoReset should have <{typeof (T).Name}> constraint for component \"{typeof (T).Name}\"."); | ||
} | ||
#endif | ||
} | ||
} | ||
|
||
public sealed class EcsComponentPool { | ||
/// <summary> | ||
/// Global component type counter. | ||
/// First component will be "1" for correct filters updating (add component on positive and remove on negative). | ||
/// </summary> | ||
internal static int ComponentTypesCount; | ||
} | ||
|
||
public interface IEcsComponentPool { | ||
Type ItemType { get; } | ||
object GetItem (int idx); | ||
void Recycle (int idx); | ||
int New (); | ||
void CopyData (int srcIdx, int dstIdx); | ||
} | ||
|
||
/// <summary> | ||
/// Helper for save reference to component. | ||
/// </summary> | ||
/// <typeparam name="T">Type of component.</typeparam> | ||
public struct EcsComponentRef<T> where T : struct { | ||
internal EcsComponentPool<T> Pool; | ||
internal int Idx; | ||
|
||
[MethodImpl (MethodImplOptions.AggressiveInlining)] | ||
public static bool AreEquals (in EcsComponentRef<T> lhs, in EcsComponentRef<T> rhs) { | ||
return lhs.Idx == rhs.Idx && lhs.Pool == rhs.Pool; | ||
} | ||
} | ||
|
||
#if ENABLE_IL2CPP | ||
[Unity.IL2CPP.CompilerServices.Il2CppSetOption (Unity.IL2CPP.CompilerServices.Option.NullChecks, false)] | ||
[Unity.IL2CPP.CompilerServices.Il2CppSetOption (Unity.IL2CPP.CompilerServices.Option.ArrayBoundsChecks, false)] | ||
#endif | ||
public static class EcsComponentRefExtensions { | ||
[MethodImpl (MethodImplOptions.AggressiveInlining)] | ||
public static ref T Unref<T> (in this EcsComponentRef<T> wrapper) where T : struct { | ||
return ref wrapper.Pool.Items[wrapper.Idx]; | ||
} | ||
|
||
[MethodImpl (MethodImplOptions.AggressiveInlining)] | ||
public static bool IsNull<T> (in this EcsComponentRef<T> wrapper) where T : struct { | ||
return wrapper.Pool == null; | ||
} | ||
} | ||
|
||
public interface IEcsComponentPoolResizeListener { | ||
void OnComponentPoolResize (); | ||
} | ||
|
||
#if ENABLE_IL2CPP | ||
[Unity.IL2CPP.CompilerServices.Il2CppSetOption (Unity.IL2CPP.CompilerServices.Option.NullChecks, false)] | ||
[Unity.IL2CPP.CompilerServices.Il2CppSetOption (Unity.IL2CPP.CompilerServices.Option.ArrayBoundsChecks, false)] | ||
#endif | ||
public sealed class EcsComponentPool<T> : IEcsComponentPool where T : struct { | ||
delegate void AutoResetHandler (ref T component); | ||
|
||
public Type ItemType { get; } | ||
public T[] Items = new T[128]; | ||
int[] _reservedItems = new int[128]; | ||
int _itemsCount; | ||
int _reservedItemsCount; | ||
readonly AutoResetHandler _autoReset; | ||
#if ENABLE_IL2CPP && !UNITY_EDITOR | ||
T _autoresetFakeInstance; | ||
#endif | ||
IEcsComponentPoolResizeListener[] _resizeListeners; | ||
int _resizeListenersCount; | ||
|
||
internal EcsComponentPool () { | ||
ItemType = typeof (T); | ||
if (EcsComponentType<T>.IsAutoReset) { | ||
var autoResetMethod = typeof (T).GetMethod (nameof (IEcsAutoReset<T>.AutoReset)); | ||
#if DEBUG | ||
|
||
if (autoResetMethod == null) { | ||
throw new Exception ( | ||
$"IEcsAutoReset<{typeof (T).Name}> explicit implementation not supported, use implicit instead."); | ||
} | ||
#endif | ||
_autoReset = (AutoResetHandler) Delegate.CreateDelegate ( | ||
typeof (AutoResetHandler), | ||
#if ENABLE_IL2CPP && !UNITY_EDITOR | ||
_autoresetFakeInstance, | ||
#else | ||
null, | ||
#endif | ||
autoResetMethod); | ||
} | ||
_resizeListeners = new IEcsComponentPoolResizeListener[128]; | ||
_reservedItemsCount = 0; | ||
} | ||
|
||
void RaiseOnResizeEvent () { | ||
for (int i = 0, iMax = _resizeListenersCount; i < iMax; i++) { | ||
_resizeListeners[i].OnComponentPoolResize (); | ||
} | ||
} | ||
|
||
public void AddResizeListener (IEcsComponentPoolResizeListener listener) { | ||
#if DEBUG | ||
if (listener == null) { throw new Exception ("Listener is null."); } | ||
#endif | ||
if (_resizeListeners.Length == _resizeListenersCount) { | ||
Array.Resize (ref _resizeListeners, _resizeListenersCount << 1); | ||
} | ||
_resizeListeners[_resizeListenersCount++] = listener; | ||
} | ||
|
||
public void RemoveResizeListener (IEcsComponentPoolResizeListener listener) { | ||
#if DEBUG | ||
if (listener == null) { throw new Exception ("Listener is null."); } | ||
#endif | ||
for (int i = 0, iMax = _resizeListenersCount; i < iMax; i++) { | ||
if (_resizeListeners[i] == listener) { | ||
_resizeListenersCount--; | ||
if (i < _resizeListenersCount) { | ||
_resizeListeners[i] = _resizeListeners[_resizeListenersCount]; | ||
} | ||
_resizeListeners[_resizeListenersCount] = null; | ||
break; | ||
} | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Sets new capacity (if more than current amount). | ||
/// </summary> | ||
/// <param name="capacity">New value.</param> | ||
public void SetCapacity (int capacity) { | ||
if (capacity > Items.Length) { | ||
Array.Resize (ref Items, capacity); | ||
RaiseOnResizeEvent (); | ||
} | ||
} | ||
|
||
[MethodImpl (MethodImplOptions.AggressiveInlining)] | ||
public int New () { | ||
int id; | ||
if (_reservedItemsCount > 0) { | ||
id = _reservedItems[--_reservedItemsCount]; | ||
} else { | ||
id = _itemsCount; | ||
if (_itemsCount == Items.Length) { | ||
Array.Resize (ref Items, _itemsCount << 1); | ||
RaiseOnResizeEvent (); | ||
} | ||
// reset brand new instance if custom AutoReset was registered. | ||
_autoReset?.Invoke (ref Items[_itemsCount]); | ||
_itemsCount++; | ||
} | ||
return id; | ||
} | ||
|
||
[MethodImpl (MethodImplOptions.AggressiveInlining)] | ||
public ref T GetItem (int idx) { | ||
return ref Items[idx]; | ||
} | ||
|
||
[MethodImpl (MethodImplOptions.AggressiveInlining)] | ||
public void Recycle (int idx) { | ||
if (_autoReset != null) { | ||
_autoReset (ref Items[idx]); | ||
} else { | ||
Items[idx] = default; | ||
} | ||
if (_reservedItemsCount == _reservedItems.Length) { | ||
Array.Resize (ref _reservedItems, _reservedItemsCount << 1); | ||
} | ||
_reservedItems[_reservedItemsCount++] = idx; | ||
} | ||
|
||
[MethodImpl (MethodImplOptions.AggressiveInlining)] | ||
public void CopyData (int srcIdx, int dstIdx) { | ||
Items[dstIdx] = Items[srcIdx]; | ||
} | ||
|
||
[MethodImpl (MethodImplOptions.AggressiveInlining)] | ||
public EcsComponentRef<T> Ref (int idx) { | ||
EcsComponentRef<T> componentRef; | ||
componentRef.Pool = this; | ||
componentRef.Idx = idx; | ||
return componentRef; | ||
} | ||
|
||
object IEcsComponentPool.GetItem (int idx) { | ||
return Items[idx]; | ||
} | ||
} | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Large diffs are not rendered by default.
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
// ---------------------------------------------------------------------------- | ||
// The Proprietary or MIT-Red License | ||
// Copyright (c) 2012-2022 Leopotam <leopotam@yandex.ru> | ||
// ---------------------------------------------------------------------------- | ||
|
||
using System; | ||
using System.Runtime.CompilerServices; | ||
|
||
namespace Leopotam.Ecs { | ||
/// <summary> | ||
/// Fast List replacement for growing only collections. | ||
/// </summary> | ||
/// <typeparam name="T">Type of item.</typeparam> | ||
public class EcsGrowList<T> { | ||
public T[] Items; | ||
public int Count; | ||
|
||
[MethodImpl (MethodImplOptions.AggressiveInlining)] | ||
public EcsGrowList (int capacity) { | ||
#if DEBUG | ||
if (capacity <= 0) { throw new Exception ("Capacity should be greater than zero."); } | ||
#endif | ||
Items = new T[capacity]; | ||
Count = 0; | ||
} | ||
|
||
[MethodImpl (MethodImplOptions.AggressiveInlining)] | ||
public void Add (T item) { | ||
if (Items.Length == Count) { | ||
Array.Resize (ref Items, Items.Length << 1); | ||
} | ||
Items[Count++] = item; | ||
} | ||
|
||
[MethodImpl (MethodImplOptions.AggressiveInlining)] | ||
public void EnsureCapacity (int count) { | ||
if (Items.Length < count) { | ||
var len = Items.Length << 1; | ||
while (len <= count) { | ||
len <<= 1; | ||
} | ||
Array.Resize (ref Items, len); | ||
} | ||
} | ||
} | ||
} | ||
|
||
#if ENABLE_IL2CPP | ||
// Unity IL2CPP performance optimization attribute. | ||
namespace Unity.IL2CPP.CompilerServices { | ||
enum Option { | ||
NullChecks = 1, | ||
ArrayBoundsChecks = 2 | ||
} | ||
|
||
[AttributeUsage (AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)] | ||
class Il2CppSetOptionAttribute : Attribute { | ||
public Option Option { get; private set; } | ||
public object Value { get; private set; } | ||
|
||
public Il2CppSetOptionAttribute (Option option, object value) { Option = option; Value = value; } | ||
} | ||
} | ||
#endif |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,384 @@ | ||
// ---------------------------------------------------------------------------- | ||
// The Proprietary or MIT-Red License | ||
// Copyright (c) 2012-2022 Leopotam <leopotam@yandex.ru> | ||
// ---------------------------------------------------------------------------- | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using System.Reflection; | ||
|
||
namespace Leopotam.Ecs { | ||
/// <summary> | ||
/// Base interface for all systems. | ||
/// </summary> | ||
public interface IEcsSystem { } | ||
|
||
/// <summary> | ||
/// Interface for PreInit systems. PreInit() will be called before Init(). | ||
/// </summary> | ||
public interface IEcsPreInitSystem : IEcsSystem { | ||
void PreInit (); | ||
} | ||
|
||
/// <summary> | ||
/// Interface for Init systems. Init() will be called before Run(). | ||
/// </summary> | ||
public interface IEcsInitSystem : IEcsSystem { | ||
void Init (); | ||
} | ||
|
||
/// <summary> | ||
/// Interface for PostDestroy systems. PostDestroy() will be called after Destroy(). | ||
/// </summary> | ||
public interface IEcsPostDestroySystem : IEcsSystem { | ||
void PostDestroy (); | ||
} | ||
|
||
/// <summary> | ||
/// Interface for Destroy systems. Destroy() will be called last in system lifetime cycle. | ||
/// </summary> | ||
public interface IEcsDestroySystem : IEcsSystem { | ||
void Destroy (); | ||
} | ||
|
||
/// <summary> | ||
/// Interface for Run systems. | ||
/// </summary> | ||
public interface IEcsRunSystem : IEcsSystem { | ||
void Run (); | ||
} | ||
|
||
#if DEBUG | ||
/// <summary> | ||
/// Debug interface for systems events processing. | ||
/// </summary> | ||
public interface IEcsSystemsDebugListener { | ||
void OnSystemsDestroyed (EcsSystems systems); | ||
} | ||
#endif | ||
|
||
/// <summary> | ||
/// Logical group of systems. | ||
/// </summary> | ||
#if ENABLE_IL2CPP | ||
[Unity.IL2CPP.CompilerServices.Il2CppSetOption (Unity.IL2CPP.CompilerServices.Option.NullChecks, false)] | ||
[Unity.IL2CPP.CompilerServices.Il2CppSetOption (Unity.IL2CPP.CompilerServices.Option.ArrayBoundsChecks, false)] | ||
#endif | ||
public sealed class EcsSystems : IEcsInitSystem, IEcsDestroySystem, IEcsRunSystem { | ||
public readonly string Name; | ||
public readonly EcsWorld World; | ||
readonly EcsGrowList<IEcsSystem> _allSystems = new EcsGrowList<IEcsSystem> (64); | ||
readonly EcsGrowList<EcsSystemsRunItem> _runSystems = new EcsGrowList<EcsSystemsRunItem> (64); | ||
readonly Dictionary<int, int> _namedRunSystems = new Dictionary<int, int> (64); | ||
readonly Dictionary<Type, object> _injections = new Dictionary<Type, object> (32); | ||
bool _injected; | ||
#if DEBUG | ||
bool _initialized; | ||
bool _destroyed; | ||
readonly List<IEcsSystemsDebugListener> _debugListeners = new List<IEcsSystemsDebugListener> (4); | ||
|
||
/// <summary> | ||
/// Adds external event listener. | ||
/// </summary> | ||
/// <param name="listener">Event listener.</param> | ||
public void AddDebugListener (IEcsSystemsDebugListener listener) { | ||
if (listener == null) { throw new Exception ("listener is null"); } | ||
_debugListeners.Add (listener); | ||
} | ||
|
||
/// <summary> | ||
/// Removes external event listener. | ||
/// </summary> | ||
/// <param name="listener">Event listener.</param> | ||
public void RemoveDebugListener (IEcsSystemsDebugListener listener) { | ||
if (listener == null) { throw new Exception ("listener is null"); } | ||
_debugListeners.Remove (listener); | ||
} | ||
#endif | ||
|
||
/// <summary> | ||
/// Creates new instance of EcsSystems group. | ||
/// </summary> | ||
/// <param name="world">EcsWorld instance.</param> | ||
/// <param name="name">Custom name for this group.</param> | ||
public EcsSystems (EcsWorld world, string name = null) { | ||
World = world; | ||
Name = name; | ||
} | ||
|
||
/// <summary> | ||
/// Adds new system to processing. | ||
/// </summary> | ||
/// <param name="system">System instance.</param> | ||
/// <param name="namedRunSystem">Optional name of system.</param> | ||
public EcsSystems Add (IEcsSystem system, string namedRunSystem = null) { | ||
#if DEBUG | ||
if (system == null) { throw new Exception ("System is null."); } | ||
if (_initialized) { throw new Exception ("Cant add system after initialization."); } | ||
if (_destroyed) { throw new Exception ("Cant touch after destroy."); } | ||
if (!string.IsNullOrEmpty (namedRunSystem) && !(system is IEcsRunSystem)) { throw new Exception ("Cant name non-IEcsRunSystem."); } | ||
#endif | ||
_allSystems.Add (system); | ||
if (system is IEcsRunSystem) { | ||
if (namedRunSystem == null && system is EcsSystems ecsSystems) { | ||
namedRunSystem = ecsSystems.Name; | ||
} | ||
if (namedRunSystem != null) { | ||
#if DEBUG | ||
if (_namedRunSystems.ContainsKey (namedRunSystem.GetHashCode ())) { | ||
throw new Exception ($"Cant add named system - \"{namedRunSystem}\" name already exists."); | ||
} | ||
#endif | ||
_namedRunSystems[namedRunSystem.GetHashCode ()] = _runSystems.Count; | ||
} | ||
_runSystems.Add (new EcsSystemsRunItem { Active = true, System = (IEcsRunSystem) system }); | ||
} | ||
return this; | ||
} | ||
|
||
public int GetNamedRunSystem (string name) { | ||
return _namedRunSystems.TryGetValue (name.GetHashCode (), out var idx) ? idx : -1; | ||
} | ||
|
||
/// <summary> | ||
/// Sets IEcsRunSystem active state. | ||
/// </summary> | ||
/// <param name="idx">Index of system.</param> | ||
/// <param name="state">New state of system.</param> | ||
public void SetRunSystemState (int idx, bool state) { | ||
#if DEBUG | ||
if (idx < 0 || idx >= _runSystems.Count) { throw new Exception ("Invalid index"); } | ||
#endif | ||
_runSystems.Items[idx].Active = state; | ||
} | ||
|
||
/// <summary> | ||
/// Gets IEcsRunSystem active state. | ||
/// </summary> | ||
/// <param name="idx">Index of system.</param> | ||
public bool GetRunSystemState (int idx) { | ||
#if DEBUG | ||
if (idx < 0 || idx >= _runSystems.Count) { throw new Exception ("Invalid index"); } | ||
#endif | ||
return _runSystems.Items[idx].Active; | ||
} | ||
|
||
/// <summary> | ||
/// Get all systems. Important: Don't change collection! | ||
/// </summary> | ||
public EcsGrowList<IEcsSystem> GetAllSystems () { | ||
return _allSystems; | ||
} | ||
|
||
/// <summary> | ||
/// Gets all run systems. Important: Don't change collection! | ||
/// </summary> | ||
public EcsGrowList<EcsSystemsRunItem> GetRunSystems () { | ||
return _runSystems; | ||
} | ||
|
||
/// <summary> | ||
/// Injects instance of object type to all compatible fields of added systems. | ||
/// </summary> | ||
/// <param name="obj">Instance.</param> | ||
/// <param name="overridenType">Overriden type, if null - typeof(obj) will be used.</param> | ||
public EcsSystems Inject (object obj, Type overridenType = null) { | ||
#if DEBUG | ||
if (_initialized) { throw new Exception ("Cant inject after initialization."); } | ||
if (obj == null) { throw new Exception ("Cant inject null instance."); } | ||
if (overridenType != null && !overridenType.IsInstanceOfType (obj)) { throw new Exception ("Invalid overriden type."); } | ||
#endif | ||
if (overridenType == null) { | ||
overridenType = obj.GetType (); | ||
} | ||
_injections[overridenType] = obj; | ||
return this; | ||
} | ||
|
||
/// <summary> | ||
/// Processes injections immediately. | ||
/// Can be used to DI before Init() call. | ||
/// </summary> | ||
public EcsSystems ProcessInjects () { | ||
#if DEBUG | ||
if (_initialized) { throw new Exception ("Cant inject after initialization."); } | ||
if (_destroyed) { throw new Exception ("Cant touch after destroy."); } | ||
#endif | ||
if (!_injected) { | ||
_injected = true; | ||
for (int i = 0, iMax = _allSystems.Count; i < iMax; i++) { | ||
if (_allSystems.Items[i] is EcsSystems nestedSystems) { | ||
foreach (var pair in _injections) { | ||
nestedSystems._injections[pair.Key] = pair.Value; | ||
} | ||
nestedSystems.ProcessInjects (); | ||
} else { | ||
InjectDataToSystem (_allSystems.Items[i], World, _injections); | ||
} | ||
} | ||
} | ||
return this; | ||
} | ||
|
||
/// <summary> | ||
/// Registers component type as one-frame for auto-removing at this point in execution sequence. | ||
/// </summary> | ||
public EcsSystems OneFrame<T> () where T : struct { | ||
return Add (new RemoveOneFrame<T> ()); | ||
} | ||
|
||
/// <summary> | ||
/// Closes registration for new systems, initialize all registered. | ||
/// </summary> | ||
public void Init () { | ||
#if DEBUG | ||
if (_initialized) { throw new Exception ("Already initialized."); } | ||
if (_destroyed) { throw new Exception ("Cant touch after destroy."); } | ||
#endif | ||
ProcessInjects (); | ||
// IEcsPreInitSystem processing. | ||
for (int i = 0, iMax = _allSystems.Count; i < iMax; i++) { | ||
var system = _allSystems.Items[i]; | ||
if (system is IEcsPreInitSystem preInitSystem) { | ||
preInitSystem.PreInit (); | ||
#if DEBUG | ||
World.CheckForLeakedEntities ($"{preInitSystem.GetType ().Name}.PreInit()"); | ||
#endif | ||
} | ||
} | ||
// IEcsInitSystem processing. | ||
for (int i = 0, iMax = _allSystems.Count; i < iMax; i++) { | ||
var system = _allSystems.Items[i]; | ||
if (system is IEcsInitSystem initSystem) { | ||
initSystem.Init (); | ||
#if DEBUG | ||
World.CheckForLeakedEntities ($"{initSystem.GetType ().Name}.Init()"); | ||
#endif | ||
} | ||
} | ||
#if DEBUG | ||
_initialized = true; | ||
#endif | ||
} | ||
|
||
/// <summary> | ||
/// Processes all IEcsRunSystem systems. | ||
/// </summary> | ||
public void Run () { | ||
#if DEBUG | ||
if (!_initialized) { throw new Exception ($"[{Name ?? "NONAME"}] EcsSystems should be initialized before."); } | ||
if (_destroyed) { throw new Exception ("Cant touch after destroy."); } | ||
#endif | ||
for (int i = 0, iMax = _runSystems.Count; i < iMax; i++) { | ||
var runItem = _runSystems.Items[i]; | ||
if (runItem.Active) { | ||
runItem.System.Run (); | ||
} | ||
#if DEBUG | ||
if (World.CheckForLeakedEntities (null)) { | ||
throw new Exception ($"Empty entity detected, possible memory leak in {_runSystems.Items[i].GetType ().Name}.Run ()"); | ||
} | ||
#endif | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Destroys registered data. | ||
/// </summary> | ||
public void Destroy () { | ||
#if DEBUG | ||
if (_destroyed) { throw new Exception ("Already destroyed."); } | ||
_destroyed = true; | ||
#endif | ||
// IEcsDestroySystem processing. | ||
for (var i = _allSystems.Count - 1; i >= 0; i--) { | ||
var system = _allSystems.Items[i]; | ||
if (system is IEcsDestroySystem destroySystem) { | ||
destroySystem.Destroy (); | ||
#if DEBUG | ||
World.CheckForLeakedEntities ($"{destroySystem.GetType ().Name}.Destroy ()"); | ||
#endif | ||
} | ||
} | ||
// IEcsPostDestroySystem processing. | ||
for (var i = _allSystems.Count - 1; i >= 0; i--) { | ||
var system = _allSystems.Items[i]; | ||
if (system is IEcsPostDestroySystem postDestroySystem) { | ||
postDestroySystem.PostDestroy (); | ||
#if DEBUG | ||
World.CheckForLeakedEntities ($"{postDestroySystem.GetType ().Name}.PostDestroy ()"); | ||
#endif | ||
} | ||
} | ||
#if DEBUG | ||
for (int i = 0, iMax = _debugListeners.Count; i < iMax; i++) { | ||
_debugListeners[i].OnSystemsDestroyed (this); | ||
} | ||
#endif | ||
} | ||
|
||
/// <summary> | ||
/// Injects custom data to fields of ISystem instance. | ||
/// </summary> | ||
/// <param name="system">ISystem instance.</param> | ||
/// <param name="world">EcsWorld instance.</param> | ||
/// <param name="injections">Additional instances for injection.</param> | ||
public static void InjectDataToSystem (IEcsSystem system, EcsWorld world, Dictionary<Type, object> injections) { | ||
var systemType = system.GetType (); | ||
var worldType = world.GetType (); | ||
var filterType = typeof (EcsFilter); | ||
var ignoreType = typeof (EcsIgnoreInjectAttribute); | ||
|
||
foreach (var f in systemType.GetFields (BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) { | ||
// skip statics or fields with [EcsIgnoreInject] attribute. | ||
if (f.IsStatic || Attribute.IsDefined (f, ignoreType)) { | ||
continue; | ||
} | ||
// EcsWorld | ||
if (f.FieldType.IsAssignableFrom (worldType)) { | ||
f.SetValue (system, world); | ||
continue; | ||
} | ||
// EcsFilter | ||
#if DEBUG | ||
if (f.FieldType == filterType) { | ||
throw new Exception ($"Cant use EcsFilter type at \"{system}\" system for dependency injection, use generic version instead"); | ||
} | ||
#endif | ||
if (f.FieldType.IsSubclassOf (filterType)) { | ||
f.SetValue (system, world.GetFilter (f.FieldType)); | ||
continue; | ||
} | ||
// Other injections. | ||
foreach (var pair in injections) { | ||
if (f.FieldType.IsAssignableFrom (pair.Key)) { | ||
f.SetValue (system, pair.Value); | ||
break; | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// System for removing OneFrame component. | ||
/// </summary> | ||
/// <typeparam name="T">OneFrame component type.</typeparam> | ||
sealed class RemoveOneFrame<T> : IEcsRunSystem where T : struct { | ||
readonly EcsFilter<T> _oneFrames = null; | ||
|
||
void IEcsRunSystem.Run () { | ||
for (var idx = _oneFrames.GetEntitiesCount () - 1; idx >= 0; idx--) { | ||
_oneFrames.GetEntity (idx).Del<T> (); | ||
} | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// IEcsRunSystem instance with active state. | ||
/// </summary> | ||
public sealed class EcsSystemsRunItem { | ||
public bool Active; | ||
public IEcsRunSystem System; | ||
} | ||
} |
Large diffs are not rendered by default.