EcsSystem.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373
  1. // ----------------------------------------------------------------------------
  2. // The MIT License
  3. // Simple Entity Component System framework https://github.com/Leopotam/ecs
  4. // Copyright (c) 2017-2020 Leopotam <leopotam@gmail.com>
  5. // ----------------------------------------------------------------------------
  6. using System;
  7. using System.Collections.Generic;
  8. using System.Reflection;
  9. namespace Leopotam.Ecs {
  10. /// <summary>
  11. /// Base interface for all systems.
  12. /// </summary>
  13. public interface IEcsSystem { }
  14. /// <summary>
  15. /// Interface for PreInit systems. PreInit() will be called before Init().
  16. /// </summary>
  17. public interface IEcsPreInitSystem : IEcsSystem {
  18. void PreInit ();
  19. }
  20. /// <summary>
  21. /// Interface for Init systems. Init() will be called before Run().
  22. /// </summary>
  23. public interface IEcsInitSystem : IEcsSystem {
  24. void Init ();
  25. }
  26. /// <summary>
  27. /// Interface for AfterDestroy systems. AfterDestroy() will be called after Destroy().
  28. /// </summary>
  29. public interface IEcsAfterDestroySystem : IEcsSystem {
  30. void AfterDestroy ();
  31. }
  32. /// <summary>
  33. /// Interface for Destroy systems. Destroy() will be called last in system lifetime cycle.
  34. /// </summary>
  35. public interface IEcsDestroySystem : IEcsSystem {
  36. void Destroy ();
  37. }
  38. /// <summary>
  39. /// Interface for Run systems.
  40. /// </summary>
  41. public interface IEcsRunSystem : IEcsSystem {
  42. void Run ();
  43. }
  44. #if DEBUG
  45. /// <summary>
  46. /// Debug interface for systems events processing.
  47. /// </summary>
  48. public interface IEcsSystemsDebugListener {
  49. void OnSystemsDestroyed ();
  50. }
  51. #endif
  52. /// <summary>
  53. /// Logical group of systems.
  54. /// </summary>
  55. #if ENABLE_IL2CPP
  56. [Unity.IL2CPP.CompilerServices.Il2CppSetOption (Unity.IL2CPP.CompilerServices.Option.NullChecks, false)]
  57. [Unity.IL2CPP.CompilerServices.Il2CppSetOption (Unity.IL2CPP.CompilerServices.Option.ArrayBoundsChecks, false)]
  58. #endif
  59. public sealed class EcsSystems : IEcsInitSystem, IEcsDestroySystem, IEcsRunSystem {
  60. public readonly string Name;
  61. public readonly EcsWorld World;
  62. readonly EcsGrowList<IEcsSystem> _allSystems = new EcsGrowList<IEcsSystem> (64);
  63. readonly EcsGrowList<EcsSystemsRunItem> _runSystems = new EcsGrowList<EcsSystemsRunItem> (64);
  64. readonly Dictionary<int, int> _namedRunSystems = new Dictionary<int, int> (64);
  65. readonly Dictionary<Type, object> _injections = new Dictionary<Type, object> (32);
  66. bool _injected;
  67. #if DEBUG
  68. bool _inited;
  69. bool _destroyed;
  70. readonly List<IEcsSystemsDebugListener> _debugListeners = new List<IEcsSystemsDebugListener> (4);
  71. /// <summary>
  72. /// Adds external event listener.
  73. /// </summary>
  74. /// <param name="listener">Event listener.</param>
  75. public void AddDebugListener (IEcsSystemsDebugListener listener) {
  76. if (listener == null) { throw new Exception ("listener is null"); }
  77. _debugListeners.Add (listener);
  78. }
  79. /// <summary>
  80. /// Removes external event listener.
  81. /// </summary>
  82. /// <param name="listener">Event listener.</param>
  83. public void RemoveDebugListener (IEcsSystemsDebugListener listener) {
  84. if (listener == null) { throw new Exception ("listener is null"); }
  85. _debugListeners.Remove (listener);
  86. }
  87. #endif
  88. /// <summary>
  89. /// Creates new instance of EcsSystems group.
  90. /// </summary>
  91. /// <param name="world">EcsWorld instance.</param>
  92. /// <param name="name">Custom name for this group.</param>
  93. public EcsSystems (EcsWorld world, string name = null) {
  94. World = world;
  95. Name = name;
  96. }
  97. /// <summary>
  98. /// Adds new system to processing.
  99. /// </summary>
  100. /// <param name="system">System instance.</param>
  101. /// <param name="namedRunSystem">Optional name of system.</param>
  102. public EcsSystems Add (IEcsSystem system, string namedRunSystem = null) {
  103. #if DEBUG
  104. if (system == null) { throw new Exception ("System is null."); }
  105. if (_inited) { throw new Exception ("Cant add system after initialization."); }
  106. if (_destroyed) { throw new Exception ("Cant touch after destroy."); }
  107. if (!string.IsNullOrEmpty (namedRunSystem) && !(system is IEcsRunSystem)) { throw new Exception ("Cant name non-IEcsRunSystem."); }
  108. #endif
  109. _allSystems.Add (system);
  110. if (system is IEcsRunSystem) {
  111. if (namedRunSystem != null) {
  112. _namedRunSystems[namedRunSystem.GetHashCode ()] = _runSystems.Count;
  113. }
  114. _runSystems.Add (new EcsSystemsRunItem () { Active = true, System = (IEcsRunSystem) system });
  115. }
  116. return this;
  117. }
  118. public int GetNamedRunSystem (string name) {
  119. return _namedRunSystems.TryGetValue (name.GetHashCode (), out var idx) ? idx : -1;
  120. }
  121. /// <summary>
  122. /// Sets IEcsRunSystem active state.
  123. /// </summary>
  124. /// <param name="idx">Index of system.</param>
  125. /// <param name="state">New state of system.</param>
  126. public void SetRunSystemState (int idx, bool state) {
  127. #if DEBUG
  128. if (idx < 0 || idx >= _runSystems.Count) { throw new Exception ("Invalid index"); }
  129. #endif
  130. _runSystems.Items[idx].Active = state;
  131. }
  132. /// <summary>
  133. /// Gets IEcsRunSystem active state.
  134. /// </summary>
  135. /// <param name="idx">Index of system.</param>
  136. public bool GetRunSystemState (int idx) {
  137. #if DEBUG
  138. if (idx < 0 || idx >= _runSystems.Count) { throw new Exception ("Invalid index"); }
  139. #endif
  140. return _runSystems.Items[idx].Active;
  141. }
  142. /// <summary>
  143. /// Get all systems. Important: Don't change collection!
  144. /// </summary>
  145. public EcsGrowList<IEcsSystem> GetAllSystems () {
  146. return _allSystems;
  147. }
  148. /// <summary>
  149. /// Gets all run systems. Important: Don't change collection!
  150. /// </summary>
  151. public EcsGrowList<EcsSystemsRunItem> GetRunSystems () {
  152. return _runSystems;
  153. }
  154. /// <summary>
  155. /// Injects instance of object type to all compatible fields of added systems.
  156. /// </summary>
  157. /// <param name="obj">Instance.</param>
  158. public EcsSystems Inject<T> (T obj) {
  159. #if DEBUG
  160. if (_inited) { throw new Exception ("Cant inject after initialization."); }
  161. #endif
  162. _injections[typeof (T)] = obj;
  163. return this;
  164. }
  165. /// <summary>
  166. /// Processes injections immediately.
  167. /// Can be used to DI before Init() call.
  168. /// </summary>
  169. public EcsSystems ProcessInjects () {
  170. #if DEBUG
  171. if (_inited) { throw new Exception ("Cant inject after initialization."); }
  172. if (_destroyed) { throw new Exception ("Cant touch after destroy."); }
  173. #endif
  174. if (!_injected) {
  175. _injected = true;
  176. for (int i = 0, iMax = _allSystems.Count; i < iMax; i++) {
  177. var nestedSystems = _allSystems.Items[i] as EcsSystems;
  178. if (nestedSystems != null) {
  179. foreach (var pair in _injections) {
  180. nestedSystems._injections[pair.Key] = pair.Value;
  181. }
  182. nestedSystems.ProcessInjects ();
  183. } else {
  184. InjectDataToSystem (_allSystems.Items[i], World, _injections);
  185. }
  186. }
  187. }
  188. return this;
  189. }
  190. /// <summary>
  191. /// Registers component type as one-frame for auto-removing at end of Run() call.
  192. /// </summary>
  193. public EcsSystems OneFrame<T> () where T : class {
  194. Add (new RemoveOneFrame<T> ());
  195. return this;
  196. }
  197. /// <summary>
  198. /// Closes registration for new systems, initialize all registered.
  199. /// </summary>
  200. public void Init () {
  201. #if DEBUG
  202. if (_inited) { throw new Exception ("Already inited."); }
  203. if (_destroyed) { throw new Exception ("Cant touch after destroy."); }
  204. #endif
  205. ProcessInjects ();
  206. // IEcsPreInitSystem processing.
  207. for (int i = 0, iMax = _allSystems.Count; i < iMax; i++) {
  208. var system = _allSystems.Items[i];
  209. if (system is IEcsPreInitSystem) {
  210. ((IEcsPreInitSystem) system).PreInit ();
  211. #if DEBUG
  212. World.CheckForLeakedEntities ($"{system.GetType ().Name}.PreInit()");
  213. #endif
  214. }
  215. }
  216. // IEcsInitSystem processing.
  217. for (int i = 0, iMax = _allSystems.Count; i < iMax; i++) {
  218. var system = _allSystems.Items[i];
  219. if (system is IEcsInitSystem) {
  220. ((IEcsInitSystem) system).Init ();
  221. #if DEBUG
  222. World.CheckForLeakedEntities ($"{system.GetType ().Name}.Init()");
  223. #endif
  224. }
  225. }
  226. #if DEBUG
  227. _inited = true;
  228. #endif
  229. }
  230. /// <summary>
  231. /// Processes all IEcsRunSystem systems.
  232. /// </summary>
  233. public void Run () {
  234. #if DEBUG
  235. if (!_inited) { throw new Exception ($"[{Name ?? "NONAME"}] EcsSystems should be initialized before."); }
  236. if (_destroyed) { throw new Exception ("Cant touch after destroy."); }
  237. #endif
  238. for (int i = 0, iMax = _runSystems.Count; i < iMax; i++) {
  239. var runItem = _runSystems.Items[i];
  240. if (runItem.Active) {
  241. runItem.System.Run ();
  242. }
  243. #if DEBUG
  244. if (World.CheckForLeakedEntities (null)) {
  245. throw new Exception ($"Empty entity detected, possible memory leak in {_runSystems.Items[i].GetType ().Name}.Run ()");
  246. }
  247. #endif
  248. }
  249. }
  250. /// <summary>
  251. /// Destroys registered data.
  252. /// </summary>
  253. public void Destroy () {
  254. #if DEBUG
  255. if (_destroyed) { throw new Exception ("Already destroyed."); }
  256. _destroyed = true;
  257. #endif
  258. // IEcsDestroySystem processing.
  259. for (var i = _allSystems.Count - 1; i >= 0; i--) {
  260. var system = _allSystems.Items[i];
  261. if (system is IEcsDestroySystem) {
  262. ((IEcsDestroySystem) system).Destroy ();
  263. #if DEBUG
  264. World.CheckForLeakedEntities ($"{system.GetType ().Name}.Destroy ()");
  265. #endif
  266. }
  267. }
  268. // IEcsAfterDestroySystem processing.
  269. for (var i = _allSystems.Count - 1; i >= 0; i--) {
  270. var system = _allSystems.Items[i];
  271. if (system is IEcsAfterDestroySystem) {
  272. ((IEcsAfterDestroySystem) system).AfterDestroy ();
  273. #if DEBUG
  274. World.CheckForLeakedEntities ($"{system.GetType ().Name}.AfterDestroy ()");
  275. #endif
  276. }
  277. }
  278. #if DEBUG
  279. for (int i = 0, iMax = _debugListeners.Count; i < iMax; i++) {
  280. _debugListeners[i].OnSystemsDestroyed ();
  281. }
  282. #endif
  283. }
  284. /// <summary>
  285. /// Injects custom data to fields of ISystem instance.
  286. /// </summary>
  287. /// <param name="system">ISystem instance.</param>
  288. /// <param name="world">EcsWorld instance.</param>
  289. /// <param name="injections">Additional instances for injection.</param>
  290. public static void InjectDataToSystem (IEcsSystem system, EcsWorld world, Dictionary<Type, object> injections) {
  291. var systemType = system.GetType ();
  292. var worldType = world.GetType ();
  293. var filterType = typeof (EcsFilter);
  294. var ignoreType = typeof (EcsIgnoreInjectAttribute);
  295. foreach (var f in systemType.GetFields (BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) {
  296. // skip statics or fields with [EcsIgnoreInject] attribute.
  297. if (f.IsStatic || Attribute.IsDefined (f, ignoreType)) {
  298. continue;
  299. }
  300. // EcsWorld
  301. if (f.FieldType.IsAssignableFrom (worldType)) {
  302. f.SetValue (system, world);
  303. continue;
  304. }
  305. // EcsFilter
  306. #if DEBUG
  307. if (f.FieldType == filterType) {
  308. throw new Exception ($"Cant use EcsFilter type at \"{system}\" system for dependency injection, use generic version instead");
  309. }
  310. #endif
  311. if (f.FieldType.IsSubclassOf (filterType)) {
  312. f.SetValue (system, world.GetFilter (f.FieldType));
  313. continue;
  314. }
  315. // Other injections.
  316. foreach (var pair in injections) {
  317. if (f.FieldType.IsAssignableFrom (pair.Key)) {
  318. f.SetValue (system, pair.Value);
  319. break;
  320. }
  321. }
  322. }
  323. }
  324. }
  325. /// <summary>
  326. /// System for removing OneFrame component.
  327. /// </summary>
  328. /// <typeparam name="T">OneFrame component type.</typeparam>
  329. sealed class RemoveOneFrame<T> : IEcsRunSystem where T : class {
  330. readonly EcsFilter<T> _oneFrames = null;
  331. void IEcsRunSystem.Run () {
  332. foreach (var idx in _oneFrames) {
  333. _oneFrames.Entities[idx].Unset<T> ();
  334. }
  335. }
  336. }
  337. /// <summary>
  338. /// IEcsRunSystem instance with active state.
  339. /// </summary>
  340. public sealed class EcsSystemsRunItem {
  341. public bool Active;
  342. public IEcsRunSystem System;
  343. }
  344. }