@mflrevan/ucp 0.5.2 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/README.md +1 -1
  2. package/bridge/com.ucp.bridge/Editor/AssemblyInfo.cs +3 -0
  3. package/bridge/com.ucp.bridge/Editor/AssemblyInfo.cs.meta +2 -0
  4. package/bridge/com.ucp.bridge/Editor/Bridge/BridgeServer.cs +10 -1
  5. package/bridge/com.ucp.bridge/Editor/Compatibility/UnityObjectCompat.cs +26 -0
  6. package/bridge/com.ucp.bridge/Editor/Controllers/AssetController.cs +2 -2
  7. package/bridge/com.ucp.bridge/Editor/Controllers/EditorModalGuard.cs +60 -0
  8. package/bridge/com.ucp.bridge/Editor/Controllers/EditorModalGuard.cs.meta +2 -0
  9. package/bridge/com.ucp.bridge/Editor/Controllers/HierarchyController.cs +56 -5
  10. package/bridge/com.ucp.bridge/Editor/Controllers/MaterialController.cs +2 -2
  11. package/bridge/com.ucp.bridge/Editor/Controllers/ObjectLocator.cs +207 -0
  12. package/bridge/com.ucp.bridge/Editor/Controllers/ObjectLocator.cs.meta +2 -0
  13. package/bridge/com.ucp.bridge/Editor/Controllers/ObjectReferenceResolver.cs +1 -1
  14. package/bridge/com.ucp.bridge/Editor/Controllers/PlayModeController.cs +1 -35
  15. package/bridge/com.ucp.bridge/Editor/Controllers/PrefabController.cs +3 -3
  16. package/bridge/com.ucp.bridge/Editor/Controllers/PropertyController.cs +1 -1
  17. package/bridge/com.ucp.bridge/Editor/Controllers/ReferenceController.cs +1 -1
  18. package/bridge/com.ucp.bridge/Editor/Controllers/SceneChangeTracker.cs +6 -6
  19. package/bridge/com.ucp.bridge/Editor/Controllers/SceneController.cs +2 -34
  20. package/bridge/com.ucp.bridge/Editor/Controllers/SnapshotController.cs +4 -4
  21. package/bridge/com.ucp.bridge/Editor/Controllers/SpatialController.cs +322 -0
  22. package/bridge/com.ucp.bridge/Editor/Controllers/SpatialController.cs.meta +2 -0
  23. package/bridge/com.ucp.bridge/Editor/Controllers/TransformController.cs +249 -0
  24. package/bridge/com.ucp.bridge/Editor/Controllers/TransformController.cs.meta +2 -0
  25. package/bridge/com.ucp.bridge/Editor/Controllers/ViewController.cs +415 -0
  26. package/bridge/com.ucp.bridge/Editor/Controllers/ViewController.cs.meta +2 -0
  27. package/bridge/com.ucp.bridge/Tests/Editor/ControllerSmokeTests.cs +63 -7
  28. package/bridge/com.ucp.bridge/Tests/Editor/SpatialVisualControllerTests.cs +252 -0
  29. package/bridge/com.ucp.bridge/Tests/Editor/SpatialVisualControllerTests.cs.meta +2 -0
  30. package/bridge/com.ucp.bridge/package.json +1 -1
  31. package/package.json +1 -1
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @mflrevan/ucp
2
2
 
3
- Version `0.5.2` of the Unity Control Protocol CLI.
3
+ Version `0.6.0` of the Unity Control Protocol CLI.
4
4
 
5
5
  This package installs the `ucp` command, downloads the matching published binary for your platform during `postinstall`, and ships the matching Unity bridge payload inside the npm package.
6
6
 
