├── .editorconfig ├── .gitignore ├── .vscode └── settings.json ├── CHANGELOG.md ├── CHANGELOG.md.meta ├── CoreECS.csproj ├── CoreECS.csproj.meta ├── Directory.Build.props ├── Directory.Build.props.meta ├── LICENSE.md ├── LICENSE.md.meta ├── README.md ├── README.md.meta ├── Runtime.meta ├── Runtime ├── Collector.meta ├── Collector │ ├── Collector.cs │ ├── Collector.cs.meta │ ├── ICollector.cs │ ├── ICollector.cs.meta │ ├── ICollectorExtensions.cs │ └── ICollectorExtensions.cs.meta ├── Component.meta ├── Component │ ├── ComponentTypeId.cs │ ├── ComponentTypeId.cs.meta │ ├── IComponent.cs │ └── IComponent.cs.meta ├── Context.meta ├── Context │ ├── Context.cs │ ├── Context.cs.meta │ ├── IContext.cs │ ├── IContext.cs.meta │ ├── IContextExtensions.cs │ └── IContextExtensions.cs.meta ├── Entity.meta ├── Entity │ ├── Entity.cs │ ├── Entity.cs.meta │ ├── IEntity.cs │ ├── IEntity.cs.meta │ ├── IEntityExtensions.cs │ └── IEntityExtensions.cs.meta ├── Group.meta ├── Group │ ├── Group.cs │ ├── Group.cs.meta │ ├── IGroup.cs │ ├── IGroup.cs.meta │ ├── IGroupExtensions.cs │ └── IGroupExtensions.cs.meta ├── JuDelCo.Core.ECS.Runtime.asmdef ├── JuDelCo.Core.ECS.Runtime.asmdef.meta ├── Matcher.meta ├── Matcher │ ├── IMatcher.cs │ ├── IMatcher.cs.meta │ ├── Matcher.cs │ ├── Matcher.cs.meta │ ├── MatcherExtensions.cs │ ├── MatcherExtensions.cs.meta │ ├── MatcherGenerator.cs │ └── MatcherGenerator.cs.meta ├── System.meta └── System │ ├── ISystem.cs │ ├── ISystem.cs.meta │ ├── ReactiveSystem.cs │ ├── ReactiveSystem.cs.meta │ ├── Systems.cs │ └── Systems.cs.meta ├── build.meta ├── build ├── JuDelCo.Lib.CoreECS.targets └── JuDelCo.Lib.CoreECS.targets.meta ├── package.json └── package.json.meta /.editorconfig: -------------------------------------------------------------------------------- 1 | ; This .editorconfig file is used to enforce code style and formatting standards 2 | ; across the project. Please adhere to these settings to ensure consistency. 3 | ; - Juan Delgado (@JuDelCo) 4 | 5 | ; Basic text editor config 6 | ; Reference: http://editorconfig.org 7 | [*] 8 | charset = utf-8 9 | end_of_line = lf 10 | 11 | ; .NET CSharp files 12 | [*.cs] 13 | indent_style = tab 14 | indent_size = 4 15 | trim_trailing_whitespace = true 16 | insert_final_newline = true 17 | 18 | ; Code analysis - Ignore rules (.NET native) 19 | [**bin/**.cs] 20 | generated_code = true 21 | [**obj/**.cs] 22 | generated_code = true 23 | 24 | ; Code analysis - Ignore rules (Unity3D) 25 | ; Extra: 26 | ; 1- Create a new file in: %UnityEditor_Folder%/Editor/Data/Resources/PackageManager/.editorconfig 27 | ; 2- Add this to the file to prevent Unity built-in packages to be analyzed: 28 | ; [**.cs] 29 | ; generated_code = true 30 | ; 3- You need to repeat it for each Unity installation 31 | [**PackageCache/**.cs] 32 | generated_code = true 33 | [Assets/Plugins/**.cs] 34 | generated_code = true 35 | 36 | ; Code analysis - Config rules 37 | ; Reference: https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-language-conventions 38 | ; Reference: https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-formatting-conventions 39 | [*.cs] 40 | csharp_prefer_simple_default_expression = false:none 41 | csharp_prefer_simple_using_statement = false:none 42 | csharp_style_prefer_switch_expression = false:none 43 | csharp_style_conditional_delegate_call = false:none 44 | csharp_style_implicit_object_creation_when_type_is_apparent = false:none 45 | csharp_space_after_cast = true 46 | csharp_space_between_parentheses = false 47 | csharp_space_between_method_declaration_parameter_list_parentheses = false 48 | csharp_space_between_method_call_parameter_list_parentheses = false 49 | dotnet_sort_system_directives_first = true 50 | dotnet_code_quality_unused_parameters = non_public:none 51 | dotnet_style_object_initializer = false:silent 52 | dotnet_style_collection_initializer = false:silent 53 | dotnet_style_readonly_field = false:silent 54 | dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent 55 | dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent 56 | dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent 57 | dotnet_style_parentheses_in_other_operators = never_if_unnecessary:none 58 | 59 | ; Supress warnings when is enabled in the project 60 | dotnet_diagnostic.CS8600.severity = none 61 | dotnet_diagnostic.CS8603.severity = none 62 | dotnet_diagnostic.CS8618.severity = none 63 | dotnet_diagnostic.CS8625.severity = none 64 | dotnet_diagnostic.CS8765.severity = none 65 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # .NET Core build folders 3 | bin/ 4 | obj/ 5 | 6 | # .NET autogenerated files 7 | *.sln 8 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.exclude": { 3 | // ------------------------------- 4 | // System / Git / Editor 5 | // ------------------------------- 6 | "**/.DS_Store": true, 7 | "**/.hidden": true, 8 | "*.lnk": true, 9 | "**/.git": true, 10 | "**/.gitignore": true, 11 | "**/.gitattributes": true, 12 | "**/.gitmodules": true, 13 | "**/.editorconfig": true, 14 | ".vscode/": true, 15 | ".vs/": true, 16 | // ------------------------------- 17 | // Visual Studio & Build Artifacts 18 | // ------------------------------- 19 | "**/*.booproj": true, 20 | "**/*.pidb": true, 21 | "**/*.suo": true, 22 | "**/*.user": true, 23 | "**/*.userprefs": true, 24 | "**/*.dll": true, 25 | "**/*.exe": true, 26 | "**/*.pdb": true, 27 | "**/bin/": true, 28 | "**/Bin/": true, 29 | "**/obj/": true, 30 | "**/Obj/": true, 31 | "**/nuget.config": true, 32 | "**/runtimes/": true, 33 | // ------------------------------- 34 | // Media Files 35 | // ------------------------------- 36 | "**/*.gif": true, 37 | "**/*.ico": true, 38 | "**/*.jpg": true, 39 | "**/*.jpeg": true, 40 | "**/*.png": true, 41 | "**/*.psd": true, 42 | "**/*.tga": true, 43 | "**/*.tif": true, 44 | "**/*.tiff": true, 45 | "**/*.mid": true, 46 | "**/*.midi": true, 47 | "**/*.wav": true, 48 | "**/*.3ds": true, 49 | "**/*.3DS": true, 50 | "**/*.fbx": true, 51 | "**/*.FBX": true, 52 | "**/*.lxo": true, 53 | "**/*.LXO": true, 54 | "**/*.ma": true, 55 | "**/*.MA": true, 56 | "**/*.obj": true, 57 | "**/*.OBJ": true, 58 | "**/*.pdf": true, 59 | // ------------------------------- 60 | // Unity & Game Assets 61 | // ------------------------------- 62 | "Library/": true, 63 | "**/*.asset": true, 64 | "**/*.asmdef": true, 65 | "**/*.anim": true, 66 | "**/*.cubemap": true, 67 | "**/*.controller": true, 68 | "**/*.flare": true, 69 | "**/*.mat": true, 70 | "**/*.meta": true, 71 | "**/*.prefab": true, 72 | "**/*.physicsMaterial2D": true, 73 | "**/*.unity": true, 74 | "**/*.unityproj": true, 75 | "**/LICENSE": true, 76 | "**/LICENSE.md": true, 77 | ".gradle/": true, 78 | }, 79 | "files.watcherExclude": { 80 | "**/.git/objects/**": true, 81 | "**/.git/subtree-cache/**": true, 82 | "**/node_modules/**": true, 83 | "**/.hg/store/**": true, 84 | "**/Resources/": true, 85 | "**/bin/": true, 86 | "**/Bin/": true, 87 | "**/obj/": true, 88 | "**/Obj/": true, 89 | } 90 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | # Changelog 3 | 4 | ## [1.14.0] - 2025-03-26 5 | 6 | ### Fixed 7 | 8 | - Fix warnings when the 'Nullable' NET project property is enabled. 9 | - Add missing .meta files for Unity. 10 | 11 | ## [1.13.0] - 2025-03-17 12 | 13 | ### Fixed 14 | 15 | - Fix an issue where the Godot NuGet package failed to compile the source-only package by renaming the "buildTransient" folder to "build". 16 | 17 | ## [1.12.0] - 2025-03-16 18 | 19 | ### Added 20 | 21 | - Add a .targets file to automatically include and compile the source files in consumer projects for seamless source-only package integration. 22 | 23 | ## [1.11.0] - 2025-03-16 24 | 25 | ### Changed 26 | 27 | - Change the NuGet package type to a source-only package. 28 | - Add a new Directory.Build.props file to improve local development for Godot. 29 | - Configure local development to target net8.0 when working with Godot. 30 | 31 | ### Improved 32 | 33 | - Include LICENSE.md and CHANGELOG.md files in the final NuGet package. 34 | - Rename the .csproj file to remove an extra dot from its filename. 35 | - Update tags in package.json. 36 | - Update the .editorconfig file. 37 | - Update VSCode settings.json. 38 | 39 | ## [1.10.0] - 2025-03-13 40 | 41 | ### Improved 42 | 43 | - Include README.md in the nuget package. 44 | - Update README.md with instructions to install the package. 45 | 46 | ## [1.9.0] - 2025-03-13 47 | 48 | ### Improved 49 | 50 | - Update csproj file to allow creating a nuget package. 51 | - Update .gitignore to exclude autogenerated solution files by dotnet. 52 | 53 | ## [1.8.0] - 2021-05-24 54 | 55 | ### Added 56 | 57 | - Add csproj file to allow compiling and using the library for native NET projects. 58 | 59 | ### Improved 60 | 61 | - Merge single usage methods to where they are called. 62 | - Use string interpolation in all exception messages. 63 | - Update VSCode settings.json file to handle generated folders by dotnet compiler. 64 | - Update .editorconfig to include generated folders by dotnet compiler. 65 | - Update .gitignore to exclude generated folders by dotnet compiler. 66 | -------------------------------------------------------------------------------- /CHANGELOG.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 911fa108efd5bff92a0a3a2bf4abcd93 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /CoreECS.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | JuCore.ECS 5 | 1.14.0 6 | JuCore.ECS 7 | Juan Delgado (@JuDelCo) 8 | Copyright (c) 2019-2025 Juan Delgado (@JuDelCo) 9 | Deterministic lightweight ECS framework 10 | JuDelCo.Lib.CoreECS 11 | Core;JuCore;ECS;EntityComponentSystem;Entity;Component;System;Deterministic;Lightweight;GameDev;Framework;Simulation;Performance 12 | README.md 13 | https://github.com/JuDelCo/CoreECS 14 | https://github.com/JuDelCo/CoreECS 15 | git 16 | MIT 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | Library 29 | false 30 | $(NoWarn);NU5128 31 | true 32 | 33 | 34 | 35 | true 36 | true 37 | true 38 | true 39 | 40 | 41 | 42 | netstandard2.0 43 | 44 | 45 | 46 | net8.0 47 | 48 | 49 | 50 | linux-x64 51 | 52 | 53 | 54 | osx-x64 55 | 56 | 57 | 58 | win-x64 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /CoreECS.csproj.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 5e94621c5a57050668f732e29c88c995 3 | DefaultImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | /opt/Godot/GodotSharp/ 16 | $(DefineConstants);GODOT4;GODOT4_3_OR_GREATER 17 | 18 | 19 | 20 | 21 | $(GodotInstallationPath)Api/Release/GodotSharp.dll 22 | 23 | 26 | 29 | 30 | -------------------------------------------------------------------------------- /Directory.Build.props.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 1101fa8bc28daa743ad47c5df8536637 3 | DefaultImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2019-2025 Juan Delgado (@JuDelCo) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /LICENSE.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 8c163f06d2bd7cd4cbd4ccd63fcd5470 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | JuCore ECS 2 | ===================== 3 | 4 | JuCore ECS is a deterministic lightweight ECS framework. 5 | 6 | It's heavily inspired in Entitas, but with some differences: 7 | 8 | - It's deterministic (does not rely on hashtables) 9 | - Does not use code generators (no reflection, less problems) 10 | - Easier to start using it (more straightforward, see documentation below) 11 | - No GC collections (when reusing destroyed entities) 12 | - Similar perfomance (10~15% less, but handles well all requirements for a game) 13 | 14 | Any feedback is welcome ! 15 | 16 | 17 | See also 18 | ===================== 19 | 20 | - [JuCore](https://github.com/JuDelCo/Core) - Core package base (service locator and useful services) 21 | - [JuCore Math](https://github.com/JuDelCo/CoreMath) - Linear algebra math library, also 2D/3D physics, noise functions and IK 22 | 23 | 24 | Install 25 | ===================== 26 | 27 | - For **Unity**, update the dependencies in the ```/Packages/manifest.json``` file in your project folder by adding: 28 | 29 | ```json 30 | "com.judelco.core.ecs": "https://github.com/JuDelCo/CoreECS.git#v1.14.0", 31 | ``` 32 | 33 | - For native **.NET projects**, **Godot**, etc... run the following command in the console of your .NET project to add the package: 34 | 35 | ```bash 36 | dotnet add package JuDelCo.Lib.CoreECS 37 | ``` 38 | 39 | 40 | Documentation 41 | ===================== 42 | 43 | Note: All components should be structs for best performance and memory efficiency. 44 | 45 | #### Defining components 46 | 47 | ```csharp 48 | public struct Speed : IComponent 49 | { 50 | public float value; 51 | 52 | public Speed(float value) // Optional, syntactic sugar for later 53 | { 54 | this.value = value; 55 | } 56 | } 57 | ``` 58 | 59 | #### Create a Context 60 | 61 | ```csharp 62 | // IMPORTANT: You need to pass the count of component types you will use to the context 63 | const int COMPONENT_TYPES_COUNT = 9; 64 | 65 | var context = new Context(Constants.COMPONENT_TYPES_COUNT); 66 | ``` 67 | 68 | #### Creating entities 69 | 70 | ```csharp 71 | var entity = context.CreateEntity(); 72 | 73 | entity.Add(new Speed(2f)); 74 | ``` 75 | 76 | #### Entity management 77 | 78 | ```csharp 79 | entity.Add(new Position(3, 7)); 80 | entity.Replace(new Health(10)); 81 | entity.Remove(); 82 | 83 | var hasSpeed = entity.Has(); 84 | var healthData = entity.Get(); 85 | 86 | // You can also chain methods! 87 | context.CreateEntity() 88 | .Add(new Speed(2f)) 89 | .Add(new Position(3, 7)) 90 | .Replace(new Health(10)) // Note: Replace will add the component if doesn't have it yet 91 | .Remove(); 92 | ``` 93 | 94 | #### Group management 95 | 96 | ```csharp 97 | // Returns a group containing always all entities with Position and Speed components. 98 | var group = context.GetGroup(MatcherGenerator.AllOf()); 99 | 100 | var entities = group.GetEntities(); 101 | 102 | foreach (var e in entities) 103 | { 104 | // ... 105 | } 106 | ``` 107 | 108 | #### Execute systems 109 | 110 | ```csharp 111 | // You can also use IInitializeSystem, ICleanupSystem and ITearDownSystem systems 112 | public class MovementSystem : IExecuteSystem 113 | { 114 | private IGroup group; 115 | 116 | public MovementSystem(IContext context) 117 | { 118 | // You can also use a shorthand for AllOf components! 119 | // This is equivalent to: GetGroup(MatcherGenerator.AllOf()) 120 | group = context.GetGroup(); 121 | } 122 | 123 | public void Execute() 124 | { 125 | foreach (var e in group.GetEntities()) 126 | { 127 | var position = e.Get(); 128 | var speed = e.Get().value; 129 | position.x += speed; 130 | 131 | e.Replace(position); 132 | } 133 | } 134 | } 135 | ``` 136 | 137 | #### Reactive systems 138 | 139 | ```csharp 140 | public class RenderPositionSystem : ReactiveSystem 141 | { 142 | private IContext context; 143 | private IGroup group; 144 | 145 | public RenderPositionSystem(IContext context) : base(context) 146 | { 147 | this.context = context; 148 | } 149 | 150 | protected override ICollector GetTrigger(IContext context) 151 | { 152 | return context.GetGroup().CreateCollector(GroupEvent.Added); 153 | } 154 | 155 | protected override bool Filter(IEntity entity) 156 | { 157 | // Yes, you can use multiple types in Has method ! 158 | return entity.Has(); 159 | } 160 | 161 | protected override void Execute(List entities) 162 | { 163 | foreach (var e in entities) 164 | { 165 | var position = e.Get(); 166 | e.Get().gameObject.transform.position = new Vector3(position.x, position.y, position.z); 167 | } 168 | } 169 | } 170 | ``` 171 | 172 | 173 | The MIT License (MIT) 174 | ===================== 175 | 176 | Copyright © 2019-2025 Juan Delgado (@JuDelCo) 177 | 178 | Permission is hereby granted, free of charge, to any person obtaining a copy 179 | of this software and associated documentation files (the "Software"), to deal 180 | in the Software without restriction, including without limitation the rights 181 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 182 | copies of the Software, and to permit persons to whom the Software is 183 | furnished to do so, subject to the following conditions: 184 | 185 | The above copyright notice and this permission notice shall be included in 186 | all copies or substantial portions of the Software. 187 | 188 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 189 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 190 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 191 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 192 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 193 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 194 | THE SOFTWARE. 195 | -------------------------------------------------------------------------------- /README.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 5b862942dd7151b4297b4f1f6910d2c2 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Runtime.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ae1cbdccf2a0b3e4688c35e838ae9aeb 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Runtime/Collector.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c7f9a676f91f81c40a27aafa8b697934 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Runtime/Collector/Collector.cs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // Copyright (c) 2019-2025 Juan Delgado (@JuDelCo) 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | 7 | namespace Ju.ECS 8 | { 9 | public class Collector : ICollector 10 | { 11 | private bool[] collectedEntitiesCheck; 12 | private readonly List collectedEntities; 13 | private readonly IGroup group; 14 | private readonly GroupEvent groupEvent; 15 | private readonly GroupChangedEvent onEntityGroupEventCache; 16 | 17 | private Collector() 18 | { 19 | collectedEntitiesCheck = new bool[0]; 20 | collectedEntities = new List(1000); 21 | onEntityGroupEventCache = OnEntityGroupEvent; 22 | } 23 | 24 | public Collector(IGroup group, GroupEvent groupEvent) : this() 25 | { 26 | this.group = group; 27 | this.groupEvent = groupEvent; 28 | 29 | Activate(); 30 | } 31 | 32 | ~Collector() 33 | { 34 | Deactivate(); 35 | } 36 | 37 | public void Activate() 38 | { 39 | switch (groupEvent) 40 | { 41 | case GroupEvent.Added: 42 | group.OnEntityAdded += onEntityGroupEventCache; 43 | break; 44 | case GroupEvent.Removed: 45 | group.OnEntityRemoved += onEntityGroupEventCache; 46 | break; 47 | case GroupEvent.AddedOrRemoved: 48 | group.OnEntityAdded += onEntityGroupEventCache; 49 | group.OnEntityRemoved += onEntityGroupEventCache; 50 | break; 51 | } 52 | } 53 | 54 | public void Deactivate() 55 | { 56 | group.OnEntityAdded -= onEntityGroupEventCache; 57 | group.OnEntityRemoved -= onEntityGroupEventCache; 58 | 59 | ClearCollectedEntities(); 60 | } 61 | 62 | public int GetCount() 63 | { 64 | return collectedEntities.Count; 65 | } 66 | 67 | public List GetCollectedEntities() 68 | { 69 | return collectedEntities; 70 | } 71 | 72 | public void ClearCollectedEntities() 73 | { 74 | for (int i = (collectedEntities.Count - 1); i >= 0; --i) 75 | { 76 | collectedEntitiesCheck[collectedEntities[i].GetUuid()] = false; 77 | collectedEntities[i].Release(); 78 | } 79 | 80 | collectedEntities.Clear(); 81 | } 82 | 83 | private void OnEntityGroupEvent(IGroup group, IEntity entity) 84 | { 85 | if (entity.GetUuid() >= collectedEntitiesCheck.Length) 86 | { 87 | // Increase Check Array 88 | { 89 | int target = (int)entity.GetUuid(); 90 | int inc = 10000; 91 | target = (target / inc) * inc + ((target % inc) > (inc / 2) ? (inc * 2) : inc); 92 | 93 | var newCollectedEntitiesCheck = new bool[target]; 94 | Array.Copy(collectedEntitiesCheck, newCollectedEntitiesCheck, collectedEntitiesCheck.Length); 95 | collectedEntitiesCheck = newCollectedEntitiesCheck; 96 | } 97 | } 98 | 99 | if (!collectedEntitiesCheck[entity.GetUuid()]) 100 | { 101 | collectedEntitiesCheck[entity.GetUuid()] = true; 102 | collectedEntities.Add(entity); 103 | entity.Retain(); 104 | } 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /Runtime/Collector/Collector.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 30ec0524594c6f64b89cc94cb6b2a1ac 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Collector/ICollector.cs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // Copyright (c) 2019-2025 Juan Delgado (@JuDelCo) 3 | 4 | using System.Collections.Generic; 5 | 6 | namespace Ju.ECS 7 | { 8 | public enum GroupEvent : byte 9 | { 10 | Added, 11 | Removed, 12 | AddedOrRemoved 13 | } 14 | 15 | public interface ICollector 16 | { 17 | void Activate(); 18 | void Deactivate(); 19 | 20 | int GetCount(); 21 | List GetCollectedEntities(); 22 | void ClearCollectedEntities(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Runtime/Collector/ICollector.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 57d21d13a4fa4524bbca35dbf2080fe8 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Collector/ICollectorExtensions.cs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // Copyright (c) 2019-2025 Juan Delgado (@JuDelCo) 3 | 4 | namespace Ju.ECS 5 | { 6 | public static class ICollectorExtensions 7 | { 8 | public static ICollector CreateCollector(this IContext context, IMatcher matcher, GroupEvent groupEvent = GroupEvent.Added) 9 | { 10 | return new Collector(context.GetGroup(matcher), groupEvent); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Runtime/Collector/ICollectorExtensions.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 8c2832daf8bc5c84ca6b5abdc2990ee2 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Component.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f9fc33f5fd27de443a7142d4a2965c38 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Runtime/Component/ComponentTypeId.cs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // Copyright (c) 2019-2025 Juan Delgado (@JuDelCo) 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | 7 | namespace Ju.ECS.Internal 8 | { 9 | public static class ComponentLookup where T : IComponent 10 | { 11 | public static readonly int Id = ComponentType.GetId(typeof(T)); 12 | public static T[] Array => componentArray; 13 | public static Stack UnusedIndices => unusedIndices; 14 | 15 | private static T[] componentArray = new T[0]; 16 | private static readonly Stack unusedIndices = new Stack(10000); 17 | 18 | public static int New() 19 | { 20 | if (unusedIndices.Count == 0) 21 | { 22 | IncreasePool(10000); 23 | } 24 | 25 | return unusedIndices.Pop(); 26 | } 27 | 28 | public static void Remove(int index) 29 | { 30 | unusedIndices.Push(index); 31 | } 32 | 33 | private static void IncreasePool(int amount) 34 | { 35 | var oldLength = componentArray.Length; 36 | var newComponentArray = new T[oldLength + amount]; 37 | System.Array.Copy(componentArray, newComponentArray, oldLength); 38 | componentArray = newComponentArray; 39 | 40 | for (int i = (componentArray.Length - 1); i >= oldLength; --i) 41 | { 42 | unusedIndices.Push(i); 43 | } 44 | } 45 | } 46 | 47 | public static class ComponentTypeIdsPerContext 48 | { 49 | private static int[][] array = new int[0][]; 50 | 51 | public static void SetContext(int contextId, int componentTypeCount) 52 | { 53 | if ((array.Length - 1) < contextId) 54 | { 55 | var oldLength = array.Length; 56 | var newArray = new int[oldLength + 1][]; 57 | System.Array.Copy(array, newArray, oldLength); 58 | array = newArray; 59 | 60 | array[contextId] = new int[componentTypeCount]; 61 | 62 | for (int i = 0; i < componentTypeCount; ++i) 63 | { 64 | array[contextId][i] = -1; 65 | } 66 | } 67 | } 68 | 69 | public static int GetTypeId(int contextId, int componentTypeId) 70 | { 71 | int index = -1; 72 | var componentArray = array[contextId]; 73 | int length = componentArray.Length; 74 | 75 | for (int i = 0; i < length; ++i) 76 | { 77 | if (componentArray[i] == componentTypeId) 78 | { 79 | index = i; 80 | break; 81 | } 82 | } 83 | 84 | if (index == -1) 85 | { 86 | for (int i = 0; i < length; ++i) 87 | { 88 | if (componentArray[i] == -1) 89 | { 90 | componentArray[i] = componentTypeId; 91 | index = i; 92 | break; 93 | } 94 | } 95 | } 96 | 97 | return index; 98 | } 99 | } 100 | 101 | public static class ComponentType 102 | { 103 | private static int typeIdGenerator = 0; 104 | private static readonly Dictionary componentTypeIds = new Dictionary(); 105 | 106 | public static int GetId(Type type) 107 | { 108 | int componentTypeId; 109 | 110 | if (componentTypeIds.ContainsKey(type)) 111 | { 112 | componentTypeId = componentTypeIds[type]; 113 | } 114 | else 115 | { 116 | componentTypeId = typeIdGenerator++; 117 | componentTypeIds.Add(type, componentTypeId); 118 | } 119 | 120 | return componentTypeId; 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /Runtime/Component/ComponentTypeId.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e8592a5b2e5819248b775739421adb29 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Component/IComponent.cs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // Copyright (c) 2019-2025 Juan Delgado (@JuDelCo) 3 | 4 | namespace Ju.ECS 5 | { 6 | public interface IComponent 7 | { 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Runtime/Component/IComponent.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e7a1e9d85d1678846b40f4ab9981a143 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Context.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a114202be632a964fb5a15257b5d7160 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Runtime/Context/Context.cs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // Copyright (c) 2019-2025 Juan Delgado (@JuDelCo) 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using Ju.ECS.Internal; 7 | 8 | namespace Ju.ECS 9 | { 10 | public class Context : IContext 11 | { 12 | public event ContextEntityEvent OnEntityCreated; 13 | public event ContextEntityEvent OnEntityDestroyed; 14 | public event ContextGroupEvent OnGroupCreated; 15 | 16 | private static int contextIdGenerator = 0; 17 | 18 | private readonly int contextId; 19 | private readonly int componentTypeCount; 20 | private readonly List entities; 21 | private readonly Stack reausableEntities; 22 | private readonly List retainedEntities; 23 | private readonly Dictionary> groupsForType; 24 | private readonly Dictionary groupCache; 25 | private readonly Stack> eventCache; 26 | private readonly EntityComponentChangedEvent onEntityComponentAddedOrRemovedCache; 27 | private readonly EntityComponentReplacedEvent onEntityComponentReplacedCache; 28 | private readonly EntityEvent onEntityReleaseCache; 29 | private readonly EntityEvent onEntityDestroyCache; 30 | private uint uuidGenerator = 0; 31 | private uint entityIdCounter = 0; 32 | 33 | public Context(int componentTypeCount) : this(componentTypeCount, 100000) 34 | { 35 | contextId = contextIdGenerator++; 36 | this.componentTypeCount = componentTypeCount; 37 | ComponentTypeIdsPerContext.SetContext(contextId, componentTypeCount); 38 | } 39 | 40 | public Context(int componentTypeCount, int capacity) 41 | { 42 | entities = new List(capacity); 43 | reausableEntities = new Stack(capacity); 44 | retainedEntities = new List(capacity); 45 | groupsForType = new Dictionary>(100); 46 | groupCache = new Dictionary(100); 47 | eventCache = new Stack>(); 48 | onEntityComponentAddedOrRemovedCache = OnEntityComponentAddedOrRemoved; 49 | onEntityComponentReplacedCache = OnEntityComponentReplaced; 50 | onEntityReleaseCache = OnEntityRelease; 51 | onEntityDestroyCache = OnEntityDestroy; 52 | } 53 | 54 | public IEntity CreateEntity() 55 | { 56 | IEntity entity; 57 | 58 | if (reausableEntities.Count > 0) 59 | { 60 | entity = reausableEntities.Pop(); 61 | entity.Reactivate(entityIdCounter++); 62 | } 63 | else 64 | { 65 | entity = new Entity(contextId, componentTypeCount, uuidGenerator++); 66 | } 67 | 68 | entity.Retain(); 69 | entities.Add(entity); 70 | 71 | entity.OnComponentAdded += onEntityComponentAddedOrRemovedCache; 72 | entity.OnComponentReplaced += onEntityComponentReplacedCache; 73 | entity.OnComponentRemoved += onEntityComponentAddedOrRemovedCache; 74 | entity.OnRelease += onEntityReleaseCache; 75 | entity.OnDestroy += onEntityDestroyCache; 76 | 77 | if (OnEntityCreated != null) 78 | { 79 | OnEntityCreated(this, entity); 80 | } 81 | 82 | return entity; 83 | } 84 | 85 | public List GetEntities() 86 | { 87 | return entities; 88 | } 89 | 90 | public IGroup GetGroup(IMatcher matcher) 91 | { 92 | IGroup group; 93 | 94 | if (groupCache.ContainsKey(matcher)) 95 | { 96 | group = groupCache[matcher]; 97 | } 98 | else 99 | { 100 | group = new Group(matcher); 101 | groupCache.Add(matcher, group); 102 | 103 | for (int i = 0, count = entities.Count; i < count; ++i) 104 | { 105 | group.HandleEntitySilently(entities[i]); 106 | } 107 | 108 | var types = matcher.GetTypes(); 109 | 110 | for (int i = (types.Count - 1); i >= 0; --i) 111 | { 112 | if (!groupsForType.ContainsKey(types[i])) 113 | { 114 | groupsForType.Add(types[i], new List()); 115 | } 116 | 117 | groupsForType[types[i]].Add(group); 118 | } 119 | 120 | if (OnGroupCreated != null) 121 | { 122 | OnGroupCreated(this, group); 123 | } 124 | } 125 | 126 | return group; 127 | } 128 | 129 | public int GetEntityCount() 130 | { 131 | return entities.Count; 132 | } 133 | 134 | public void DestroyAllEntities() 135 | { 136 | for (int i = (entities.Count - 1); i >= 0; --i) 137 | { 138 | entities[i].Destroy(); 139 | } 140 | 141 | entities.Clear(); 142 | } 143 | 144 | private void OnEntityComponentAddedOrRemoved(IEntity entity, int componentTypeId) 145 | { 146 | if (groupsForType.ContainsKey(componentTypeId)) 147 | { 148 | var groups = groupsForType[componentTypeId]; 149 | 150 | List groupEvents; 151 | 152 | if (eventCache.Count != 0) 153 | { 154 | groupEvents = eventCache.Pop(); 155 | } 156 | else 157 | { 158 | groupEvents = new List(100); 159 | } 160 | 161 | for (int i = 0, count = groups.Count; i < count; ++i) 162 | { 163 | groupEvents.Add(groups[i].HandleEntity(entity)); 164 | } 165 | 166 | for (int i = 0, count = groupEvents.Count; i < count; ++i) 167 | { 168 | if (groupEvents[i] != null) 169 | { 170 | groupEvents[i](groups[i], entity); 171 | } 172 | } 173 | 174 | groupEvents.Clear(); 175 | eventCache.Push(groupEvents); 176 | } 177 | } 178 | 179 | private void OnEntityComponentReplaced(IEntity entity, int componentTypeId) 180 | { 181 | if (groupsForType.ContainsKey(componentTypeId)) 182 | { 183 | var groups = groupsForType[componentTypeId]; 184 | 185 | for (int i = 0, count = groups.Count; i < count; ++i) 186 | { 187 | groups[i].UpdateEntity(entity); 188 | } 189 | } 190 | } 191 | 192 | private void OnEntityRelease(IEntity entity) 193 | { 194 | retainedEntities.Remove(entity); 195 | reausableEntities.Push(entity); 196 | } 197 | 198 | private void OnEntityDestroy(IEntity entity) 199 | { 200 | if (!entities.Remove(entity)) 201 | { 202 | throw new Exception("Context doesn't contain the entity"); 203 | } 204 | 205 | entity.InternalDestroy(); 206 | 207 | if (OnEntityDestroyed != null) 208 | { 209 | OnEntityDestroyed(this, entity); 210 | } 211 | 212 | if (entity.GetRetainCount() == 1) 213 | { 214 | entity.OnRelease -= onEntityReleaseCache; 215 | reausableEntities.Push(entity); 216 | } 217 | else 218 | { 219 | retainedEntities.Add(entity); 220 | } 221 | 222 | entity.Release(); 223 | } 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /Runtime/Context/Context.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 1b88f1b23a8139047bd7b05ca41cfd49 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Context/IContext.cs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // Copyright (c) 2019-2025 Juan Delgado (@JuDelCo) 3 | 4 | using System.Collections.Generic; 5 | 6 | namespace Ju.ECS 7 | { 8 | public delegate void ContextEntityEvent(IContext context, IEntity entity); 9 | public delegate void ContextGroupEvent(IContext context, IGroup group); 10 | 11 | public interface IContext 12 | { 13 | event ContextEntityEvent OnEntityCreated; 14 | event ContextEntityEvent OnEntityDestroyed; 15 | event ContextGroupEvent OnGroupCreated; 16 | 17 | IEntity CreateEntity(); 18 | List GetEntities(); 19 | IGroup GetGroup(IMatcher matcher); 20 | int GetEntityCount(); 21 | void DestroyAllEntities(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Runtime/Context/IContext.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 79bf3b83f278d594fb3f6c7e5ee82134 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Context/IContextExtensions.cs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // Copyright (c) 2019-2025 Juan Delgado (@JuDelCo) 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | 7 | namespace Ju.ECS 8 | { 9 | public static class IContextExtensions 10 | { 11 | public static List GetEntities(this IContext context, IMatcher matcher) 12 | { 13 | return context.GetGroup(matcher).GetEntities(); 14 | } 15 | 16 | public static IGroup GetGroup(this IContext context) where T : IComponent 17 | { 18 | return context.GetGroup(MatcherGenerator.AllOf()); 19 | } 20 | 21 | public static IGroup GetGroup(this IContext context) where T1 : IComponent where T2 : IComponent 22 | { 23 | return context.GetGroup(MatcherGenerator.AllOf()); 24 | } 25 | 26 | public static IGroup GetGroup(this IContext context) where T1 : IComponent where T2 : IComponent where T3 : IComponent 27 | { 28 | return context.GetGroup(MatcherGenerator.AllOf()); 29 | } 30 | 31 | public static IGroup GetGroup(this IContext context) where T1 : IComponent where T2 : IComponent where T3 : IComponent where T4 : IComponent 32 | { 33 | return context.GetGroup(MatcherGenerator.AllOf()); 34 | } 35 | 36 | public static IEntity Set(this IContext context, T component) where T : IComponent 37 | { 38 | if (context.Has()) 39 | { 40 | throw new Exception("The group have already a single entity with that component"); 41 | } 42 | 43 | return context.CreateEntity().Add(component); 44 | } 45 | 46 | public static IEntity Replace(this IContext context, T component) where T : IComponent 47 | { 48 | var e = context.GetEntity(); 49 | 50 | if (e == null) 51 | { 52 | e = context.Set(component); 53 | } 54 | else 55 | { 56 | e.Replace(component); 57 | } 58 | 59 | return e; 60 | } 61 | 62 | public static void Remove(this IContext context) where T : IComponent 63 | { 64 | context.GetEntity()?.Destroy(); 65 | } 66 | 67 | public static bool Has(this IContext context) where T : IComponent 68 | { 69 | return (context.GetEntity() != null); 70 | } 71 | 72 | public static T Get(this IContext context) where T : IComponent 73 | { 74 | var e = context.GetEntity(); 75 | 76 | if (e == null) 77 | { 78 | throw new Exception("The group doesn't have a single entity with that component"); 79 | } 80 | 81 | return e.Get(); 82 | } 83 | 84 | private static IEntity GetEntity(this IContext context) where T : IComponent 85 | { 86 | return context.GetGroup(MatcherGenerator.AllOf()).GetSingleEntity(); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /Runtime/Context/IContextExtensions.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 4df1f046a8a6aea4996ee3e933377367 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Entity.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f0ae7ad64fa787c47b9cb93f37484f33 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Runtime/Entity/Entity.cs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // Copyright (c) 2019-2025 Juan Delgado (@JuDelCo) 3 | 4 | using System.Collections.Generic; 5 | using Ju.ECS.Internal; 6 | 7 | namespace Ju.ECS 8 | { 9 | public class Entity : IEntity 10 | { 11 | public event EntityComponentChangedEvent OnComponentAdded; 12 | public event EntityComponentReplacedEvent OnComponentReplaced; 13 | public event EntityComponentChangedEvent OnComponentRemoved; 14 | public event EntityEvent OnRelease; 15 | public event EntityEvent OnDestroy; 16 | 17 | private readonly int contextId; 18 | private readonly List componentTypes; 19 | private readonly int[] componentPoolIndices; 20 | private readonly Stack[] componentUnusedIndices; 21 | private readonly uint uuid; 22 | private bool isEnabled; 23 | private uint entityId; 24 | private int retainCount = 0; 25 | 26 | public Entity(int contextId, int componentTypeCount, uint entityId) 27 | { 28 | this.contextId = contextId; 29 | 30 | uuid = entityId; 31 | Reactivate(entityId); 32 | 33 | componentTypes = new List(componentTypeCount); 34 | componentPoolIndices = new int[componentTypeCount]; 35 | componentUnusedIndices = new Stack[componentTypeCount]; 36 | 37 | for (int i = 0; i < componentTypeCount; ++i) 38 | { 39 | componentPoolIndices[i] = -1; 40 | } 41 | } 42 | 43 | public void AddComponent(int componentTypeId, int componentPoolIndex, Stack unusedIndicesRef) 44 | { 45 | var contextComponentTypeId = GetContextComponentTypeId(componentTypeId); 46 | 47 | componentTypes.Add(componentTypeId); 48 | componentPoolIndices[contextComponentTypeId] = componentPoolIndex; 49 | componentUnusedIndices[contextComponentTypeId] = unusedIndicesRef; 50 | 51 | if (OnComponentAdded != null) 52 | { 53 | OnComponentAdded(this, componentTypeId); 54 | } 55 | } 56 | 57 | public void ReplaceComponent(int componentTypeId) 58 | { 59 | if (OnComponentReplaced != null) 60 | { 61 | OnComponentReplaced(this, componentTypeId); 62 | } 63 | } 64 | 65 | public void RemoveComponent(int componentTypeId) 66 | { 67 | var contextComponentTypeId = GetContextComponentTypeId(componentTypeId); 68 | 69 | componentTypes.Remove(componentTypeId); 70 | 71 | componentUnusedIndices[contextComponentTypeId].Push(componentPoolIndices[contextComponentTypeId]); 72 | componentUnusedIndices[contextComponentTypeId] = null; 73 | componentPoolIndices[contextComponentTypeId] = -1; 74 | 75 | if (OnComponentRemoved != null) 76 | { 77 | OnComponentRemoved(this, componentTypeId); 78 | } 79 | } 80 | 81 | public bool HasComponent(int componentTypeId) 82 | { 83 | return componentPoolIndices[GetContextComponentTypeId(componentTypeId)] >= 0; 84 | } 85 | 86 | public int GetComponentPoolIndex(int componentTypeId) 87 | { 88 | return componentPoolIndices[GetContextComponentTypeId(componentTypeId)]; 89 | } 90 | 91 | public int GetTotalComponents() 92 | { 93 | return componentTypes.Count; 94 | } 95 | 96 | public void RemoveAllComponents() 97 | { 98 | for (int i = (componentTypes.Count - 1); i >= 0; --i) 99 | { 100 | RemoveComponent(componentTypes[i]); 101 | } 102 | } 103 | 104 | public uint GetUuid() 105 | { 106 | return uuid; 107 | } 108 | 109 | public uint GetEntityId() 110 | { 111 | return entityId; 112 | } 113 | 114 | public bool IsEnabled() 115 | { 116 | return isEnabled; 117 | } 118 | 119 | public void Destroy() 120 | { 121 | if (OnDestroy != null) 122 | { 123 | OnDestroy(this); 124 | } 125 | } 126 | 127 | public void Retain() 128 | { 129 | retainCount++; 130 | } 131 | 132 | public void Release() 133 | { 134 | retainCount--; 135 | 136 | if (retainCount == 0) 137 | { 138 | if (OnRelease != null) 139 | { 140 | OnRelease(this); 141 | OnRelease = null; 142 | } 143 | } 144 | } 145 | 146 | public void Reactivate(uint entityId) 147 | { 148 | this.entityId = entityId; 149 | isEnabled = true; 150 | } 151 | 152 | public int GetRetainCount() 153 | { 154 | return retainCount; 155 | } 156 | 157 | public void InternalDestroy() 158 | { 159 | isEnabled = false; 160 | 161 | RemoveAllComponents(); 162 | 163 | OnComponentAdded = null; 164 | OnComponentReplaced = null; 165 | OnComponentRemoved = null; 166 | OnDestroy = null; 167 | } 168 | 169 | public override bool Equals(object obj) 170 | { 171 | if (obj == null || obj.GetType() != GetType() || obj.GetHashCode() != GetHashCode()) 172 | { 173 | return false; 174 | } 175 | 176 | return true; 177 | } 178 | 179 | public override int GetHashCode() 180 | { 181 | return (int)GetUuid(); 182 | } 183 | 184 | private int GetContextComponentTypeId(int componentTypeId) 185 | { 186 | var index = ComponentTypeIdsPerContext.GetTypeId(contextId, componentTypeId); 187 | 188 | if (index == -1) 189 | { 190 | throw new System.Exception("Entity can't handle more component types"); 191 | } 192 | 193 | return index; 194 | } 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /Runtime/Entity/Entity.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7519e0594af0cc948a4928761b3183f0 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Entity/IEntity.cs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // Copyright (c) 2019-2025 Juan Delgado (@JuDelCo) 3 | 4 | using System.Collections.Generic; 5 | 6 | namespace Ju.ECS 7 | { 8 | public delegate void EntityComponentChangedEvent(IEntity entity, int componentTypeId); 9 | public delegate void EntityComponentReplacedEvent(IEntity entity, int componentTypeId); 10 | public delegate void EntityEvent(IEntity entity); 11 | 12 | public interface IEntity 13 | { 14 | event EntityComponentChangedEvent OnComponentAdded; 15 | event EntityComponentReplacedEvent OnComponentReplaced; 16 | event EntityComponentChangedEvent OnComponentRemoved; 17 | event EntityEvent OnRelease; 18 | event EntityEvent OnDestroy; 19 | 20 | void AddComponent(int componentTypeId, int componentPoolIndex, Stack unusedIndicesRef); 21 | void ReplaceComponent(int componentTypeId); 22 | void RemoveComponent(int componentTypeId); 23 | bool HasComponent(int componentTypeId); 24 | int GetComponentPoolIndex(int componentTypeId); 25 | 26 | int GetTotalComponents(); 27 | void RemoveAllComponents(); 28 | 29 | uint GetUuid(); 30 | uint GetEntityId(); 31 | bool IsEnabled(); 32 | void Destroy(); 33 | void Retain(); 34 | void Release(); 35 | 36 | void Reactivate(uint entityId); 37 | int GetRetainCount(); 38 | void InternalDestroy(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Runtime/Entity/IEntity.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 244df6cb8d5f1da4482af1ae823b7fb9 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Entity/IEntityExtensions.cs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // Copyright (c) 2019-2025 Juan Delgado (@JuDelCo) 3 | 4 | using System; 5 | using Ju.ECS.Internal; 6 | 7 | namespace Ju.ECS 8 | { 9 | public static class IEntityExtensions 10 | { 11 | public static IEntity Add(this IEntity entity, T component) where T : IComponent 12 | { 13 | var componentTypeId = ComponentLookup.Id; 14 | 15 | if (entity.HasComponent(componentTypeId)) 16 | { 17 | throw new Exception($"Entity already has a component of type {typeof(T).Name}"); 18 | } 19 | 20 | var componentPoolIndex = ComponentLookup.New(); 21 | ComponentLookup.Array[componentPoolIndex] = component; 22 | entity.AddComponent(componentTypeId, componentPoolIndex, ComponentLookup.UnusedIndices); 23 | 24 | return entity; 25 | } 26 | 27 | public static IEntity Replace(this IEntity entity, T component) where T : IComponent 28 | { 29 | var componentTypeId = ComponentLookup.Id; 30 | 31 | if (entity.HasComponent(componentTypeId)) 32 | { 33 | var componentPoolIndex = entity.GetComponentPoolIndex(componentTypeId); 34 | ComponentLookup.Array[componentPoolIndex] = component; 35 | entity.ReplaceComponent(componentTypeId); 36 | } 37 | else 38 | { 39 | entity.Add(component); 40 | } 41 | 42 | return entity; 43 | } 44 | 45 | public static IEntity Remove(this IEntity entity) where T : IComponent 46 | { 47 | var componentTypeId = ComponentLookup.Id; 48 | 49 | if (!entity.HasComponent(componentTypeId)) 50 | { 51 | throw new Exception($"Entity doesn't have a component of type {typeof(T).Name}"); 52 | } 53 | 54 | entity.RemoveComponent(componentTypeId); 55 | 56 | return entity; 57 | } 58 | 59 | public static bool Has(this IEntity entity) where T : IComponent 60 | { 61 | return entity.HasComponent(ComponentLookup.Id); 62 | } 63 | 64 | public static bool Has(this IEntity entity) where T1 : IComponent where T2 : IComponent 65 | { 66 | return entity.HasComponent(ComponentLookup.Id) && 67 | entity.HasComponent(ComponentLookup.Id); 68 | } 69 | 70 | public static bool Has(this IEntity entity) where T1 : IComponent where T2 : IComponent where T3 : IComponent 71 | { 72 | return entity.HasComponent(ComponentLookup.Id) && 73 | entity.HasComponent(ComponentLookup.Id) && 74 | entity.HasComponent(ComponentLookup.Id); 75 | } 76 | 77 | public static bool Has(this IEntity entity) where T1 : IComponent where T2 : IComponent where T3 : IComponent where T4 : IComponent 78 | { 79 | return entity.HasComponent(ComponentLookup.Id) && 80 | entity.HasComponent(ComponentLookup.Id) && 81 | entity.HasComponent(ComponentLookup.Id) && 82 | entity.HasComponent(ComponentLookup.Id); 83 | } 84 | 85 | public static T Get(this IEntity entity) where T : IComponent 86 | { 87 | var componentTypeId = ComponentLookup.Id; 88 | 89 | if (!entity.HasComponent(componentTypeId)) 90 | { 91 | throw new Exception($"Entity doesn't have a component of type {typeof(T).Name}"); 92 | } 93 | 94 | return ComponentLookup.Array[entity.GetComponentPoolIndex(componentTypeId)]; 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /Runtime/Entity/IEntityExtensions.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b6b8a753d1192e04395557d4cc42b87c 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Group.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a628d10deb010ca48ab598ece8c82916 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Runtime/Group/Group.cs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // Copyright (c) 2019-2025 Juan Delgado (@JuDelCo) 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | 7 | namespace Ju.ECS 8 | { 9 | public class Group : IGroup 10 | { 11 | public event GroupChangedEvent OnEntityAdded; 12 | public event GroupChangedEvent OnEntityRemoved; 13 | 14 | private readonly IMatcher matcher; 15 | private readonly List entities; 16 | private bool[] entitiesCheck; 17 | 18 | public Group(IMatcher matcher) 19 | { 20 | entitiesCheck = new bool[0]; 21 | entities = new List(); 22 | this.matcher = matcher; 23 | } 24 | 25 | public int GetCount() 26 | { 27 | return entities.Count; 28 | } 29 | 30 | public List GetEntities() 31 | { 32 | return entities; 33 | } 34 | 35 | public IEntity GetSingleEntity() 36 | { 37 | IEntity entity = null; 38 | 39 | if (entities.Count > 1) 40 | { 41 | throw new Exception($"The group doesn't have a single entity (count: {entities.Count})"); 42 | } 43 | else if (entities.Count == 1) 44 | { 45 | entity = entities[0]; 46 | } 47 | 48 | return entity; 49 | } 50 | 51 | public IMatcher GetMatcher() 52 | { 53 | return matcher; 54 | } 55 | 56 | public void HandleEntitySilently(IEntity entity) 57 | { 58 | if (entity.GetUuid() >= entitiesCheck.Length) 59 | { 60 | IncreaseCheckArray((int)entity.GetUuid()); 61 | } 62 | 63 | if (matcher.Matches(entity)) 64 | { 65 | if (!entitiesCheck[entity.GetUuid()]) 66 | { 67 | entitiesCheck[entity.GetUuid()] = true; 68 | entities.Add(entity); 69 | entity.Retain(); 70 | } 71 | } 72 | else 73 | { 74 | if (entitiesCheck[entity.GetUuid()]) 75 | { 76 | entitiesCheck[entity.GetUuid()] = false; 77 | entities.Remove(entity); 78 | entity.Release(); 79 | } 80 | } 81 | } 82 | 83 | public GroupChangedEvent HandleEntity(IEntity entity) 84 | { 85 | GroupChangedEvent groupEvent = null; 86 | 87 | if (entity.GetUuid() >= entitiesCheck.Length) 88 | { 89 | IncreaseCheckArray((int)entity.GetUuid()); 90 | } 91 | 92 | if (matcher.Matches(entity)) 93 | { 94 | if (!entitiesCheck[entity.GetUuid()]) 95 | { 96 | entitiesCheck[entity.GetUuid()] = true; 97 | entities.Add(entity); 98 | entity.Retain(); 99 | groupEvent = OnEntityAdded; 100 | } 101 | } 102 | else 103 | { 104 | if (entitiesCheck[entity.GetUuid()]) 105 | { 106 | entitiesCheck[entity.GetUuid()] = false; 107 | entities.Remove(entity); 108 | entity.Release(); 109 | groupEvent = OnEntityRemoved; 110 | } 111 | } 112 | 113 | return groupEvent; 114 | } 115 | 116 | public void UpdateEntity(IEntity entity) 117 | { 118 | if (entity.GetUuid() >= entitiesCheck.Length) 119 | { 120 | IncreaseCheckArray((int)entity.GetUuid()); 121 | } 122 | 123 | if (entitiesCheck[entity.GetUuid()]) 124 | { 125 | if (OnEntityRemoved != null) 126 | { 127 | OnEntityRemoved(this, entity); 128 | } 129 | 130 | if (OnEntityAdded != null) 131 | { 132 | OnEntityAdded(this, entity); 133 | } 134 | } 135 | } 136 | 137 | private void IncreaseCheckArray(int target) 138 | { 139 | int inc = 10000; 140 | target = (target / inc) * inc + ((target % inc) > (inc / 2) ? (inc * 2) : inc); 141 | 142 | var newEntitiesCheck = new bool[target]; 143 | Array.Copy(entitiesCheck, newEntitiesCheck, entitiesCheck.Length); 144 | entitiesCheck = newEntitiesCheck; 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /Runtime/Group/Group.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 75613cf47306af64ca4e2308a3ee2571 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Group/IGroup.cs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // Copyright (c) 2019-2025 Juan Delgado (@JuDelCo) 3 | 4 | using System.Collections.Generic; 5 | 6 | namespace Ju.ECS 7 | { 8 | public delegate void GroupChangedEvent(IGroup group, IEntity entity); 9 | public delegate void GroupUpdatedEvent(IGroup group, IEntity entity); 10 | 11 | public interface IGroup 12 | { 13 | event GroupChangedEvent OnEntityAdded; 14 | event GroupChangedEvent OnEntityRemoved; 15 | 16 | int GetCount(); 17 | 18 | List GetEntities(); 19 | IEntity GetSingleEntity(); 20 | 21 | IMatcher GetMatcher(); 22 | 23 | void HandleEntitySilently(IEntity entity); 24 | GroupChangedEvent HandleEntity(IEntity entity); 25 | void UpdateEntity(IEntity entity); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Runtime/Group/IGroup.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7460c01adf0507c479d7cb30a1871a76 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Group/IGroupExtensions.cs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // Copyright (c) 2019-2025 Juan Delgado (@JuDelCo) 3 | 4 | namespace Ju.ECS 5 | { 6 | public static class IGroupExtensions 7 | { 8 | public static ICollector CreateCollector(this IGroup group, GroupEvent groupEvent = GroupEvent.Added) 9 | { 10 | return new Collector(group, groupEvent); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Runtime/Group/IGroupExtensions.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d8335c97d8dc2b044aad0c97b49b587f 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/JuDelCo.Core.ECS.Runtime.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "JuDelCo.Core.ECS.Runtime", 3 | "references": [], 4 | "includePlatforms": [], 5 | "excludePlatforms": [], 6 | "allowUnsafeCode": false, 7 | "overrideReferences": false, 8 | "precompiledReferences": [], 9 | "autoReferenced": true, 10 | "defineConstraints": [], 11 | "versionDefines": [], 12 | "noEngineReferences": false 13 | } -------------------------------------------------------------------------------- /Runtime/JuDelCo.Core.ECS.Runtime.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a85414737791cff44a6aff99378180f9 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Runtime/Matcher.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7ce979ff979a2f44aa862d1bd4b86faf 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Runtime/Matcher/IMatcher.cs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // Copyright (c) 2019-2025 Juan Delgado (@JuDelCo) 3 | 4 | using System.Collections.Generic; 5 | 6 | namespace Ju.ECS 7 | { 8 | public interface IMatcher 9 | { 10 | bool Matches(IEntity entity); 11 | IMatcher AllOf(List componentTypeIds); 12 | IMatcher AnyOf(List componentTypeIds); 13 | IMatcher NoneOf(List componentTypeIds); 14 | List GetTypes(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Runtime/Matcher/IMatcher.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7f70d2d6b7c741549b7a196481b1fb9b 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Matcher/Matcher.cs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // Copyright (c) 2019-2025 Juan Delgado (@JuDelCo) 3 | 4 | using System.Collections.Generic; 5 | 6 | namespace Ju.ECS 7 | { 8 | public partial class Matcher : IMatcher 9 | { 10 | private readonly List allOfTypes; 11 | private readonly List anyOfTypes; 12 | private readonly List noneOfTypes; 13 | private readonly List allTypes; 14 | 15 | private bool isHashCached = false; 16 | private int cachedHash = 0; 17 | 18 | private Matcher() 19 | { 20 | allOfTypes = new List(); 21 | anyOfTypes = new List(); 22 | noneOfTypes = new List(); 23 | allTypes = new List(); 24 | } 25 | 26 | public Matcher(List allOf, List anyOf, List noneOf) : this() 27 | { 28 | AddTypes(allOfTypes, allOf); 29 | AddTypes(anyOfTypes, anyOf); 30 | AddTypes(noneOfTypes, noneOf); 31 | } 32 | 33 | public bool Matches(IEntity entity) 34 | { 35 | bool result = true; 36 | 37 | for (int i = (allOfTypes.Count - 1); i >= 0; --i) 38 | { 39 | if (!entity.HasComponent(allOfTypes[i])) 40 | { 41 | result = false; 42 | break; 43 | } 44 | } 45 | 46 | if (anyOfTypes.Count > 0) 47 | { 48 | var found = false; 49 | 50 | for (int i = (anyOfTypes.Count - 1); i >= 0; --i) 51 | { 52 | if (entity.HasComponent(anyOfTypes[i])) 53 | { 54 | found = true; 55 | break; 56 | } 57 | } 58 | 59 | if (found == false) 60 | { 61 | result = false; 62 | } 63 | } 64 | 65 | for (int i = (noneOfTypes.Count - 1); i >= 0; --i) 66 | { 67 | if (entity.HasComponent(noneOfTypes[i])) 68 | { 69 | result = false; 70 | break; 71 | } 72 | } 73 | 74 | return result; 75 | } 76 | 77 | public IMatcher AllOf(List componentTypeIds) 78 | { 79 | AddTypes(allOfTypes, componentTypeIds); 80 | return this; 81 | } 82 | 83 | public IMatcher AnyOf(List componentTypeIds) 84 | { 85 | AddTypes(anyOfTypes, componentTypeIds); 86 | return this; 87 | } 88 | 89 | public IMatcher NoneOf(List componentTypeIds) 90 | { 91 | AddTypes(noneOfTypes, componentTypeIds); 92 | return this; 93 | } 94 | 95 | public List GetTypes() 96 | { 97 | return allTypes; 98 | } 99 | 100 | private void AddTypes(List target, List types) 101 | { 102 | if (types != null) 103 | { 104 | for (int i = 0, count = types.Count; i < count; ++i) 105 | { 106 | int type = types[i]; 107 | 108 | if (!target.Contains(type)) 109 | { 110 | target.Add(type); 111 | 112 | if (!allTypes.Contains(type)) 113 | { 114 | allTypes.Add(type); 115 | } 116 | } 117 | } 118 | } 119 | } 120 | 121 | public override bool Equals(object obj) 122 | { 123 | if (obj == null || obj.GetType() != GetType() || obj.GetHashCode() != GetHashCode()) 124 | { 125 | return false; 126 | } 127 | 128 | return true; 129 | } 130 | 131 | public override int GetHashCode() 132 | { 133 | if (!isHashCached) 134 | { 135 | unchecked 136 | { 137 | cachedHash = 19; 138 | 139 | for (int i = (allTypes.Count - 1); i >= 0; --i) 140 | { 141 | cachedHash = cachedHash * 31 + allTypes[i]; 142 | } 143 | } 144 | 145 | isHashCached = true; 146 | } 147 | 148 | return cachedHash; 149 | } 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /Runtime/Matcher/Matcher.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 995addd5693e44d4eb4d3d469135797f 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Matcher/MatcherExtensions.cs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // Copyright (c) 2019-2025 Juan Delgado (@JuDelCo) 3 | 4 | using System.Collections.Generic; 5 | using Ju.ECS.Internal; 6 | 7 | namespace Ju.ECS 8 | { 9 | public partial class Matcher : IMatcher 10 | { 11 | public IMatcher AllOf() where T : IComponent 12 | { 13 | AddTypes(allOfTypes, new List() { ComponentLookup.Id }); 14 | return this; 15 | } 16 | 17 | public IMatcher AllOf() where T1 : IComponent where T2 : IComponent 18 | { 19 | AddTypes(allOfTypes, new List() { ComponentLookup.Id, ComponentLookup.Id }); 20 | return this; 21 | } 22 | 23 | public IMatcher AllOf() where T1 : IComponent where T2 : IComponent where T3 : IComponent 24 | { 25 | AddTypes(allOfTypes, new List() { ComponentLookup.Id, ComponentLookup.Id, ComponentLookup.Id }); 26 | return this; 27 | } 28 | 29 | public IMatcher AllOf() where T1 : IComponent where T2 : IComponent where T3 : IComponent where T4 : IComponent 30 | { 31 | AddTypes(allOfTypes, new List() { ComponentLookup.Id, ComponentLookup.Id, ComponentLookup.Id, ComponentLookup.Id }); 32 | return this; 33 | } 34 | 35 | public IMatcher AnyOf() where T : IComponent 36 | { 37 | AddTypes(anyOfTypes, new List() { ComponentLookup.Id }); 38 | return this; 39 | } 40 | 41 | public IMatcher AnyOf() where T1 : IComponent where T2 : IComponent 42 | { 43 | AddTypes(anyOfTypes, new List() { ComponentLookup.Id, ComponentLookup.Id }); 44 | return this; 45 | } 46 | 47 | public IMatcher AnyOf() where T1 : IComponent where T2 : IComponent where T3 : IComponent 48 | { 49 | AddTypes(anyOfTypes, new List() { ComponentLookup.Id, ComponentLookup.Id, ComponentLookup.Id }); 50 | return this; 51 | } 52 | 53 | public IMatcher AnyOf() where T1 : IComponent where T2 : IComponent where T3 : IComponent where T4 : IComponent 54 | { 55 | AddTypes(anyOfTypes, new List() { ComponentLookup.Id, ComponentLookup.Id, ComponentLookup.Id, ComponentLookup.Id }); 56 | return this; 57 | } 58 | 59 | public IMatcher NoneOf() where T : IComponent 60 | { 61 | AddTypes(noneOfTypes, new List() { ComponentLookup.Id }); 62 | return this; 63 | } 64 | 65 | public IMatcher NoneOf() where T1 : IComponent where T2 : IComponent 66 | { 67 | AddTypes(noneOfTypes, new List() { ComponentLookup.Id, ComponentLookup.Id }); 68 | return this; 69 | } 70 | 71 | public IMatcher NoneOf() where T1 : IComponent where T2 : IComponent where T3 : IComponent 72 | { 73 | AddTypes(noneOfTypes, new List() { ComponentLookup.Id, ComponentLookup.Id, ComponentLookup.Id }); 74 | return this; 75 | } 76 | 77 | public IMatcher NoneOf() where T1 : IComponent where T2 : IComponent where T3 : IComponent where T4 : IComponent 78 | { 79 | AddTypes(noneOfTypes, new List() { ComponentLookup.Id, ComponentLookup.Id, ComponentLookup.Id, ComponentLookup.Id }); 80 | return this; 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /Runtime/Matcher/MatcherExtensions.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7e1df934566047a488cb88aa3b681262 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/Matcher/MatcherGenerator.cs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // Copyright (c) 2019-2025 Juan Delgado (@JuDelCo) 3 | 4 | using System.Collections.Generic; 5 | using Ju.ECS.Internal; 6 | 7 | namespace Ju.ECS 8 | { 9 | public static class MatcherGenerator 10 | { 11 | public static IMatcher AllOf() where T : IComponent 12 | { 13 | var types = new List() { ComponentLookup.Id }; 14 | return new Matcher(types, null, null); 15 | } 16 | 17 | public static IMatcher AllOf() where T1 : IComponent where T2 : IComponent 18 | { 19 | var types = new List() { ComponentLookup.Id, ComponentLookup.Id }; 20 | return new Matcher(types, null, null); 21 | } 22 | 23 | public static IMatcher AllOf() where T1 : IComponent where T2 : IComponent where T3 : IComponent 24 | { 25 | var types = new List() { ComponentLookup.Id, ComponentLookup.Id, ComponentLookup.Id }; 26 | return new Matcher(types, null, null); 27 | } 28 | 29 | public static IMatcher AllOf() where T1 : IComponent where T2 : IComponent where T3 : IComponent where T4 : IComponent 30 | { 31 | var types = new List() { ComponentLookup.Id, ComponentLookup.Id, ComponentLookup.Id, ComponentLookup.Id }; 32 | return new Matcher(types, null, null); 33 | } 34 | 35 | public static IMatcher AnyOf() where T : IComponent 36 | { 37 | var types = new List() { ComponentLookup.Id }; 38 | return new Matcher(null, types, null); 39 | } 40 | 41 | public static IMatcher AnyOf() where T1 : IComponent where T2 : IComponent 42 | { 43 | var types = new List() { ComponentLookup.Id, ComponentLookup.Id }; 44 | return new Matcher(null, types, null); 45 | } 46 | 47 | public static IMatcher AnyOf() where T1 : IComponent where T2 : IComponent where T3 : IComponent 48 | { 49 | var types = new List() { ComponentLookup.Id, ComponentLookup.Id, ComponentLookup.Id }; 50 | return new Matcher(null, types, null); 51 | } 52 | 53 | public static IMatcher AnyOf() where T1 : IComponent where T2 : IComponent where T3 : IComponent where T4 : IComponent 54 | { 55 | var types = new List() { ComponentLookup.Id, ComponentLookup.Id, ComponentLookup.Id, ComponentLookup.Id }; 56 | return new Matcher(null, types, null); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Runtime/Matcher/MatcherGenerator.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 343ef87f658ca074f85f2b1c58090526 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/System.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 3d4fa443a40d13b428874309049fd3ad 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Runtime/System/ISystem.cs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // Copyright (c) 2019-2025 Juan Delgado (@JuDelCo) 3 | 4 | namespace Ju.ECS 5 | { 6 | public interface ISystem 7 | { 8 | } 9 | 10 | public interface IInitializeSystem : ISystem 11 | { 12 | void Initialize(); 13 | } 14 | 15 | public interface IExecuteSystem : ISystem 16 | { 17 | void Execute(); 18 | } 19 | 20 | public interface IReactiveSystem : IExecuteSystem 21 | { 22 | void Activate(); 23 | void Deactivate(); 24 | void Clear(); 25 | } 26 | 27 | public interface ICleanupSystem : ISystem 28 | { 29 | void Cleanup(); 30 | } 31 | 32 | public interface ITearDownSystem : ISystem 33 | { 34 | void TearDown(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Runtime/System/ISystem.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 8e41ac00d18f7b544aa43eb719d42ecb 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/System/ReactiveSystem.cs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // Copyright (c) 2019-2025 Juan Delgado (@JuDelCo) 3 | 4 | using System.Collections.Generic; 5 | 6 | namespace Ju.ECS 7 | { 8 | public abstract class ReactiveSystem : IReactiveSystem 9 | { 10 | private readonly ICollector collector; 11 | private readonly List entities; 12 | 13 | public ReactiveSystem(IContext context) 14 | { 15 | entities = new List(); 16 | collector = GetTrigger(context); 17 | } 18 | 19 | ~ReactiveSystem() 20 | { 21 | Deactivate(); 22 | } 23 | 24 | protected abstract ICollector GetTrigger(IContext context); 25 | protected abstract bool Filter(IEntity entity); 26 | protected abstract void Execute(List entities); 27 | 28 | public void Activate() 29 | { 30 | collector.Activate(); 31 | } 32 | 33 | public void Deactivate() 34 | { 35 | collector.Deactivate(); 36 | } 37 | 38 | public void Clear() 39 | { 40 | collector.ClearCollectedEntities(); 41 | } 42 | 43 | public void Execute() 44 | { 45 | if (collector.GetCount() != 0) 46 | { 47 | var collectedEntities = collector.GetCollectedEntities(); 48 | 49 | for (int i = (collectedEntities.Count - 1); i >= 0; --i) 50 | { 51 | if (Filter(collectedEntities[i])) 52 | { 53 | entities.Add(collectedEntities[i]); 54 | collectedEntities[i].Retain(); 55 | } 56 | } 57 | 58 | collector.ClearCollectedEntities(); 59 | 60 | if (entities.Count != 0) 61 | { 62 | try 63 | { 64 | Execute(entities); 65 | } 66 | finally 67 | { 68 | for (int i = (entities.Count - 1); i >= 0; --i) 69 | { 70 | entities[i].Release(); 71 | } 72 | 73 | entities.Clear(); 74 | } 75 | } 76 | } 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Runtime/System/ReactiveSystem.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e06ba0f2ac193a94bad77a30d4e34aac 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/System/Systems.cs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // Copyright (c) 2019-2025 Juan Delgado (@JuDelCo) 3 | 4 | using System.Collections.Generic; 5 | 6 | namespace Ju.ECS 7 | { 8 | public class Systems : IInitializeSystem, IExecuteSystem, ICleanupSystem, ITearDownSystem 9 | { 10 | private readonly List initializeSystems; 11 | private readonly List executeSystems; 12 | private readonly List cleanupSystems; 13 | private readonly List tearDownSystems; 14 | 15 | public Systems() 16 | { 17 | initializeSystems = new List(); 18 | executeSystems = new List(); 19 | cleanupSystems = new List(); 20 | tearDownSystems = new List(); 21 | } 22 | 23 | public Systems Add(ISystem system) 24 | { 25 | if (system is IInitializeSystem initializeSystem) 26 | { 27 | initializeSystems.Add(initializeSystem); 28 | } 29 | 30 | if (system is IExecuteSystem executeSystem) 31 | { 32 | executeSystems.Add(executeSystem); 33 | } 34 | 35 | if (system is ICleanupSystem cleanupSystem) 36 | { 37 | cleanupSystems.Add(cleanupSystem); 38 | } 39 | 40 | if (system is ITearDownSystem tearDownSystem) 41 | { 42 | tearDownSystems.Add(tearDownSystem); 43 | } 44 | 45 | return this; 46 | } 47 | 48 | public void Initialize() 49 | { 50 | for (int i = 0, count = initializeSystems.Count; i < count; ++i) 51 | { 52 | initializeSystems[i].Initialize(); 53 | } 54 | } 55 | 56 | public void Execute() 57 | { 58 | for (int i = 0, count = executeSystems.Count; i < count; ++i) 59 | { 60 | executeSystems[i].Execute(); 61 | } 62 | } 63 | 64 | public void Cleanup() 65 | { 66 | for (int i = 0, count = cleanupSystems.Count; i < count; ++i) 67 | { 68 | cleanupSystems[i].Cleanup(); 69 | } 70 | } 71 | 72 | public void TearDown() 73 | { 74 | for (int i = 0, count = tearDownSystems.Count; i < count; ++i) 75 | { 76 | tearDownSystems[i].TearDown(); 77 | } 78 | } 79 | 80 | public void ActivateReactiveSystems() 81 | { 82 | for (int i = 0, count = executeSystems.Count; i < count; ++i) 83 | { 84 | var system = executeSystems[i]; 85 | 86 | if (system is IReactiveSystem reactiveSystem) 87 | { 88 | reactiveSystem.Activate(); 89 | } 90 | 91 | if (system is Systems systems) 92 | { 93 | systems.ActivateReactiveSystems(); 94 | } 95 | } 96 | } 97 | 98 | public void DeactivateReactiveSystems() 99 | { 100 | for (int i = 0, count = executeSystems.Count; i < count; ++i) 101 | { 102 | var system = executeSystems[i]; 103 | 104 | if (system is IReactiveSystem reactiveSystem) 105 | { 106 | reactiveSystem.Deactivate(); 107 | } 108 | 109 | if (system is Systems systems) 110 | { 111 | systems.DeactivateReactiveSystems(); 112 | } 113 | } 114 | } 115 | 116 | public void ClearReactiveSystems() 117 | { 118 | for (int i = 0, count = executeSystems.Count; i < count; ++i) 119 | { 120 | var system = executeSystems[i]; 121 | 122 | if (system is IReactiveSystem reactiveSystem) 123 | { 124 | reactiveSystem.Clear(); 125 | } 126 | 127 | if (system is Systems systems) 128 | { 129 | systems.ClearReactiveSystems(); 130 | } 131 | } 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /Runtime/System/Systems.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7ae311d9c95a4014e8b1e6d3e315ac37 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /build.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 486ee2682b32eab4e8182d492e62d7a5 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /build/JuDelCo.Lib.CoreECS.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /build/JuDelCo.Lib.CoreECS.targets.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9a9777a8d1d5da84fb26c92990b11cf7 3 | DefaultImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.judelco.core.ecs", 3 | "displayName": "JuCore ECS", 4 | "description": "Deterministic lightweight ECS framework", 5 | "version": "1.14.0", 6 | "unity": "2018.3", 7 | "dependencies": {}, 8 | "type": "library", 9 | "license": "MIT", 10 | "keywords": [ 11 | "core", 12 | "jucore", 13 | "ecs", 14 | "entitycomponentsystem", 15 | "entity", 16 | "component", 17 | "system", 18 | "deterministic", 19 | "lightweight", 20 | "gamedev", 21 | "framework", 22 | "simulation", 23 | "performance" 24 | ], 25 | "author": { 26 | "name": "JuDelCo", 27 | "url": "https://twitter.com/JuDelCo" 28 | }, 29 | "repository": { 30 | "type": "git", 31 | "url": "git+https://github.com/JuDelCo/CoreECS.git" 32 | } 33 | } -------------------------------------------------------------------------------- /package.json.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 63475f7d4f81f4947b432b64dd15cf02 3 | PackageManifestImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | --------------------------------------------------------------------------------