From 6f2593e18e48773b52e84c4e7eb5cd524d1d009d Mon Sep 17 00:00:00 2001 From: Scrolen Date: Mon, 22 Jun 2026 17:46:12 -0400 Subject: [PATCH] Fix Unity 6.5 SceneHandle and object ID API errors Adds a Unity compatibility helper for SceneHandle and UnityEngine.Object runtime IDs, replacing obsolete SceneHandle int conversions and Object.GetInstanceID usage that fail compilation in Unity 6.5. Also removes reliance on GetInstanceID sign checks when cleaning up runtime observer condition instances. Validated in Unity 6000.5.0f1 with zero compile errors, host/client connection, reconnect, global scene load/unload, and late client global scene synchronization. --- .../Managing/Scened/SceneLookupData.cs | 131 +++++++++++++++--- .../Runtime/Managing/Scened/SceneManager.cs | 65 +++++---- .../Runtime/Managing/Scened/UnloadedScene.cs | 15 +- .../Runtime/Observing/NetworkObserver.cs | 14 +- .../Runtime/Serializing/Helping/Comparers.cs | 11 +- .../Runtime/Serializing/SceneComparer.cs | 7 +- Assets/FishNet/Runtime/UnityCompatibility.cs | 54 ++++++++ .../Runtime/UnityCompatibility.cs.meta | 2 + 8 files changed, 238 insertions(+), 61 deletions(-) create mode 100644 Assets/FishNet/Runtime/UnityCompatibility.cs create mode 100644 Assets/FishNet/Runtime/UnityCompatibility.cs.meta diff --git a/Assets/FishNet/Runtime/Managing/Scened/SceneLookupData.cs b/Assets/FishNet/Runtime/Managing/Scened/SceneLookupData.cs index 6e3a4554..a84ec970 100644 --- a/Assets/FishNet/Runtime/Managing/Scened/SceneLookupData.cs +++ b/Assets/FishNet/Runtime/Managing/Scened/SceneLookupData.cs @@ -1,4 +1,5 @@ -using GameKit.Dependencies.Utilities; +using FishNet.Utility; +using GameKit.Dependencies.Utilities; using System; using System.Collections.Generic; using UnityEngine.SceneManagement; @@ -24,7 +25,6 @@ public static string[] GetNames(this SceneLookupData[] datas) return names; } - /// /// Returns Names from SceneLookupData. /// @@ -46,13 +46,33 @@ public static string[] GetNamesOnly(this SceneLookupData[] datas) public class SceneLookupData : IEquatable { /// - /// Handle of the scene. If value is 0, then handle is not used. + /// Raw handle of the scene. If value is 0, then handle is not used. /// - public int Handle; + private ulong _rawHandle; + + /// + /// Legacy 32-bit handle view. + /// + public int Handle + { + get => unchecked((int)_rawHandle); + set => _rawHandle = unchecked((uint)value); + } + + /// + /// Raw scene handle value. + /// + public ulong RawHandle + { + get => _rawHandle; + set => _rawHandle = value; + } + /// /// Name of the scene. /// public string Name = string.Empty; + /// /// Returns the scene name without a directory path should one exist. /// @@ -62,16 +82,17 @@ public string NameOnly { if (string.IsNullOrEmpty(Name)) return string.Empty; - + string name = System.IO.Path.GetFileName(Name); return RemoveUnityExtension(name); } } + /// /// Returns if this data is valid for use. /// Being valid does not mean that the scene exist, rather that there is enough data to try and lookup a scene. /// - public bool IsValid => Name != string.Empty || Handle != 0; + public bool IsValid => Name != string.Empty || RawHandle != 0; #region Const /// @@ -89,7 +110,7 @@ public SceneLookupData() { } /// Scene to generate from. public SceneLookupData(Scene scene) { - Handle = scene.handle; + RawHandle = UnityCompatibility.GetSceneHandleRaw(scene); Name = scene.name; } @@ -109,6 +130,14 @@ public SceneLookupData(int handle) Handle = handle; } + /// + /// + /// Raw scene handle to generate from. + public SceneLookupData(ulong handle) + { + RawHandle = handle; + } + /// /// /// Scene handle to generate from. @@ -119,6 +148,16 @@ public SceneLookupData(int handle, string name) Name = name; } + /// + /// + /// Raw scene handle to generate from. + /// Name to generate from if handle is 0. + public SceneLookupData(ulong handle, string name) + { + RawHandle = handle; + Name = name; + } + #region Comparers. public static bool operator ==(SceneLookupData sldA, SceneLookupData sldB) { @@ -159,10 +198,10 @@ public bool Equals(SceneLookupData sld) return false; // True if both handles are empty. - bool bothHandlesEmpty = Handle == 0 && sld.Handle == 0; + bool bothHandlesEmpty = RawHandle == 0 && sld.RawHandle == 0; // If both have handles and they match. - if (!bothHandlesEmpty && sld.Handle == Handle) + if (!bothHandlesEmpty && sld.RawHandle == RawHandle) return true; // If neither have handles and name matches. else if (bothHandlesEmpty && sld.Name == Name) @@ -175,7 +214,7 @@ public bool Equals(SceneLookupData sld) public override int GetHashCode() { int hashCode = 2053068273; - hashCode = hashCode * -1521134295 + Handle.GetHashCode(); + hashCode = hashCode * -1521134295 + RawHandle.GetHashCode(); hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Name); return hashCode; } @@ -187,7 +226,7 @@ public override bool Equals(object obj) public override string ToString() { - return $"Name {Name}, Handle {Handle}"; + return $"Name {Name}, Handle {RawHandle}"; // return base.ToString(); } #endregion @@ -214,6 +253,13 @@ public override string ToString() /// public static SceneLookupData CreateData(int handle) => new(handle); + /// + /// Returns a new SceneLookupData. + /// + /// Raw scene handle to create from. + /// + public static SceneLookupData CreateData(ulong handle) => new(handle); + /// /// Returns a SceneLookupData collection. /// @@ -235,6 +281,13 @@ public override string ToString() /// public static SceneLookupData[] CreateData(List handles) => CreateData(handles.ToArray()); + /// + /// Returns a SceneLookupData collection. + /// + /// Raw scene handles to create from. + /// + public static SceneLookupData[] CreateData(List handles) => CreateData(handles.ToArray()); + /// /// Returns a SceneLookupData collection. /// @@ -297,12 +350,12 @@ public static SceneLookupData[] ValidateData(SceneLookupData[] datas) for (int i = 0; i < result.Count; i++) { bool nameMatches = result[i].Name == item.Name; - bool handleMatches = result[i].Handle == item.Handle; + bool handleMatches = result[i].RawHandle == item.RawHandle; // Handle is the same (could be 0 handle). if (handleMatches) { // If handle matches and not default then the same scene was added multiple times. - if (item.Handle != 0) + if (item.RawHandle != 0) failingIndex = i; } // Name is the same. @@ -365,6 +418,32 @@ public static SceneLookupData[] CreateData(int[] handles) return result.ToArray(); } + + /// + /// Returns a SceneLookupData collection. + /// + /// Raw scene handles to create from. + /// + public static SceneLookupData[] CreateData(ulong[] handles) + { + bool invalidFound = false; + List result = new(); + foreach (ulong item in handles) + { + if (item == 0) + { + invalidFound = true; + continue; + } + + result.Add(CreateData(item)); + } + + if (invalidFound) + NetworkManagerExtensions.LogWarning(INVALID_SCENE); + + return result.ToArray(); + } #endregion /// @@ -390,7 +469,7 @@ public Scene GetScene(out bool foundByHandle, bool warnIfDuplicates = true) { foundByHandle = false; - if (Handle == 0 && string.IsNullOrEmpty(NameOnly)) + if (RawHandle == 0 && string.IsNullOrEmpty(NameOnly)) { NetworkManagerExtensions.LogWarning("Scene handle and name is unset; scene cannot be returned."); return default; @@ -398,11 +477,11 @@ public Scene GetScene(out bool foundByHandle, bool warnIfDuplicates = true) Scene result = default; - // Lookup my handle. - if (Handle != 0) + // Lookup by handle first. + if (RawHandle != 0) { - result = SceneManager.GetScene(Handle); - if (result.handle != 0) + result = GetSceneByRawHandle(RawHandle); + if (UnityCompatibility.HasValidSceneHandle(result)) foundByHandle = true; } @@ -412,5 +491,21 @@ public Scene GetScene(out bool foundByHandle, bool warnIfDuplicates = true) return result; } + + /// + /// Returns a currently loaded scene by raw scene handle. + /// + private static Scene GetSceneByRawHandle(ulong rawHandle) + { + int count = UnityEngine.SceneManagement.SceneManager.sceneCount; + for (int i = 0; i < count; i++) + { + Scene scene = UnityEngine.SceneManagement.SceneManager.GetSceneAt(i); + if (scene.IsValid() && UnityCompatibility.GetSceneHandleRaw(scene) == rawHandle) + return scene; + } + + return default; + } } } \ No newline at end of file diff --git a/Assets/FishNet/Runtime/Managing/Scened/SceneManager.cs b/Assets/FishNet/Runtime/Managing/Scened/SceneManager.cs index 6bae7b5c..d53b8378 100644 --- a/Assets/FishNet/Runtime/Managing/Scened/SceneManager.cs +++ b/Assets/FishNet/Runtime/Managing/Scened/SceneManager.cs @@ -5,6 +5,7 @@ using FishNet.Object; using FishNet.Serializing.Helping; using FishNet.Transporting; +using FishNet.Utility; using GameKit.Dependencies.Utilities; using GameKit.Dependencies.Utilities.Types; using System; @@ -34,11 +35,11 @@ public class PendingClientSceneLoads /// /// Scene handles which have clients that have not yet confirmed the loading status. /// - private Dictionary> _scenesWithPendingLoads = new(); + private Dictionary> _scenesWithPendingLoads = new(); /// /// Clients with pending loads, and each scene handle pending. /// - private Dictionary> _clientsWithPendingLoads = new(); + private Dictionary> _clientsWithPendingLoads = new(); /// /// Clients which have been sent the initial scene load with no scenes specified. /// @@ -49,11 +50,11 @@ public class PendingClientSceneLoads /// /// Adds a pending load for a client. /// - public void AddClientToScene(NetworkConnection connection, int sceneHandle) + public void AddClientToScene(NetworkConnection connection, ulong sceneHandle) { /* The client has 1 or more pending loads already. See * if the specified scene is already pending. */ - if (_clientsWithPendingLoads.TryGetValueIL2CPP(connection, out List sceneList)) + if (_clientsWithPendingLoads.TryGetValueIL2CPP(connection, out List sceneList)) { //Scene is already marked for the connection. if (sceneList.Contains(sceneHandle)) @@ -81,14 +82,14 @@ public void AddClientToScene(NetworkConnection connection, int sceneHandle) /// Removes a client from all pending loads. /// Scene handles which no longer have clients pending loads. - internal List RemoveClientFromAllScenes(NetworkConnection conn) + internal List RemoveClientFromAllScenes(NetworkConnection conn) { - List emptyScenes = new(); + List emptyScenes = new(); - if (!_clientsWithPendingLoads.TryGetValueIL2CPP(conn, out List sceneList)) + if (!_clientsWithPendingLoads.TryGetValueIL2CPP(conn, out List sceneList)) return emptyScenes; - foreach (int sceneHandle in sceneList) + foreach (ulong sceneHandle in sceneList) { /* If the scene is in the clients list then it should * be in scenesWithPendingLoads. This is a safety check but @@ -113,7 +114,7 @@ internal List RemoveClientFromAllScenes(NetworkConnection conn) /// Becomes true if the scene which the connection is being removed from has no more pending loads. /// True if the client had the specified scene as pending. - internal bool RemoveClientFromScene(NetworkConnection conn, int sceneHandle, out bool sceneHasNoPendingLoads) + internal bool RemoveClientFromScene(NetworkConnection conn, ulong sceneHandle, out bool sceneHasNoPendingLoads) { // The scene has does not have any pending clients. if (!_scenesWithPendingLoads.TryGetValueIL2CPP(sceneHandle, out HashSet connectionsLoadingScene)) @@ -136,7 +137,7 @@ internal bool RemoveClientFromScene(NetworkConnection conn, int sceneHandle, out /* If client does not have any pending loads then * there is nothing to remove, which means the requested * scene still has clients in it. */ - if (!_clientsWithPendingLoads.TryGetValueIL2CPP(conn, out List sceneList)) + if (!_clientsWithPendingLoads.TryGetValueIL2CPP(conn, out List sceneList)) { sceneHasNoPendingLoads = false; return false; @@ -176,7 +177,7 @@ internal bool RemoveClientFromScene(NetworkConnection conn, int sceneHandle, out /// /// Returns if a scene has any number of clients still pending load. /// - internal bool HasSceneAnyPendingLoads(int sceneHandle) => _scenesWithPendingLoads.TryGetValueIL2CPP(sceneHandle, out _); + internal bool HasSceneAnyPendingLoads(ulong sceneHandle) => _scenesWithPendingLoads.TryGetValueIL2CPP(sceneHandle, out _); /// /// Clears all information. @@ -576,7 +577,7 @@ void SendEmptyBroadcast() }; foreach (SceneLookupData lookupData in sceneLookupData) - _pendingClientSceneLoads.AddClientToScene(connection, lookupData.Handle); + _pendingClientSceneLoads.AddClientToScene(connection, lookupData.RawHandle); connection.Broadcast(msg, requireAuthenticated: true); } @@ -635,7 +636,7 @@ private void ClientDisconnected(NetworkConnection conn) /* True if SceneConnections has no more connections * in its scene, as well if the scene checked is in * has no other clients pending load confirmation. */ - bool isSceneNowEmpty = removed && hs.Count == 0 && !_pendingClientSceneLoads.HasSceneAnyPendingLoads(scene.handle); + bool isSceneNowEmpty = removed && hs.Count == 0 && !_pendingClientSceneLoads.HasSceneAnyPendingLoads(UnityCompatibility.GetSceneHandleRaw(scene)); //True if not a global scene and not in scenes to be manually unloaded. bool notGlobalAndNotManualUnload = !IsGlobalScene(scene) && !_manualUnloadScenes.Contains(scene); @@ -691,9 +692,9 @@ private void OnClientLoadedScenes(NetworkConnection conn, ClientScenesLoadedBroa foreach (SceneLookupData item in msg.SceneLookupDatas) { //Make sure the sceneId is pending. - if (!_pendingClientSceneLoads.RemoveClientFromScene(conn, item.Handle, out _)) + if (!_pendingClientSceneLoads.RemoveClientFromScene(conn, item.RawHandle, out _)) { - KickClient($"Client {conn.ToString()} sent a scene load response for handle [{item.Handle}], but client was not sent a load for this handle."); + KickClient($"Client {conn.ToString()} sent a scene load response for handle [{item.RawHandle}], but client was not sent a load for this handle."); break; } @@ -1058,7 +1059,7 @@ private IEnumerator __LoadScenes() /* Scene queue data scenes. * All scenes in the scene queue data whether they will be loaded or not. */ List requestedLoadSceneNames = new(); - List requestedLoadSceneHandles = new(); + List requestedLoadSceneHandles = new(); /* Make a null filled array. This will be populated * using loaded scenes, or already loaded (eg cannot be loaded) scenes. */ @@ -1081,7 +1082,7 @@ private IEnumerator __LoadScenes() { requestedLoadSceneNames.Add(s.name); if (byHandle) - requestedLoadSceneHandles.Add(s.handle); + requestedLoadSceneHandles.Add(UnityCompatibility.GetSceneHandleRaw(s)); } if (CanLoadScene(data, lookupData)) @@ -1124,7 +1125,7 @@ private IEnumerator __LoadScenes() } // Connection scenes handles prior to ConnectionScenes being modified. - List connectionScenesHandlesCached = new(); + List connectionScenesHandlesCached = new(); // If replacing scenes. if (replaceScenes != ReplaceOption.None) { @@ -1137,7 +1138,7 @@ private IEnumerator __LoadScenes() { Scene[] sceneConnectionsKeys = SceneConnections.Keys.ToArray(); for (int i = 0; i < sceneConnectionsKeys.Length; i++) - connectionScenesHandlesCached.Add(sceneConnectionsKeys[i].handle); + connectionScenesHandlesCached.Add(UnityCompatibility.GetSceneHandleRaw(sceneConnectionsKeys[i])); // If global then remove all connections from all scenes. if (data.ScopeType == SceneScopeType.Global) @@ -1155,7 +1156,7 @@ private IEnumerator __LoadScenes() else { foreach (Scene s in NetworkManager.ClientManager.Connection.Scenes) - connectionScenesHandlesCached.Add(s.handle); + connectionScenesHandlesCached.Add(UnityCompatibility.GetSceneHandleRaw(s)); } } @@ -1181,7 +1182,7 @@ private IEnumerator __LoadScenes() if (requestedLoadSceneNames.Contains(s.name)) continue; // Same as above but using handles. - if (requestedLoadSceneHandles.Contains(s.handle)) + if (requestedLoadSceneHandles.Contains(UnityCompatibility.GetSceneHandleRaw(s))) continue; /* Cannot unload global scenes. If * replace scenes was used for a global @@ -1193,7 +1194,7 @@ private IEnumerator __LoadScenes() if (_manualUnloadScenes.Contains(s)) continue; - bool inScenesCache = connectionScenesHandlesCached.Contains(s.handle); + bool inScenesCache = connectionScenesHandlesCached.Contains(UnityCompatibility.GetSceneHandleRaw(s)); HashSet conns; bool inScenesCurrent = SceneConnections.ContainsKey(s); // If was in scenes previously but isnt now then no connections reside in the scene. @@ -1328,7 +1329,7 @@ void InvokePercentageChange(int index, float maximumWorth, float currentScenePer /* If the first lookup data contains a handle and the scene * is found for that handle then use that as the moved to scene. * Nobs always move to the first specified scene. */ - if (sceneLoadData.SceneLookupDatas[0].Handle != 0 && !string.IsNullOrEmpty(firstScene.name)) + if (sceneLoadData.SceneLookupDatas[0].RawHandle != 0 && !string.IsNullOrEmpty(firstScene.name)) { firstValidScene = firstScene; } @@ -1503,7 +1504,7 @@ void AddClientPendingLoads(NetworkConnection[] lConns) { SceneLookupData[] slds = msg.QueueData.SceneLoadData.SceneLookupDatas; foreach (SceneLookupData sld in slds) - AddPendingLoad(lConns, sld.Handle); + AddPendingLoad(lConns, sld.RawHandle); } } /* If running as client then send a message @@ -2246,12 +2247,22 @@ public static Scene GetScene(string sceneName, NetworkManager nm = null, bool wa /// /// public static Scene GetScene(int sceneHandle) + { + return GetScene(unchecked((uint)sceneHandle)); + } + + /// + /// Returns a scene by raw handle. + /// + /// + /// + public static Scene GetScene(ulong sceneHandle) { int count = UnitySceneManager.sceneCount; for (int i = 0; i < count; i++) { Scene s = UnitySceneManager.GetSceneAt(i); - if (s.handle == sceneHandle) + if (UnityCompatibility.GetSceneHandleRaw(s) == sceneHandle) return s; } @@ -2354,7 +2365,7 @@ private void RemoveOccupiedScenes(List scenes) { Scene s = scenes[i]; - if (SceneConnections.TryGetValueIL2CPP(s, out _) || _pendingClientSceneLoads.HasSceneAnyPendingLoads(s.handle)) + if (SceneConnections.TryGetValueIL2CPP(s, out _) || _pendingClientSceneLoads.HasSceneAnyPendingLoads(UnityCompatibility.GetSceneHandleRaw(s))) { scenes.RemoveAt(i); i--; @@ -2365,7 +2376,7 @@ private void RemoveOccupiedScenes(List scenes) /// /// Adds a pending load for a connection. /// - private void AddPendingLoad(NetworkConnection[] conns, int sceneHandle) + private void AddPendingLoad(NetworkConnection[] conns, ulong sceneHandle) { foreach (NetworkConnection c in conns) { diff --git a/Assets/FishNet/Runtime/Managing/Scened/UnloadedScene.cs b/Assets/FishNet/Runtime/Managing/Scened/UnloadedScene.cs index 5f89fb49..2fcb0dd4 100644 --- a/Assets/FishNet/Runtime/Managing/Scened/UnloadedScene.cs +++ b/Assets/FishNet/Runtime/Managing/Scened/UnloadedScene.cs @@ -1,19 +1,26 @@ -using UnityEngine.SceneManagement; +using FishNet.Utility; +using UnityEngine.SceneManagement; namespace FishNet.Managing.Scened { public struct UnloadedScene { public readonly string Name; - public readonly int Handle; + public readonly ulong Handle; public UnloadedScene(Scene s) { Name = s.name; - Handle = s.handle; + Handle = UnityCompatibility.GetSceneHandleRaw(s); } public UnloadedScene(string name, int handle) + { + Name = name; + Handle = unchecked((uint)handle); + } + + public UnloadedScene(string name, ulong handle) { Name = name; Handle = handle; @@ -30,7 +37,7 @@ public Scene GetScene() for (int i = 0; i < loadedScenes; i++) { Scene s = UnityEngine.SceneManagement.SceneManager.GetSceneAt(i); - if (s.IsValid() && s.handle == Handle) + if (s.IsValid() && UnityCompatibility.GetSceneHandleRaw(s) == Handle) return s; } diff --git a/Assets/FishNet/Runtime/Observing/NetworkObserver.cs b/Assets/FishNet/Runtime/Observing/NetworkObserver.cs index fd0fb35d..c9a05a48 100644 --- a/Assets/FishNet/Runtime/Observing/NetworkObserver.cs +++ b/Assets/FishNet/Runtime/Observing/NetworkObserver.cs @@ -155,10 +155,12 @@ internal void Deinitialize(bool destroyed) foreach (ObserverCondition item in _observerConditions) { item.Deinitialize(destroyed); - /* Use GetInstanceId to ensure the object is actually - * instantiated. If Id is negative, then it's instantiated - * and not a reference to the original object. */ - if (destroyed && item.GetInstanceID() < 0) + + /* Conditions are replaced with instantiated copies during Initialize. + * Destroy the runtime copies when this observer is being destroyed. + * Do not rely on InstanceID sign because InstanceID is obsolete in Unity 6.5+. + */ + if (destroyed) Destroy(item); } @@ -309,10 +311,10 @@ internal ObserverStateChange RebuildObservers(NetworkConnection connection, bool if (!_initialized) { string goName = gameObject == null ? "Empty" : gameObject.name; - + NetworkManager nm = _networkObject == null ? null : _networkObject.NetworkManager; nm.LogError($"{GetType().Name} is not initialized on NetworkObject [{goName}]. RebuildObservers should not be called. If you are able to reproduce this error consistently please report this issue."); - + return ObserverStateChange.Unchanged; } diff --git a/Assets/FishNet/Runtime/Serializing/Helping/Comparers.cs b/Assets/FishNet/Runtime/Serializing/Helping/Comparers.cs index f294ae66..7ab92cbe 100644 --- a/Assets/FishNet/Runtime/Serializing/Helping/Comparers.cs +++ b/Assets/FishNet/Runtime/Serializing/Helping/Comparers.cs @@ -1,4 +1,5 @@ -using System; +using FishNet.Utility; +using System; using System.Collections.Generic; using UnityEngine.SceneManagement; @@ -10,6 +11,7 @@ public class PublicPropertyComparer /// Compare if T is default. /// public static Func IsDefault { get; set; } + /// /// Compare if T is the same as T2. /// @@ -48,8 +50,11 @@ public bool Equals(Scene a, Scene b) if (!a.IsValid() || !b.IsValid()) return false; - if (a.handle != 0 || b.handle != 0) - return a.handle == b.handle; + ulong aHandle = UnityCompatibility.GetSceneHandleRaw(a); + ulong bHandle = UnityCompatibility.GetSceneHandleRaw(b); + + if (aHandle != 0 || bHandle != 0) + return aHandle == bHandle; return a.name == b.name; } diff --git a/Assets/FishNet/Runtime/Serializing/SceneComparer.cs b/Assets/FishNet/Runtime/Serializing/SceneComparer.cs index d4a4e076..d09b802f 100644 --- a/Assets/FishNet/Runtime/Serializing/SceneComparer.cs +++ b/Assets/FishNet/Runtime/Serializing/SceneComparer.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using FishNet.Utility; +using System.Collections.Generic; using UnityEngine.SceneManagement; namespace FishNet.Serializing.Helping @@ -7,12 +8,12 @@ internal sealed class SceneHandleEqualityComparer : EqualityComparer { public override bool Equals(Scene a, Scene b) { - return a.handle == b.handle; + return UnityCompatibility.GetSceneHandleRaw(a) == UnityCompatibility.GetSceneHandleRaw(b); } public override int GetHashCode(Scene obj) { - return obj.handle; + return UnityCompatibility.GetSceneHandleRaw(obj).GetHashCode(); } } } \ No newline at end of file diff --git a/Assets/FishNet/Runtime/UnityCompatibility.cs b/Assets/FishNet/Runtime/UnityCompatibility.cs new file mode 100644 index 00000000..81f26979 --- /dev/null +++ b/Assets/FishNet/Runtime/UnityCompatibility.cs @@ -0,0 +1,54 @@ +using UnityEngine.SceneManagement; + +namespace FishNet.Utility +{ + /// + /// Compatibility helpers for Unity API changes across supported Unity versions. + /// + public static class UnityCompatibility + { + /// + /// Returns a runtime object identifier using Unity's current object identity API. + /// + public static ulong GetObjectRuntimeId(UnityEngine.Object obj) + { +#if UNITY_6000_5_OR_NEWER + return obj.GetEntityId().GetRawData(); +#else + return unchecked((uint)obj.GetInstanceID()); +#endif + } + + /// + /// Returns a raw scene handle value using Unity's current SceneHandle API. + /// + public static ulong GetSceneHandleRaw(Scene scene) + { +#if UNITY_6000_5_OR_NEWER + return scene.handle.GetRawData(); +#else + return unchecked((uint)(int)scene.handle); +#endif + } + + /// + /// Creates a SceneHandle from raw scene handle data. + /// + public static SceneHandle SceneHandleFromRaw(ulong rawHandle) + { +#if UNITY_6000_5_OR_NEWER + return SceneHandle.FromRawData(rawHandle); +#else + return (SceneHandle)(int)rawHandle; +#endif + } + + /// + /// Returns true if the scene has a non-zero raw handle. + /// + public static bool HasValidSceneHandle(Scene scene) + { + return scene.IsValid() && GetSceneHandleRaw(scene) != 0; + } + } +} \ No newline at end of file diff --git a/Assets/FishNet/Runtime/UnityCompatibility.cs.meta b/Assets/FishNet/Runtime/UnityCompatibility.cs.meta new file mode 100644 index 00000000..897e9ea9 --- /dev/null +++ b/Assets/FishNet/Runtime/UnityCompatibility.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: c3a88ed478506c145a5efcd3e7c04fd6 \ No newline at end of file