@@ -0,0 +1,3 @@
1
+ using System.Runtime.CompilerServices;
2
+
3
+ [assembly: InternalsVisibleTo("UCP.Bridge.Editor.Tests")]
@@ -0,0 +1,2 @@
1
+ fileFormatVersion: 2
2
+ guid: a1b2c3d4e5f60718293a4b5c6d7e8f90
@@ -26,7 +26,7 @@ namespace UCP.Bridge
26
26
  private const int DefaultPort = 21342;
27
27
  private const int MaxPort = 21352;
28
28
  private const int MaxConnections = 4;
29
- private const string ProtocolVersion = "0.5.2";
29
+ private const string ProtocolVersion = "0.6.0";
30
30
 
31
31
  private static TcpListener s_listener;
32
32
  private static CancellationTokenSource s_cts;
@@ -145,6 +145,15 @@ namespace UCP.Bridge
145
145
  // Hierarchy Operations
146
146
  HierarchyController.Register(s_router);
147
147
 
148
+ // Transform authoring (move/rotate/scale/look-at, bulk read)
149
+ TransformController.Register(s_router);
150
+
151
+ // Spatial queries (raycast/overlap/bounds/ground/nearest)
152
+ SpatialController.Register(s_router);
153
+
154
+ // Composed visual perception (capture/isolate/orbit)
155
+ ViewController.Register(s_router);
156
+
148
157
  // Asset Management
149
158
  AssetController.Register(s_router);
150
159
  ImporterController.Register(s_router);
@@ -1,13 +1,39 @@
1
1
  using UnityEditor;
2
2
  using UnityEngine;
3
+ using UnityEngine.SceneManagement;
3
4
 
4
5
  namespace UCP.Bridge
5
6
  {
6
7
  internal static class UnityObjectCompat
7
8
  {
9
+ public static int GetId(this Object obj)
10
+ {
11
+ #if UNITY_6000_5_OR_NEWER
12
+ return unchecked((int)EntityId.ToULong(obj.GetEntityId()));
13
+ #else
14
+ return obj.GetInstanceID();
15
+ #endif
16
+ }
17
+
18
+ public static long GetSceneHandle(Scene scene)
19
+ {
20
+ #if UNITY_6000_5_OR_NEWER
21
+ return unchecked((long)scene.handle.GetRawData());
22
+ #else
23
+ // On 6000.0–6000.4, Scene.handle is a SceneHandle with implicit int and
24
+ // uint operators; widening straight to long is ambiguous (CS0457). Pin the
25
+ // int conversion explicitly — handles are 32-bit on these versions.
26
+ return (int)scene.handle;
27
+ #endif
28
+ }
29
+
8
30
  public static Object ResolveByInstanceId(int instanceId)
9
31
  {
32
+ #if UNITY_6000_5_OR_NEWER
33
+ return EditorUtility.EntityIdToObject(EntityId.FromULong(unchecked((ulong)instanceId)));
34
+ #else
10
35
  return EditorUtility.InstanceIDToObject(instanceId);
36
+ #endif
11
37
  }
12
38
 
13
39
  public static T ResolveByInstanceId<T>(int instanceId) where T : Object
@@ -125,7 +125,7 @@ namespace UCP.Bridge
125
125
  ["name"] = asset.name,
126
126
  ["type"] = asset.GetType().Name,
127
127
  ["fullType"] = asset.GetType().FullName,
128
- ["instanceId"] = asset.GetInstanceID(),
128
+ ["instanceId"] = asset.GetId(),
129
129
  ["guid"] = AssetDatabase.AssetPathToGUID(assetPath)
130
130
  };
131
131
 
@@ -496,7 +496,7 @@ namespace UCP.Bridge
496
496
  ["status"] = "ok",
497
497
  ["path"] = assetPath,
498
498
  ["type"] = soType.Name,
499
- ["instanceId"] = instance.GetInstanceID()
499
+ ["instanceId"] = instance.GetId()
500
500
  };
501
501
  }
502
502
 
