From fb8bc402e7892bc746a0b91c95d3b14518559ad4 Mon Sep 17 00:00:00 2001 From: Harald Csaszar Date: Fri, 16 May 2025 15:27:04 +0200 Subject: [PATCH] [unity] Added option for unsafe data loading avoiding some allocations on skel.bytes data. Closes #2851. --- CHANGELOG.md | 1 + .../Editor/Utility/BuildSettings.cs | 40 +++++++++++-------- .../Editor/Windows/SpinePreferences.cs | 20 ++++++++++ .../Assets/Spine/Runtime/spine-unity.asmdef | 3 +- .../Asset Types/SkeletonDataAsset.cs | 40 +++++++++++++++++-- spine-unity/Assets/Spine/package.json | 2 +- 6 files changed, 85 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 40be0079d..c350defcf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -175,6 +175,7 @@ 2. Add a `RenderExistingMeshGraphic` component. 3. In the `RenderExistingMeshGraphic` component Inspector at `Reference Skeleton Graphic` assign the original `SkeletonGraphic` object. 4. At `Replacement Material` assign e.g. the included _SkeletonGraphicDefaultOutline_ material to replace all materials with this material. Alternatively, if `Multiple CanvasRenderers` is enabled at the reference SkeletonGraphic, you can add entries to the `Replacement Materials` list and at each entry assign the original SkeletonGraphic material (e.g. _SkeletonGraphicDefault_) to be replaced and the respective `Replacement Material` (e.g. _SkeletonGraphicDefaultOutline_). + - Added option for unsafe direct data loading when loading skeleton binary data to avoid some allocations, enabled via build define `SPINE_ALLOW_UNSAFE`. This define can be set via Spine Preferences, setting `Unsafe Build Defines - Direct data access`. The define is disabled by default to maintain existing behaviour. Changed asmdef setting for spine-unity assembly to allow unsafe code, has no effect other than allowing setting the `SPINE_ALLOW_UNSAFE` define. - **Breaking changes** diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/BuildSettings.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/BuildSettings.cs index 686eb3a66..42628cc06 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/BuildSettings.cs +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/BuildSettings.cs @@ -83,6 +83,8 @@ namespace Spine.Unity.Editor { } public static class SpineBuildEnvUtility { + public const string SPINE_ALLOW_UNSAFE_CODE = "SPINE_ALLOW_UNSAFE"; + static bool IsInvalidGroup (BuildTargetGroup group) { int gi = (int)group; return @@ -99,15 +101,18 @@ namespace Spine.Unity.Editor { if (IsInvalidGroup(group)) continue; - string defines = PlayerSettings.GetScriptingDefineSymbolsForGroup(group); - if (!defines.Contains(define)) { - wasDefineAdded = true; - if (defines.EndsWith(";", System.StringComparison.Ordinal)) - defines += define; - else - defines += ";" + define; + try { + string defines = PlayerSettings.GetScriptingDefineSymbolsForGroup(group); + if (!defines.Contains(define)) { + wasDefineAdded = true; + if (defines.EndsWith(";", System.StringComparison.Ordinal)) + defines += define; + else + defines += ";" + define; - PlayerSettings.SetScriptingDefineSymbolsForGroup(group, defines); + PlayerSettings.SetScriptingDefineSymbolsForGroup(group, defines); + } + } catch (System.Exception) { } } Debug.LogWarning("Please ignore errors \"PlayerSettings Validation: Requested build target group doesn't exist\" above"); @@ -127,15 +132,18 @@ namespace Spine.Unity.Editor { if (IsInvalidGroup(group)) continue; - string defines = PlayerSettings.GetScriptingDefineSymbolsForGroup(group); - if (defines.Contains(define)) { - wasDefineRemoved = true; - if (defines.Contains(define + ";")) - defines = defines.Replace(define + ";", ""); - else - defines = defines.Replace(define, ""); + try { + string defines = PlayerSettings.GetScriptingDefineSymbolsForGroup(group); + if (defines.Contains(define)) { + wasDefineRemoved = true; + if (defines.Contains(define + ";")) + defines = defines.Replace(define + ";", ""); + else + defines = defines.Replace(define, ""); - PlayerSettings.SetScriptingDefineSymbolsForGroup(group, defines); + PlayerSettings.SetScriptingDefineSymbolsForGroup(group, defines); + } + } catch (System.Exception) { } } diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Windows/SpinePreferences.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Windows/SpinePreferences.cs index 6337521fb..27b98fe1c 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Windows/SpinePreferences.cs +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Windows/SpinePreferences.cs @@ -39,6 +39,14 @@ #define HAS_ON_POSTPROCESS_PREFAB #endif +#if UNITY_2021_2_OR_NEWER +#define TEXT_ASSET_HAS_GET_DATA_BYTES +#endif + +#if TEXT_ASSET_HAS_GET_DATA_BYTES +#define HAS_ANY_UNSAFE_OPTIONS +#endif + using System.Threading; using UnityEditor; using UnityEngine; @@ -356,6 +364,18 @@ namespace Spine.Unity.Editor { } #endif +#if HAS_ANY_UNSAFE_OPTIONS + GUILayout.Space(20); + EditorGUILayout.LabelField("Unsafe Build Defines", EditorStyles.boldLabel); + using (new GUILayout.HorizontalScope()) { + EditorGUILayout.PrefixLabel(new GUIContent("Direct data access", "Allow unsafe direct data access. Currently affects reading .skel.bytes files, reading with fewer allocations.")); + if (GUILayout.Button("Enable", GUILayout.Width(64))) + SpineBuildEnvUtility.EnableBuildDefine(SpineBuildEnvUtility.SPINE_ALLOW_UNSAFE_CODE); + if (GUILayout.Button("Disable", GUILayout.Width(64))) + SpineBuildEnvUtility.DisableBuildDefine(SpineBuildEnvUtility.SPINE_ALLOW_UNSAFE_CODE); + } +#endif + #if SPINE_TK2D_DEFINE bool isTK2DDefineSet = true; #else diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity.asmdef b/spine-unity/Assets/Spine/Runtime/spine-unity.asmdef index 11660b1c6..621cb738e 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity.asmdef +++ b/spine-unity/Assets/Spine/Runtime/spine-unity.asmdef @@ -1,4 +1,5 @@ { "name": "spine-unity", - "references": [ "spine-csharp" ] + "references": [ "spine-csharp" ], + "allowUnsafeCode": true } diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/SkeletonDataAsset.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/SkeletonDataAsset.cs index 8cc55179a..e4c3c002d 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/SkeletonDataAsset.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/SkeletonDataAsset.cs @@ -27,14 +27,37 @@ * SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ +//#define SPINE_ALLOW_UNSAFE // note: this define can be set via Edit - Preferences - Spine. + +#if UNITY_2021_2_OR_NEWER +#define TEXT_ASSET_HAS_GET_DATA_BYTES +#endif + +#if SPINE_ALLOW_UNSAFE && TEXT_ASSET_HAS_GET_DATA_BYTES +#define UNSAFE_DIRECT_ACCESS_TEXT_ASSET_DATA +#endif + using System; using System.Collections.Generic; using System.IO; +using Unity.Collections; using UnityEngine; - using CompatibilityProblemInfo = Spine.Unity.SkeletonDataCompatibility.CompatibilityProblemInfo; namespace Spine.Unity { +#if UNSAFE_DIRECT_ACCESS_TEXT_ASSET_DATA + public static class TextAssetExtensions { + public static Stream GetStreamUnsafe (this TextAsset textAsset) { + NativeArray dataNativeArray = textAsset.GetData(); + return dataNativeArray.GetUnmanagedMemoryStream(); + } + + public static unsafe UnmanagedMemoryStream GetUnmanagedMemoryStream (this NativeArray nativeArray) where T : struct { + return new UnmanagedMemoryStream((byte*)global::Unity.Collections.LowLevel.Unsafe. + NativeArrayUnsafeUtility.GetUnsafeReadOnlyPtr(nativeArray), nativeArray.Length); + } + } +#endif [CreateAssetMenu(fileName = "New SkeletonDataAsset", menuName = "Spine/SkeletonData Asset")] public class SkeletonDataAsset : ScriptableObject { @@ -188,9 +211,13 @@ namespace Spine.Unity { SkeletonData loadedSkeletonData = null; try { - if (hasBinaryExtension) + if (hasBinaryExtension) { +#if UNSAFE_DIRECT_ACCESS_TEXT_ASSET_DATA + loadedSkeletonData = SkeletonDataAsset.ReadSkeletonData(skeletonJSON.GetStreamUnsafe(), attachmentLoader, skeletonDataScale); +#else loadedSkeletonData = SkeletonDataAsset.ReadSkeletonData(skeletonJSON.bytes, attachmentLoader, skeletonDataScale); - else +#endif + } else loadedSkeletonData = SkeletonDataAsset.ReadSkeletonData(skeletonJSON.text, attachmentLoader, skeletonDataScale); } catch (Exception ex) { if (!quiet) @@ -287,6 +314,13 @@ namespace Spine.Unity { } } + internal static SkeletonData ReadSkeletonData (Stream assetStream, AttachmentLoader attachmentLoader, float scale) { + SkeletonBinary binary = new SkeletonBinary(attachmentLoader) { + Scale = scale + }; + return binary.ReadSkeletonData(assetStream); + } + internal static SkeletonData ReadSkeletonData (string text, AttachmentLoader attachmentLoader, float scale) { StringReader input = new StringReader(text); SkeletonJson json = new SkeletonJson(attachmentLoader) { diff --git a/spine-unity/Assets/Spine/package.json b/spine-unity/Assets/Spine/package.json index 5831705fd..716b4dc59 100644 --- a/spine-unity/Assets/Spine/package.json +++ b/spine-unity/Assets/Spine/package.json @@ -2,7 +2,7 @@ "name": "com.esotericsoftware.spine.spine-unity", "displayName": "spine-unity Runtime", "description": "This plugin provides the spine-unity runtime core.", - "version": "4.2.104", + "version": "4.2.105", "unity": "2018.3", "author": { "name": "Esoteric Software",