#Dealing with delay between bringing in scene and have it subscribe to event?

1 messages · Page 1 of 1 (latest)

tame notch
#

Hello everyone, I have a scene that upon Start() subscribes to an event. No matter how I load the scene if I invoke the event directly after, the new scene hasn't subscribed to the event yet and the required action doesn't fire. If I put a 1 second delay in the code then everything works as expected.

Any thoughts/suggestions?

bleak ocean
#

use Awake to subscribe, perhaps

#

Awake is run when the gameobject is first activated (including upon load), but Start is run on the first frame the gameobject is active and the component is enabled

#

if you need to use Start for some reason, then wait a frame before firing the event

tame notch
#

Oh, that would be great. I'm refactoring the code. Hopefully I can test those ideas in the next 30 minutes.

#

Thank you!

tame notch
#

@bleak ocean : Unfortunately that didn't work. I tried async and syncronous and it didn't make a difference. I just setup an event that triggers after my scene controller loads and even that doesn't load. Seems like awake() + start() don't happen until the next frame? Is there really no way to make this work without waiting to the next frame? That feels really hacky

mossy marlin
bleak ocean
#

shouldn't it be called whne the scene loads directly, not frame-bound?

mossy marlin
#

I think so. My point was that it should be called by the point of the first frame. Definitely not any later than that.

smoky island
#

loadSceneAsync and ditch out awake..

tame notch
#

Sorry...I left out something critical. I'm pulling in the scenes additively via a persistant scene.

wild idol
#

Id load the scene, find and init some component myself so then it happens in time (hopefully)

#

I basically always init components manually and thus avoid this awake/start dance

tame notch
#

Ok. Let me take a step back. This all stems from me not understanding how to communicate between scenes properly. As I said prior, I am running core logic in a persistent scene (P), there is currently another scene running (A), it pulls in a scene (B) and wants to run some code from that scene. The only way I know of to handle this is via delegates. The problem is, I need scene B to subscribe to the delegate. Even after I fully load in scene B, it has not run the code in awake() / has not subscribed so in script A, when I invoke the delegate, it doesn't run the code.

#

Sorry for being so stupid! I can't get my head around this yet.

wild idol
#

So its a timing issue
As I said, you could load scene b, find a component in it, init it manually to cause the event sub to happen then.
When is the event invoked in relation to loading scene B?

tame notch
#

Directly after.

wild idol
#

How is the scene additively loaded, scene manager? addressables?

tame notch
#

scene manager script in the persistant sene. I'm not sure what addressables means

#

(singleton)

wild idol
#

what line of code does the load?

#

if SceneManager.LoadSceneAsync() is awaited I "think" that would be the best way to know for sure that scene B has been fully loaded

#

LoadScene() mentions that it completes the load 1 frame later

kind warren
#

You should be able to use find methods or scan root objects

tame notch
#
SceneController.instance.LoadQuick(sceneName);

scenecontroller =

    public void LoadQuick(string sceneName)
    {
        scenesAdd.Add(sceneName);
        Load();
    }

public void Load()
{
    // Remove scenes
    for (int i = 0; i < scenesRemove.Count; i++)
    {
        if (LoadTest(scenesRemove[i]))
        {
            UnloadScene(scenesRemove[i]);
        }
    }
    // Add in scenes
    for (int i = 0; i < scenesAdd.Count; i++)
    {
        if (LoadTest(scenesAdd[i]) == false)
        {
            LoadScene(scenesAdd[i]);
        }
    }
    // Track progress
    StartCoroutine(LoadingProgress());
}

 public IEnumerator LoadingProgress()
 {
     if (progressScene)
     {
         LoadingController.instance.Enable();
     }
     if (loadAsync && scenesList.Count > 0 && 1 ==2)
     {
         for (int i = 0; i < scenesList.Count; i++)
         {
             while (!scenesList[i].isDone)
             {
                 float progress = 0;
                 foreach (AsyncOperation scene in scenesList)
                 {
                     progress += scene.progress;
                 }
                 progress = (progress / scenesList.Count);
                 EventController.loadingProgress?.Invoke(progress);
                 yield return null;
             }
         }
     }
     EventController.sceneComplete?.Invoke();
     if (progressScene)
     {
         LoadingController.instance.Disable();
     }
 }
wild idol
#

hmm the function that matters is not there

tame notch
#

sorry

