Search Unity

Feature Request Better Bootstrap/Global scene workflow

Discussion in 'Editor Workflows' started by peaj_metric, Mar 6, 2023.

  1. peaj_metric

    peaj_metric

    Joined:
    Sep 15, 2014
    Posts:
    146
    It is a bit tricky to explain the problem I am trying to solve but I will do my best.

    The recent Multiplayer webinar reminded me of the fact that almost every mid sized Unity project uses some kind of bootsrap scene to load and initialize global (game wide) mangers, services etc.
    The boss room sample has a boostrap scene as well as the FPS sample project.
    I also have this concept of either a bootstrap or global scene (or both) in all of my bigger projects.

    The problem with this scene flow is that it isnt compatible with the "playmode from everywhere" workflow.
    To keep iteration times low it it is a best practice to build the project so you can enter playmode from every scene but the bootstrap scene breaks this.

    There are multiple ways to work around this, with editor scripting etc. But none of the solutions is perfect and everyone has to build their own custom solution for that. Also it gets a lot more complicated when async operations are used in managers/services (e.g. loading settings or save games)

    So the ideal scene flow would be the follwing:
    1. load bootstrap scene
    2. initialize bootstrap scene (wait for async operations)
    3. load "level 1"
    4. initialize "level 1" (OnEnable/Awake)
    5. access bootstrap manager/service from "level 1"
    This perfectly works at runtime or when you enter playmode from bootstrap scene.
    But when you press play in "level 1" the "access manager" step will fail as the bootstrap scene is not present.
    It is possible to additively load the bootstrap scene either manually or via editor tooling but it is not possible to ensure that it is fully initialized (e.g. async operations done) before other scripts from "level 1" will access it.

    So the goal is to keep the flow consistent no matter where you start from.
    The best solution I could come up with to achieve this execution order consistency is to use playModeStartScene to override the current scene with the bootstrap scene when starting playmode. You can then store the currently open scenes (in playerprefs or a file) to reload them after the bootstrap scene has finished initializing:
    1. save open scene paths
    2. unload open scenes
    3. load bootstrap (playmodestart) scene
    4. initialize bootstrap scene (wait for async operations)
    5. load opened scenes (from stored scene paths)
    6. initialize opened scenes (OnEnable/Awake)
    7. access bootstrap manager/service from opened scene (level 1)
    This works to keep consistency. This does greatly increase the time to playmode though as the currently open scene(s) has to be unloaded and reloaded again.

    So I am stuck and have to choose between execution consistency or and iteration time.

    The ideal editor flow would be the following:
    1. disable all open scenes
    2. load bootstrap scene
    3. initialize bootstrap scene (wait for async operations)
    4. initialize all open scenes (OnEnable/Awake)
    5. access bootstrap manager/service from opened scne (level 1)
    This would provide a consistent execution order without sacrificing iteration time.

    So I have some proposals for possible Unity features to make this workflow possible.

    1. Global scene scope
    This would be a big change but having a global scope for game objects would make it easy to manage game objects that have the same live time as the game and will not be unloaded by scene loading. Having this scope always loaded and initialized before any other scene gets initialized also helps

    2. Having a static initialization API
    A static API call like "InitializeOnload" that can delay scene initialization and be used to instantiate or initialize the manager/service objects before initializing any other scene objects

    3. More scene settings/flags

    Having scene flags like "DontDestroyOnLoad" and a scene setting for initialization order could enable workflows where a bootstrap scene is initialized forst and disables other scenes until it is done.

    I think most of this boils down to being able to execute code before scenes get initialized.
    So I am also curious if there are any other possible solutions to this.
     
    FelipeCavaco likes this.
  2. peaj_metric

    peaj_metric

    Joined:
    Sep 15, 2014
    Posts:
    146
    Here is the ChatGPT TLDR:

    Issue: Mid-sized Unity projects commonly use a bootstrap scene to load and initialize global managers and services, but this workflow is not compatible with the "playmode from everywhere" workflow. When play mode is started from a scene other than the bootstrap scene, the global managers and services may not be properly initialized, leading to issues.

    Proposed feature: Add a new feature to Unity that allows for consistent initialization of global managers and services in mid-sized projects without sacrificing iteration time. This feature could involve a way to disable all open scenes, load the bootstrap scene, initialize it (waiting for async operations), initialize all open scenes, and then access the global managers and services. This would provide a consistent execution order without sacrificing iteration time.
     
  3. noio

    noio

    Joined:
    Dec 17, 2013
    Posts:
    232
    Yeah, always run into the same thing. I guess everyone has their own workaround.

    - In my "Level 1" scene, I don't rely on
    Awake()
    /
    OnEnable
    to initalize.
    - If I press play from Level 1, a script makes sure that Bootstrap is loaded first.
    - When Bootstrap is done loading (and all the managers etc..), it loads Level 1 (if Level 1 was already open in Editor, it does nothing)
    - Then, Bootstrap fires some event ( let's say
    SceneLoaded
    )
    that the objects in "Level 1" use to know that the game is ready to load (instead of Awake())

    Let's add it to the list of "every mid-size project has re-invented this" :D, one of those typical things that it'd be nice if Unity had something out of the box.

    I like your proposed solutions though! My "don't use
    Awake()
    " solution is quite terrible of course because it goes against the standard way of doing things and you have to keep that in mind.
     
    peaj_metric likes this.
  4. noio

    noio

    Joined:
    Dec 17, 2013
    Posts:
    232
    I like your solution 3 the best.

    A solution that would be close to the way people are already doing things could be:

    Having a single designated "Bootstrap" scene (that you can set in Project Settings, or something), of which Unity makes sure that it's always loaded first when you click play. I think that's essentially what most people are doing already? Kind of what you described as "setting for initialization order"
     
    peaj_metric likes this.
  5. peaj_metric

    peaj_metric

    Joined:
    Sep 15, 2014
    Posts:
    146
    Yeah avoiding
    Awake()
    /
    OnEnable
    is also a an option I considered.
    This has two main problems though:
    1) It is hard to ensure everybody on the team abides to that limitation (I would need to enforce it technically I guess)
    2) Third party scripts and Unity components don't abide to this.

    This means e.g. rigidbodies will start falling before your initialization code has run. This sounds like a minor thing but I too often encountered issues that only happen when starting/not starting from bootstrap.
     
  6. peaj_metric

    peaj_metric

    Joined:
    Sep 15, 2014
    Posts:
    146
    I just stumbled upon the fact that Godot actually has a system like that.
    It is called Autoloads and is a list of global scenes that are always loaded before anything else.
    This allows to easily run your open scene in editor while still loading all your global services/managers first.
    I am not sure though if it allows blocking scene intitialization unitil your initialization is ready.