├── .gitignore ├── src ├── helpers.cs.meta ├── systems.cs.meta ├── helpers.cs └── systems.cs ├── LICENSE.md.meta ├── README.md.meta ├── package.json.meta ├── src.meta ├── Leopotam.EcsLite.Threads.asmdef.meta ├── Leopotam.EcsLite.Threads.asmdef ├── package.json ├── LICENSE.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | *.sln 2 | .vscode 3 | .idea 4 | .DS_Store 5 | bin 6 | obj 7 | Library 8 | Temp 9 | -------------------------------------------------------------------------------- /src/helpers.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0a4a340407394ac09f28be4632c41a91 3 | timeCreated: 1621180243 -------------------------------------------------------------------------------- /src/systems.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 6042f5acd720e4d5bbe7be15057e3802 3 | timeCreated: 1620938498 -------------------------------------------------------------------------------- /LICENSE.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0ad93e15d7ab54fd5b84b53996cdcf31 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /README.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: cb0b75e98411d47cbbfa7fc9ef965613 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /package.json.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e4ccf7a1c9b8f4ccfacf94c912bfa487 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /src.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: af5c7d78f74dd48258cb6d28a5d7fda5 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Leopotam.EcsLite.Threads.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a758ff47af0484038b71625d05877e22 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Leopotam.EcsLite.Threads.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Leopotam.EcsLite.Threads", 3 | "rootNamespace": "Leopotam.EcsLite.Threads", 4 | "references": [ 5 | "Leopotam.EcsLite" 6 | ], 7 | "includePlatforms": [], 8 | "excludePlatforms": [], 9 | "allowUnsafeCode": false, 10 | "overrideReferences": false, 11 | "precompiledReferences": [], 12 | "autoReferenced": true, 13 | "defineConstraints": [], 14 | "versionDefines": [], 15 | "noEngineReferences": false 16 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.leopotam.ecslite.threads", 3 | "author": "Leopotam", 4 | "displayName": "LeoECS Lite Threads", 5 | "description": "LeoECS Lite Threads - Поддержка многопоточной обработки.", 6 | "unity": "2020.3", 7 | "version": "2025.4.22", 8 | "keywords": [ 9 | "leoecslite", 10 | "leoecs", 11 | "ecs", 12 | "performance", 13 | "threads" 14 | ], 15 | "dependencies": {}, 16 | "repository": { 17 | "type": "git", 18 | "url": "https://github.com/Leopotam/ecslite-threads.git" 19 | } 20 | } -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012-2025 leopotam@yandex.ru 2 | 3 | Данное программное обеспечение и сопутствующая документация (далее - Продукт) 4 | выпускается под MIT-Red лицензией. 5 | 6 | MIT-Red регулируется совокупностью следующих правил, если хотя бы 7 | одно из них невыполнимо - использование Продукта запрещено: 8 | 9 | Если вы за применение opensource программного обеспечения в 10 | военной сфере - вы не можете использовать этот Продукт. 11 | 12 | Если вы испытываете ненависть к русским или поддерживаете 13 | любые нападки на них - вы не можете использовать этот Продукт. 14 | 15 | Данная лицензия разрешает лицам, получившим копию данного Продукта, 16 | безвозмездно использовать Программное обеспечение без ограничений, включая 17 | неограниченное право на использование, копирование, изменение, слияние, 18 | публикацию и распространение копий Продукта. 19 | 20 | Указанное выше уведомление об авторском праве и данные условия должны быть 21 | включены во все копии или значимые части данного Продукта. 22 | 23 | ДАННЫЙ ПРОДУКТ ПРЕДОСТАВЛЯЕТСЯ «КАК ЕСТЬ», БЕЗ КАКИХ-ЛИБО ГАРАНТИЙ, ЯВНО 24 | ВЫРАЖЕННЫХ ИЛИ ПОДРАЗУМЕВАЕМЫХ, ВКЛЮЧАЯ ГАРАНТИИ ТОВАРНОЙ ПРИГОДНОСТИ, 25 | СООТВЕТСТВИЯ ПО ЕГО КОНКРЕТНОМУ НАЗНАЧЕНИЮ И ОТСУТСТВИЯ НАРУШЕНИЙ, НО 26 | НЕ ОГРАНИЧИВАЯСЬ ИМИ. НИ В КАКОМ СЛУЧАЕ АВТОРЫ ИЛИ ПРАВООБЛАДАТЕЛИ НЕ НЕСУТ 27 | ОТВЕТСТВЕННОСТИ ПО КАКИМ-ЛИБО ИСКАМ, ЗА УЩЕРБ ИЛИ ПО ИНЫМ ТРЕБОВАНИЯМ, 28 | В ТОМ ЧИСЛЕ, ПРИ ДЕЙСТВИИ КОНТРАКТА, ДЕЛИКТЕ ИЛИ ИНОЙ СИТУАЦИИ, ВОЗНИКШИМ 29 | ИЗ-ЗА ИСПОЛЬЗОВАНИЯ ПРОГРАММНОГО ОБЕСПЕЧЕНИЯ ИЛИ ИНЫХ ДЕЙСТВИЙ С ПРОДУКТОМ. -------------------------------------------------------------------------------- /src/helpers.cs: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // The MIT-Red License 3 | // Copyright (c) 2012-2025 Leopotam 4 | // ---------------------------------------------------------------------------- 5 | 6 | using System; 7 | using System.Threading; 8 | 9 | namespace Leopotam.EcsLite.Threads { 10 | static class ThreadService { 11 | static ThreadDesc[] _descs; 12 | static ThreadWorkerHandler _task; 13 | static readonly int DescsCount; 14 | 15 | static ThreadService () { 16 | DescsCount = Environment.ProcessorCount; 17 | _descs = new ThreadDesc[DescsCount]; 18 | for (var i = 0; i < _descs.Length; i++) { 19 | ref var desc = ref _descs[i]; 20 | desc.Thread = new Thread (ThreadProc) { IsBackground = true }; 21 | desc.HasWork = new ManualResetEvent (false); 22 | desc.WorkDone = new ManualResetEvent (true); 23 | desc.Thread.Start (i); 24 | } 25 | } 26 | 27 | public static void Run (ThreadWorkerHandler worker, int count, int chunkSize) { 28 | #if DEBUG && !LEOECSLITE_NO_SANITIZE_CHECKS 29 | if (_task != null) { throw new Exception ("Calls from multiple threads not supported."); } 30 | #endif 31 | if (count <= 0 || chunkSize <= 0) { 32 | return; 33 | } 34 | _task = worker; 35 | // _task = task.Execute; 36 | var processed = 0; 37 | var jobSize = count / DescsCount; 38 | int workersCount; 39 | if (jobSize >= chunkSize) { 40 | workersCount = DescsCount; 41 | } else { 42 | workersCount = count / chunkSize; 43 | jobSize = chunkSize; 44 | } 45 | if (workersCount <= 0) { 46 | workersCount = 1; 47 | } 48 | for (int i = 0, iMax = workersCount - 1; i < iMax; i++) { 49 | ref var desc = ref _descs[i]; 50 | desc.FromIndex = processed; 51 | processed += jobSize; 52 | desc.BeforeIndex = processed; 53 | desc.WorkDone.Reset (); 54 | desc.HasWork.Set (); 55 | } 56 | ref var lastDesc = ref _descs[workersCount - 1]; 57 | lastDesc.FromIndex = processed; 58 | lastDesc.BeforeIndex = count; 59 | lastDesc.WorkDone.Reset (); 60 | lastDesc.HasWork.Set (); 61 | 62 | for (int i = 0, iMax = workersCount; i < iMax; i++) { 63 | _descs[i].WorkDone.WaitOne (); 64 | } 65 | _task = null; 66 | } 67 | 68 | static void ThreadProc (object raw) { 69 | var threadId = (int) raw; 70 | ref var desc = ref _descs[threadId]; 71 | try { 72 | while (Thread.CurrentThread.IsAlive) { 73 | desc.HasWork.WaitOne (); 74 | desc.HasWork.Reset (); 75 | _task.Invoke (threadId, desc.FromIndex, desc.BeforeIndex); 76 | desc.WorkDone.Set (); 77 | } 78 | } catch { 79 | // ignored 80 | } 81 | } 82 | 83 | struct ThreadDesc { 84 | public Thread Thread; 85 | public ManualResetEvent HasWork; 86 | public ManualResetEvent WorkDone; 87 | public int FromIndex; 88 | public int BeforeIndex; 89 | } 90 | } 91 | 92 | delegate void ThreadWorkerHandler (int threadId, int fromIndex, int beforeIndex); 93 | } 94 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LeoECS Lite Threads - Поддержка многопоточной обработки 2 | Поддержка обработки сущностей в несколько системных потоков. 3 | 4 | > **ВАЖНО!** АКТИВНАЯ РАЗРАБОТКА ПРЕКРАЩЕНА, ВОЗМОЖНО ТОЛЬКО ИСПРАВЛЕНИЕ ОБНАРУЖЕННЫХ ОШИБОК. СОСТОЯНИЕ СТАБИЛЬНОЕ, ИЗВЕСТНЫХ ОШИБОК НЕ ОБНАРУЖЕНО. ЗА НОВЫМ ПОКОЛЕНИЕМ ФРЕЙМВОРКА СТОИТ СЛЕДИТЬ В БЛОГЕ https://leopotam.ru/ 5 | 6 | > **ВАЖНО!** ПОДДЕРЖКА БУДЕТ ПРЕКРАЩЕНА И РЕПОЗИТОРИЙ БУДЕТ ОТПРАВЛЕН В АРХИВ 22.04.2026. 7 | 8 | > Проверено на Unity 2020.3 (не зависит от Unity) и содержит asmdef-описания для компиляции в виде отдельных сборок и уменьшения времени рекомпиляции основного проекта. 9 | 10 | # Содержание 11 | * [Социальные ресурсы](#Социальные-ресурсы) 12 | * [Установка](#Установка) 13 | * [В виде unity модуля](#В-виде-unity-модуля) 14 | * [В виде исходников](#В-виде-исходников) 15 | * [Пример использования](#Пример-использования) 16 | * [Component](#Component) 17 | * [ThreadSystem](#ThreadSystem) 18 | * [Thread](#Thread) 19 | * [Лицензия](#Лицензия) 20 | 21 | # Социальные ресурсы 22 | [Блог разработчика](https://leopotam.ru/) 23 | 24 | # Установка 25 | 26 | ## В виде unity модуля 27 | Поддерживается установка в виде unity-модуля через git-ссылку в PackageManager или прямое редактирование `Packages/manifest.json`: 28 | ``` 29 | "com.leopotam.ecslite.threads": "https://github.com/Leopotam/ecslite-threads.git", 30 | ``` 31 | По умолчанию используется последняя релизная версия. Если требуется версия "в разработке" с актуальными изменениями - следует переключиться на ветку `develop`: 32 | ``` 33 | "com.leopotam.ecslite.threads": "https://github.com/Leopotam/ecslite-threads.git#develop", 34 | ``` 35 | 36 | ## В виде исходников 37 | Код так же может быть склонирован или получен в виде архива со страницы релизов. 38 | 39 | # Пример использования 40 | 41 | ## Component 42 | ```c# 43 | struct C1 { 44 | public int Id; 45 | } 46 | ``` 47 | ## ThreadSystem 48 | ```c# 49 | class TestThreadSystem : EcsThreadSystem { 50 | protected override int GetChunkSize (IEcsSystems systems) { 51 | // Минимальное количество сущностей, после которого 52 | // произойдет разделение обработки на новый поток. 53 | // Этот метод вызывается каждый цикл обновления. 54 | return 1000; 55 | } 56 | 57 | protected override EcsWorld GetWorld (IEcsSystems systems) { 58 | // Мир, в котором содержатся фильтры и компонентные пулы. 59 | return systems.GetWorld (); 60 | } 61 | 62 | protected override EcsFilter GetFilter (EcsWorld world) { 63 | // Фильтр, сущности из которого будут обрабатываться. 64 | return world.Filter ().End (); 65 | } 66 | 67 | // Дополнительная (опциональная) инициализация данных, 68 | // которые необходимы для расчетов в потоках. 69 | protected override void SetData (IEcsSystems systems, ref TestThread thread) { 70 | thread.DeltaTime = Time.deltaTime; 71 | } 72 | } 73 | ``` 74 | > **ВАЖНО!** EcsThreadSystem поддерживает до 4 типов компонентов. 75 | 76 | ## Thread 77 | ```c# 78 | struct TestThread : IEcsThread { 79 | public float DeltaTime; 80 | int[] _entities; 81 | C1[] _pool1; 82 | int[] _indices1; 83 | 84 | public void Init (int[] entities, C1[] pool1, int[] indices1) { 85 | // Сохранение массива сущностей. 86 | _entities = entities; 87 | // Сохранение dense-массива пула компонентов. 88 | _pool1 = pool1; 89 | // Сохранение sparse-массива пула компонентов. 90 | _indices1 = indices1; 91 | } 92 | 93 | public void Execute (int threadId, int fromIndex, int beforeIndex) { 94 | for (int i = fromIndex; i < beforeIndex; i++) { 95 | var e = _entities[i]; 96 | ref var c1 = ref _pool1[_indices1[e]]; 97 | c1.Id = (c1.Id + 1) % 10000; 98 | } 99 | } 100 | } 101 | ``` 102 | > **ВАЖНО!** Внутри обработчика **запрещено** изменять состояние мира: нельзя создавать / удалять сущности, нельзя добавлять / удалять компоненты на сущности. Допускается только модификация данных внутри существующих компонентов. 103 | 104 | # Лицензия 105 | Пакет выпускается под [MIT-Red лицензией](./LICENSE.md). 106 | 107 | В случаях лицензирования по условиям MIT-Red не стоит расчитывать на 108 | персональные консультации или какие-либо гарантии. -------------------------------------------------------------------------------- /src/systems.cs: -------------------------------------------------------------------------------- 1 | // ---------------------------------------------------------------------------- 2 | // The MIT-Red License 3 | // Copyright (c) 2012-2025 Leopotam 4 | // ---------------------------------------------------------------------------- 5 | 6 | namespace Leopotam.EcsLite.Threads { 7 | public abstract class EcsThreadSystem : EcsThreadSystemBase, IEcsRunSystem 8 | where TThread : struct, IEcsThread 9 | where T1 : struct { 10 | EcsFilter _filter; 11 | EcsPool _pool1; 12 | TThread _thread; 13 | ThreadWorkerHandler _worker; 14 | 15 | public void Run (IEcsSystems systems) { 16 | if (_filter == null) { 17 | var world = GetWorld (systems); 18 | _pool1 = world.GetPool (); 19 | _filter = GetFilter (world); 20 | _thread = new TThread (); 21 | _worker = Execute; 22 | } 23 | _thread.Init ( 24 | _filter.GetRawEntities (), 25 | _pool1.GetRawDenseItems (), _pool1.GetRawSparseItems ()); 26 | SetData (systems, ref _thread); 27 | ThreadService.Run (_worker, _filter.GetEntitiesCount (), GetChunkSize (systems)); 28 | } 29 | 30 | void Execute (int threadId, int fromIndex, int beforeIndex) { 31 | _thread.Execute (threadId, fromIndex, beforeIndex); 32 | } 33 | 34 | protected virtual void SetData (IEcsSystems systems, ref TThread thread) { } 35 | } 36 | 37 | public abstract class EcsThreadSystem : EcsThreadSystemBase, IEcsRunSystem 38 | where TThread : struct, IEcsThread 39 | where T1 : struct 40 | where T2 : struct { 41 | EcsFilter _filter; 42 | EcsPool _pool1; 43 | EcsPool _pool2; 44 | TThread _thread; 45 | ThreadWorkerHandler _worker; 46 | 47 | public void Run (IEcsSystems systems) { 48 | if (_filter == null) { 49 | var world = GetWorld (systems); 50 | _pool1 = world.GetPool (); 51 | _pool2 = world.GetPool (); 52 | _filter = GetFilter (world); 53 | _thread = new TThread (); 54 | _worker = Execute; 55 | } 56 | _thread.Init ( 57 | _filter.GetRawEntities (), 58 | _pool1.GetRawDenseItems (), _pool1.GetRawSparseItems (), 59 | _pool2.GetRawDenseItems (), _pool2.GetRawSparseItems ()); 60 | SetData (systems, ref _thread); 61 | ThreadService.Run (_worker, _filter.GetEntitiesCount (), GetChunkSize (systems)); 62 | } 63 | 64 | void Execute (int threadId, int fromIndex, int beforeIndex) { 65 | _thread.Execute (threadId, fromIndex, beforeIndex); 66 | } 67 | 68 | protected virtual void SetData (IEcsSystems systems, ref TThread thread) { } 69 | } 70 | 71 | public abstract class EcsThreadSystem : EcsThreadSystemBase, IEcsRunSystem 72 | where TThread : struct, IEcsThread 73 | where T1 : struct 74 | where T2 : struct 75 | where T3 : struct { 76 | EcsFilter _filter; 77 | EcsPool _pool1; 78 | EcsPool _pool2; 79 | EcsPool _pool3; 80 | TThread _thread; 81 | ThreadWorkerHandler _worker; 82 | 83 | public void Run (IEcsSystems systems) { 84 | if (_filter == null) { 85 | var world = GetWorld (systems); 86 | _pool1 = world.GetPool (); 87 | _pool2 = world.GetPool (); 88 | _pool3 = world.GetPool (); 89 | _filter = GetFilter (world); 90 | _thread = new TThread (); 91 | _worker = Execute; 92 | } 93 | _thread.Init ( 94 | _filter.GetRawEntities (), 95 | _pool1.GetRawDenseItems (), _pool1.GetRawSparseItems (), 96 | _pool2.GetRawDenseItems (), _pool2.GetRawSparseItems (), 97 | _pool3.GetRawDenseItems (), _pool3.GetRawSparseItems ()); 98 | SetData (systems, ref _thread); 99 | ThreadService.Run (_worker, _filter.GetEntitiesCount (), GetChunkSize (systems)); 100 | } 101 | 102 | void Execute (int threadId, int fromIndex, int beforeIndex) { 103 | _thread.Execute (threadId, fromIndex, beforeIndex); 104 | } 105 | 106 | protected virtual void SetData (IEcsSystems systems, ref TThread thread) { } 107 | } 108 | 109 | public abstract class EcsThreadSystem : EcsThreadSystemBase, IEcsRunSystem 110 | where TThread : struct, IEcsThread 111 | where T1 : struct 112 | where T2 : struct 113 | where T3 : struct 114 | where T4 : struct { 115 | EcsFilter _filter; 116 | EcsPool _pool1; 117 | EcsPool _pool2; 118 | EcsPool _pool3; 119 | EcsPool _pool4; 120 | TThread _thread; 121 | ThreadWorkerHandler _worker; 122 | 123 | public void Run (IEcsSystems systems) { 124 | if (_filter == null) { 125 | var world = GetWorld (systems); 126 | _pool1 = world.GetPool (); 127 | _pool2 = world.GetPool (); 128 | _pool3 = world.GetPool (); 129 | _pool4 = world.GetPool (); 130 | _filter = GetFilter (world); 131 | _thread = new TThread (); 132 | _worker = Execute; 133 | } 134 | _thread.Init ( 135 | _filter.GetRawEntities (), 136 | _pool1.GetRawDenseItems (), _pool1.GetRawSparseItems (), 137 | _pool2.GetRawDenseItems (), _pool2.GetRawSparseItems (), 138 | _pool3.GetRawDenseItems (), _pool3.GetRawSparseItems (), 139 | _pool4.GetRawDenseItems (), _pool4.GetRawSparseItems ()); 140 | SetData (systems, ref _thread); 141 | ThreadService.Run (_worker, _filter.GetEntitiesCount (), GetChunkSize (systems)); 142 | } 143 | 144 | void Execute (int threadId, int fromIndex, int beforeIndex) { 145 | _thread.Execute (threadId, fromIndex, beforeIndex); 146 | } 147 | 148 | protected virtual void SetData (IEcsSystems systems, ref TThread thread) { } 149 | } 150 | 151 | public abstract class EcsThreadSystemBase { 152 | protected abstract int GetChunkSize (IEcsSystems systems); 153 | protected abstract EcsFilter GetFilter (EcsWorld world); 154 | protected abstract EcsWorld GetWorld (IEcsSystems systems); 155 | } 156 | 157 | public interface IEcsThreadBase { 158 | void Execute (int threadId, int fromIndex, int beforeIndex); 159 | } 160 | 161 | public interface IEcsThread : IEcsThreadBase 162 | where T1 : struct { 163 | void Init ( 164 | int[] entities, 165 | T1[] pool1, int[] indices1); 166 | } 167 | 168 | public interface IEcsThread : IEcsThreadBase 169 | where T1 : struct 170 | where T2 : struct { 171 | void Init ( 172 | int[] entities, 173 | T1[] pool1, int[] indices1, 174 | T2[] pool2, int[] indices2); 175 | } 176 | 177 | public interface IEcsThread : IEcsThreadBase 178 | where T1 : struct 179 | where T2 : struct 180 | where T3 : struct { 181 | void Init ( 182 | int[] entities, 183 | T1[] pool1, int[] indices1, 184 | T2[] pool2, int[] indices2, 185 | T3[] pool3, int[] indices3); 186 | } 187 | 188 | public interface IEcsThread : IEcsThreadBase 189 | where T1 : struct 190 | where T2 : struct 191 | where T3 : struct 192 | where T4 : struct { 193 | void Init ( 194 | int[] entities, 195 | T1[] pool1, int[] indices1, 196 | T2[] pool2, int[] indices2, 197 | T3[] pool3, int[] indices3, 198 | T4[] pool4, int[] indices4); 199 | } 200 | } 201 | --------------------------------------------------------------------------------