diff --git a/.yamato/global.metafile b/.yamato/global.metafile index 581b9bcee..d1f77434f 100644 --- a/.yamato/global.metafile +++ b/.yamato/global.metafile @@ -1,24 +1,24 @@ test_editors: - version: 2021.3 - - version: 2022.3 - version: 6000.0 - - version: 6000.2 - version: 6000.3 - version: 6000.4 + - version: 6000.5 - version: trunk nightly_test_editors: - - version: 2022.3 - version: 6000.0 - - version: 6000.2 - version: 6000.3 - version: 6000.4 - - version: trunk + - version: 6000.5 + - version: trunk clean_console_test_editors: - version: 6000.0 - - version: 6000.2 - - version: trunk + - version: 6000.3 + - version: 6000.4 + - version: 6000.5 + - version: trunk promotion_test_editors: - version: 2021.3 diff --git a/com.unity.formats.alembic/CHANGELOG.md b/com.unity.formats.alembic/CHANGELOG.md index e335b71ab..8b3ce7ac7 100644 --- a/com.unity.formats.alembic/CHANGELOG.md +++ b/com.unity.formats.alembic/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to this package will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2.4.5] - 2026-04-02 +### Changed +- Update deprecated and obsolete APIs available in Unity 6000.4 and above. + ## [2.4.4] - 2026-01-29 ### Changed - Signed binaries on Windows and Mac. diff --git a/com.unity.formats.alembic/Runtime/Scripts/Analytics/AlembicExporterAnalytics.cs b/com.unity.formats.alembic/Runtime/Scripts/Analytics/AlembicExporterAnalytics.cs index ebb999157..2f67662c1 100644 --- a/com.unity.formats.alembic/Runtime/Scripts/Analytics/AlembicExporterAnalytics.cs +++ b/com.unity.formats.alembic/Runtime/Scripts/Analytics/AlembicExporterAnalytics.cs @@ -43,7 +43,10 @@ static IEnumerable GetTargets(this AlembicRecorderSettings settings) where { return settings.TargetBranch != null ? settings.TargetBranch.GetComponentsInChildren() : Enumerable.Empty(); } -#if UNITY_2023_1_OR_NEWER +#if UNITY_6000_4_OR_NEWER + // Analytics only counts component instances; order does not affect the result. + return Object.FindObjectsByType(); +#elif UNITY_2023_1_OR_NEWER return Object.FindObjectsByType(FindObjectsSortMode.InstanceID); #else return Object.FindObjectsOfType(); diff --git a/com.unity.formats.alembic/Runtime/Scripts/Exporter/AlembicExporter_impl.cs b/com.unity.formats.alembic/Runtime/Scripts/Exporter/AlembicExporter_impl.cs index 15578f28b..c2c2a16e1 100644 --- a/com.unity.formats.alembic/Runtime/Scripts/Exporter/AlembicExporter_impl.cs +++ b/com.unity.formats.alembic/Runtime/Scripts/Exporter/AlembicExporter_impl.cs @@ -2,6 +2,7 @@ using System.IO; using System.Collections.Generic; using System.Reflection; +using System.Text; #if UNITY_EDITOR using UnityEditor; #endif @@ -546,7 +547,11 @@ public override void Setup(Component c) m_target = null; return; } +#if UNITY_6000_4_OR_NEWER + abcObject = parent.abcObject.NewXform(target.name + " (" + EntityId.ToULong(target.GetEntityId()).ToString("X16") + ")", timeSamplingIndex); +#else abcObject = parent.abcObject.NewXform(target.name + " (" + target.GetInstanceID().ToString("X8") + ")", timeSamplingIndex); +#endif m_target = target; } @@ -834,7 +839,11 @@ public void Dispose() class CaptureNode { +#if UNITY_6000_4_OR_NEWER + public ulong entityId; +#else public int instanceID; +#endif public Type componentType; public CaptureNode parent; public Transform transform; @@ -872,9 +881,15 @@ class CapturerRecord aeContext m_ctx; ComponentCapturer m_root; +#if UNITY_6000_4_OR_NEWER + Dictionary m_nodes; + List m_newNodes; + List m_entityIdsToRemove; +#else Dictionary m_nodes; List m_newNodes; List m_iidToRemove; +#endif int m_lastTimeSamplingIndex; int m_startFrameOfLastTimeSampling; @@ -947,16 +962,96 @@ public static void ForceDisableBatching() #endif +#if UNITY_6000_4_OR_NEWER + /// + /// Sorts in place into a stable order derived from each object’s scene and + /// transform hierarchy (see ). + /// + /// + /// + /// On Unity 6.4 and newer, sort mode is deprecated for FindObjectsByType, so the returned array has no + /// defined order. Sorting by InstanceID / EntityId is also discouraged. This method reorders the + /// array so exporter iteration (for example from GetTargets) is deterministic and aligned with scene and + /// hierarchy rather than identity allocation. + /// + /// + /// Implementation: allocate a parallel string[] of the same length, fill each entry by clearing a shared + /// and a shared List<int> scratchpad, calling + /// , and assigning + /// keys[i] = sb.ToString(). Then Array.Sort(keys, components, StringComparer.Ordinal) + /// reorders to match ascending key order. Arrays of length 0 or 1, or a null + /// reference, are left unchanged without allocating. + /// + /// + internal static void SortComponentsByStableSceneHierarchy(Component[] components) + { + if (components == null || components.Length <= 1) + return; + + var keys = new string[components.Length]; + var sb = new StringBuilder(128); + var scratchpad = new List(8); + for (int i = 0; i < components.Length; i++) + { + sb.Clear(); + scratchpad.Clear(); + AppendStableHierarchySortKey(sb, components[i], scratchpad); + keys[i] = sb.ToString(); + } + + Array.Sort(keys, components, StringComparer.Ordinal); + } + + /// + /// Appends a deterministic sort key for to . + /// + /// + /// + /// Unity 6.4+ no longer guarantees an ordering for FindObjectsByType, and engine guidance is to avoid + /// sorting by InstanceID / EntityId. This key instead fixes a stable order from scene identity + /// and transform hierarchy so export iteration is reproducible. + /// + /// + /// The key is built as: scene.path, a U+0001 separator, then the path from scene root to the component’s transform as dot-separated + /// values, each formatted as eight digits (D8) so lexical + /// string order matches numeric sibling order. Walking from leaf to root and emitting indices root-first yields + /// depth-first hierarchy order when keys are compared with . + /// + /// + internal static void AppendStableHierarchySortKey(StringBuilder sb, Component c, List scratchpad) + { + var scene = c.gameObject.scene; + sb.Append(scene.path); + sb.Append('\x1'); + var t = c.transform; + while (t != null) + { + scratchpad.Add(t.GetSiblingIndex()); + t = t.parent; + } + for (int i = scratchpad.Count - 1; i >= 0; i--) + { + if (i < scratchpad.Count - 1) + sb.Append('.'); + sb.Append(scratchpad[i].ToString("D8")); + } + } +#endif Component[] GetTargets(Type type) { if (m_settings.Scope == ExportScope.TargetBranch && TargetBranch != null) return TargetBranch.GetComponentsInChildren(type); - else -#if UNITY_2023_1_OR_NEWER - return Array.ConvertAll(GameObject.FindObjectsByType(type, FindObjectsSortMode.InstanceID), e => (Component)e); + +#if UNITY_6000_4_OR_NEWER + var objects = GameObject.FindObjectsByType(type); + var components = Array.ConvertAll(objects, e => (Component)e); + SortComponentsByStableSceneHierarchy(components); + return components; +#elif UNITY_2023_1_OR_NEWER + return Array.ConvertAll(GameObject.FindObjectsByType(type, FindObjectsSortMode.InstanceID), e => (Component)e); #else - return Array.ConvertAll(GameObject.FindObjectsOfType(type), e => (Component)e); + return Array.ConvertAll(GameObject.FindObjectsOfType(type), e => (Component)e); #endif } @@ -974,8 +1069,21 @@ CaptureNode ConstructTree(Transform node) { if (node == null) { return null; } +#if UNITY_6000_4_OR_NEWER + ulong entityId = EntityId.ToULong(node.gameObject.GetEntityId()); +#else int iid = node.gameObject.GetInstanceID(); +#endif CaptureNode cn; +#if UNITY_6000_4_OR_NEWER + if (m_nodes.TryGetValue(entityId, out cn)) { return cn; } + + cn = new CaptureNode(); + cn.entityId = entityId; + cn.transform = node; + cn.parent = ConstructTree(node.parent); + m_nodes.Add(entityId, cn); +#else if (m_nodes.TryGetValue(iid, out cn)) { return cn; } cn = new CaptureNode(); @@ -983,6 +1091,7 @@ CaptureNode ConstructTree(Transform node) cn.transform = node; cn.parent = ConstructTree(node.parent); m_nodes.Add(iid, cn); +#endif m_newNodes.Add(cn); return cn; } @@ -1028,9 +1137,15 @@ void SetupComponentCapturer(CaptureNode node) if (parent != null && parent.transformCapturer == null) { SetupComponentCapturer(parent); +#if UNITY_6000_4_OR_NEWER + if (!m_nodes.ContainsKey(parent.entityId) || !m_newNodes.Contains(parent)) + { + m_nodes.Add(parent.entityId, parent); +#else if (!m_nodes.ContainsKey(parent.instanceID) || !m_newNodes.Contains(parent)) { m_nodes.Add(parent.instanceID, parent); +#endif m_newNodes.Add(parent); } } @@ -1143,9 +1258,15 @@ public bool BeginRecording() } m_root = new RootCapturer(this, m_ctx.topObject); +#if UNITY_6000_4_OR_NEWER + m_nodes = new Dictionary(); + m_newNodes = new List(); + m_entityIdsToRemove = new List(); +#else m_nodes = new Dictionary(); m_newNodes = new List(); m_iidToRemove = new List(); +#endif m_lastTimeSamplingIndex = 1; m_startFrameOfLastTimeSampling = 0; @@ -1170,7 +1291,11 @@ public void EndRecording() { if (!m_recording) { return; } +#if UNITY_6000_4_OR_NEWER + m_entityIdsToRemove = null; +#else m_iidToRemove = null; +#endif m_newNodes = null; m_nodes = null; m_root = null; @@ -1213,14 +1338,24 @@ public void ProcessRecording() var node = kvp.Value; node.Capture(); if (node.transform == null) +#if UNITY_6000_4_OR_NEWER + m_entityIdsToRemove.Add(node.entityId); +#else m_iidToRemove.Add(node.instanceID); +#endif } m_ctx.MarkFrameEnd(); // remove deleted GameObjects +#if UNITY_6000_4_OR_NEWER + foreach (ulong entityId in m_entityIdsToRemove) + m_nodes.Remove(entityId); + m_entityIdsToRemove.Clear(); +#else foreach (int iid in m_iidToRemove) m_nodes.Remove(iid); m_iidToRemove.Clear(); +#endif // advance time ++m_frameCount; diff --git a/com.unity.formats.alembic/Runtime/Scripts/Importer/AlembicMesh.cs b/com.unity.formats.alembic/Runtime/Scripts/Importer/AlembicMesh.cs index 7c8bd66e4..bba1a880e 100644 --- a/com.unity.formats.alembic/Runtime/Scripts/Importer/AlembicMesh.cs +++ b/com.unity.formats.alembic/Runtime/Scripts/Importer/AlembicMesh.cs @@ -533,7 +533,7 @@ Mesh AddMeshComponents(GameObject go) internal static Material GetDefaultMaterial() { - var pipelineAsset = GraphicsSettings.renderPipelineAsset; + var pipelineAsset = GraphicsSettings.defaultRenderPipeline; if (pipelineAsset != null) { return pipelineAsset.defaultMaterial; diff --git a/com.unity.formats.alembic/Runtime/Scripts/Importer/AlembicStream.cs b/com.unity.formats.alembic/Runtime/Scripts/Importer/AlembicStream.cs index a19dc2fb4..6035853da 100644 --- a/com.unity.formats.alembic/Runtime/Scripts/Importer/AlembicStream.cs +++ b/com.unity.formats.alembic/Runtime/Scripts/Importer/AlembicStream.cs @@ -1,6 +1,9 @@ using System; using System.Collections.Generic; using System.IO; +#if UNITY_6000_4_OR_NEWER +using System.Threading; +#endif using Unity.Jobs; using UnityEngine; #if UNITY_EDITOR @@ -87,6 +90,16 @@ public void Execute() static List s_streams = new List(); +#if UNITY_6000_4_OR_NEWER + // Native aiContext keys are int; use a monotonic surrogate instead of narrowing EntityId (ulong → int + // would silently discard the upper 32 bits). Starts at 0; Interlocked.Increment returns 1 on the + // first call, so UID 0 is never issued — matching GetInstanceID's guarantee for live objects. + // Theoretical int overflow after ~2 billion AbcLoad calls is impossible in practice. + static int s_nextNativeContextUid; + + static int AllocateNativeContextUid() => Interlocked.Increment(ref s_nextNativeContextUid); +#endif + public static void DisconnectStreamsWithPath(string path) { aiContext.DestroyByPath(path); @@ -222,7 +235,11 @@ void ClearMotionVectors(AlembicTreeNode node) public bool AbcLoad(bool createMissingNodes, bool serializeMesh) { m_time = 0.0f; +#if UNITY_6000_4_OR_NEWER + m_context = new SafeContext(aiContext.Create(AllocateNativeContextUid())); +#else m_context = new SafeContext(aiContext.Create(m_abcTreeRoot.gameObject.GetInstanceID())); +#endif var settings = m_streamDesc.Settings; m_config.swapHandedness = settings.SwapHandedness; diff --git a/com.unity.formats.alembic/Runtime/Scripts/Importer/Behaviours/AlembicCurvesRenderer.cs b/com.unity.formats.alembic/Runtime/Scripts/Importer/Behaviours/AlembicCurvesRenderer.cs index 1c706e3e0..706abc1d0 100644 --- a/com.unity.formats.alembic/Runtime/Scripts/Importer/Behaviours/AlembicCurvesRenderer.cs +++ b/com.unity.formats.alembic/Runtime/Scripts/Importer/Behaviours/AlembicCurvesRenderer.cs @@ -274,7 +274,7 @@ public void Execute(int curveIdx) static Material GetDefaultMaterial() { - return GraphicsSettings.renderPipelineAsset != null ? GraphicsSettings.renderPipelineAsset.defaultMaterial : new Material(Shader.Find("Diffuse")); + return GraphicsSettings.defaultRenderPipeline != null ? GraphicsSettings.defaultRenderPipeline.defaultMaterial : new Material(Shader.Find("Diffuse")); } } } diff --git a/com.unity.formats.alembic/Tests/Runtime/StableHierarchySortTests.cs b/com.unity.formats.alembic/Tests/Runtime/StableHierarchySortTests.cs new file mode 100644 index 000000000..16822fa91 --- /dev/null +++ b/com.unity.formats.alembic/Tests/Runtime/StableHierarchySortTests.cs @@ -0,0 +1,111 @@ +#if UNITY_6000_4_OR_NEWER +using System.Collections; +using System.Collections.Generic; +using System.Text; +using NUnit.Framework; +using UnityEngine; +using UnityEngine.Formats.Alembic.Util; +using UnityEngine.SceneManagement; +using UnityEngine.TestTools; + +namespace UnityEditor.Formats.Alembic.Exporter.UnitTests +{ + /// + /// Tests for deterministic export ordering (scene + hierarchy path), Unity 6.4+. + /// + class StableHierarchySortTests + { + [Test] + public void SortComponentsByStableSceneHierarchy_NullOrTrivial_DoesNotThrow() + { + AlembicRecorder.SortComponentsByStableSceneHierarchy(null); + AlembicRecorder.SortComponentsByStableSceneHierarchy(new Component[0]); + var go = new GameObject("single"); + AlembicRecorder.SortComponentsByStableSceneHierarchy(new Component[] { go.AddComponent() }); + Object.DestroyImmediate(go); + } + + [UnityTest] + public IEnumerator AppendStableHierarchySortKey_SiblingOrderMatchesSiblingIndex() + { + var sceneName = "StableHierarchySortTests_" + System.Guid.NewGuid(); + var scene = SceneManager.CreateScene(sceneName); + SceneManager.SetActiveScene(scene); + + var first = new GameObject("first"); + var second = new GameObject("second"); + first.transform.SetSiblingIndex(0); + second.transform.SetSiblingIndex(1); + + var a = first.AddComponent(); + var b = second.AddComponent(); + + var sbA = new StringBuilder(); + var sbB = new StringBuilder(); + var scratchpad = new List(); + AlembicRecorder.AppendStableHierarchySortKey(sbA, a, scratchpad); + scratchpad.Clear(); + AlembicRecorder.AppendStableHierarchySortKey(sbB, b, scratchpad); + + Assert.Less(string.CompareOrdinal(sbA.ToString(), sbB.ToString()), 0); + + yield return SceneManager.UnloadSceneAsync(scene); + } + + [UnityTest] + public IEnumerator AppendStableHierarchySortKey_ParentBeforeChild() + { + var sceneName = "StableHierarchySortTests_" + System.Guid.NewGuid(); + var scene = SceneManager.CreateScene(sceneName); + SceneManager.SetActiveScene(scene); + + var parent = new GameObject("parent"); + var child = new GameObject("child"); + child.transform.SetParent(parent.transform); + + var cParent = parent.AddComponent(); + var cChild = child.AddComponent(); + + var sbP = new StringBuilder(); + var sbC = new StringBuilder(); + var scratchpad = new List(); + AlembicRecorder.AppendStableHierarchySortKey(sbP, cParent, scratchpad); + scratchpad.Clear(); + AlembicRecorder.AppendStableHierarchySortKey(sbC, cChild, scratchpad); + + Assert.Less(string.CompareOrdinal(sbP.ToString(), sbC.ToString()), 0); + + yield return SceneManager.UnloadSceneAsync(scene); + } + + [UnityTest] + public IEnumerator SortComponentsByStableSceneHierarchy_OrdersByHierarchy() + { + var sceneName = "StableHierarchySortTests_" + System.Guid.NewGuid(); + var scene = SceneManager.CreateScene(sceneName); + SceneManager.SetActiveScene(scene); + + var root = new GameObject("root"); + var childA = new GameObject("a"); + var childB = new GameObject("b"); + childA.transform.SetParent(root.transform); + childB.transform.SetParent(root.transform); + childA.transform.SetSiblingIndex(0); + childB.transform.SetSiblingIndex(1); + + var r = root.AddComponent(); + var a = childA.AddComponent(); + var b = childB.AddComponent(); + + var components = new[] { b, r, a }; + AlembicRecorder.SortComponentsByStableSceneHierarchy(components); + + Assert.AreSame(r, components[0]); + Assert.AreSame(a, components[1]); + Assert.AreSame(b, components[2]); + + yield return SceneManager.UnloadSceneAsync(scene); + } + } +} +#endif diff --git a/com.unity.formats.alembic/Tests/Runtime/StableHierarchySortTests.cs.meta b/com.unity.formats.alembic/Tests/Runtime/StableHierarchySortTests.cs.meta new file mode 100644 index 000000000..978357ecc --- /dev/null +++ b/com.unity.formats.alembic/Tests/Runtime/StableHierarchySortTests.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 73af66f210dad064e93b2943baafc8b4 \ No newline at end of file diff --git a/com.unity.formats.alembic/ValidationExceptions.json b/com.unity.formats.alembic/ValidationExceptions.json index 08a371bc2..8c5272eb8 100644 --- a/com.unity.formats.alembic/ValidationExceptions.json +++ b/com.unity.formats.alembic/ValidationExceptions.json @@ -1,9 +1,9 @@ { - "ErrorExceptions": [ + "ErrorExceptions": [ { "ValidationTest": "Package Unity Version Validation", "ExceptionMessage": "The Unity version requirement is more strict than in the previous version of the package. Increment the minor version of the package to leave patch versions available for previous version. Read more about this error and potential solutions at https://docs.unity3d.com/Packages/com.unity.package-validation-suite@latest/index.html?preview=1&subfolder=/manual/package_unity_version_validation_error.html#the-unity-version-requirement-is-more-strict-than-in-the-previous-version-of-the-package", - "PackageVersion": "2.4.4" + "PackageVersion": "2.4.5" } ], "WarningExceptions": [] diff --git a/com.unity.formats.alembic/package.json b/com.unity.formats.alembic/package.json index 4c4225600..3daf6f492 100644 --- a/com.unity.formats.alembic/package.json +++ b/com.unity.formats.alembic/package.json @@ -16,5 +16,5 @@ ], "name": "com.unity.formats.alembic", "unity": "2021.3", - "version": "2.4.4" + "version": "2.4.5" }