@@ -0,0 +1,60 @@
1
+ using UnityEditor;
2
+ using UnityEditor.SceneManagement;
3
+ using UnityEngine.SceneManagement;
4
+
5
+ namespace UCP.Bridge
6
+ {
7
+ /// <summary>
8
+ /// Centralizes editor operations that must stay non-blocking when the bridge drives
9
+ /// the editor headlessly.
10
+ ///
11
+ /// Bridge commands run on the main thread via EditorApplication.update. Any modal
12
+ /// dialog (EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo,
13
+ /// EditorUtility.DisplayDialog, file/folder panels, ...) blocks that loop with no user
14
+ /// present to dismiss it, hanging every queued command and timing out the CLI. Bridge
15
+ /// code must therefore never call a modal API. Scene saving is the most common trap, so
16
+ /// it lives here behind a single audited, modal-free implementation shared by every
17
+ /// controller instead of being copy-pasted per controller.
18
+ /// </summary>
19
+ internal static class EditorModalGuard
20
+ {
21
+ /// <summary>
22
+ /// Saves every loaded dirty scene without prompting. Dirty untitled scenes have no
23
+ /// path to save to; they are discarded (replaced with a fresh empty scene) when
24
+ /// <paramref name="discardUntitled"/> is true, otherwise an exception is thrown so
25
+ /// the caller surfaces a clear error instead of silently losing work or blocking on
26
+ /// a save dialog.
27
+ /// </summary>
28
+ public static void SaveOpenDirtyScenes(bool saveDirtyScenes, bool discardUntitled)
29
+ {
30
+ if (!saveDirtyScenes)
31
+ return;
32
+
33
+ var requiresUntitledDiscard = false;
34
+
35
+ for (var index = 0; index < SceneManager.sceneCount; index++)
36
+ {
37
+ var scene = SceneManager.GetSceneAt(index);
38
+ if (!scene.isLoaded || !scene.isDirty)
39
+ continue;
40
+
41
+ if (string.IsNullOrEmpty(scene.path))
42
+ {
43
+ if (!discardUntitled)
44
+ throw new System.InvalidOperationException("Dirty untitled scene cannot be auto-saved. Retry with discardUntitled=true.");
45
+
46
+ requiresUntitledDiscard = true;
47
+ continue;
48
+ }
49
+
50
+ if (!EditorSceneManager.SaveScene(scene))
51
+ throw new System.InvalidOperationException($"Failed to auto-save dirty scene: {scene.path}");
52
+
53
+ SceneChangeTracker.ClearScene(scene);
54
+ }
55
+
56
+ if (requiresUntitledDiscard)
57
+ EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Single);
58
+ }
59
+ }
60
+ }
@@ -0,0 +1,2 @@
1
+ fileFormatVersion: 2
2
+ guid: c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6
@@ -26,7 +26,19 @@ namespace UCP.Bridge
26
26
  if (p != null && p.TryGetValue("name", out var nameObj) && nameObj != null)
27
27
  name = nameObj.ToString();
28
28
 
29
- var go = new GameObject(name);
29
+ // Optional primitive: build a Cube/Sphere/etc. with mesh + collider in one step.
30
+ // Without this an agent has to hand-assemble MeshFilter + MeshRenderer and somehow
31
+ // reference the built-in mesh, which is not practical over the CLI.
32
+ GameObject go;
33
+ if (p != null && p.TryGetValue("primitive", out var primObj) && primObj != null)
34
+ {
35
+ go = GameObject.CreatePrimitive(ParsePrimitive(primObj.ToString()));
36
+ go.name = name;
37
+ }
38
+ else
39
+ {
40
+ go = new GameObject(name);
41
+ }
30
42
  Undo.RegisterCreatedObjectUndo(go, "UCP Create GameObject");
31
43
 
32
44
  // Optional parent
@@ -61,7 +73,7 @@ namespace UCP.Bridge
61
73
  return new Dictionary<string, object>
