Search Unity

Question SubScene Questions

Discussion in 'Entity Component System' started by florianhanke, Oct 27, 2020.

  1. florianhanke

    florianhanke

    Joined:
    Jun 8, 2018
    Posts:
    426
    1. I assumed SubScenes (because they are saved) were meant for reuse over several scenes in addition to performing a GO -> entity conversion ahead of time. But I think I am wrong (re the former) and they are only used in one (their) scene – can anyone confirm/deny? If they cannot be reused, why not?

    Explanation: For networking, I have a Ghost Collection SubScene, which I intended to simply reuse in each scene:
    Screenshot 2020-10-27 at 22.13.51.png
    But it looks like using a GhostCollection prefab is still the way to go, and pull that into each scene's subscene. Like that, the Ghost Collection needs to be converted separately for each of these subscenes instead. It would be great if I could just pull in the subscene with other needed components into each scene.

    2. Using the above Ghost Collection SubScene – when I call Instantiate on a Prefab from the Ghost Collection list inside the Ghost Collection SubScene, new Prefab instances "live" in the Ghost Collection SubScene (looking at the Entities editor). I'd much prefer these instances to live top-level or in another subscene (e.g. "Scenario A"). How can I instantiate them to live there?

    3. Are there in-depth Unity docs on SubScenes? I found the conversion pieces here: https://docs.unity3d.com/Packages/com.unity.entities@0.14/manual/gp_overview.html If there are, can anyone point me to them. It would be great to have some design decision infos as well so I know how to use them "properly", if something like that has already cristallized :)
     
    Samsle, Piefayth, jdtec and 1 other person like this.
  2. florianhanke

    florianhanke

    Joined:
    Jun 8, 2018
    Posts:
    426
    Does anybody have any ideas?
     
    bb8_1 likes this.
  3. Enzi

    Enzi

    Joined:
    Jan 28, 2013
    Posts:
    966
    With subscenes I needed to re-think my approach to scene loading and the scene structure in general because it got really weird with finding a comfortable setup that works both for the editor and builds.

    Previously I had ghost, server, environment scene loaded but now I just have a loader scene open which handles the loading. Subscene loading is fast, so when programming it's not an issue to load some scenes before you are actually in-game. For artists it's harder to quickly go ingame but working in the editor and in a scene, I don't think they need to that much.

    Loading subscenes goes like this:
    Code (CSharp):
    1. var sceneSystem = World.GetExistingSystem<SceneSystem>();
    2. sceneSystem.LoadSceneAsync(SceneGUID);
    The subscene can be referenced to find out the SceneGUID. Don't hardcore it. My builds were crashing with hardcoded scene guids.

    In the subscene I also created an entity SubsceneName_SubsceneLoaded for every subscene I have, which works as singleton entity to check if the subscene already has been loaded. Some and myself had problems with subscenes loading multiple times and that got it under control.
    With this singleton you can also find out when the subscene is loaded to start updating other systems that depend on it.

    There's also a nasty bug where unloaded scenes in the hierarchy are loaded twice and can't be really closed. One stays open. If this gets fixed a loader could be written that also finds closed scenes and re-uses them which enables having multiple scenes in the editor open.

    2. I don't think it's a real problem but yeah, finding instantiated entities that weren't logged is really hard when you have lots of entities.
     
    Samsle, florianhanke and bb8_1 like this.
  4. kanesteven

    kanesteven

    Joined:
    Aug 30, 2018
    Posts:
    45
    Honestly, @florianhanke and @Enzi I have found the use of SubScenes to be extremely confusing bordering on infuriating and insane. I have spent a full week trying to get an application architecture together for my multiplayer game to allow me to have SubScenes which may be loaded / unloaded by the server as well as the client in their respective worlds.

    The workflow I am aiming for is as follows:

    SERVER STARTUP
    Create Server World
    Start Server Listen
    Load SubSceneReferences SubScene into Server World
    Load GhostPrefabs SubScene into Server World
    Load LevelStaticGeometry SubScene into Server World
    Load LevelGhosts SubScene into Server World

    CLIENT STARTUP
    Initialize Client Application World
    Initialize Client World
    Load SubSceneReferences SubScene into Client Application World
    Load GhostPrefabs SubScene into Client World


    CLIENT CONNECT TO SERVER
    Create Client World
    Start Connect to Server
    Load LevelStaticGeometry SubScene into Client World
    Appropriate Ghosts should stream to you from the server

    CLIENT DISCONNECT FROM SERVER
    Disconnect from Server
    Destroy the Client World Completely!

    This is what my "BootStrap Scene" looks like. This scene is the first scene to load during a build.

    upload_2020-11-15_22-38-20.png

    Normally, when I boot up the scene I would like to have both SubSceneReferences and SharedResources closed, not loaded, and with autoload disabled. It looks like this.

    upload_2020-11-15_22-39-45.png

    However, when I try to actually LOAD a SubScene I just completely strike out. I can call
    sceneSystem.LoadSceneAsync(MySubSceneGUID)
    on any world including the Server or Client worlds. However, the scene does not seem to ACTUALLY load in any case. I am completely unsure why this is. This workflow is disastrously-bad currently and has mystified and confounded me despite intense efforts to understand it. The application-bootstrap flow I cited above should be very easy to setup and get running but has proven to be incredibly difficult.
     
    alexchisholm343, Samsle and bb8_1 like this.
  5. bb8_1

    bb8_1

    Joined:
    Jan 20, 2019
    Posts:
    100
    @timjohansson can you help with issues mentioned in posts above^^^^?
     
  6. florianhanke

    florianhanke

    Joined:
    Jun 8, 2018
    Posts:
    426
    Thanks, Enzi, for your help.
    This is one cat that can be skinned in many ways. By trying quite a few approaches and avoiding unhelpful errors such as "Loading Entity (Sub)Scene failed because the entity header file couldn't be resolved.", I eventually got it to run.

    My setup is as follows:
    Screenshot 2020-11-16 at 16.37.31.png
    An open separate Loader scene, parallel to the actual scene, "FireTest", with the GhostCollection SubScene and a Settings scene. Both are not loaded and do not autoload.

    Then I have a SubSceneLoader:
    Screenshot 2020-11-16 at 16.37.43.png
    It can be passed SubScenes.

    The system picking this up is the SubSceneLoaderSystem:
    Code (CSharp):
    1. using BUD.Components.Management.Authoring;
    2.  
    3. using Unity.Entities;
    4. using Unity.NetCode;
    5. using Unity.Scenes;
    6.  
    7. using UnityEngine;
    8.  
    9. using Hash128 = Unity.Entities.Hash128;
    10.  
    11. namespace BUD.Systems.Managers
    12. {
    13.     [UpdateInWorld(UpdateInWorld.TargetWorld.ClientAndServer)]
    14.     public class SubSceneLoaderSystem : SystemBase
    15.     {
    16.         private SceneSystem    sceneSystem;
    17.         private SubSceneLoader subSceneLoader;
    18.         private bool        loaded;
    19.  
    20.         protected override void OnCreate()
    21.         {
    22.             sceneSystem = World.GetExistingSystem<SceneSystem>();
    23.             subSceneLoader = (SubSceneLoader) Object.FindObjectOfType(typeof(SubSceneLoader));
    24.         }
    25.  
    26.         protected override void OnUpdate()
    27.         {
    28.             if (loaded)
    29.             {
    30.                 return;
    31.             }
    32.  
    33.             foreach (var subScene in subSceneLoader.subScenes)
    34.             {
    35.                 if (subScene.SceneGUID.IsValid)
    36.                 {
    37.                     loadSubScene(subScene.SceneGUID);
    38.                     loaded = true;
    39.                 }
    40.             }
    41.         }
    42.  
    43.         protected Entity loadSubScene(Hash128 hash)
    44.         {
    45.             var loadParameters = new SceneSystem.LoadParameters
    46.             {
    47.                 Flags = SceneLoadFlags.LoadAdditive
    48.             };
    49.             return sceneSystem.LoadSceneAsync(hash, loadParameters);
    50.         }
    51.     }
    52. }
    I'm posting this not because its code is great – it is not – but maybe it helps somebody. This system could of course be split into Client and Server via UpdateInWorld and Subscenes only loaded in one or the other.
    Note: Loading SubScenes not via MonoBehaviour but IComponentData did not work, but the error was not helpful, so I never figured out the issue.

    @Enzi If you have the time, I'd be really interested in your setup.

    Personally, I am wondering if I could make the SubScenes load themselves in the Loader Scene, by moving the SubSceneLoader to the Loader scene.

    It depends if you want to load/unload subscenes during play, doesn't it? Imagine spawning NSCs, then unloading the SubScene they are located in – but they themselves are not unloaded since they do not live in that SubScene.
    I'm just noticing that I am unsure how unloading and loading works – whether dynamically added entities would survive…?
    What's frustrating is that these are questions that could easily be answered in a small document describing the behaviour and perhaps best practices of SubScenes… :( (So, yeah, I know where you are coming from, @kanesteven )
     
    Last edited: Nov 16, 2020
    jdtec, bb8_1 and Samsle like this.
  7. kanesteven

    kanesteven

    Joined:
    Aug 30, 2018
    Posts:
    45
    @florianhanke @Enzi Ok good news I suppose: I did a bunch more work on this and found out what I think the magic formula is for properly using SubScenes to organize/load/unload data during gameplay / bootstrap. The code below is a custom bootstrap that creates two worlds:
    GameWorld
    and
    ApplicationWorld
    . The
    GameWorld
    supports all the systems you would expect including
    NetCode, Physics, and the HybridRenderer
    . The
    ApplicationWorld
    currently strips out
    HybridRenderer
    systems as it does NOT support multiple worlds currently. You could imagine tailoring the systems for the
    ApplicationWorld
    to suit your needs but beware that you need all the necessary systems to support SubScene management that are present in the
    SceneSystemGroup
    .

    This code will load all SubScenes it finds in your application's initial scene but you can imagine it doing something more specific easily by simply changing or removing that logic.


    Code (CSharp):
    1. using System;
    2. using System.Linq;
    3. using Unity.Entities;
    4. using Unity.Scenes;
    5. using Unity.Rendering;
    6. using UnityEngine;
    7. using Hash128 = Unity.Entities.Hash128;
    8.  
    9. public class CustomBootstrap : ICustomBootstrap {
    10.   public static bool InGroup<T>(object t) => t is UpdateInGroupAttribute && ((UpdateInGroupAttribute)t).GroupType == typeof(T);
    11.  
    12.   public static bool AnyTypeOf<A, B, C>(Type t) => t == typeof(A) || t == typeof(B) || t == typeof(C);
    13.  
    14.   // Filter out systems not appropriate for the application world.
    15.   // In this case, we omit HybridRenderer Systems as it does not support multiple worlds currently
    16.   public static bool IsApplicationSystem(Type type) {
    17.     var customAttributes = type.GetCustomAttributes(inherit: true);
    18.  
    19.     if (customAttributes.Any(InGroup<DeformationsInPresentation>)) {
    20.       return false;
    21.     }
    22.     if (AnyTypeOf<HybridRendererSystem, DeformationsInPresentation, MatrixPreviousSystem>(type)) {
    23.       return false;
    24.     }
    25.     return true;
    26.   }
    27.  
    28.   // Uses a simple, custom SubSceneRequest system to create entities for the SceneSystem
    29.   public static Entity CreateSubSceneLoadRequest(EntityManager entityManager, SubScene subScene) {
    30.     var loadRequestEntity = entityManager.CreateEntity(typeof(SubSceneLoadRequest));
    31.  
    32.     entityManager.SetComponentData(loadRequestEntity, new SubSceneLoadRequest { SceneHash = subScene.SceneGUID });
    33.     return loadRequestEntity;
    34.   }
    35.  
    36.   // Uses the SceneSystem API directly. This does NOT work from Bootstrap!
    37.   public static Entity LoadSubSceneIntoWorld(World world, SubScene subScene) {
    38.     var loadParameters = new SceneSystem.LoadParameters {
    39.       Flags = SceneLoadFlags.LoadAdditive
    40.     };
    41.  
    42.     return world.GetExistingSystem<SceneSystem>().LoadSceneAsync(subScene.SceneGUID, loadParameters);
    43.   }
    44.  
    45.   public bool Initialize(string defaultWorldName) {
    46.     var gameWorld = new World("Game World");
    47.     var applicationWorld = new World("Application World");
    48.     var systems = DefaultWorldInitialization.GetAllSystems(WorldSystemFilterFlags.Default);
    49.     var applicationSystems = systems.Where(IsApplicationSystem);
    50.  
    51.     World.DefaultGameObjectInjectionWorld = gameWorld;
    52.     DefaultWorldInitialization.AddSystemsToRootLevelSystemGroups(gameWorld, systems);
    53.     ScriptBehaviourUpdateOrder.AddWorldToCurrentPlayerLoop(gameWorld);
    54.     DefaultWorldInitialization.AddSystemsToRootLevelSystemGroups(applicationWorld, applicationSystems);
    55.     ScriptBehaviourUpdateOrder.AddWorldToCurrentPlayerLoop(applicationWorld);
    56.  
    57.     // Let's load all SubScenes that are not already loaded on startup
    58.     foreach (var subScene in GameObject.FindObjectsOfType<SubScene>()) {
    59.       if (!subScene.AutoLoadScene && !subScene.IsLoaded) {
    60.         CreateSubSceneLoadRequest(gameWorld.EntityManager, subScene);
    61.         CreateSubSceneLoadRequest(applicationWorld.EntityManager, subScene);
    62.         // These API calls directly to the world's SceneSystem do NOT work in Bootstrap
    63.         // LoadSubSceneIntoWorld(gameWorld, subScene);
    64.         // LoadSubSceneIntoWorld(applicationWorld, subScene);
    65.       }
    66.     }
    67.     return true;
    68.   }
    69. }
    70.  
    71. public struct SubSceneLoadRequest : IComponentData {
    72.   public Hash128 SceneHash;
    73. }
    74.  
    75. public struct SubSceneUnloadRequest : IComponentData {
    76.   public Hash128 SceneHash;
    77. }
    78.  
    79. [AlwaysUpdateSystem]
    80. [UpdateInGroup(typeof(SceneSystemGroup))]
    81. [UpdateBefore(typeof(SceneSystem))]
    82. public class SubSceneRequestSystem : SystemBase {
    83.   SceneSystem SceneSystem;
    84.  
    85.   protected override void OnCreate() {
    86.     SceneSystem = World.GetExistingSystem<SceneSystem>();
    87.   }
    88.  
    89.   protected override void OnUpdate() {
    90.     Entities
    91.     .ForEach((Entity e, ref SubSceneLoadRequest loadRequest) => {
    92.       SceneSystem.LoadSceneAsync(loadRequest.SceneHash, new SceneSystem.LoadParameters { Flags = SceneLoadFlags.LoadAdditive });
    93.       EntityManager.DestroyEntity(e);
    94.     })
    95.     .WithStructuralChanges()
    96.     .WithoutBurst()
    97.     .Run();
    98.  
    99.     Entities
    100.     .ForEach((Entity e, ref SubSceneUnloadRequest unloadRequest) => {
    101.       SceneSystem.UnloadScene(unloadRequest.SceneHash);
    102.       EntityManager.DestroyEntity(e);
    103.     })
    104.     .WithStructuralChanges()
    105.     .WithoutBurst()
    106.     .Run();
    107.   }
    108. }
    Here are some images from the editor showing the results of this initialization process:

    Edit-mode with SubScene unloaded and no AutoLoad
    upload_2020-11-17_13-3-15.png

    Play-mode with SubScene loaded into
    GameWorld

    upload_2020-11-17_13-4-17.png

    Play-mode with SubScene loaded into
    ApplicationWorld

    upload_2020-11-17_13-5-23.png

    I hope this helps people out there that are struggling with this problem. It took me significant effort to sort this out but I do now feel I have a pretty firm grasp of the intended architecture for a multiplayer game managing multiple worlds.
     

    Attached Files: