Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Enable/Disable systems based on active scene?

Discussion in 'Entity Component System' started by TheGabelle, Nov 26, 2020.

  1. TheGabelle

    TheGabelle

    Joined:
    Aug 23, 2013
    Posts:
    242
    I have an exploratory project with many scenes. Each scene is used to isolate a particular concept with respective components and systems. The problem is every system is running no matter what scene is active causing chaos in the debug tools. I often find myself commenting out entire sections of code or jamming a 'return' at the beginning of unwanted system methods for a given scene, but this is obviously a pain when switching between scenes. Is there a smarter way of dealing with unwanted systems? I don't want to create a new project for each concept because it's a massive waste of storage.

    Apologies if this is a naive question.
     
    Egad_McDad and dannyalgorithmic like this.
  2. TheGabelle

    TheGabelle

    Joined:
    Aug 23, 2013
    Posts:
    242
    Found an ugly work-around.

    Originally I tried using a MonoBehaviour to control a static 'active scene' enum which systems check during their OnCreate(), disabling themselves accordingly. This doesn't work. The log "System Create Hit" (line 30) always ran before the log "Mono Enable Hit" (line 20), even after adjusting the script execution order. Each system sees SceneMaster.Active as None using this approach because that was the initialized value.

    So, when switching scenes, I'll also have to update the SceneMaster.Active initialization value manually within the script. It's a single yet annoying additional step and one I'll likely forget often.

    There's got to be a better way, but for now here is something:
    Code (CSharp):
    1. public static class SceneMaster {
    2.     public enum Scene {
    3.         None,
    4.         SceneA,
    5.         SceneB,
    6.         SceneC
    7.         // add new scenes here
    8.     }
    9.  
    10.     public static Scene Active = Scene.None; // manually change this when switching scenes
    11. }
    12.  
    13. /*
    14. public class SceneController : MonoBehaviour
    15. {
    16.     public SceneMaster.Scene active;
    17.  
    18.     void OnEnable()
    19.     {
    20.         Debug.Log("Mono Enable Hit, SceneMaster.Active == " + SceneMaster.Active.ToString());
    21.         SceneMaster.Active = active;
    22.         Destroy(gameObject);
    23.     }
    24. }
    25. */
    26.  
    27. public class SystemA : SystemBase
    28. {
    29.     protected override void OnCreate() {
    30.         Debug.Log("System Create Hit, SystemA OnCreate()");
    31.         Enabled = SceneMaster.Active == SceneMaster.Scene.SceneA;
    32.     }
    33.  
    34.     protected override void OnUpdate() {
    35.         Debug.Log("SystemA hit");
    36.     }
    37. }
    38.  
    39. public class SystemB : SystemBase
    40. {
    41.     protected override void OnCreate() {
    42.         Enabled = SceneMaster.Active == SceneMaster.Scene.SceneB;
    43.     }
    44.  
    45.     protected override void OnUpdate() {
    46.         Debug.Log("SystemB hit");
    47.     }
    48. }
    49.  
    50. public class SystemC : SystemBase
    51. {
    52.     protected override void OnCreate() {
    53.         Enabled = SceneMaster.Active == SceneMaster.Scene.SceneC;
    54.     }
    55.     protected override void OnUpdate() {
    56.         Debug.Log("SystemC hit");
    57.     }
    58. }
    59.  
     
    Last edited: Nov 27, 2020
  3. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,937
    Egad_McDad, PhilSA and TheGabelle like this.
  4. TheGabelle

    TheGabelle

    Joined:
    Aug 23, 2013
    Posts:
    242
    Nice! That's definitely a much more deployable scene handling solution. You've pointed me in the the right direction. Thank you for sharing, Latios.
     
  5. TheGabelle

    TheGabelle

    Joined:
    Aug 23, 2013
    Posts:
    242
    Trying to create a cleaner solution, but running into an error. Looks like a initialization timing issue between SceneSystemToggleSystem.cs and the test systems. I'm not familiar enough with system scheduling / lifecycle to determine how to resolve this yet.

    Code (CSharp):
    1. [UpdateInGroup(typeof(SimulationSystemGroup))]
    2. [UpdateBefore(typeof(BeginSimulationEntityCommandBufferSystem))]
    3. public class SceneSystemToggleSystem : SystemBase
    4. {
    5.     // debug vars
    6.     int count = 0;
    7.     int max = 1000;
    8.  
    9.     // Will likely use a yaml file later on, but for now this will do.
    10.     private Dictionary<string, List<Type>> m_sceneNameToSystemTypeMap = new Dictionary<string, List<Type>>
    11.     {
    12.         {"TestSceneA", new List<Type>{typeof(TestSystemA)}},
    13.         {"TestSceneB", new List<Type>{typeof(TestSystemB)}}
    14.     };
    15.  
    16.    
    17.     private string m_currentSceneName;
    18.    
    19.     protected override void OnCreate()
    20.     {
    21.         m_currentSceneName = SceneManager.GetActiveScene().name;
    22.         Debug.Log("initial scene name: " + m_currentSceneName.ToString());
    23.         ToggleAllSceneSystems (false);
    24.         ToggleSceneSystems (m_currentSceneName, true);
    25.     }
    26.    
    27.     protected override void OnUpdate()
    28.     {
    29.         // var event = …query for scene change event
    30.         // HandleSceneChange(event.newSceneName);
    31.  
    32.         // debug
    33.         count++;
    34.         if( count == max)
    35.         {
    36.             Debug.Log("SceneSystemToggleSystem is running!");
    37.             count = 0;
    38.         }
    39.     }
    40.  
    41.  
    42.     protected void HandleSceneChange (string newSceneName)
    43.     {
    44.         if (m_currentSceneName != newSceneName)
    45.         {
    46.             ToggleSceneSystems (m_currentSceneName, false);
    47.             ToggleSceneSystems (newSceneName, true);
    48.             m_currentSceneName = newSceneName;
    49.         }
    50.     }
    51.    
    52.     protected void ToggleSceneSystems(string sceneName, bool enabled)
    53.     {
    54.         var systemTypes = m_sceneNameToSystemTypeMap[sceneName];
    55.         foreach (var sysType in systemTypes)
    56.         {
    57.             var sys = World.GetOrCreateSystem(sysType);
    58.             if(sys != null) sys.Enabled = enabled;
    59.         }
    60.     }
    61.    
    62.     protected void ToggleAllSceneSystems (bool enabled)
    63.     {
    64.         foreach (KeyValuePair<string, List<Type>> entry in m_sceneNameToSystemTypeMap)
    65.         {
    66.             foreach (var sysType in entry.Value)
    67.             {
    68.                 var sys = World.GetOrCreateSystem(sysType);
    69.                 if(sys != null) sys.Enabled = enabled;
    70.                 Debug.Log(sys);
    71.             }
    72.         }
    73.     }
    74. }
    Code (CSharp):
    1. public class TestSystemA : SystemBase
    2. {
    3.     int count = 0;
    4.     int max = 1000;
    5.     protected override void OnUpdate()
    6.     {
    7.         count++;
    8.         if( count == max)
    9.         {
    10.             Debug.Log("TestSystemA");
    11.             count = 0;
    12.         }
    13.     }
    14. }
    Code (CSharp):
    1. public class TestSystemB : SystemBase
    2. {
    3.     int count = 0;
    4.     int max = 1000;
    5.     protected override void OnUpdate()
    6.     {
    7.         count++;
    8.         if( count == max)
    9.         {
    10.             Debug.Log("TestSystemB");
    11.             count = 0;
    12.         }
    13.     }
    14. }
    Code (CSharp):
    1.  
    2. InvalidOperationException: object is not initialized or has already been destroyed
    3. Unity.Entities.ComponentSystemBase.CheckedState () (at Library/PackageCache/com.unity.entities@0.16.0-preview.21/Unity.Entities/ComponentSystemBase.cs:31)
    4. Unity.Entities.ComponentSystemBase.set_Enabled (System.Boolean value) (at Library/PackageCache/com.unity.entities@0.16.0-preview.21/Unity.Entities/ComponentSystemBase.cs:41)
    5. SceneSystemToggleSystem.ToggleAllSceneSystems (System.Boolean enabled) (at Assets/SceneManagement/SceneSystemToggleSystem.cs:80)
    6. SceneSystemToggleSystem.OnCreate () (at Assets/SceneManagement/SceneSystemToggleSystem.cs:34)
    7. Unity.Entities.ComponentSystemBase.CreateInstance (Unity.Entities.World world) (at Library/PackageCache/com.unity.entities@0.16.0-preview.21/Unity.Entities/ComponentSystemBase.cs:129)
    8. Unity.Entities.World.AddSystem_OnCreate_Internal (Unity.Entities.ComponentSystemBase system) (at Library/PackageCache/com.unity.entities@0.16.0-preview.21/Unity.Entities/World.cs:648)
    9. Unity.Entities.World.GetOrCreateSystemsAndLogException (System.Collections.Generic.IEnumerable`1[T] types, System.Int32 typesCount) (at Library/PackageCache/com.unity.entities@0.16.0-preview.21/Unity.Entities/World.cs:858)
    10. UnityEngine.Debug:LogException(Exception)
    11. Unity.Debug:LogException(Exception) (at Library/PackageCache/com.unity.entities@0.16.0-preview.21/Unity.Entities/Stubs/Unity/Debug.cs:19)
    12. Unity.Entities.World:GetOrCreateSystemsAndLogException(IEnumerable`1, Int32) (at Library/PackageCache/com.unity.entities@0.16.0-preview.21/Unity.Entities/World.cs:862)
    13. Unity.Entities.DefaultWorldInitialization:AddSystemToRootLevelSystemGroupsInternal(World, IEnumerable`1, Int32) (at Library/PackageCache/com.unity.entities@0.16.0-preview.21/Unity.Entities/DefaultWorldInitialization.cs:190)
    14. Unity.Entities.DefaultWorldInitialization:Initialize(String, Boolean) (at Library/PackageCache/com.unity.entities@0.16.0-preview.21/Unity.Entities/DefaultWorldInitialization.cs:131)
    15. Unity.Entities.AutomaticWorldBootstrap:Initialize() (at Library/PackageCache/com.unity.entities@0.16.0-preview.21/Unity.Entities.Hybrid/Injection/AutomaticWorldBootstrap.cs:15)
    16.  
     
    Last edited: Nov 27, 2020
  6. TheGabelle

    TheGabelle

    Joined:
    Aug 23, 2013
    Posts:
    242
    Figured it out. OnStartRunning() was what I was looking for.
    Code (CSharp):
    1. [UpdateInGroup(typeof(SimulationSystemGroup), OrderFirst=true)]
    2. public class SceneSystemToggleSystem : SystemBase
    3. {
    4.     // Will likely use a yaml file later on, but for now this will do.
    5.     private Dictionary<string, List<Type>> m_sceneNameToSystemTypeMap = new Dictionary<string, List<Type>>
    6.     {
    7.         {"TestSceneA", new List<Type>{typeof(TestSystemA)}},
    8.         {"TestSceneB", new List<Type>{typeof(TestSystemB)}}
    9.     };
    10.  
    11.     private string m_currentSceneName;
    12.    
    13.     protected override void OnStartRunning()
    14.     {
    15.         m_currentSceneName = SceneManager.GetActiveScene().name;
    16.         Debug.Log("initial scene name: " + m_currentSceneName.ToString());
    17.         ToggleAllSceneSystems (false);
    18.         ToggleSceneSystems (m_currentSceneName, true);
    19.     }
    20.    
    21.     protected override void OnUpdate()
    22.     {
    23.         // var event = …query for scene change event
    24.         // HandleSceneChange(event.newSceneName);
    25.     }
    26.  
    27.  
    28.     protected void HandleSceneChange (string newSceneName)
    29.     {
    30.         if (m_currentSceneName != newSceneName)
    31.         {
    32.             ToggleSceneSystems (m_currentSceneName, false);
    33.             ToggleSceneSystems (newSceneName, true);
    34.             m_currentSceneName = newSceneName;
    35.         }
    36.     }
    37.    
    38.     protected void ToggleSceneSystems(string sceneName, bool enabled)
    39.     {
    40.         var systemTypes = m_sceneNameToSystemTypeMap[sceneName];
    41.         foreach (var sysType in systemTypes)
    42.         {
    43.             var sys = World.GetOrCreateSystem(sysType);
    44.             if(sys != null) sys.Enabled = enabled;
    45.         }
    46.     }
    47.    
    48.     protected void ToggleAllSceneSystems (bool enabled)
    49.     {
    50.         foreach (KeyValuePair<string, List<Type>> entry in m_sceneNameToSystemTypeMap)
    51.         {
    52.             foreach (var sysType in entry.Value)
    53.             {
    54.                 var sys = World.GetOrCreateSystem(sysType);
    55.                 if(sys != null) sys.Enabled = enabled;
    56.             }
    57.         }
    58.     }
    59. }
     
  7. Cell-i-Zenit

    Cell-i-Zenit

    Joined:
    Mar 11, 2016
    Posts:
    288
    What iam doing is have an entity for each scene and just keeping one of them active at the same time. Then each system can implement "RequireEntityForUpdate" by themselves.

    This way some systems will only be active in "editor scene" some systems will be active in "ingame scene"
     
  8. TheGabelle

    TheGabelle

    Joined:
    Aug 23, 2013
    Posts:
    242
    Maybe I'm over engineering, but I thinking systems shouldn't know anything about scenes. By deactivating systems I can remove unwanted systems in the entity debugger. I don't think my current solution is production quality and the naming is pretty poor, but it does remove systems dependency on scene knowledge and cleans up the debugger -- my primary goals. The downside is managing two datasets for one outcome: build scenes and a scene-to-systems map. I'm a bit concerned I'm introducing spaghetti early on -- I know from experience how rapidly code pasta spirals out of control and recedes one's hairline.

    I didn't know DOTS could run during editor mode, which is super interesting. How have you achieved this?
     
    Last edited: Nov 28, 2020
  9. Cell-i-Zenit

    Cell-i-Zenit

    Joined:
    Mar 11, 2016
    Posts:
    288
    No this is not what i was talking about.

    I have one singleton entity called "EditorScene" (since my game has a map editor), i also have a singleton entity called "IngameScene" (when the player is trying their map).

    I turn these on and off depending on what the player is currently doing (he is ingame or he is in editor)

    Now all systems which should only run while the player is in editor, implement "RequireSingletonForUpdate<EditorScene>()" in the start method.
     
  10. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,937
    While the particular example I shared directly checked the scene (because it was convenient for that application), I usually have a tag component or enum-containing component on a singleton I check instead. I still really like the cleanness I get from only having to check in the ComponentSystemGroup rather than every system.
    The biggest factor is having a clear understanding of your execution order. You probably want to initialize your Dictionary in an ICustomBootstrap.
     
  11. TheGabelle

    TheGabelle

    Joined:
    Aug 23, 2013
    Posts:
    242
    I'll continue with my version for prototyping since the project will never be deployed. However, I'm leaning heavily towards a solution very similar to yours for production.
     
  12. calabi

    calabi

    Joined:
    Oct 29, 2009
    Posts:
    232
    I think "RequireSingletonForUpdate" is currently the best simplest method, I used it for a pause system. But I'm hoping Unity are going to implement it so you can just click in the editor which systems you want to run before you run the program, they are aware, it has been requested.