62
74
  {
63
75
  ["status"] = "ok",
64
- ["instanceId"] = go.GetInstanceID(),
76
+ ["instanceId"] = go.GetId(),
65
77
  ["name"] = go.name
66
78
  };
67
79
  }
@@ -140,7 +152,12 @@ namespace UCP.Bridge
140
152
  string prefabPath = prefabObj.ToString();
141
153
  var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(prefabPath);
142
154
  if (prefab == null)
143
- throw new ArgumentException($"Prefab not found: {prefabPath}");
155
+ {
156
+ var hint = LooksLikePrimitive(prefabPath)
157
+ ? " To create a built-in primitive, use object create with a primitive instead (e.g. `ucp object create MyCube --primitive Cube`); instantiate is only for prefab assets under Assets/."
158
+ : string.Empty;
159
+ throw new ArgumentException($"Prefab not found: {prefabPath}.{hint}");
160
+ }
144
161
  source = prefab;
145
162
  }
146
163
  // Instantiate from existing scene object (clone)
@@ -192,7 +209,7 @@ namespace UCP.Bridge
192
209
  return new Dictionary<string, object>
193
210
  {
194
211
  ["status"] = "ok",
195
- ["instanceId"] = instance.GetInstanceID(),
212
+ ["instanceId"] = instance.GetId(),
196
213
  ["name"] = instance.name
197
214
  };
198
215
  }
@@ -267,6 +284,40 @@ namespace UCP.Bridge
267
284
  };
268
285
  }
269
286
 
287
+ private static bool LooksLikePrimitive(string path)
288
+ {
289
+ if (string.IsNullOrEmpty(path)) return false;
290
+ var token = path.Replace("PrimitiveType.", "").Trim();
291
+ switch (token.ToLowerInvariant())
292
+ {
293
+ case "cube":
294
+ case "sphere":
295
+ case "capsule":
296
+ case "cylinder":
297
+ case "plane":
298
+ case "quad":
299
+ return true;
300
+ default:
301
+ return path.IndexOf("PrimitiveType", System.StringComparison.OrdinalIgnoreCase) >= 0;
302
+ }
303
+ }
304
+
305
+ private static PrimitiveType ParsePrimitive(string value)
306
+ {
307
+ switch (value.Trim().ToLowerInvariant())
308
+ {
309
+ case "cube": return PrimitiveType.Cube;
310
+ case "sphere": return PrimitiveType.Sphere;
311
+ case "capsule": return PrimitiveType.Capsule;
312
+ case "cylinder": return PrimitiveType.Cylinder;
313
+ case "plane": return PrimitiveType.Plane;
314
+ case "quad": return PrimitiveType.Quad;
315
+ default:
316
+ throw new ArgumentException(
317
+ $"Unknown primitive '{value}'. Use one of: Cube, Sphere, Capsule, Cylinder, Plane, Quad.");
318
+ }
319
+ }
320
+
270
321
  private static Type ResolveComponentType(string typeName)
271
322
  {
272
323
  // Try exact match first
@@ -314,7 +365,7 @@ namespace UCP.Bridge
314
365
 
315
366
  private static GameObject FindInHierarchy(GameObject go, int instanceId)
316
367
  {
317
- if (go.GetInstanceID() == instanceId) return go;
368
+ if (go.GetId() == instanceId) return go;
318
369
  for (int i = 0; i < go.transform.childCount; i++)
319
370
  {
320
371
  var found = FindInHierarchy(go.transform.GetChild(i).gameObject, instanceId);
@@ -50,7 +50,7 @@ namespace UCP.Bridge
50
50
  ["path"] = path,
51
51
  ["name"] = material.name,
52
52
  ["shader"] = shader.name,
53
- ["instanceId"] = material.GetInstanceID()
53
+ ["instanceId"] = material.GetId()
54
54
  };
55
55
  }
56
56
 
@@ -278,7 +278,7 @@ namespace UCP.Bridge
278
278
  {
279
279
  ["name"] = tex.name,
280
280
  ["path"] = texPath,
281
- ["instanceId"] = tex.GetInstanceID()
281
+ ["instanceId"] = tex.GetId()
282
282
  };
283
283
  }
