Singleton Issue When Switching Scenes

Hi all,

I’ve got 2 manager scripts that control a large portion of the game, which are meant to be singletons. As stands, they get initialized once, and then the scene switches from Menu to Match, they get reparented to the new root (with no issues), and then the match ends and the scene switches from Match to Menu, which is where the trouble seems to be.

Quick important code bits (two scripts look identical in this regard, so just going to copy what’s necessary):

GameManager.prototype.initialize = function() {
    if (window.GManager !== undefined) {
        this.entity.destroy();
    }
    
    window.GManager = this;
    //additional code
}
GameManager.prototype.ToMainMenu = function() {
    let oldRoot = pc.app.root.findByName("Root");
    let menuScene = pc.app.scenes.find("Menu");
    
    pc.app.scenes.loadSceneHierarchy(menuScene.url, (err, newRoot) => {
        if (err)
            throw err;
        
        GClient.entity.reparent(newRoot);
        GManager.entity.reparent(newRoot);
        
        oldRoot.destroy();
        //additional code
    });
}

So the code is identical for entering match and menu, but the key difference is the appearance of the first block of code’s if statement, where it looks to see if GClient/GManager already exists. On

oldRoot.destroy();

I get the following error:

playcanvas-stable.dbg.js:27007 Uncaught TypeError: Cannot read property 'data' of undefined
    at ScriptComponentSystem.removeComponent (playcanvas-stable.dbg.js:27007)
    at Entity.destroy (playcanvas-stable.dbg.js:43000)
    at Entity.destroy (playcanvas-stable.dbg.js:43009)
    at gameManager.js?id=31393588&branchId=7541d15a-ca7f-4e76-87f6-b3bcbb9c6757:425
    at _loaded (playcanvas-stable.dbg.js:26825)
    at Application._preloadScripts (playcanvas-stable.dbg.js:26111)
    at playcanvas-stable.dbg.js:26828
    at launch.js:10932

which traces down to (Entity.destroy, lines 42999-43013)

for (name in this.c) {
      this.c[name].system.removeComponent(this);
    }
    if (this._parent) {
      this._parent.removeChild(this);
    }
    var children = this._children;
    var child = children.shift();
    while (child) {
      if (child instanceof pc.Entity) {
        child.destroy();
      }
      child._parent = null;
      child = children.shift();
    }

and finally (removeComponent)

var record = this.store[entity.getGuid()];
    var component = entity.c[this.id];
    this.fire("beforeremove", entity, component);
    delete this.store[entity.getGuid()];
    delete entity[this.id];
    delete entity.c[this.id];
    this.fire("remove", entity, record.data);

If I understand the issue correctly, the removal of the GClient/GManager entity pair is being called when the scene loads, and then oldRoot.destroy() attempts to destroy all children in the scene, including the now destroyed entity pair, which causes an error.

I suppose this is a long way of asking, but is this a bug, or is my method of doing attempting to carry a singleton script between scenes flawed? The idea was to follow what I do in Unity (you’d call it in awake there), but it looks like the issue isn’t in the logic, but a conflict with the PlayCanvas engine itself.

There are a couple of ways of doing this. The way I would do it is to have a Scene that contains all the singleton scripts and a child that the actual scenes get parented to. An example can be found here: https://developer.playcanvas.com/en/tutorials/additive-loading-scenes/

As for the bug itself, it’s very possible that calling destroy on initialise causes issues as part of the load and will need investigation.

Thanks @yaustar, that’s certainly one way to solve the problem. I’m halfway through implementing it now, but I don’t see any issues with the approach, so it should be smooth sailing.

1 Like

@TingleyWJ I had a go at this and couldn’t reproduce your error: https://playcanvas.com/editor/scene/939684

(Don’t try to load the same scene twice in a row :sweat_smile:)

Your SceneRoot suggestion ended up working out for me, so I abandoned the singleton approach. I’ve moved quite a bit forward with the project since then (and don’t really have the time to mess around with it further), so to anyone coming across this thread in the future, if you’re trying to set up objects like Unity’s DontDestroyOnLoad(), just follow @yaustar’s example/advice.

Unless you want to try and get singleton to work. If you do, pop a reply here, I’m sure others would be interested.