From e447a8ef0647722ac29a7a29060939b26777bbff Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 13 Sep 2026 16:21:55 +0300 Subject: [PATCH] Fix Unity 6000.6 InstanceIDToObjectCompat NotImplementedException On Unity 6000.6.0f1, both EditorUtility.InstanceIDToObject(int) and EntityId's implicit int->EntityId conversion are unimplemented stubs at runtime (verified via reflection against a live 6000.6.0f1 Editor) even though the CS0619-avoidance reflection shim resolves the MethodInfo successfully. Every call into InstanceIDToObjectCompat therefore threw NotImplementedException, breaking manage_components (set_property/add) whenever GameObjectLookup resolved a target - including by_name lookups, since FindByTarget round-trips through an instance ID internally - and breaking the gameobject/{id} and gameobject/{id}/components resources. The only reverse-resolution path that works on 6000.6 is EditorUtility.EntityIdToObject(EntityId.FromULong(fullUlong)), which needs the full 64-bit id. GetInstanceIDCompat already discards the high 32 bits by truncating to int for wire compatibility, so by the time InstanceIDToObjectCompat receives the int there's no way to reconstruct a valid EntityId from it alone. Cache the full 64-bit value behind the truncated int at mint time and use that cache to resolve instead of trying to rebuild the EntityId from the int. This keeps the existing int wire format and the already-documented session-scoped id contract, with no changes needed to any of the ~30 call sites or the wire protocol. Co-Authored-By: Claude Sonnet 5 --- .../Runtime/Helpers/UnityObjectIdCompat.cs | 47 +++++++++++-------- 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/MCPForUnity/Runtime/Helpers/UnityObjectIdCompat.cs b/MCPForUnity/Runtime/Helpers/UnityObjectIdCompat.cs index a10a99167..3a0ea6cfa 100644 --- a/MCPForUnity/Runtime/Helpers/UnityObjectIdCompat.cs +++ b/MCPForUnity/Runtime/Helpers/UnityObjectIdCompat.cs @@ -1,8 +1,10 @@ using UnityEngine; #if UNITY_EDITOR -using System.Reflection; using UnityEditor; #endif +#if UNITY_6000_6_OR_NEWER +using System.Collections.Generic; +#endif namespace MCPForUnity.Runtime.Helpers { @@ -16,6 +18,18 @@ namespace MCPForUnity.Runtime.Helpers /// public static class UnityObjectIdCompat { +#if UNITY_6000_6_OR_NEWER + // 6000.6 stubs out both EditorUtility.InstanceIDToObject(int) and EntityId's + // implicit int->EntityId conversion (both throw NotImplementedException at + // runtime despite existing in the API surface - verified against 6000.6.0f1). + // The only working reverse path is EntityIdToObject(EntityId) fed a value built + // via EntityId.FromULong(ulong), which needs the full 64-bit id, not the + // truncated int this class hands out. So we cache the full value behind the + // truncated int at mint time and look it up here instead of trying to + // reconstruct it from the int alone. + private static readonly Dictionary _fullIdCache = new Dictionary(); +#endif + /// /// Returns a session-scoped int handle for the object. On 6.5+ truncates the /// EntityId's underlying ulong; lossy but stable within a session and preserves @@ -30,40 +44,35 @@ public static int GetInstanceIDCompat(this Object obj) } #if UNITY_6000_5_OR_NEWER - return (int)EntityId.ToULong(obj.GetEntityId()); + ulong full = EntityId.ToULong(obj.GetEntityId()); + int truncated = (int)full; +#if UNITY_6000_6_OR_NEWER + _fullIdCache[truncated] = full; +#endif + return truncated; #else return obj.GetInstanceID(); #endif } #if UNITY_EDITOR -#if UNITY_6000_6_OR_NEWER - private static MethodInfo _instanceIdToObject; - private static bool _instanceIdToObjectInitialized; -#endif - /// /// Resolves an int instance ID handle back to a UnityEngine.Object. /// Pre-6.0 : EditorUtility.InstanceIDToObject(int) /// 6.0–6.5 : EditorUtility.EntityIdToObject(int) (implicit int→EntityId cast) - /// 6.6+ : reflection on InstanceIDToObject(int) — the API still exists at runtime - /// but is obsolete-as-error; reflection bypasses CS0619 until the public - /// EntityId(int) ctor stabilizes. + /// 6.6+ : EditorUtility.EntityIdToObject(EntityId.FromULong(full)) using the + /// full 64-bit value cached in GetInstanceIDCompat, since the int-only + /// paths (including the implicit int→EntityId cast) are unimplemented + /// stubs on this Unity version. Only resolves ids minted this session. /// public static Object InstanceIDToObjectCompat(int instanceId) { #if UNITY_6000_6_OR_NEWER - if (!_instanceIdToObjectInitialized) + if (_fullIdCache.TryGetValue(instanceId, out ulong full)) { - _instanceIdToObject = typeof(EditorUtility).GetMethod( - "InstanceIDToObject", - BindingFlags.Public | BindingFlags.Static, - null, - new[] { typeof(int) }, - null); - _instanceIdToObjectInitialized = true; + return EditorUtility.EntityIdToObject(EntityId.FromULong(full)); } - return _instanceIdToObject?.Invoke(null, new object[] { instanceId }) as Object; + return null; #elif UNITY_6000_3_OR_NEWER return EditorUtility.EntityIdToObject(instanceId); #else