284
284
  return null;
@@ -0,0 +1,207 @@
1
+ using System;
2
+ using System.Collections.Generic;
3
+ using UnityEngine;
4
+ using UnityEngine.SceneManagement;
5
+
6
+ namespace UCP.Bridge
7
+ {
8
+ /// <summary>
9
+ /// Shared GameObject resolution for the spatial/visual controllers.
10
+ ///
11
+ /// A target may be addressed three ways, tried in priority order:
12
+ /// 1. instanceId (int) — canonical, survives nothing but a domain reload; preferred.
13
+ /// 2. path (string) — hierarchy path "Root/Child/Leaf" (leading '/' optional),
14
+ /// resolved across all loaded scenes; survives reloads.
15
+ /// 3. name (string) — first GameObject whose name matches; ambiguous under
16
+ /// duplicates, so it is the last resort.
17
+ ///
18
+ /// instanceId stays the deterministic handle. path/name are convenience fallbacks so an
19
+ /// agent does not have to re-snapshot after every reload just to re-acquire an id.
20
+ /// </summary>
21
+ internal static class ObjectLocator
22
+ {
23
+ /// <summary>
24
+ /// Resolve a GameObject from a params dictionary using whichever of
25
+ /// instanceId / id / path / name is present (in that priority order).
26
+ /// Throws ArgumentException if none are present or nothing resolves.
27
+ /// </summary>
28
+ internal static GameObject Resolve(Dictionary<string, object> p)
29
+ {
30
+ if (p == null)
31
+ throw new ArgumentException("Missing target: provide 'instanceId', 'path', or 'name'");
32
+
33
+ if ((p.TryGetValue("instanceId", out var idObj) || p.TryGetValue("id", out idObj)) && idObj != null)
34
+ {
35
+ var id = Convert.ToInt32(idObj);
36
+ var byId = FindByInstanceId(id);
37
+ if (byId != null)
38
+ return byId;
39
+ throw new ArgumentException($"GameObject not found for instanceId {id}");
40
+ }
41
+
42
+ if (p.TryGetValue("path", out var pathObj) && pathObj != null)
43
+ {
44
+ var path = pathObj.ToString();
45
+ var byPath = FindByPath(path);
46
+ if (byPath != null)
47
+ return byPath;
48
+ throw new ArgumentException($"GameObject not found for path '{path}'");
49
+ }
50
+
51
+ if (p.TryGetValue("name", out var nameObj) && nameObj != null)
52
+ {
53
+ var name = nameObj.ToString();
54
+ var byName = FindByName(name);
55
+ if (byName != null)
56
+ return byName;
57
+ throw new ArgumentException($"GameObject not found for name '{name}'");
58
+ }
59
+
60
+ throw new ArgumentException("Missing target: provide 'instanceId', 'path', or 'name'");
61
+ }
62
+
63
+ internal static GameObject FindByInstanceId(int instanceId)
64
+ {
65
+ var direct = UnityObjectCompat.ResolveByInstanceId<GameObject>(instanceId);
66
+ if (direct != null)
67
+ return direct;
68
+
69
+ for (var i = 0; i < SceneManager.sceneCount; i++)
70
+ {
71
+ var scene = SceneManager.GetSceneAt(i);
72
+ if (!scene.isLoaded)
73
+ continue;
74
+ foreach (var root in scene.GetRootGameObjects())
75
+ {
76
+ var found = FindInHierarchyById(root, instanceId);
77
+ if (found != null)
78
+ return found;
79
+ }
80
+ }
81
+
82
+ return null;
83
+ }
84
+
85
+ private static GameObject FindInHierarchyById(GameObject go, int instanceId)
86
+ {
87
+ if (go.GetId() == instanceId)
88
+ return go;
89
+ foreach (Transform child in go.transform)
90
+ {
91
+ var found = FindInHierarchyById(child.gameObject, instanceId);
92
+ if (found != null)
93
+ return found;
94
+ }
95
+ return null;
96
+ }
97
+
98
+ internal static GameObject FindByPath(string path)
99
+ {
100
+ if (string.IsNullOrEmpty(path))
101
+ return null;
102
+
103
+ var trimmed = path.Trim('/');
104
+ var segments = trimmed.Split('/');
105
+ if (segments.Length == 0)
106
+ return null;
107
+
108
+ for (var i = 0; i < SceneManager.sceneCount; i++)
109
+ {
110
+ var scene = SceneManager.GetSceneAt(i);
111
+ if (!scene.isLoaded)
112
+ continue;
113
+ foreach (var root in scene.GetRootGameObjects())
114
+ {
115
+ if (root.name != segments[0])
116
+ continue;
117
+ var resolved = WalkPath(root.transform, segments, 1);
118
+ if (resolved != null)
119
+ return resolved;
120
+ }
121
+ }
122
+
123
+ return null;
124
+ }
125
+
126
+ private static GameObject WalkPath(Transform current, string[] segments, int index)
127
+ {
128
+ if (index >= segments.Length)
129
+ return current.gameObject;
130
+
131
+ foreach (Transform child in current)
132
+ {
133
+ if (child.name == segments[index])
134
+ {
135
+ var resolved = WalkPath(child, segments, index + 1);
136
+ if (resolved != null)
137
+ return resolved;
138
+ }
139
+ }
140
+
141
+ return null;
142
+ }
143
+
144
+ internal static GameObject FindByName(string name)
145
+ {
146
+ if (string.IsNullOrEmpty(name))
147
+ return null;
148
+
149
+ for (var i = 0; i < SceneManager.sceneCount; i++)
150
+ {
151
+ var scene = SceneManager.GetSceneAt(i);
152
+ if (!scene.isLoaded)
153
+ continue;
154
+ foreach (var root in scene.GetRootGameObjects())
155
+ {
156
+ var found = FindInHierarchyByName(root, name);
157
+ if (found != null)
158
+ return found;
159
+ }
160
+ }
161
+
162
+ return null;
163
+ }
164
+
165
+ private static GameObject FindInHierarchyByName(GameObject go, string name)
166
+ {
167
+ if (go.name == name)
168
+ return go;
169
+ foreach (Transform child in go.transform)
170
+ {
171
+ var found = FindInHierarchyByName(child.gameObject, name);
172
+ if (found != null)
173
+ return found;
174
+ }
175
+ return null;
176
+ }
177
+
178
+ /// <summary>Compute a world-space AABB encapsulating the object's renderers and colliders.</summary>
179
+ internal static bool TryComputeWorldBounds(GameObject target, bool includeChildren, out Bounds bounds)
180
+ {
181
+ var hasBounds = false;
182
+ bounds = new Bounds(target.transform.position, Vector3.zero);
183
+
184
+ var renderers = includeChildren
185
+ ? target.GetComponentsInChildren<Renderer>()
186
+ : target.GetComponents<Renderer>();
187
+ foreach (var renderer in renderers)
188
+ {
189
+ if (!hasBounds) { bounds = renderer.bounds; hasBounds = true; }
190
+ else bounds.Encapsulate(renderer.bounds);
191
+ }
192
+
193
+ var colliders = includeChildren
194
+ ? target.GetComponentsInChildren<Collider>()
195
+ : target.GetComponents<Collider>();
196
+ foreach (var collider in colliders)
197
+ {
198
+ if (!hasBounds) { bounds = collider.bounds; hasBounds = true; }
199
+ else bounds.Encapsulate(collider.bounds);
200
+ }
201
+
202
+ return hasBounds;
203
+ }
204
+
205
+ internal static List<object> Vec3(Vector3 v) => new List<object> { v.x, v.y, v.z };
206
+ }
207
+ }
@@ -0,0 +1,2 @@
1
+ fileFormatVersion: 2
2
+ guid: 0a1b2c3d4e5f60718293a4b5c6d7e8f9
@@ -14,7 +14,7 @@ namespace UCP.Bridge
14
14
 
