├── Runtime ├── IUpdateSystem.cs ├── IEcsFeature.cs ├── RegistrationDefinition.cs ├── Scellecs.Morpeh.Elysium.Startup.asmdef.meta ├── ResolveInfo.cs ├── EcsStartup.cs.meta ├── IEcsFeature.cs.meta ├── ResolveInfo.cs.meta ├── IStartupContainer.cs.meta ├── StartupResolver.cs.meta ├── VContainerResolver.cs.meta ├── DefineSymbolHandler.cs.meta ├── RegistrationDefinition.cs.meta ├── IUpdateSystem.cs.meta ├── IStartupContainer.cs ├── Scellecs.Morpeh.Elysium.Startup.asmdef ├── DefineSymbolHandler.cs ├── StartupResolver.cs ├── VContainerResolver.cs └── EcsStartup.cs ├── LICENSE.md.meta ├── README.md.meta ├── package.json.meta ├── Runtime.meta ├── package.json ├── LICENSE.md ├── .gitignore └── README.md /Runtime/IUpdateSystem.cs: -------------------------------------------------------------------------------- 1 | namespace Scellecs.Morpeh.Elysium 2 | { 3 | public interface IUpdateSystem : ISystem { } 4 | } 5 | -------------------------------------------------------------------------------- /LICENSE.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 32e7997017e92d240824d22b4055d45a 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /README.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 4100249b021cc194bac75643e56f8c89 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Runtime/IEcsFeature.cs: -------------------------------------------------------------------------------- 1 | namespace Scellecs.Morpeh.Elysium 2 | { 3 | public interface IEcsFeature 4 | { 5 | public void Configure(EcsStartup.FeatureBuilder builder); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /package.json.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 90219cb510b7ef243bd5cda33ea4dc69 3 | PackageManifestImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Runtime.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: fc4042acc3d54c240959c2b0725b0888 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Runtime/RegistrationDefinition.cs: -------------------------------------------------------------------------------- 1 | namespace Scellecs.Morpeh.Elysium 2 | { 3 | public enum RegistrationDefinition 4 | { 5 | Initializer = 0, 6 | System = 1, 7 | Feature = 2 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Runtime/Scellecs.Morpeh.Elysium.Startup.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 55db836df77fa934daa1244c4db8a184 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Runtime/ResolveInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Scellecs.Morpeh.Elysium 4 | { 5 | internal struct ResolveInfo 6 | { 7 | public RegistrationDefinition definition; 8 | public bool injected; 9 | public Type type; 10 | public int order; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Runtime/EcsStartup.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 91a2bf432e77b0541acb2e010f2d31e6 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/IEcsFeature.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 3dd28b3b83ec4214e85dd3c3c67cca22 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/ResolveInfo.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9c186ea75f62bc24bac9ab935715b1eb 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/IStartupContainer.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d438fa674853873458ae4eddcfd423a4 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/StartupResolver.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 1dbc26d3ed5e4024cba49a696b5d9a5d 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/VContainerResolver.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 3acc60b1a018e8b459a294a9eb02dc26 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/DefineSymbolHandler.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 98f7b2026828f7e40a4fe89721a9aa09 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/RegistrationDefinition.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 66c8d1b23d669b04b85c684548e26d2d 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.scellecs.morpeh.startup", 3 | "author": "Heymeepo", 4 | "displayName": "Morpeh Startup", 5 | "description": "Morpeh ECS simple startup with DI integration", 6 | "unity": "2020.3", 7 | "version": "1.0.1", 8 | "keywords": ["morpeh", "ecs", "utility", "extensions"], 9 | "dependencies": {} 10 | } -------------------------------------------------------------------------------- /Runtime/IUpdateSystem.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ce88ff068bd1b264787404c501c40a1e 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {fileID: 2800000, guid: 493eb45005e8adb45b6b2cb31be411e9, type: 3} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/IStartupContainer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Scellecs.Morpeh.Elysium 4 | { 5 | public interface IStartupContainer : IDisposable 6 | { 7 | public void BuildFeaturesContainer(); 8 | public void BuildSystemsContainer(); 9 | public void Register(Type type, RegistrationDefinition definition); 10 | public object Resolve(Type type, RegistrationDefinition definition); 11 | } 12 | } -------------------------------------------------------------------------------- /Runtime/Scellecs.Morpeh.Elysium.Startup.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Scellecs.Morpeh.Elysium.Startup", 3 | "rootNamespace": "", 4 | "references": [ 5 | "GUID:3377deebfb0acde45876d12c6e3d27cc", 6 | "GUID:b0214a6008ed146ff8f122a6a9c2f6cc" 7 | ], 8 | "includePlatforms": [], 9 | "excludePlatforms": [], 10 | "allowUnsafeCode": false, 11 | "overrideReferences": false, 12 | "precompiledReferences": [], 13 | "autoReferenced": true, 14 | "defineConstraints": [], 15 | "versionDefines": [], 16 | "noEngineReferences": false 17 | } -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Matvey Reshchikov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /Runtime/DefineSymbolHandler.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_EDITOR 2 | using UnityEditor; 3 | 4 | namespace Scellecs.Morpeh.Elysium 5 | { 6 | [InitializeOnLoad] 7 | internal sealed class DefineSymbolHandler 8 | { 9 | private const string SYMBOL = "MORPEH_ELYSIUM"; 10 | 11 | static DefineSymbolHandler() 12 | { 13 | if (IsDefineSymbolPresent(SYMBOL) == false) 14 | { 15 | AddDefineSymbol(SYMBOL); 16 | } 17 | } 18 | 19 | private static bool IsDefineSymbolPresent(string define) 20 | { 21 | string existingDefines = PlayerSettings.GetScriptingDefineSymbolsForGroup(EditorUserBuildSettings.selectedBuildTargetGroup); 22 | return existingDefines.Contains(define); 23 | } 24 | 25 | private static void AddDefineSymbol(string define) 26 | { 27 | string existingDefines = PlayerSettings.GetScriptingDefineSymbolsForGroup(EditorUserBuildSettings.selectedBuildTargetGroup); 28 | PlayerSettings.SetScriptingDefineSymbolsForGroup(EditorUserBuildSettings.selectedBuildTargetGroup, existingDefines + ";" + define); 29 | } 30 | } 31 | } 32 | #endif 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # This .gitignore file should be placed at the root of your Unity project directory 2 | # 3 | # Get latest from https://github.com/github/gitignore/blob/main/Unity.gitignore 4 | # 5 | /[Ll]ibrary/ 6 | /[Tt]emp/ 7 | /[Oo]bj/ 8 | /[Bb]uild/ 9 | /[Bb]uilds/ 10 | /[Ll]ogs/ 11 | /[Uu]ser[Ss]ettings/ 12 | 13 | # MemoryCaptures can get excessive in size. 14 | # They also could contain extremely sensitive data 15 | /[Mm]emoryCaptures/ 16 | 17 | # Recordings can get excessive in size 18 | /[Rr]ecordings/ 19 | 20 | # Uncomment this line if you wish to ignore the asset store tools plugin 21 | # /[Aa]ssets/AssetStoreTools* 22 | 23 | # Autogenerated Jetbrains Rider plugin 24 | /[Aa]ssets/Plugins/Editor/JetBrains* 25 | 26 | # Visual Studio cache directory 27 | .vs/ 28 | 29 | # Gradle cache directory 30 | .gradle/ 31 | 32 | # Autogenerated VS/MD/Consulo solution and project files 33 | ExportedObj/ 34 | .consulo/ 35 | *.csproj 36 | *.unityproj 37 | *.sln 38 | *.suo 39 | *.tmp 40 | *.user 41 | *.userprefs 42 | *.pidb 43 | *.booproj 44 | *.svd 45 | *.pdb 46 | *.mdb 47 | *.opendb 48 | *.VC.db 49 | 50 | # Unity3D generated meta files 51 | *.pidb.meta 52 | *.pdb.meta 53 | *.mdb.meta 54 | 55 | # Unity3D generated file on crash reports 56 | sysinfo.txt 57 | 58 | # Builds 59 | *.apk 60 | *.aab 61 | *.unitypackage 62 | *.app 63 | 64 | # Crashlytics generated file 65 | crashlytics-build.properties 66 | 67 | # Packed Addressables 68 | /[Aa]ssets/[Aa]ddressable[Aa]ssets[Dd]ata/*/*.bin* 69 | 70 | # Temporary auto-generated Android Assets 71 | /[Aa]ssets/[Ss]treamingAssets/aa.meta 72 | /[Aa]ssets/[Ss]treamingAssets/aa/* -------------------------------------------------------------------------------- /Runtime/StartupResolver.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Scellecs.Morpeh.Elysium 5 | { 6 | internal sealed class StartupResolver : IDisposable 7 | { 8 | private readonly IStartupContainer container; 9 | private Dictionary registrationMap; 10 | 11 | public StartupResolver(IStartupContainer container) 12 | { 13 | registrationMap = new Dictionary(); 14 | this.container = container; 15 | } 16 | 17 | public void Register(Type type, RegistrationDefinition definition, bool injected, object instance) 18 | { 19 | if (injected && container == null) 20 | { 21 | throw new ArgumentException("You haven't passed the implementation of the DI container into the constructor arguments of the startup, but you are attempting to use injection methods."); 22 | } 23 | 24 | if (injected) 25 | { 26 | container.Register(type, definition); 27 | } 28 | else if (registrationMap.ContainsKey(type) == false) 29 | { 30 | registrationMap.Add(type, instance); 31 | } 32 | } 33 | 34 | public object Resolve(Type type, RegistrationDefinition definition, bool injected) => injected ? container.Resolve(type, definition) : registrationMap[type]; 35 | 36 | public void BuildFeaturesContainer() => container?.BuildFeaturesContainer(); 37 | 38 | public void BuildSystemsContainer() => container?.BuildSystemsContainer(); 39 | 40 | public void Cleanup() => registrationMap = null; 41 | 42 | public void Dispose() => container?.Dispose(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Runtime/VContainerResolver.cs: -------------------------------------------------------------------------------- 1 | #if ENABLE_MONO || ENABLE_IL2CPP 2 | #define MORPEH_UNITY 3 | #endif 4 | 5 | #if VCONTAINER && !MORPEH_UNITY 6 | #undef VCONTAINER 7 | #endif 8 | 9 | #if VCONTAINER 10 | using System; 11 | using VContainer.Unity; 12 | using VContainer; 13 | using System.Collections.Generic; 14 | 15 | namespace Scellecs.Morpeh.Elysium 16 | { 17 | public sealed class VContainerResolver : IStartupContainer 18 | { 19 | private LifetimeScope scope; 20 | private LifetimeScope featuresScope; 21 | private LifetimeScope systemsScope; 22 | 23 | private HashSet featuresTypes; 24 | private HashSet systemsTypes; 25 | 26 | public VContainerResolver(LifetimeScope currentScope) 27 | { 28 | scope = currentScope; 29 | featuresTypes = new HashSet(64); 30 | systemsTypes = new HashSet(64); 31 | } 32 | 33 | public void BuildFeaturesContainer() => featuresScope = scope.CreateChild(builder => RegisterTypesInContainer(builder, featuresTypes)); 34 | 35 | public void BuildSystemsContainer() => systemsScope = scope.CreateChild(builder => RegisterTypesInContainer(builder, systemsTypes)); 36 | 37 | public void Register(Type type, RegistrationDefinition definition) 38 | { 39 | if (definition == RegistrationDefinition.Feature) 40 | { 41 | featuresTypes.Add(type); 42 | } 43 | else 44 | { 45 | systemsTypes.Add(type); 46 | } 47 | } 48 | 49 | public object Resolve(Type type, RegistrationDefinition definition) 50 | { 51 | if (definition == RegistrationDefinition.Feature) 52 | { 53 | return featuresScope.Container.Resolve(type); 54 | } 55 | else 56 | { 57 | return systemsScope.Container.Resolve(type); 58 | } 59 | } 60 | 61 | public void Dispose() 62 | { 63 | if (featuresScope != null) 64 | { 65 | featuresScope.Dispose(); 66 | } 67 | if (systemsScope != null) 68 | { 69 | systemsScope.Dispose(); 70 | } 71 | } 72 | 73 | private static void RegisterTypesInContainer(IContainerBuilder builder, HashSet types) 74 | { 75 | foreach (var type in types) 76 | { 77 | builder.Register(type, Lifetime.Transient); 78 | } 79 | } 80 | } 81 | } 82 | #endif 83 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Morpeh Startup 2 | 3 | Simple startup with DI integration for [Morpeh ECS](https://github.com/scellecs/morpeh) 4 | 5 | ## Installation 6 | 7 | Install via git URL 8 | 9 | ```bash 10 | https://github.com/heymeepo/morpeh.startup.git 11 | ``` 12 | 13 | ## Usage 14 | 15 | *Attention: All your update systems should be inherited from the IUpdateSystem interface instead of ISystem.* 16 | 17 | ```csharp 18 | public class Game : MonoBehaviour 19 | { 20 | private EcsStartup startup; 21 | 22 | private void Awake() 23 | { 24 | startup = new EcsStartup(); 25 | 26 | startup 27 | .AddSystemsGroup() 28 | .AddInitializer(new GameInitializer()) 29 | .AddUpdateSystem(new InputSystem()) 30 | .AddLateSystem(new GameOverSystem()); 31 | 32 | startup 33 | .AddSystemsGroup() 34 | .AddCleanupSystem(new DestroyEntitySystem()); 35 | 36 | startup.Initialize(updateByUnity: true); 37 | } 38 | 39 | private void OnDestroy() 40 | { 41 | startup?.Dispose(); 42 | } 43 | } 44 | ``` 45 | 46 | All added systems after ```AddSystemsGroup()``` are placed in one systems group. If you need to add systems to different systems groups, you should call ```startup.AddSystemsGroup()``` again, as in the example above. 47 | 48 | You can manually update the startup by passing 'updateByUnity: false' and calling the methods ```startup.Update()```, ```startup.FixedUpdate()```, and ```startup.LateUpdate()``` as needed. 49 | 50 | You can also pass an instance of the ```World``` into the constructor, otherwise, ```World.Default``` will be used. 51 | 52 | ## Features 53 | A feature is a wrapper around a set of systems responsible for some specific functionality. To create a feature, declare a new class and inherit it from the ```IEcsFeature``` interface. Inside the feature, all functionality for adding systems is available, except for ```AddSystemsGroup``` and ```AddFeature``` 54 | 55 | ```csharp 56 | public class Game : MonoBehaviour 57 | { 58 | private EcsStartup startup; 59 | 60 | private void Awake() 61 | { 62 | startup = new EcsStartup(); 63 | 64 | startup 65 | .AddSystemsGroup() 66 | .AddInitializer(new GameInitializer()) 67 | .AddFeature(new AnimationFeature()); 68 | 69 | startup.Initialize(updateByUnity: true); 70 | } 71 | } 72 | 73 | public sealed class AnimationFeature : IEcsFeature 74 | { 75 | public void Configure(EcsStartup.FeatureBuilder builder) 76 | { 77 | builder 78 | .AddUpdateSystem(new AnimatorInitializeSystem()) 79 | .AddUpdateSystem(new IdleAnimationSystem()) 80 | .AddUpdateSystem(new MovementAnimationSystem()) 81 | .AddUpdateSystem(new DieAnimationSystem()) 82 | .AddUpdateSystem(new AnimatorSystem()); 83 | } 84 | } 85 | ``` 86 | 87 | ## VContainer 88 | Ensure that you have imported the [VContainer](https://github.com/hadashiA/VContainer) package and define ```VCONTAINER``` in 89 | *Project Settings -> Player -> Scripting Define Symbols* 90 | 91 | Now, to create an EcsStartup, you need to pass ```VContainerResolver``` with the current ```LifetimeScope``` to its constructor. Specify all the necessary dependencies in the constructors of your systems and features. 92 | 93 | You can use the methods with the 'Injected' postfix to add your systems using the container. However, you still have the option to add systems manually. 94 | 95 | ```csharp 96 | public class EcsModule : IStartable, IDisposable 97 | { 98 | private readonly LifetimeScope scope; 99 | private EcsStartup startup; 100 | 101 | [Inject] 102 | public EcsModule(LifetimeScope scope) 103 | { 104 | this.scope = scope; 105 | } 106 | 107 | public void Start() 108 | { 109 | startup = new EcsStartup(new VContainerResolver(scope)); 110 | 111 | startup 112 | .AddSystemsGroup() 113 | .AddInitializerInjected() 114 | .AddUpdateSystemInjected(); 115 | 116 | startup 117 | .AddSystemsGroup() 118 | .AddFeatureInjected() 119 | .AddFeatureInjected(); 120 | 121 | startup 122 | .AddSystemsGroup() 123 | .AddCleanupSystem(new DestroyEntitySystem()); 124 | 125 | startup.Initialize(updateByUnity: true); 126 | } 127 | 128 | public void Dispose() 129 | { 130 | startup?.Dispose(); 131 | } 132 | } 133 | 134 | public sealed class AnimationFeature : IEcsFeature 135 | { 136 | private readonly IGameSettingsService gameSettings; 137 | 138 | [Inject] 139 | public AnimationFeature(IGameSettingsService gameSettings) 140 | { 141 | this.gameSettings = gameSettings; 142 | } 143 | 144 | public void Configure(EcsStartup.FeatureBuilder builder) 145 | { 146 | builder 147 | .AddUpdateSystemInjected() 148 | .AddUpdateSystemInjected() 149 | .AddUpdateSystemInjected() 150 | .AddUpdateSystemInjected(); 151 | 152 | if (gameSettings.Graphics.EnableExperimentalAnimations) 153 | { 154 | builder.AddUpdateSystem(new ExperimentalAnimatorSystem(...)); 155 | } 156 | else 157 | { 158 | builder.AddUpdateSystemInjected(); 159 | } 160 | } 161 | } 162 | ``` 163 | 164 | *Clarification: You don't need to register your systems or features in the LifetimeScope, the startup does it automatically.* 165 | 166 | ## Custom DI container (Advanced) 167 | 168 | To add support for another DI solution: 169 | 170 | - Define the ```STARTUP_DI``` directive. 171 | - Create a class that implements ```IStartupContainer```. 172 | - Implement all necessary methods similar to how it's done in the ```VContainerResolver``` class. 173 | - Pass an instance of this class to the constructor of EcsStartup. 174 | 175 | Here's a brief explanation: 176 | 177 | Since we support injection both into features and systems, we need to use two different containers. 178 | 179 | Why is that? 180 | 181 | The issue arises because, besides directly added systems and features, we also have a ```Configure``` method inside features. These methods additionally want to register their systems in the container. Hence, we cannot build the systems' container until all systems are registered. However, to invoke the ```Configure``` method of features, we need to resolve them. Therefore, features cannot reside in the same container as systems. 182 | 183 | Due to this, we first build the container with features. Afterward, we call the Configure methods. Only then can we build the container with systems and create systems groups. 184 | 185 | ## License 186 | 187 | [MIT](https://choosealicense.com/licenses/mit/) -------------------------------------------------------------------------------- /Runtime/EcsStartup.cs: -------------------------------------------------------------------------------- 1 | #if ENABLE_MONO || ENABLE_IL2CPP 2 | #define MORPEH_UNITY 3 | #endif 4 | 5 | #if VCONTAINER || STARTUP_DI 6 | #define DI_ENABLED 7 | #endif 8 | 9 | using System; 10 | using System.Collections.Generic; 11 | using System.Runtime.CompilerServices; 12 | 13 | namespace Scellecs.Morpeh.Elysium 14 | { 15 | public sealed class EcsStartup : IDisposable 16 | { 17 | public World World { get; private set; } 18 | 19 | private int currentSystemGroupOrder; 20 | private Dictionary systemsGroups; 21 | 22 | private Queue defferedCommands; 23 | private Queue directCommands; 24 | 25 | private StartupResolver resolver; 26 | 27 | private bool initialized; 28 | private bool disposed; 29 | 30 | public EcsStartup(IStartupContainer container = null, World world = null) 31 | { 32 | World = world.IsNullOrDisposed() ? World.Default : world; 33 | currentSystemGroupOrder = 0; 34 | systemsGroups = new Dictionary(); 35 | defferedCommands = new Queue(64); 36 | directCommands = new Queue(64); 37 | resolver = new StartupResolver(container); 38 | initialized = false; 39 | disposed = false; 40 | } 41 | 42 | public StartupBuilder AddSystemsGroup() => new StartupBuilder(this, currentSystemGroupOrder++); 43 | 44 | public void Initialize(bool updateByUnity) 45 | { 46 | if (initialized) 47 | { 48 | if (disposed) 49 | { 50 | LogWarning("The EcsStartup has already been disposed. Create a new one to use it."); 51 | } 52 | else 53 | { 54 | LogWarning("EcsStartup has already been initialized."); 55 | } 56 | 57 | return; 58 | } 59 | 60 | if (World.IsNullOrDisposed()) 61 | { 62 | World = World.Create(); 63 | } 64 | 65 | World.UpdateByUnity = updateByUnity; 66 | 67 | resolver.BuildFeaturesContainer(); 68 | SetupCommands(); 69 | resolver.BuildSystemsContainer(); 70 | CreateSystemsGroups(); 71 | resolver.Cleanup(); 72 | initialized = true; 73 | } 74 | 75 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 76 | public void Update(float deltaTime) 77 | { 78 | if (World.IsNullOrDisposed() == false && World.UpdateByUnity == false) 79 | { 80 | World.Update(deltaTime); 81 | } 82 | } 83 | 84 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 85 | public void FixedUpdate(float fixedDeltaTime) 86 | { 87 | if (World.IsNullOrDisposed() == false && World.UpdateByUnity == false) 88 | { 89 | World.FixedUpdate(fixedDeltaTime); 90 | } 91 | } 92 | 93 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 94 | public void LateUpdate(float deltaTime) 95 | { 96 | if (World.IsNullOrDisposed() == false && World.UpdateByUnity == false) 97 | { 98 | World.LateUpdate(deltaTime); 99 | World.CleanupUpdate(deltaTime); 100 | } 101 | } 102 | 103 | public World DisconnectWorld(bool removeSystemsGroups = true) 104 | { 105 | var world = World; 106 | World = null; 107 | 108 | if (world.IsNullOrDisposed() == false && removeSystemsGroups) 109 | { 110 | foreach (var sysGroup in systemsGroups.Values) 111 | { 112 | world.RemoveSystemsGroup(sysGroup); 113 | } 114 | } 115 | 116 | return world; 117 | } 118 | 119 | public void Dispose() 120 | { 121 | if (initialized && disposed == false) 122 | { 123 | systemsGroups.Clear(); 124 | resolver.Dispose(); 125 | 126 | if (World.IsNullOrDisposed() == false) 127 | { 128 | World.Dispose(); 129 | } 130 | 131 | World = null; 132 | disposed = true; 133 | } 134 | } 135 | 136 | private void AddRegistrationInjected(RegistrationDefinition definition, int order, bool deffered, Type type) 137 | { 138 | var info = new ResolveInfo() 139 | { 140 | definition = definition, 141 | injected = true, 142 | type = type, 143 | order = order 144 | }; 145 | 146 | AddCommand(ref info, deffered); 147 | resolver.Register(type, definition, true, null); 148 | } 149 | 150 | private void AddRegistration(RegistrationDefinition definition, int order, bool deffered, object instance) 151 | { 152 | var type = instance.GetType(); 153 | 154 | var info = new ResolveInfo() 155 | { 156 | definition = definition, 157 | injected = false, 158 | type = type, 159 | order = order 160 | }; 161 | 162 | AddCommand(ref info, deffered); 163 | resolver.Register(type, definition, false, instance); 164 | } 165 | 166 | private void AddCommand(ref ResolveInfo command, bool deffered) 167 | { 168 | if (deffered) 169 | { 170 | defferedCommands.Enqueue(command); 171 | } 172 | else 173 | { 174 | directCommands.Enqueue(command); 175 | } 176 | } 177 | 178 | private void SetupCommands() 179 | { 180 | while (defferedCommands.Count > 0) 181 | { 182 | var info = defferedCommands.Dequeue(); 183 | 184 | if (info.definition == RegistrationDefinition.Feature) 185 | { 186 | var feature = resolver.Resolve(info.type, RegistrationDefinition.Feature, info.injected) as IEcsFeature; 187 | feature.Configure(new FeatureBuilder(this, info.order)); 188 | } 189 | else 190 | { 191 | directCommands.Enqueue(info); 192 | } 193 | } 194 | } 195 | 196 | private void CreateSystemsGroups() 197 | { 198 | while (directCommands.Count > 0) 199 | { 200 | var info = directCommands.Dequeue(); 201 | var instance = resolver.Resolve(info.type, info.definition, info.injected); 202 | var systemsGroup = GetOrCreateSystemsGroup(info.order); 203 | 204 | if (info.definition == RegistrationDefinition.Initializer) 205 | { 206 | systemsGroup.AddInitializer(instance as IInitializer); 207 | } 208 | else if (info.definition == RegistrationDefinition.System) 209 | { 210 | systemsGroup.AddSystem(instance as ISystem); 211 | } 212 | } 213 | 214 | foreach (var group in systemsGroups) 215 | { 216 | World.AddSystemsGroup(group.Key, group.Value); 217 | } 218 | } 219 | 220 | private SystemsGroup GetOrCreateSystemsGroup(int order) 221 | { 222 | if (systemsGroups.TryGetValue(order, out SystemsGroup systemsGroup) == false) 223 | { 224 | systemsGroup = systemsGroups[order] = World.CreateSystemsGroup(); 225 | } 226 | 227 | return systemsGroup; 228 | } 229 | 230 | private static void LogWarning(string message) 231 | { 232 | #if MORPEH_UNITY 233 | UnityEngine.Debug.LogWarning(message); 234 | #else 235 | Console.WriteLine(message); 236 | #endif 237 | } 238 | 239 | public readonly struct StartupBuilder 240 | { 241 | private readonly EcsStartup ecsStartup; 242 | private readonly int order; 243 | 244 | public StartupBuilder(EcsStartup ecsStartup, int order) 245 | { 246 | this.ecsStartup = ecsStartup; 247 | this.order = order; 248 | } 249 | #if DI_ENABLED 250 | public StartupBuilder AddInitializerInjected() where T : class, IInitializer 251 | { 252 | ecsStartup.AddRegistrationInjected(RegistrationDefinition.Initializer, order, true, typeof(T)); 253 | return this; 254 | } 255 | 256 | public StartupBuilder AddUpdateSystemInjected() where T : class, IUpdateSystem 257 | { 258 | ecsStartup.AddRegistrationInjected(RegistrationDefinition.System, order, true, typeof(T)); 259 | return this; 260 | } 261 | 262 | public StartupBuilder AddFixedSystemInjected() where T : class, IFixedSystem 263 | { 264 | ecsStartup.AddRegistrationInjected(RegistrationDefinition.System, order, true, typeof(T)); 265 | return this; 266 | } 267 | 268 | public StartupBuilder AddLateSystemInjected() where T : class, ILateSystem 269 | { 270 | ecsStartup.AddRegistrationInjected(RegistrationDefinition.System, order, true, typeof(T)); 271 | return this; 272 | } 273 | 274 | public StartupBuilder AddCleanupSystemInjected() where T : class, ICleanupSystem 275 | { 276 | ecsStartup.AddRegistrationInjected(RegistrationDefinition.System, order, true, typeof(T)); 277 | return this; 278 | } 279 | 280 | public StartupBuilder AddFeatureInjected() where T : class, IEcsFeature 281 | { 282 | ecsStartup.AddRegistrationInjected(RegistrationDefinition.Feature, order, true, typeof(T)); 283 | return this; 284 | } 285 | #endif 286 | public StartupBuilder AddInitializer(T initializer) where T : class, IInitializer 287 | { 288 | ecsStartup.AddRegistration(RegistrationDefinition.Initializer, order, true, initializer); 289 | return this; 290 | } 291 | 292 | public StartupBuilder AddUpdateSystem(T system) where T : class, IUpdateSystem 293 | { 294 | ecsStartup.AddRegistration(RegistrationDefinition.System, order, true, system); 295 | return this; 296 | } 297 | 298 | public StartupBuilder AddFixedSystem(T system) where T : class, IFixedSystem 299 | { 300 | ecsStartup.AddRegistration(RegistrationDefinition.System, order, true, system); 301 | return this; 302 | } 303 | 304 | public StartupBuilder AddLateSystem(T system) where T : class, ILateSystem 305 | { 306 | ecsStartup.AddRegistration(RegistrationDefinition.System, order, true, system); 307 | return this; 308 | } 309 | 310 | public StartupBuilder AddCleanupSystem(T system) where T : class, ICleanupSystem 311 | { 312 | ecsStartup.AddRegistration(RegistrationDefinition.System, order, true, system); 313 | return this; 314 | } 315 | 316 | public StartupBuilder AddFeature(T feature) where T : class, IEcsFeature 317 | { 318 | ecsStartup.AddRegistration(RegistrationDefinition.Feature, order, true, feature); 319 | return this; 320 | } 321 | } 322 | 323 | public readonly struct FeatureBuilder 324 | { 325 | private readonly EcsStartup ecsStartup; 326 | private readonly int order; 327 | 328 | public FeatureBuilder(EcsStartup ecsStartup, int order) 329 | { 330 | this.ecsStartup = ecsStartup; 331 | this.order = order; 332 | } 333 | #if DI_ENABLED 334 | public FeatureBuilder AddInitializerInjected() where T : class, IInitializer 335 | { 336 | ecsStartup.AddRegistrationInjected(RegistrationDefinition.Initializer, order, false, typeof(T)); 337 | return this; 338 | } 339 | 340 | public FeatureBuilder AddUpdateSystemInjected() where T : class, IUpdateSystem 341 | { 342 | ecsStartup.AddRegistrationInjected(RegistrationDefinition.System, order, false, typeof(T)); 343 | return this; 344 | } 345 | 346 | public FeatureBuilder AddFixedSystemInjected() where T : class, IFixedSystem 347 | { 348 | ecsStartup.AddRegistrationInjected(RegistrationDefinition.System, order, false, typeof(T)); 349 | return this; 350 | } 351 | 352 | public FeatureBuilder AddLateSystemInjected() where T : class, ILateSystem 353 | { 354 | ecsStartup.AddRegistrationInjected(RegistrationDefinition.System, order, false, typeof(T)); 355 | return this; 356 | } 357 | 358 | public FeatureBuilder AddCleanupSystemInjected() where T : class, ICleanupSystem 359 | { 360 | ecsStartup.AddRegistrationInjected(RegistrationDefinition.System, order, false, typeof(T)); 361 | return this; 362 | } 363 | #endif 364 | public FeatureBuilder AddInitializer(T initializer) where T : class, IInitializer 365 | { 366 | ecsStartup.AddRegistration(RegistrationDefinition.Initializer, order, false, initializer); 367 | return this; 368 | } 369 | 370 | public FeatureBuilder AddUpdateSystem(T system) where T : class, IUpdateSystem 371 | { 372 | ecsStartup.AddRegistration(RegistrationDefinition.System, order, false, system); 373 | return this; 374 | } 375 | 376 | public FeatureBuilder AddFixedSystem(T system) where T : class, IFixedSystem 377 | { 378 | ecsStartup.AddRegistration(RegistrationDefinition.System, order, false, system); 379 | return this; 380 | } 381 | 382 | public FeatureBuilder AddLateSystem(T system) where T : class, ILateSystem 383 | { 384 | ecsStartup.AddRegistration(RegistrationDefinition.System, order, false, system); 385 | return this; 386 | } 387 | 388 | public FeatureBuilder AddCleanupSystem(T system) where T : class, ICleanupSystem 389 | { 390 | ecsStartup.AddRegistration(RegistrationDefinition.System, order, false, system); 391 | return this; 392 | } 393 | } 394 | } 395 | } 396 | --------------------------------------------------------------------------------