#
    public void LoadScene(string sceneName)
    {
        SceneIndex sceneIndex = (SceneIndex)Enum.Parse(typeof(SceneIndex), sceneName);
        if (loadAsync)
        {
            scenesList.Add(SceneManager.LoadSceneAsync((int)sceneIndex, LoadSceneMode.Additive));
        }
        else
        {
            SceneManager.LoadScene((int)sceneIndex, LoadSceneMode.Additive);
        }
    }
#
    private List<AsyncOperation> scenesList = new List<AsyncOperation>();
wild idol
#

does this use the sync or async load scene function?

#

(scene b)

#

Adding a single frame wait after the scene has loaded would probably solve this problem and I do this myself with addressable additive scene loads

tame notch
#

I tried with both settings

wild idol
#

and does this other logic use EventController.sceneComplete to know when the scene loaded?

tame notch
#

yeah, it subscribes to it

wild idol
#

scene b?

tame notch
#

correct. The one that just got loaded in.

wild idol
#

that makes no sense

tame notch
#

haha....its the best I got

wild idol
#

why would the NEWLY loaded scene use an event relating to its own loading?

#

Or did i miss understand?

tame notch
#

The newly loaded scene (B) subscribes to an event that is being invoked in scene (A) directly after it has been loaded.

#

Thats the best way I could come up with to run code between scenes

wild idol
#

why not just get a component in the newly loaded scene B and invoke a function directly?

tame notch
#

All I can say is that I researched this hours and talked a lot of people at they made it sound like that was not possible.

#

I just want to run code in the other scene and adhere to best practices

wild idol
#

its so so easy

#

a Find() would work because that looks over ALL loaded scenes

tame notch
#

Does it make a difference which one?

wild idol
#

well second may be a little faster but I usually just do something like this:

await LoadNewSceneAsync(newLevelScene.address);

var levelInit = GameObject.FindObjectOfType<LevelInitBase>();
await InitLevelInitBase(levelInit, levelTemplate);
#

2022 hence older api

tame notch
#

Ok. That seems a lot easier. I'm going to explore it. You can do something similar with tags right?

wild idol
#

well ill point out that you already have a component that subs to this event so why not locate that specifically and call a function on that instance?

tame notch
#

That sounds like what I want. Do you mean via FindObjectofType?

wild idol
#

Yea or by searching the root gameobjects:

SomeComponent c = null;
foreach(var rootOb in scene.GetRootGameObjects())
{
  if(rootOb.TryGetComponent(out c))
  {
    break;
  }
}

if(c != null)
{
  //Yay we found it!
}
else Debug.LogError("Special component was not found");
willow flint
# tame notch Thats the best way I could come up with to run code between scenes

you could put a script in the loaded scene, in its Start() it finds some sort of Registry object in your persistent scene and calls Hello() on that registry. You can use a ServiceLocator singleton to do that or use FindObjectByType(). This would be the most 'unity' way to do it. Your event-subscription thing to execute code sounds weird. that would kinda invert the inversion of the observer pattern.

wild idol
#

regardless of what does the "handshake" I hope the information is now there so you can pick

tame notch
#

@willow flint : I'd like to understand all possible options. The problem that I had was my script wanted to run code from the newly additively added scene right away. So the registery object idea I think has the same issue as my event approach.

wild idol
#

then get this component from the new scene via a method like we discussed above and then do the shit

willow flint
tame notch
#

@willow flint : I'm sorry, I just don't understand your suggestion. How is the ServiceLocator approach different then a delegate system?

#

So in scene A, I want to bring in scene B and then run some code that requires passing in variables. I also want to load in scene B other times and run different code / not the same as this situation.

wild idol
#

its your choice

tame notch
#

I just want to learn all the different options

wild idol
#

just find/get/whatever the component from scene b and bam happy days

tame notch
#

I'm not opposed to doing find at all

wild idol
#

Id say first get it working and then think about if you want to implement such a design

tame notch
#

he said he did 100 times another way as far as I know there is literally only one way (what you said)

wild idol
#

thats because we pick a design we like and use it a lot

#

ive worked in projects where we had 1 large context reference we would pass to EVERYTHING.
we all hated it but the project was too big and old to really change it so we just kept it

tame notch
#

Yeah...its exactly that part that scares me. I don't want to make some big fundamental decision in the beginning and regret it down the road. I'm not talking about this part per say but the hundreds of choice I have to make.

wild idol
#