15
15
  var result = new Dictionary<string, object>
16
16
  {
17
- ["instanceId"] = obj.GetInstanceID(),
17
+ ["instanceId"] = obj.GetId(),
18
18
  ["name"] = obj.name,
19
19
  ["type"] = obj.GetType().Name
20
20
  };
@@ -1,6 +1,4 @@
1
1
  using UnityEditor;
2
- using UnityEditor.SceneManagement;
3
- using UnityEngine.SceneManagement;
4
2
  using System.Collections.Generic;
5
3
  using System;
6
4
 
@@ -65,7 +63,7 @@ namespace UCP.Bridge
65
63
  var saveDirtyScenes = GetBoolParam(paramsJson, "saveDirtyScenes", true);
66
64
  var discardUntitled = GetBoolParam(paramsJson, "discardUntitled", true);
67
65
  var logFile = GetStringParam(paramsJson, "logFile");
68
- SaveDirtyScenesIfRequested(saveDirtyScenes, discardUntitled);
66
+ EditorModalGuard.SaveOpenDirtyScenes(saveDirtyScenes, discardUntitled);
69
67
  if (!string.IsNullOrEmpty(logFile))
70
68
  LogsController.StartFileCapture(logFile);
71
69
 
@@ -91,38 +89,6 @@ namespace UCP.Bridge
91
89
  return defaultValue;
