Persistent sceneid #563
Fix sceneId bugs once and for all, hopefully.
This version generates a random sceneId of form 0x00FFFFFF in OnValidate and sets the scene dirty to force the user to save it.
OnPostProcessScene then copies a buildIndex byte into it, so 0x00FFFFFF becomes 0x12FFFFFF or similar. This makes sure that sceneIds are unique across scenes.
This works perfectly fine with uMMORPG and NetworkZones.
Note: when building a scene that wasn't opened with this Mirror version yet (where the sceneIds weren't set yet), the generated sceneIds won't be saved but an error message will be shown at runtime in OnPostProcessScene that tells the user to resave that scene.
referenced this pull request
Mar 8, 2019
Posting this here too for future reference:
Update: very happy to announce that the Persistent SceneId fix is live [ https://github.com//pull/563 ].
This bug caused me tremendous amounts of pain during the last three years. In fact, I was the one who originally reported it to the UNET team back then.
Original UNET used FindObjectsOfType to assign sceneIds with a simple counter. The problem was that the FindObjectsOfType order was not deterministic. In other words, the result was often scrambled. So when building a game and connecting to it with the Editor, everything worked fine until we restarted the Editor and got a completely different FindObjectsOfTypeOrder, at which point the Editor and the Build would get out of sync. This resulted in situations where sometimes a player's move on the server would actually move a monster on the client, which is a complete nightmare to deal with.
Later on when starting HLAPI Community Edition, I found a workaround where FindObjectsOfType was sorted by the actual order in the Hierarchy (by the sibling path to be exact). This worked perfectly fine for one scene, and in fact the UNET team even ported my fix to UNET. Instead of using the sibling path, they used only the root index though, which would still result in the same type of errors if the scene object was a child object of something else.
Eventually people started switching scenes at runtime, which exposed a flaw in my original fix. When loading a scene for the second time, it's possible (in fact, likely) that objects like the NetworkManager were moved into the DontDestroyOnLoad scene, hence offseting the sibling path by -1 and getting the client and the server out of sync again.
The next approach was to use Unity's fileID. Unity stores a fileID value for each object and each component in the Scene. It's persistent across restarts, and can be found in the Scene.unity files. The fileID itself is not directly available in Unity. There is however a workaround where a script is used to set the Inspector to debug view, at which point the LocalIdentifierInFile could be read. This was the perfect solution, except that it didn't work all the time. the LocalIdentifierInFile is sometimes 0 if Play wasn't pressed yet, and it even changes at runtime occasionally. There is no documentation on this field either, but it seems like this is not the true fileID at all times, only sometimes.
My final solution was to generate a random sceneID of the format 0x00FFFFFF in Edit time, whenever OnValidate is called. Generating the sceneID sets the scene as dirty, which adds a '*' behind the scene name to indicate the user that it needs to be resaved. In other words, each NetworkIdentity gets a random sceneID that is stored on disk and never saved again afterwards.
This alone was not enough yet though. If a user duplicates SceneA.unity to SceneB.unity, then ObjectA from SceneA will have the same sceneID as ObjectA in SceneB. This second problem was solved by shifting the scene's build index into the SceneID when the scene is first loaded. The previous sceneId of 0x00FFFFFF would then change to 0x01FFFFFF for the scene at index = 1. As result, we get a unique sceneId that even works if the user duplicates a scene.
The third critical problem were unopened scenes. A lot of people will upgrade to the latest Mirror version for the fix, and there is a small chance that a user with SceneA and SceneB might only open SceneA, get all sceneIDs for SceneA assigned and then build the project without ever actually opening SceneB again, leaving all the SceneB sceneIDs still empty. In that case, Unity would call OnValidate for each object in SceneB, Mirror would generate a sceneID, but the scene would never get saved because the modifications happened at build time. This issue would not affect new Mirror users (because they always opened each of their new scenes with the latest Mirror version), but even if we were to add a huge "PLEASE RESAVE ALL SCENES ONCE" warning, someone would still miss it and run into strange missing sceneID errors.
This third problem was solved by a simple isBuilding check to never assign sceneIDs in OnValidate. Instead, users with yet unopened scenes will receive an Error message that even cancels the build process, notifying them to resave the unopened scene once.
In the end, we get perfectly persistent sceneIds that work across multiple scenes in (so far) all possible scenarios. This fix will allow you to implemented advanced features like zones, instanced dungeons, player housing and (possibly) additive scene loading.
I believe this was the last critical bug in Mirror. Enjoy!