EcsWorld.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463
  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.Runtime.CompilerServices;
  9. using System.Runtime.InteropServices;
  10. namespace Leopotam.Ecs {
  11. /// <summary>
  12. /// Ecs data context.
  13. /// </summary>
  14. #if ENABLE_IL2CPP
  15. [Unity.IL2CPP.CompilerServices.Il2CppSetOption (Unity.IL2CPP.CompilerServices.Option.NullChecks, false)]
  16. [Unity.IL2CPP.CompilerServices.Il2CppSetOption (Unity.IL2CPP.CompilerServices.Option.ArrayBoundsChecks, false)]
  17. #endif
  18. // ReSharper disable once ClassWithVirtualMembersNeverInherited.Global
  19. public class EcsWorld {
  20. // ReSharper disable MemberCanBePrivate.Global
  21. protected EcsEntityData[] Entities = new EcsEntityData[1024];
  22. protected int EntitiesCount;
  23. protected readonly EcsGrowList<int> FreeEntities = new EcsGrowList<int> (1024);
  24. protected readonly EcsGrowList<EcsFilter> Filters = new EcsGrowList<EcsFilter> (128);
  25. protected readonly Dictionary<int, EcsGrowList<EcsFilter>> FilterByIncludedComponents = new Dictionary<int, EcsGrowList<EcsFilter>> (64);
  26. protected readonly Dictionary<int, EcsGrowList<EcsFilter>> FilterByExcludedComponents = new Dictionary<int, EcsGrowList<EcsFilter>> (64);
  27. int _usedComponentsCount;
  28. /// <summary>
  29. /// Component pools cache.
  30. /// </summary>
  31. // ReSharper restore MemberCanBePrivate.Global
  32. public EcsComponentPool[] ComponentPools = new EcsComponentPool[512];
  33. #if DEBUG
  34. internal readonly List<IEcsWorldDebugListener> DebugListeners = new List<IEcsWorldDebugListener> (4);
  35. readonly EcsGrowList<EcsEntity> _leakedEntities = new EcsGrowList<EcsEntity> (256);
  36. bool _isDestroyed;
  37. bool _inDestroying;
  38. /// <summary>
  39. /// Adds external event listener.
  40. /// </summary>
  41. /// <param name="listener">Event listener.</param>
  42. public void AddDebugListener (IEcsWorldDebugListener listener) {
  43. if (listener == null) { throw new Exception ("Listener is null."); }
  44. DebugListeners.Add (listener);
  45. }
  46. /// <summary>
  47. /// Removes external event listener.
  48. /// </summary>
  49. /// <param name="listener">Event listener.</param>
  50. public void RemoveDebugListener (IEcsWorldDebugListener listener) {
  51. if (listener == null) { throw new Exception ("Listener is null."); }
  52. DebugListeners.Remove (listener);
  53. }
  54. #endif
  55. /// <summary>
  56. /// Destroys world and exist entities.
  57. /// </summary>
  58. public virtual void Destroy () {
  59. #if DEBUG
  60. if (_isDestroyed || _inDestroying) { throw new Exception ("EcsWorld already destroyed."); }
  61. _inDestroying = true;
  62. CheckForLeakedEntities ("Destroy");
  63. #endif
  64. EcsEntity entity;
  65. entity.Owner = this;
  66. for (var i = EntitiesCount - 1; i >= 0; i--) {
  67. ref var entityData = ref Entities[i];
  68. if (entityData.ComponentsCountX2 > 0) {
  69. entity.Id = i;
  70. entity.Gen = entityData.Gen;
  71. entity.Destroy ();
  72. }
  73. }
  74. #if DEBUG
  75. _isDestroyed = true;
  76. for (var i = DebugListeners.Count - 1; i >= 0; i--) {
  77. DebugListeners[i].OnWorldDestroyed ();
  78. }
  79. #endif
  80. }
  81. /// <summary>
  82. /// Creates new entity.
  83. /// </summary>
  84. [MethodImpl (MethodImplOptions.AggressiveInlining)]
  85. public EcsEntity NewEntity () {
  86. #if DEBUG
  87. if (_isDestroyed) { throw new Exception ("EcsWorld already destroyed."); }
  88. #endif
  89. EcsEntity entity;
  90. entity.Owner = this;
  91. // try to reuse entity from pool.
  92. if (FreeEntities.Count > 0) {
  93. entity.Id = FreeEntities.Items[--FreeEntities.Count];
  94. ref var entityData = ref Entities[entity.Id];
  95. entity.Gen = entityData.Gen;
  96. entityData.ComponentsCountX2 = 0;
  97. } else {
  98. // create new entity.
  99. if (EntitiesCount == Entities.Length) {
  100. Array.Resize (ref Entities, EntitiesCount << 1);
  101. }
  102. entity.Id = EntitiesCount++;
  103. ref var entityData = ref Entities[entity.Id];
  104. entityData.Components = new int[EcsHelpers.EntityComponentsCountX2];
  105. entityData.Gen = 1;
  106. entity.Gen = entityData.Gen;
  107. entityData.ComponentsCountX2 = 0;
  108. }
  109. #if DEBUG
  110. _leakedEntities.Add (entity);
  111. for (var ii = 0; ii < DebugListeners.Count; ii++) {
  112. DebugListeners[ii].OnEntityCreated (entity);
  113. }
  114. #endif
  115. return entity;
  116. }
  117. /// <summary>
  118. /// Creates entity and attaches component.
  119. /// </summary>
  120. /// <typeparam name="T1">Type of component1.</typeparam>
  121. [MethodImpl (MethodImplOptions.AggressiveInlining)]
  122. public EcsEntity NewEntityWith<T1> (out T1 c1) where T1 : class {
  123. var entity = NewEntity ();
  124. c1 = entity.Set<T1> ();
  125. return entity;
  126. }
  127. /// <summary>
  128. /// Creates entity and attaches components.
  129. /// </summary>
  130. /// <typeparam name="T1">Type of component1.</typeparam>
  131. /// <typeparam name="T2">Type of component2.</typeparam>
  132. [MethodImpl (MethodImplOptions.AggressiveInlining)]
  133. public EcsEntity NewEntityWith<T1, T2> (out T1 c1, out T2 c2) where T1 : class where T2 : class {
  134. var entity = NewEntity ();
  135. c1 = entity.Set<T1> ();
  136. c2 = entity.Set<T2> ();
  137. return entity;
  138. }
  139. /// <summary>
  140. /// Creates entity and attaches components.
  141. /// </summary>
  142. /// <typeparam name="T1">Type of component1.</typeparam>
  143. /// <typeparam name="T2">Type of component2.</typeparam>
  144. /// <typeparam name="T3">Type of component3.</typeparam>
  145. [MethodImpl (MethodImplOptions.AggressiveInlining)]
  146. public EcsEntity NewEntityWith<T1, T2, T3> (out T1 c1, out T2 c2, out T3 c3) where T1 : class where T2 : class where T3 : class {
  147. var entity = NewEntity ();
  148. c1 = entity.Set<T1> ();
  149. c2 = entity.Set<T2> ();
  150. c3 = entity.Set<T3> ();
  151. return entity;
  152. }
  153. /// <summary>
  154. /// Creates entity and attaches components.
  155. /// </summary>
  156. /// <typeparam name="T1">Type of component1.</typeparam>
  157. /// <typeparam name="T2">Type of component2.</typeparam>
  158. /// <typeparam name="T3">Type of component3.</typeparam>
  159. /// <typeparam name="T4">Type of component4.</typeparam>
  160. [MethodImpl (MethodImplOptions.AggressiveInlining)]
  161. public EcsEntity NewEntityWith<T1, T2, T3, T4> (out T1 c1, out T2 c2, out T3 c3, out T4 c4) where T1 : class where T2 : class where T3 : class where T4 : class {
  162. var entity = NewEntity ();
  163. c1 = entity.Set<T1> ();
  164. c2 = entity.Set<T2> ();
  165. c3 = entity.Set<T3> ();
  166. c4 = entity.Set<T4> ();
  167. return entity;
  168. }
  169. /// <summary>
  170. /// Restores EcsEntity from internal id. For internal use only!
  171. /// </summary>
  172. /// <param name="id">Internal id.</param>
  173. [MethodImpl (MethodImplOptions.AggressiveInlining)]
  174. public EcsEntity RestoreEntityFromInternalId (int id) {
  175. EcsEntity entity;
  176. entity.Owner = this;
  177. entity.Id = id;
  178. entity.Gen = 0;
  179. ref var entityData = ref GetEntityData (entity);
  180. entity.Gen = entityData.Gen;
  181. return entity;
  182. }
  183. /// <summary>
  184. /// Request exist filter or create new one. For internal use only!
  185. /// </summary>
  186. /// <param name="filterType">Filter type.</param>
  187. public EcsFilter GetFilter (Type filterType) {
  188. #if DEBUG
  189. if (filterType == null) { throw new Exception ("FilterType is null."); }
  190. if (!filterType.IsSubclassOf (typeof (EcsFilter))) { throw new Exception ($"Invalid filter type: {filterType}."); }
  191. if (_isDestroyed) { throw new Exception ("EcsWorld already destroyed."); }
  192. #endif
  193. // check already exist filters.
  194. for (int i = 0, iMax = Filters.Count; i < iMax; i++) {
  195. if (Filters.Items[i].GetType () == filterType) {
  196. return Filters.Items[i];
  197. }
  198. }
  199. // create new filter.
  200. var filter = (EcsFilter) Activator.CreateInstance (filterType, true);
  201. #if DEBUG
  202. for (var filterIdx = 0; filterIdx < Filters.Count; filterIdx++) {
  203. if (filter.AreComponentsSame (Filters.Items[filterIdx])) {
  204. throw new Exception (
  205. $"Invalid filter \"{filter.GetType ()}\": Another filter \"{Filters.Items[filterIdx].GetType ()}\" already has same components, but in different order.");
  206. }
  207. }
  208. #endif
  209. Filters.Add (filter);
  210. // add to component dictionaries for fast compatibility scan.
  211. for (int i = 0, iMax = filter.IncludedTypeIndices.Length; i < iMax; i++) {
  212. if (!FilterByIncludedComponents.TryGetValue (filter.IncludedTypeIndices[i], out var filtersList)) {
  213. filtersList = new EcsGrowList<EcsFilter> (8);
  214. FilterByIncludedComponents[filter.IncludedTypeIndices[i]] = filtersList;
  215. }
  216. filtersList.Add (filter);
  217. }
  218. if (filter.ExcludedTypeIndices != null) {
  219. for (int i = 0, iMax = filter.ExcludedTypeIndices.Length; i < iMax; i++) {
  220. if (!FilterByExcludedComponents.TryGetValue (filter.ExcludedTypeIndices[i], out var filtersList)) {
  221. filtersList = new EcsGrowList<EcsFilter> (8);
  222. FilterByExcludedComponents[filter.ExcludedTypeIndices[i]] = filtersList;
  223. }
  224. filtersList.Add (filter);
  225. }
  226. }
  227. #if DEBUG
  228. for (var ii = 0; ii < DebugListeners.Count; ii++) {
  229. DebugListeners[ii].OnFilterCreated (filter);
  230. }
  231. #endif
  232. return filter;
  233. }
  234. /// <summary>
  235. /// Gets stats of internal data.
  236. /// </summary>
  237. public EcsWorldStats GetStats () {
  238. var stats = new EcsWorldStats () {
  239. ActiveEntities = EntitiesCount - FreeEntities.Count,
  240. ReservedEntities = FreeEntities.Count,
  241. Filters = Filters.Count,
  242. Components = _usedComponentsCount
  243. };
  244. return stats;
  245. }
  246. /// <summary>
  247. /// Recycles internal entity data to pool.
  248. /// </summary>
  249. /// <param name="id">Entity id.</param>
  250. /// <param name="entityData">Entity internal data.</param>
  251. protected internal void RecycleEntityData (int id, ref EcsEntityData entityData) {
  252. #if DEBUG
  253. if (entityData.ComponentsCountX2 != 0) { throw new Exception ("Cant recycle invalid entity."); }
  254. #endif
  255. entityData.ComponentsCountX2 = -2;
  256. entityData.Gen = (ushort) ((entityData.Gen + 1) % ushort.MaxValue);
  257. FreeEntities.Add (id);
  258. }
  259. #if DEBUG
  260. /// <summary>
  261. /// Checks exist entities but without components.
  262. /// </summary>
  263. /// <param name="errorMsg">Prefix for error message.</param>
  264. public bool CheckForLeakedEntities (string errorMsg) {
  265. if (_leakedEntities.Count > 0) {
  266. for (int i = 0, iMax = _leakedEntities.Count; i < iMax; i++) {
  267. if (GetEntityData (_leakedEntities.Items[i]).ComponentsCountX2 == 0) {
  268. if (errorMsg != null) {
  269. throw new Exception ($"{errorMsg}: Empty entity detected, possible memory leak.");
  270. }
  271. return true;
  272. }
  273. }
  274. _leakedEntities.Count = 0;
  275. }
  276. return false;
  277. }
  278. #endif
  279. /// <summary>
  280. /// Updates filters.
  281. /// </summary>
  282. /// <param name="typeIdx">Component type index.abstract Positive for add operation, negative for remove operation.</param>
  283. /// <param name="entity">Target entity.</param>
  284. /// <param name="entityData">Target entity data.</param>
  285. [MethodImpl (MethodImplOptions.AggressiveInlining)]
  286. protected internal void UpdateFilters (int typeIdx, in EcsEntity entity, in EcsEntityData entityData) {
  287. #if DEBUG
  288. if (_isDestroyed) { throw new Exception ("EcsWorld already destroyed."); }
  289. #endif
  290. EcsGrowList<EcsFilter> filters;
  291. if (typeIdx < 0) {
  292. // remove component.
  293. if (FilterByIncludedComponents.TryGetValue (-typeIdx, out filters)) {
  294. for (int i = 0, iMax = filters.Count; i < iMax; i++) {
  295. if (filters.Items[i].IsCompatible (entityData, 0)) {
  296. #if DEBUG
  297. var isValid = false;
  298. foreach (var idx in filters.Items[i]) {
  299. if (filters.Items[i].Entities[idx].Id == entity.Id) {
  300. isValid = true;
  301. break;
  302. }
  303. }
  304. if (!isValid) { throw new Exception ($"{filters.Items[i]}Entity not in filter."); }
  305. #endif
  306. filters.Items[i].OnRemoveEntity (entity);
  307. }
  308. }
  309. }
  310. if (FilterByExcludedComponents.TryGetValue (-typeIdx, out filters)) {
  311. for (int i = 0, iMax = filters.Count; i < iMax; i++) {
  312. if (filters.Items[i].IsCompatible (entityData, typeIdx)) {
  313. #if DEBUG
  314. var isValid = true;
  315. foreach (var idx in filters.Items[i]) {
  316. if (filters.Items[i].Entities[idx].Id == entity.Id) {
  317. isValid = false;
  318. break;
  319. }
  320. }
  321. if (!isValid) { throw new Exception ($"{filters.Items[i]}Entity already in filter."); }
  322. #endif
  323. filters.Items[i].OnAddEntity (entity);
  324. }
  325. }
  326. }
  327. } else {
  328. // add component.
  329. if (FilterByIncludedComponents.TryGetValue (typeIdx, out filters)) {
  330. for (int i = 0, iMax = filters.Count; i < iMax; i++) {
  331. if (filters.Items[i].IsCompatible (entityData, 0)) {
  332. #if DEBUG
  333. var isValid = true;
  334. foreach (var idx in filters.Items[i]) {
  335. if (filters.Items[i].Entities[idx].Id == entity.Id) {
  336. isValid = false;
  337. break;
  338. }
  339. }
  340. if (!isValid) { throw new Exception ($"{filters.Items[i]}Entity already in filter."); }
  341. #endif
  342. filters.Items[i].OnAddEntity (entity);
  343. }
  344. }
  345. }
  346. if (FilterByExcludedComponents.TryGetValue (typeIdx, out filters)) {
  347. for (int i = 0, iMax = filters.Count; i < iMax; i++) {
  348. if (filters.Items[i].IsCompatible (entityData, -typeIdx)) {
  349. #if DEBUG
  350. var isValid = false;
  351. foreach (var idx in filters.Items[i]) {
  352. if (filters.Items[i].Entities[idx].Id == entity.Id) {
  353. isValid = true;
  354. break;
  355. }
  356. }
  357. if (!isValid) { throw new Exception ($"{filters.Items[i]}Entity not in filter."); }
  358. #endif
  359. filters.Items[i].OnRemoveEntity (entity);
  360. }
  361. }
  362. }
  363. }
  364. }
  365. /// <summary>
  366. /// Returns internal state of entity. For internal use!
  367. /// </summary>
  368. /// <param name="entity">Entity.</param>
  369. [MethodImpl (MethodImplOptions.AggressiveInlining)]
  370. public ref EcsEntityData GetEntityData (in EcsEntity entity) {
  371. #if DEBUG
  372. if (_isDestroyed) { throw new Exception ("EcsWorld already destroyed."); }
  373. if (entity.Id < 0 || entity.Id > EntitiesCount) { throw new Exception ($"Invalid entity {entity.Id}"); }
  374. #endif
  375. return ref Entities[entity.Id];
  376. }
  377. /// <summary>
  378. /// Internal state of entity.
  379. /// </summary>
  380. [StructLayout (LayoutKind.Sequential, Pack = 2)]
  381. public struct EcsEntityData {
  382. public ushort Gen;
  383. public short ComponentsCountX2;
  384. public int[] Components;
  385. }
  386. [MethodImpl (MethodImplOptions.AggressiveInlining)]
  387. public EcsComponentPool GetPool<T> () where T : class {
  388. var typeIdx = EcsComponentType<T>.TypeIndex;
  389. if (ComponentPools.Length < typeIdx) {
  390. var len = ComponentPools.Length << 1;
  391. while (len <= typeIdx) {
  392. len <<= 1;
  393. }
  394. Array.Resize (ref ComponentPools, len);
  395. }
  396. var pool = ComponentPools[typeIdx];
  397. if (pool == null) {
  398. pool = new EcsComponentPool (EcsComponentType<T>.Type, EcsComponentType<T>.IsAutoReset);
  399. ComponentPools[typeIdx] = pool;
  400. _usedComponentsCount++;
  401. }
  402. return pool;
  403. }
  404. }
  405. /// <summary>
  406. /// Stats of EcsWorld instance.
  407. /// </summary>
  408. public struct EcsWorldStats {
  409. /// <summary>
  410. /// Amount of active entities.
  411. /// </summary>
  412. public int ActiveEntities;
  413. /// <summary>
  414. /// Amount of cached (not in use) entities.
  415. /// </summary>
  416. public int ReservedEntities;
  417. /// <summary>
  418. /// Amount of registered filters.
  419. /// </summary>
  420. public int Filters;
  421. /// <summary>
  422. /// Amount of registered component types.
  423. /// </summary>
  424. public int Components;
  425. }
  426. #if DEBUG
  427. /// <summary>
  428. /// Debug interface for world events processing.
  429. /// </summary>
  430. public interface IEcsWorldDebugListener {
  431. void OnEntityCreated (EcsEntity entity);
  432. void OnEntityDestroyed (EcsEntity entity);
  433. void OnFilterCreated (EcsFilter filter);
  434. void OnComponentListChanged (EcsEntity entity);
  435. void OnWorldDestroyed ();
  436. }
  437. #endif
  438. }