92
90
  }
93
91
 
94
- private static void SaveDirtyScenesIfRequested(bool saveDirtyScenes, bool discardUntitled)
95
- {
96
- if (!saveDirtyScenes)
97
- return;
98
-
99
- var requiresUntitledDiscard = false;
100
-
101
- for (var index = 0; index < SceneManager.sceneCount; index++)
102
- {
103
- var scene = SceneManager.GetSceneAt(index);
104
- if (!scene.isLoaded || !scene.isDirty)
105
- continue;
106
-
107
- if (string.IsNullOrEmpty(scene.path))
108
- {
109
- if (!discardUntitled)
110
- throw new System.InvalidOperationException("Dirty untitled scene cannot be auto-saved. Retry with discardUntitled=true.");
111
-
112
- requiresUntitledDiscard = true;
113
- continue;
114
- }
115
-
116
- if (!EditorSceneManager.SaveScene(scene))
117
- throw new System.InvalidOperationException($"Failed to auto-save dirty scene: {scene.path}");
118
-
119
- SceneChangeTracker.ClearScene(scene);
120
- }
121
-
122
- if (requiresUntitledDiscard)
123
- EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Single);
124
- }
125
-
126
92
  private static object HandleStop(string paramsJson)
127
93
  {
128
94
  if (!EditorApplication.isPlaying)
@@ -179,8 +179,8 @@ namespace UCP.Bridge
179
179
  ["status"] = "ok",
180
180
  ["path"] = savePath,
181
181
  ["name"] = prefab.name,
182
- ["instanceId"] = prefab.GetInstanceID(),
183
- ["sceneInstanceId"] = go.GetInstanceID(),
182
+ ["instanceId"] = prefab.GetId(),
183
+ ["sceneInstanceId"] = go.GetId(),
184
184
  ["isPrefabInstance"] = PrefabUtility.IsPartOfPrefabInstance(go)
185
185
  };
186
186
  }
@@ -221,7 +221,7 @@ namespace UCP.Bridge
221
221
  added.Add(new Dictionary<string, object>
222
222
  {
223
223
  ["component"] = ac.instanceComponent.GetType().Name,
224
- ["instanceId"] = ac.instanceComponent.GetInstanceID()
224
+ ["instanceId"] = ac.instanceComponent.GetId()
225
225
  });