what i showed above was something I had in a base shared LevelLoadService that was made to easily load some gameplay scene and initialize it

#

It was used in a few games

#

I learnt to avoid singletons because everything using some static field can be a pain in the ass to undo later

willow flint
#

a service locator is a singleton because it has to be, its just like a dependency injection container or really anything that you NEED to get the one static reference to the app itself. there is no way around having it. Even if you use Find() you use effectively a static global variable. The key is that it is the only one and you don't use it to couple code to concrete implementations. All you do with that service locator (or DI container) is asking "please give me instance of type IMyInterface", and you only do that in Start() and cache it and make all services have a longer lifetime than any of their users. You use editor references where you can, service locator only for dynamically loaded things you don't yet have a reference to. you keep cross-scene comms in a persistent bootstrap/systems scene and never use any static fields for communication ever.

The only option that works completely without a service locator is forward-inejcting all dependencies from a main() method, but that is very anti-unity and blocks many very useful compositional workflows in the editor.

tame notch
#

A ok...that makes sense. I am familar with the idea of dependency injection. Is there a script can you recommend to look at? If I went this approach is it something I should code one myself?

#

a service locator singleton I mean

willow flint
#

a service locator is trivial to DIY, its like 50 lines of code

tame notch
#

Ok..so that's about two days work for me...haha

#

I'm going to do some research...writing it will help me learn

#

Thank you @wild idol @willow flint so much. You really make a big difference

willow flint
# tame notch Ok..so that's about two days work for me...haha

212 lines, sorry https://hastebin.com/share/yegekuziyi.csharp
Important: populate that service locator in Awake() of a bootstrap script of your persistent orchestration scene. Then never change it until your orchestration scene unloads (use the lifecycle binding API in the locator to automate that). Then just Services.Get<T>() the interfaces you need in Start() of dynamically loaded objects to find their 'Registry/System/Manager'. Once they have that reference they call Hello()/Register() on the -System. In OnDestroy() they call Bye()/Unregister() on any -System they previously joined. Each -SystemComponent only registers with one -System and is owned by it. Multiple different components can sit on the same GameObject, each registering with different -System to create complex behaviour objects.

#

one last point, in your journey of discovery you will encounter non-performance ECS (architecture only,), Dependency Injection Containers (Zenject, VContainer) and a minimalistic Service Locator like the one i shared + the "no architecture, do what you want" approaches. the latter one is often called "every project needs to reinvent the wheel". You'll eventually land in one of these camps, and you may switch camps a couple of times. Typically the ServiceLocator is the best compromise that works for solo to small teams, and gracefully can adopt ECS and DI patterns when you actually need them. It does the most important first step: lets you depend on interfaces and is zero extra work to set up (vs Singleton)

wild idol
#

"How many ways can we invent to move around pointers"

willow flint
#

some false alternative is the message broker/bus/pipe, they solve local problems not the overall architectural foundation of your app. If you go down the DI container route. the MessageBus often becomes necessary as a global mechanism. But when you're at a point to actually need it, you are also running a cash-cow live service game.

tame notch
#

What is it about using a DI container that necessitates a message bus?

willow flint
# tame notch What is it about using a DI container that necessitates a message bus?

if you go full DI and IoC, 'clean' communication across systems becomes very tedious, you have to constantly pass messages to the parent scope up to the root and then back down to the target. this target logic is PITA and messes up all the benefits of DI, a message bus is an attempt to put a strong contract around messaging and make it less painful. The problem with DI is generally that it creates a lot of overhead, has very opaque dependency resolution logic and is very easy to produce config bugs that only surface late at runtime (you loose a lot of static code analysis).

#

Overall DI is very dangerous because it so powerful. Used carelessly it causes more problems than it solves. Its very easy to turn it into an overengineered singleton god object hellhole. Also the whole pattern is NOT inherently suited to game projects with objects that have wildly different dynamic lifecycles. Each game-DI framework has its own idea how to gameify the pattern.

tame notch
#

So, I've been doing web development for a couple decades. We use DI everywhere but it makes much more sense in that architecture. Gaming is so different...and so hard.

willow flint
#

but in large teams and live-services it becomes a necessary evil due to high turnover, extreme need for protection of the codebase against careless and low-skill developers. It ultimately helps to make your code more pluggable and testable (if you can afford the overhead)

#

web stuff has entirely different needs/dynamics almost nothing from that world translates well to games.

tame notch
#

haha...yeah, I've learnt that.