226
226
  }
227
227
 
@@ -538,7 +538,7 @@ namespace UCP.Bridge
538
538
 
539
539
  private static GameObject FindInHierarchy(GameObject go, int instanceId)
540
540
  {
541
- if (go.GetInstanceID() == instanceId) return go;
541
+ if (go.GetId() == instanceId) return go;
542
542
  for (int i = 0; i < go.transform.childCount; i++)
543
543
  {
544
544
  var found = FindInHierarchy(go.transform.GetChild(i).gameObject, instanceId);
@@ -31,7 +31,7 @@ namespace UCP.Bridge
31
31
  {
32
32
  { "serializationMode", mode },
33
33
  { "forceText", mode == 2 },
34
- { "visibleMetaFiles", EditorSettings.externalVersionControl == "Visible Meta Files" }
34
+ { "visibleMetaFiles", VersionControlSettings.mode == "Visible Meta Files" }
35
35
  };
36
36
  }
37
37
  finally
@@ -17,7 +17,7 @@ namespace UCP.Bridge
17
17
  public HashSet<string> Components = new();
18
18
  }
19
19
 
20
- private static readonly Dictionary<int, Dictionary<string, TrackedSceneChange>> s_changesByScene = new();
20
+ private static readonly Dictionary<long, Dictionary<string, TrackedSceneChange>> s_changesByScene = new();
21
21
 
22
22
  static SceneChangeTracker()
23
23
  {
@@ -33,7 +33,7 @@ namespace UCP.Bridge
33
33
  if (gameObject == null)
34
34
  return;
35
35
 
36
- RecordSceneChange(gameObject.scene, gameObject.GetInstanceID(), gameObject.name, componentName);
36
+ RecordSceneChange(gameObject.scene, gameObject.GetId(), gameObject.name, componentName);
37
37
  }
38
38
 
39
39
  public static void RecordDeletedObject(Scene scene, int instanceId, string name, string componentName)
@@ -56,7 +56,7 @@ namespace UCP.Bridge
56
56
  var modifications = new List<object>();
57
57
  var omittedCount = 0;
58
58
 
59
- if (scene.IsValid() && s_changesByScene.TryGetValue(scene.handle, out var trackedChanges))
59
+ if (scene.IsValid() && s_changesByScene.TryGetValue(UnityObjectCompat.GetSceneHandle(scene), out var trackedChanges))
60
60
  {
61
61
  var ordered = trackedChanges.Values
62
62
  .OrderBy(change => change.InstanceId.HasValue ? 0 : 1)
@@ -101,7 +101,7 @@ namespace UCP.Bridge
101
101
  if (!scene.IsValid())
102
102
  return;
103
103
 
104
- s_changesByScene.Remove(scene.handle);
104
+ s_changesByScene.Remove(UnityObjectCompat.GetSceneHandle(scene));
105
105
  }
106
106
 
107
107
  private static UndoPropertyModification[] OnPostprocessModifications(UndoPropertyModification[] modifications)
@@ -140,10 +140,10 @@ namespace UCP.Bridge
140
140
  if (!scene.IsValid() || !scene.isLoaded)
141
141
  return;
142
142
 
143
- if (!s_changesByScene.TryGetValue(scene.handle, out var sceneChanges))
143
+ if (!s_changesByScene.TryGetValue(UnityObjectCompat.GetSceneHandle(scene), out var sceneChanges))
144
144
  {
145
145
  sceneChanges = new Dictionary<string, TrackedSceneChange>();
146
- s_changesByScene[scene.handle] = sceneChanges;
146
+ s_changesByScene[UnityObjectCompat.GetSceneHandle(scene)] = sceneChanges;
147
147
  }
148
148
 
149
149
  var key = instanceId.HasValue ? instanceId.Value.ToString() : $"scene::{name}";