From 76e85387d1dc2ff734a99e8be41ceaeac06feadb Mon Sep 17 00:00:00 2001 From: Harald Csaszar Date: Fri, 8 Sep 2023 19:21:08 +0200 Subject: [PATCH] [unity] Implemented first experimental preview package version of delayed on-demand loading of Atlas assets. See #1890. --- .../Asset Types/SpineAtlasAssetInspector.cs | 10 +- .../Editor/Utility/SpineBuildProcessor.cs | 71 ++++ .../Editor/Utility/SpineEditorUtilities.cs | 2 +- .../spine-unity/Asset Types/AtlasAssetBase.cs | 35 ++ .../Asset Types/OnDemandTextureLoader.cs | 90 ++++ .../Asset Types/OnDemandTextureLoader.cs.meta | 11 + .../Asset Types/SpineAtlasAsset.cs | 5 +- .../Components/SkeletonRenderer.cs | 22 + spine-unity/Assets/Spine/package.json | 2 +- .../Documentation.meta | 8 + .../Documentation/README.md | 7 + .../Documentation/README.md.meta | 7 + .../Editor.meta | 8 + .../AddressablesTextureLoaderInspector.cs | 94 +++++ ...AddressablesTextureLoaderInspector.cs.meta | 11 + .../Editor/spine-addressables-editor.asmdef | 25 ++ .../spine-addressables-editor.asmdef.meta | 7 + .../LICENSE.md | 26 ++ .../LICENSE.md.meta | 7 + .../Runtime.meta | 8 + .../Runtime/AddressablesTextureLoader.cs | 96 +++++ .../Runtime/AddressablesTextureLoader.cs.meta | 11 + .../Runtime/spine-addressables.asmdef | 20 + .../Runtime/spine-addressables.asmdef.meta | 7 + .../package.json | 22 + .../package.json.meta | 7 + .../Editor.meta | 8 + .../GenericOnDemandTextureLoaderInspector.cs | 390 ++++++++++++++++++ ...ericOnDemandTextureLoaderInspector.cs.meta | 11 + .../spine-on-demand-loading-editor.asmdef | 20 + ...spine-on-demand-loading-editor.asmdef.meta | 7 + .../LICENSE.md | 26 ++ .../LICENSE.md.meta | 7 + .../Runtime.meta | 8 + .../Runtime/GenericOnDemandTextureLoader.cs | 316 ++++++++++++++ .../GenericOnDemandTextureLoader.cs.meta | 11 + .../Runtime/spine-on-demand-loading.asmdef | 17 + .../spine-on-demand-loading.asmdef.meta | 7 + .../package.json | 21 + .../package.json.meta | 7 + 40 files changed, 1471 insertions(+), 4 deletions(-) create mode 100644 spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/OnDemandTextureLoader.cs create mode 100644 spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/OnDemandTextureLoader.cs.meta create mode 100644 spine-unity/Modules/com.esotericsoftware.spine.addressables/Documentation.meta create mode 100644 spine-unity/Modules/com.esotericsoftware.spine.addressables/Documentation/README.md create mode 100644 spine-unity/Modules/com.esotericsoftware.spine.addressables/Documentation/README.md.meta create mode 100644 spine-unity/Modules/com.esotericsoftware.spine.addressables/Editor.meta create mode 100644 spine-unity/Modules/com.esotericsoftware.spine.addressables/Editor/AddressablesTextureLoaderInspector.cs create mode 100644 spine-unity/Modules/com.esotericsoftware.spine.addressables/Editor/AddressablesTextureLoaderInspector.cs.meta create mode 100644 spine-unity/Modules/com.esotericsoftware.spine.addressables/Editor/spine-addressables-editor.asmdef create mode 100644 spine-unity/Modules/com.esotericsoftware.spine.addressables/Editor/spine-addressables-editor.asmdef.meta create mode 100644 spine-unity/Modules/com.esotericsoftware.spine.addressables/LICENSE.md create mode 100644 spine-unity/Modules/com.esotericsoftware.spine.addressables/LICENSE.md.meta create mode 100644 spine-unity/Modules/com.esotericsoftware.spine.addressables/Runtime.meta create mode 100644 spine-unity/Modules/com.esotericsoftware.spine.addressables/Runtime/AddressablesTextureLoader.cs create mode 100644 spine-unity/Modules/com.esotericsoftware.spine.addressables/Runtime/AddressablesTextureLoader.cs.meta create mode 100644 spine-unity/Modules/com.esotericsoftware.spine.addressables/Runtime/spine-addressables.asmdef create mode 100644 spine-unity/Modules/com.esotericsoftware.spine.addressables/Runtime/spine-addressables.asmdef.meta create mode 100644 spine-unity/Modules/com.esotericsoftware.spine.addressables/package.json create mode 100644 spine-unity/Modules/com.esotericsoftware.spine.addressables/package.json.meta create mode 100644 spine-unity/Modules/com.esotericsoftware.spine.on-demand-loading/Editor.meta create mode 100644 spine-unity/Modules/com.esotericsoftware.spine.on-demand-loading/Editor/GenericOnDemandTextureLoaderInspector.cs create mode 100644 spine-unity/Modules/com.esotericsoftware.spine.on-demand-loading/Editor/GenericOnDemandTextureLoaderInspector.cs.meta create mode 100644 spine-unity/Modules/com.esotericsoftware.spine.on-demand-loading/Editor/spine-on-demand-loading-editor.asmdef create mode 100644 spine-unity/Modules/com.esotericsoftware.spine.on-demand-loading/Editor/spine-on-demand-loading-editor.asmdef.meta create mode 100644 spine-unity/Modules/com.esotericsoftware.spine.on-demand-loading/LICENSE.md create mode 100644 spine-unity/Modules/com.esotericsoftware.spine.on-demand-loading/LICENSE.md.meta create mode 100644 spine-unity/Modules/com.esotericsoftware.spine.on-demand-loading/Runtime.meta create mode 100644 spine-unity/Modules/com.esotericsoftware.spine.on-demand-loading/Runtime/GenericOnDemandTextureLoader.cs create mode 100644 spine-unity/Modules/com.esotericsoftware.spine.on-demand-loading/Runtime/GenericOnDemandTextureLoader.cs.meta create mode 100644 spine-unity/Modules/com.esotericsoftware.spine.on-demand-loading/Runtime/spine-on-demand-loading.asmdef create mode 100644 spine-unity/Modules/com.esotericsoftware.spine.on-demand-loading/Runtime/spine-on-demand-loading.asmdef.meta create mode 100644 spine-unity/Modules/com.esotericsoftware.spine.on-demand-loading/package.json create mode 100644 spine-unity/Modules/com.esotericsoftware.spine.on-demand-loading/package.json.meta diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Asset Types/SpineAtlasAssetInspector.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Asset Types/SpineAtlasAssetInspector.cs index 3cc2f614d4..5da5af73c4 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Asset Types/SpineAtlasAssetInspector.cs +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Asset Types/SpineAtlasAssetInspector.cs @@ -42,7 +42,7 @@ namespace Spine.Unity.Editor { [CustomEditor(typeof(SpineAtlasAsset)), CanEditMultipleObjects] public class SpineAtlasAssetInspector : UnityEditor.Editor { - SerializedProperty atlasFile, materials; + SerializedProperty atlasFile, materials, textureLoadingMode, onDemandTextureLoader; SpineAtlasAsset atlasAsset; GUIContent spriteSlicesLabel; @@ -70,6 +70,8 @@ public class SpineAtlasAssetInspector : UnityEditor.Editor { SpineEditorUtilities.ConfirmInitialization(); atlasFile = serializedObject.FindProperty("atlasFile"); materials = serializedObject.FindProperty("materials"); + textureLoadingMode = serializedObject.FindProperty("textureLoadingMode"); + onDemandTextureLoader = serializedObject.FindProperty("onDemandTextureLoader"); materials.isExpanded = true; atlasAsset = (SpineAtlasAsset)target; #if REGION_BAKING_MESH @@ -132,6 +134,12 @@ public class SpineAtlasAssetInspector : UnityEditor.Editor { } } + if (textureLoadingMode != null) { + EditorGUILayout.Space(); + EditorGUILayout.PropertyField(textureLoadingMode); + EditorGUILayout.PropertyField(onDemandTextureLoader); + } + EditorGUILayout.Space(); if (SpineInspectorUtility.LargeCenteredButton(SpineInspectorUtility.TempContent("Set Mipmap Bias to " + SpinePreferences.DEFAULT_MIPMAPBIAS, tooltip: "This may help textures with mipmaps be less blurry when used for 2D sprites."))) { foreach (Material m in atlasAsset.materials) { diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/SpineBuildProcessor.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/SpineBuildProcessor.cs index 018715402b..49c0216048 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/SpineBuildProcessor.cs +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/SpineBuildProcessor.cs @@ -46,6 +46,8 @@ #define HAS_SAVE_ASSET_IF_DIRTY #endif +#define SPINE_OPTIONAL_ON_DEMAND_LOADING + using System.Collections.Generic; using UnityEditor; using UnityEditor.Build; @@ -60,6 +62,9 @@ public class SpineBuildProcessor { #if HAS_ON_POSTPROCESS_PREFAB static List prefabsToRestore = new List(); +#endif +#if SPINE_OPTIONAL_ON_DEMAND_LOADING + static List textureLoadersToRestore = new List(); #endif static Dictionary spriteAtlasTexturesToRestore = new Dictionary(); @@ -68,6 +73,9 @@ public class SpineBuildProcessor { #if HAS_ON_POSTPROCESS_PREFAB if (SpineEditorUtilities.Preferences.removePrefabPreviewMeshes) PreprocessSpinePrefabMeshes(); +#endif +#if SPINE_OPTIONAL_ON_DEMAND_LOADING + PreprocessOnDemandTextureLoaders(); #endif PreprocessSpriteAtlases(); } @@ -77,6 +85,9 @@ public class SpineBuildProcessor { #if HAS_ON_POSTPROCESS_PREFAB if (SpineEditorUtilities.Preferences.removePrefabPreviewMeshes) PostprocessSpinePrefabMeshes(); +#endif +#if SPINE_OPTIONAL_ON_DEMAND_LOADING + PostprocessOnDemandTextureLoaders(); #endif PostprocessSpriteAtlases(); } @@ -132,6 +143,66 @@ public class SpineBuildProcessor { } } #endif + +#if SPINE_OPTIONAL_ON_DEMAND_LOADING + internal static void PreprocessOnDemandTextureLoaders () { + BuildUtilities.IsInSkeletonAssetBuildPreProcessing = true; + try { + AssetDatabase.StartAssetEditing(); + textureLoadersToRestore.Clear(); + string[] loaderAssets = AssetDatabase.FindAssets("t:OnDemandTextureLoader"); + foreach (string loaderAsset in loaderAssets) { + string assetPath = AssetDatabase.GUIDToAssetPath(loaderAsset); + OnDemandTextureLoader loader = AssetDatabase.LoadAssetAtPath(assetPath); + bool isLoaderUsed = loader.atlasAsset && loader.atlasAsset.OnDemandTextureLoader == loader && + loader.atlasAsset.TextureLoadingMode == AtlasAssetBase.LoadingMode.OnDemand; + if (isLoaderUsed) { + IEnumerable modifiedMaterials; + textureLoadersToRestore.Add(assetPath); + loader.AssignPlaceholderTextures(out modifiedMaterials); + +#if HAS_SAVE_ASSET_IF_DIRTY + foreach (Material material in modifiedMaterials) { + AssetDatabase.SaveAssetIfDirty(material); + } +#endif + } + } + EditorUtility.UnloadUnusedAssetsImmediate(); + AssetDatabase.StopAssetEditing(); +#if !HAS_SAVE_ASSET_IF_DIRTY + if (textureLoadersToRestore.Length > 0) + AssetDatabase.SaveAssets(); +#endif + } finally { + BuildUtilities.IsInSkeletonAssetBuildPreProcessing = false; + } + } + + internal static void PostprocessOnDemandTextureLoaders () { + BuildUtilities.IsInSkeletonAssetBuildPostProcessing = true; + try { + foreach (string assetPath in textureLoadersToRestore) { + OnDemandTextureLoader loader = AssetDatabase.LoadAssetAtPath(assetPath); + IEnumerable modifiedMaterials; + loader.AssignTargetTextures(out modifiedMaterials); +#if HAS_SAVE_ASSET_IF_DIRTY + foreach (Material material in modifiedMaterials) { + AssetDatabase.SaveAssetIfDirty(material); + } +#endif + } +#if !HAS_SAVE_ASSET_IF_DIRTY + if (textureLoadersToRestore.Count > 0) + AssetDatabase.SaveAssets(); +#endif + textureLoadersToRestore.Clear(); + + } finally { + BuildUtilities.IsInSkeletonAssetBuildPostProcessing = false; + } + } +#endif internal static void PreprocessSpriteAtlases () { BuildUtilities.IsInSpriteAtlasBuildPreProcessing = true; try { diff --git a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/SpineEditorUtilities.cs b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/SpineEditorUtilities.cs index 21d558fdf1..da69156334 100644 --- a/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/SpineEditorUtilities.cs +++ b/spine-unity/Assets/Spine/Editor/spine-unity/Editor/Utility/SpineEditorUtilities.cs @@ -238,7 +238,7 @@ public partial class SpineEditorUtilities : AssetPostprocessor { } public static void ConfirmInitialization () { - if (!initialized || Icons.skeleton == null) + if (!initialized) Initialize(); } diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/AtlasAssetBase.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/AtlasAssetBase.cs index c8b81aed80..59b5bf5b6f 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/AtlasAssetBase.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/AtlasAssetBase.cs @@ -27,6 +27,8 @@ * SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *****************************************************************************/ +#define SPINE_OPTIONAL_ON_DEMAND_LOADING + using System.Collections.Generic; using UnityEngine; @@ -39,5 +41,38 @@ public abstract class AtlasAssetBase : ScriptableObject { public abstract bool IsLoaded { get; } public abstract void Clear (); public abstract Atlas GetAtlas (bool onlyMetaData = false); + +#if SPINE_OPTIONAL_ON_DEMAND_LOADING + public enum LoadingMode { + Normal = 0, + OnDemand + } + public virtual LoadingMode TextureLoadingMode { + get { return textureLoadingMode; } + set { textureLoadingMode = value; } + } + public OnDemandTextureLoader OnDemandTextureLoader { + get { return onDemandTextureLoader; } + set { onDemandTextureLoader = value; } + } + + public virtual void BeginCustomTextureLoading () { + if (onDemandTextureLoader) + onDemandTextureLoader.BeginCustomTextureLoading(); + } + + public virtual void EndCustomTextureLoading () { + if (onDemandTextureLoader) + onDemandTextureLoader.EndCustomTextureLoading(); + } + + public virtual void RequireTexturesLoaded (Material material, ref Material overrideMaterial) { + if (onDemandTextureLoader) + onDemandTextureLoader.RequestLoadMaterialTextures(material, ref overrideMaterial); + } + + [SerializeField] protected LoadingMode textureLoadingMode = LoadingMode.Normal; + [SerializeField] protected OnDemandTextureLoader onDemandTextureLoader = null; +#endif } } diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/OnDemandTextureLoader.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/OnDemandTextureLoader.cs new file mode 100644 index 0000000000..16de1b725a --- /dev/null +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/OnDemandTextureLoader.cs @@ -0,0 +1,90 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated July 28, 2023. Replaces all prior versions. + * + * Copyright (c) 2013-2023, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software or + * otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE + * SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#define SPINE_OPTIONAL_ON_DEMAND_LOADING + +using System.Collections.Generic; +using UnityEngine; + +#if SPINE_OPTIONAL_ON_DEMAND_LOADING +namespace Spine.Unity { + public abstract class OnDemandTextureLoader : ScriptableObject { + public AtlasAssetBase atlasAsset; + + /// Original texture name without extension. + /// The placeholder texture's name for a given original target texture name. + public abstract string GetPlaceholderTextureName (string originalTextureName); + /// + /// Assigns previously setup placeholder textures at each Material of the associated AtlasAssetBase. + /// True on success, false if the placeholder texture could not be assigned at any of the + /// AtlasAssetBase's materials. + public abstract bool AssignPlaceholderTextures (out IEnumerable modifiedMaterials); + /// + /// Returns whether any placeholder textures are assigned at the Material of the associated AtlasAssetBase. + /// + /// A newly created list of materials which has a placeholder texture assigned. + /// True, if any placeholder texture is assigned at a Material of the associated AtlasAssetBase. + public abstract bool HasPlaceholderTexturesAssigned (out List placeholderMaterials); + /// + /// Assigns previously setup target textures at each Material where placeholder textures are setup. + /// True on success, false if the target texture could not be assigned at any of the + /// AtlasAssetBase's materials. + public abstract bool AssignTargetTextures (out IEnumerable modifiedMaterials); + public abstract void BeginCustomTextureLoading (); + public abstract void EndCustomTextureLoading (); + public abstract bool HasPlaceholderAssigned (Material material); + public abstract void RequestLoadMaterialTextures (Material material, ref Material overrideMaterial); + public abstract void Clear (bool clearAtlasAsset = false); + + #region Event delegates + public delegate void TextureLoadDelegate (OnDemandTextureLoader loader, Material material, int textureIndex); + protected event TextureLoadDelegate onTextureLoaded; + protected event TextureLoadDelegate onTextureUnloaded; + + public event TextureLoadDelegate TextureLoaded { + add { onTextureLoaded += value; } + remove { onTextureLoaded -= value; } + } + public event TextureLoadDelegate TextureUnloaded { + add { onTextureUnloaded += value; } + remove { onTextureUnloaded -= value; } + } + + protected void OnTextureLoaded (Material material, int textureIndex) { + if (onTextureLoaded != null) + onTextureLoaded(this, material, textureIndex); + } + protected void OnTextureUnloaded (Material material, int textureIndex) { + if (onTextureUnloaded != null) + onTextureUnloaded(this, material, textureIndex); + } + #endregion + } +} +#endif diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/OnDemandTextureLoader.cs.meta b/spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/OnDemandTextureLoader.cs.meta new file mode 100644 index 0000000000..f22c0cb750 --- /dev/null +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/OnDemandTextureLoader.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 57d54a357da79654aad505112bb63b9e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/SpineAtlasAsset.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/SpineAtlasAsset.cs index fd56993539..4206ed57db 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/SpineAtlasAsset.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Asset Types/SpineAtlasAsset.cs @@ -263,7 +263,10 @@ public class MaterialsTextureLoader : TextureLoader { Debug.LogError("Material is missing texture: " + other.name, other); return; } - if (other.mainTexture.name == name) { + string textureName = other.mainTexture.name; + if (textureName == name || + (atlasAsset.OnDemandTextureLoader != null && + textureName == atlasAsset.OnDemandTextureLoader.GetPlaceholderTextureName(name))) { material = other; break; } diff --git a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonRenderer.cs b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonRenderer.cs index fd1584926c..ebdcb7e583 100644 --- a/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonRenderer.cs +++ b/spine-unity/Assets/Spine/Runtime/spine-unity/Components/SkeletonRenderer.cs @@ -49,6 +49,7 @@ #define SPINE_OPTIONAL_RENDEROVERRIDE #define SPINE_OPTIONAL_MATERIALOVERRIDE +#define SPINE_OPTIONAL_ON_DEMAND_LOADING using System.Collections.Generic; using UnityEngine; @@ -565,6 +566,10 @@ public class SpriteMaskInteractionMaterials { AssignSpriteMaskMaterials(); } #endif +#if SPINE_OPTIONAL_ON_DEMAND_LOADING + if (Application.isPlaying) + HandleOnDemandLoading(); +#endif #if PER_MATERIAL_PROPERTY_BLOCKS if (fixDrawOrder && meshRenderer.sharedMaterials.Length > 2) { @@ -743,6 +748,23 @@ public class SpriteMaskInteractionMaterials { #endif //#if BUILT_IN_SPRITE_MASK_COMPONENT +#if SPINE_OPTIONAL_ON_DEMAND_LOADING + void HandleOnDemandLoading () { + foreach (AtlasAssetBase atlasAsset in skeletonDataAsset.atlasAssets) { + if (atlasAsset.TextureLoadingMode != AtlasAssetBase.LoadingMode.Normal) { + atlasAsset.BeginCustomTextureLoading(); + for (int i = 0, count = meshRenderer.sharedMaterials.Length; i < count; ++i) { + Material overrideMaterial = null; + atlasAsset.RequireTexturesLoaded(meshRenderer.sharedMaterials[i], ref overrideMaterial); + if (overrideMaterial != null) + meshRenderer.sharedMaterials[i] = overrideMaterial; + } + atlasAsset.EndCustomTextureLoading(); + } + } + } +#endif + #if PER_MATERIAL_PROPERTY_BLOCKS private MaterialPropertyBlock reusedPropertyBlock; public static readonly int SUBMESH_DUMMY_PARAM_ID = Shader.PropertyToID("_Submesh"); diff --git a/spine-unity/Assets/Spine/package.json b/spine-unity/Assets/Spine/package.json index 0ab2779966..2b820140df 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.1.20", + "version": "4.1.21", "unity": "2018.3", "author": { "name": "Esoteric Software", diff --git a/spine-unity/Modules/com.esotericsoftware.spine.addressables/Documentation.meta b/spine-unity/Modules/com.esotericsoftware.spine.addressables/Documentation.meta new file mode 100644 index 0000000000..b52354a72b --- /dev/null +++ b/spine-unity/Modules/com.esotericsoftware.spine.addressables/Documentation.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 189dc4f0c0a8a6141b2c0f4c95fba6bd +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/spine-unity/Modules/com.esotericsoftware.spine.addressables/Documentation/README.md b/spine-unity/Modules/com.esotericsoftware.spine.addressables/Documentation/README.md new file mode 100644 index 0000000000..754ab83e62 --- /dev/null +++ b/spine-unity/Modules/com.esotericsoftware.spine.addressables/Documentation/README.md @@ -0,0 +1,7 @@ +## Spine Addressables Extensions [Experimental] + +This experimental plugin provides integration of Addressables on-demand texture loading for the spine-unity runtime. Please be sure to test this package first and create backups of your project before using. + +### Usage + +First declare your target Material textures as addressable. Then select the SpineAtlasAsset, right-click the SpineAtlasAsset Inspector heading and select 'Add Addressables Loader'. This generates an 'AddressableTextureLoader' asset providing configuration parameters and sets up low-resolution placeholder textures which are automatically assigned in a pre-build step when building your game executable. diff --git a/spine-unity/Modules/com.esotericsoftware.spine.addressables/Documentation/README.md.meta b/spine-unity/Modules/com.esotericsoftware.spine.addressables/Documentation/README.md.meta new file mode 100644 index 0000000000..fbe9930ae9 --- /dev/null +++ b/spine-unity/Modules/com.esotericsoftware.spine.addressables/Documentation/README.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 40d839470102af5458205023f6984224 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/spine-unity/Modules/com.esotericsoftware.spine.addressables/Editor.meta b/spine-unity/Modules/com.esotericsoftware.spine.addressables/Editor.meta new file mode 100644 index 0000000000..4593c74ce2 --- /dev/null +++ b/spine-unity/Modules/com.esotericsoftware.spine.addressables/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 6c3739a10096f12488814c2027b2f5c1 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/spine-unity/Modules/com.esotericsoftware.spine.addressables/Editor/AddressablesTextureLoaderInspector.cs b/spine-unity/Modules/com.esotericsoftware.spine.addressables/Editor/AddressablesTextureLoaderInspector.cs new file mode 100644 index 0000000000..6f3c136d04 --- /dev/null +++ b/spine-unity/Modules/com.esotericsoftware.spine.addressables/Editor/AddressablesTextureLoaderInspector.cs @@ -0,0 +1,94 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated July 28, 2023. Replaces all prior versions. + * + * Copyright (c) 2013-2023, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software or + * otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE + * SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#define SPINE_OPTIONAL_ON_DEMAND_LOADING + +#if SPINE_OPTIONAL_ON_DEMAND_LOADING + +using System; +using System.Collections.Generic; +using UnityEditor; +using UnityEngine; +using UnityEngine.AddressableAssets; +using UnityEngine.ResourceManagement.AsyncOperations; + +namespace Spine.Unity.Editor { + + using GenericTextureLoader = GenericOnDemandTextureLoader; + using GenericTextureLoaderInspector = GenericOnDemandTextureLoaderInspector; + using PlaceholderMaterialMapping = AddressablesTextureLoader.PlaceholderMaterialMapping; + using PlaceholderTextureMapping = AddressablesTextureLoader.PlaceholderTextureMapping; + + [CustomEditor(typeof(AddressablesTextureLoader)), CanEditMultipleObjects] + public class AddressablesTextureLoaderInspector : GenericTextureLoaderInspector { + public string LoaderSuffix { get { return "_Addressable"; } } + + public class AddressablesMethodImplementations : StaticMethodImplementations { + public override GenericTextureLoader GetOrCreateLoader (string loaderPath) { + GenericTextureLoader loader = AssetDatabase.LoadAssetAtPath(loaderPath); + if (loader == null) { + loader = GenericTextureLoader.CreateInstance(); + AssetDatabase.CreateAsset(loader, loaderPath); + loader = AssetDatabase.LoadAssetAtPath(loaderPath); + } else { + loader.Clear(clearAtlasAsset: false); + } + return loader; + } + + public override bool SetupOnDemandLoadingReference ( + ref AddressableTextureReference targetTextureReference, Texture targetTexture) { + + string targetTextureGUID = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(targetTexture)); + if (string.IsNullOrEmpty(targetTextureGUID)) + return false; + targetTextureReference.assetReference = new AssetReferenceTexture(targetTextureGUID); + return targetTextureReference.assetReference.IsValid(); + } + } + +#region Context Menu Item + [MenuItem("CONTEXT/AtlasAssetBase/Add Addressables Loader")] + static void AddAddressablesLoader (MenuCommand cmd) { + if (staticMethods == null) + staticMethods = new AddressablesMethodImplementations(); + staticMethods.AddOnDemandLoader(cmd); + } + #endregion + + protected override StaticMethodImplementations CreateStaticMethodImplementations () { + return new AddressablesMethodImplementations(); + } + + protected override void DrawSingleLineTargetTextureProperty (SerializedProperty property) { + EditorGUILayout.PropertyField(property.FindPropertyRelative("assetReference"), GUIContent.none, true); + } + } +} +#endif diff --git a/spine-unity/Modules/com.esotericsoftware.spine.addressables/Editor/AddressablesTextureLoaderInspector.cs.meta b/spine-unity/Modules/com.esotericsoftware.spine.addressables/Editor/AddressablesTextureLoaderInspector.cs.meta new file mode 100644 index 0000000000..40cbe7aa95 --- /dev/null +++ b/spine-unity/Modules/com.esotericsoftware.spine.addressables/Editor/AddressablesTextureLoaderInspector.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 31994ef86a03628459a588131067023f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/spine-unity/Modules/com.esotericsoftware.spine.addressables/Editor/spine-addressables-editor.asmdef b/spine-unity/Modules/com.esotericsoftware.spine.addressables/Editor/spine-addressables-editor.asmdef new file mode 100644 index 0000000000..e32007a871 --- /dev/null +++ b/spine-unity/Modules/com.esotericsoftware.spine.addressables/Editor/spine-addressables-editor.asmdef @@ -0,0 +1,25 @@ +{ + "name": "spine-addressables-editor", + "rootNamespace": "", + "references": [ + "spine-unity", + "spine-unity-editor", + "spine-addressables", + "spine-on-demand-loading", + "spine-on-demand-loading-editor", + "Unity.Addressables", + "Unity.Addressables.Editor", + "Unity.ResourceManager" + ], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/spine-unity/Modules/com.esotericsoftware.spine.addressables/Editor/spine-addressables-editor.asmdef.meta b/spine-unity/Modules/com.esotericsoftware.spine.addressables/Editor/spine-addressables-editor.asmdef.meta new file mode 100644 index 0000000000..09f1eb943c --- /dev/null +++ b/spine-unity/Modules/com.esotericsoftware.spine.addressables/Editor/spine-addressables-editor.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 0743404dced125d4b8d86290e414812c +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/spine-unity/Modules/com.esotericsoftware.spine.addressables/LICENSE.md b/spine-unity/Modules/com.esotericsoftware.spine.addressables/LICENSE.md new file mode 100644 index 0000000000..4425c61220 --- /dev/null +++ b/spine-unity/Modules/com.esotericsoftware.spine.addressables/LICENSE.md @@ -0,0 +1,26 @@ +# Spine Runtimes License Agreement +Last updated July 28, 2023. Replaces all prior versions. + +Copyright (c) 2013-2023, Esoteric Software LLC + +Integration of the Spine Runtimes into software or otherwise creating +derivative works of the Spine Runtimes is permitted under the terms and +conditions of Section 2 of the Spine Editor License Agreement: +http://esotericsoftware.com/spine-editor-license + +Otherwise, it is permitted to integrate the Spine Runtimes into software or +otherwise create derivative works of the Spine Runtimes (collectively, +"Products"), provided that each user of the Products must obtain their own +Spine Editor license and redistribution of the Products in any form must +include this license and copyright notice. + +THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, +BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE +SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/spine-unity/Modules/com.esotericsoftware.spine.addressables/LICENSE.md.meta b/spine-unity/Modules/com.esotericsoftware.spine.addressables/LICENSE.md.meta new file mode 100644 index 0000000000..f3eea63f07 --- /dev/null +++ b/spine-unity/Modules/com.esotericsoftware.spine.addressables/LICENSE.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 3369e909ffdf92847a4b8d95b983a7ff +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/spine-unity/Modules/com.esotericsoftware.spine.addressables/Runtime.meta b/spine-unity/Modules/com.esotericsoftware.spine.addressables/Runtime.meta new file mode 100644 index 0000000000..5dc72ec5d8 --- /dev/null +++ b/spine-unity/Modules/com.esotericsoftware.spine.addressables/Runtime.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 219b86dfc3643544e80d60785c29d034 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/spine-unity/Modules/com.esotericsoftware.spine.addressables/Runtime/AddressablesTextureLoader.cs b/spine-unity/Modules/com.esotericsoftware.spine.addressables/Runtime/AddressablesTextureLoader.cs new file mode 100644 index 0000000000..be137bb4b7 --- /dev/null +++ b/spine-unity/Modules/com.esotericsoftware.spine.addressables/Runtime/AddressablesTextureLoader.cs @@ -0,0 +1,96 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated July 28, 2023. Replaces all prior versions. + * + * Copyright (c) 2013-2023, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software or + * otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE + * SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#define SPINE_OPTIONAL_ON_DEMAND_LOADING + +#if SPINE_OPTIONAL_ON_DEMAND_LOADING + +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +using UnityEngine.AddressableAssets; +using UnityEngine.ResourceManagement.AsyncOperations; + +namespace Spine.Unity { + + [System.Serializable] + public struct AddressableTextureReference : ITargetTextureReference { + [SerializeField] public AssetReferenceTexture assetReference; + +#if UNITY_EDITOR + public Texture EditorTexture { + get { + return (Texture)assetReference.editorAsset; + } + } +#endif + } + + public struct AddressableRequest : IOnDemandRequest { + public AsyncOperationHandle handle; + + public bool WasRequested { + get { return handle.IsValid(); } + } + + public bool WasSuccessfullyLoaded { + get { return handle.IsValid() && handle.Status == AsyncOperationStatus.Succeeded; } + } + + public bool IsTarget (Texture texture) { + return handle.Result == texture; + } + + public void Release () { + Addressables.Release(handle); + } + } + + [System.Serializable] + public class AddressablesTextureLoader : GenericOnDemandTextureLoader { + public override void CreateTextureRequest (AddressableTextureReference targetReference, + MaterialOnDemandData materialData, int textureIndex, Material materialToUpdate) { + + materialData.textureRequests[textureIndex].handle = targetReference.assetReference.LoadAssetAsync(); + materialData.textureRequests[textureIndex].handle.Completed += (obj) => { + if (obj.Status == AsyncOperationStatus.Succeeded) { + materialToUpdate.mainTexture = (Texture)targetReference.assetReference.Asset; + OnTextureLoaded(materialToUpdate, textureIndex); + } + }; + } + + public override Texture GetAlreadyLoadedTexture (int materialIndex, int textureIndex) { + AddressableTextureReference targetReference = placeholderMap[materialIndex].textures[textureIndex].targetTextureReference; + return (Texture)targetReference.assetReference.Asset; + } + } +} +#endif diff --git a/spine-unity/Modules/com.esotericsoftware.spine.addressables/Runtime/AddressablesTextureLoader.cs.meta b/spine-unity/Modules/com.esotericsoftware.spine.addressables/Runtime/AddressablesTextureLoader.cs.meta new file mode 100644 index 0000000000..f217da02dd --- /dev/null +++ b/spine-unity/Modules/com.esotericsoftware.spine.addressables/Runtime/AddressablesTextureLoader.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fbdfbb13f312e5041973e2127739f846 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {fileID: 2800000, guid: c33b04c403c19614395e83e691d3170a, type: 3} + userData: + assetBundleName: + assetBundleVariant: diff --git a/spine-unity/Modules/com.esotericsoftware.spine.addressables/Runtime/spine-addressables.asmdef b/spine-unity/Modules/com.esotericsoftware.spine.addressables/Runtime/spine-addressables.asmdef new file mode 100644 index 0000000000..b0a03f9076 --- /dev/null +++ b/spine-unity/Modules/com.esotericsoftware.spine.addressables/Runtime/spine-addressables.asmdef @@ -0,0 +1,20 @@ +{ + "name": "spine-addressables", + "rootNamespace": "", + "references": [ + "spine-unity", + "spine-csharp", + "spine-on-demand-loading", + "Unity.Addressables", + "Unity.ResourceManager" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/spine-unity/Modules/com.esotericsoftware.spine.addressables/Runtime/spine-addressables.asmdef.meta b/spine-unity/Modules/com.esotericsoftware.spine.addressables/Runtime/spine-addressables.asmdef.meta new file mode 100644 index 0000000000..b90aea4c35 --- /dev/null +++ b/spine-unity/Modules/com.esotericsoftware.spine.addressables/Runtime/spine-addressables.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: d2261bc30a9ceb04ebbc867c3654e30e +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/spine-unity/Modules/com.esotericsoftware.spine.addressables/package.json b/spine-unity/Modules/com.esotericsoftware.spine.addressables/package.json new file mode 100644 index 0000000000..56662e1507 --- /dev/null +++ b/spine-unity/Modules/com.esotericsoftware.spine.addressables/package.json @@ -0,0 +1,22 @@ +{ + "name": "com.esotericsoftware.spine.addressables", + "displayName": "Spine Addressables Extensions [Experimental]", + "description": "This experimental plugin provides integration of Addressables on-demand texture loading for the spine-unity runtime.\nPlease be sure to test this package first and create backups of your project before using.\n\nUsage: First declare your target Material textures as addressable. Then select the SpineAtlasAsset, right-click the SpineAtlasAsset Inspector heading and select 'Add Addressables Loader'. This generates an 'AddressableTextureLoader' asset providing configuration parameters and sets up low-resolution placeholder textures which are automatically assigned in a pre-build step when building your game executable.\n\nPrerequisites:\nIt requires a working installation of the spine-unity runtime (via the spine-unity unitypackage), version 4.1.\n(See http://esotericsoftware.com/git/spine-runtimes/spine-unity)", + "version": "4.1.0-preview.1", + "unity": "2018.3", + "author": { + "name": "Esoteric Software", + "email": "contact@esotericsoftware.com", + "url": "http://esotericsoftware.com/" + }, + "dependencies": { + "com.unity.addressables": "1.18.19", + "com.esotericsoftware.spine.spine-unity": "4.1.21", + "com.esotericsoftware.spine.on-demand-loading": "4.1.0" + }, + "keywords": [ + "spine", + "addressables", + "preview" + ] +} diff --git a/spine-unity/Modules/com.esotericsoftware.spine.addressables/package.json.meta b/spine-unity/Modules/com.esotericsoftware.spine.addressables/package.json.meta new file mode 100644 index 0000000000..f1bcbe1b34 --- /dev/null +++ b/spine-unity/Modules/com.esotericsoftware.spine.addressables/package.json.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: ece84be6a21fe8841a65fdc9d5038711 +PackageManifestImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/spine-unity/Modules/com.esotericsoftware.spine.on-demand-loading/Editor.meta b/spine-unity/Modules/com.esotericsoftware.spine.on-demand-loading/Editor.meta new file mode 100644 index 0000000000..7f71f9a58d --- /dev/null +++ b/spine-unity/Modules/com.esotericsoftware.spine.on-demand-loading/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: f7d10bf3e021d1845b6cb3ffcc0d4cb2 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/spine-unity/Modules/com.esotericsoftware.spine.on-demand-loading/Editor/GenericOnDemandTextureLoaderInspector.cs b/spine-unity/Modules/com.esotericsoftware.spine.on-demand-loading/Editor/GenericOnDemandTextureLoaderInspector.cs new file mode 100644 index 0000000000..8f113d682f --- /dev/null +++ b/spine-unity/Modules/com.esotericsoftware.spine.on-demand-loading/Editor/GenericOnDemandTextureLoaderInspector.cs @@ -0,0 +1,390 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated July 28, 2023. Replaces all prior versions. + * + * Copyright (c) 2013-2023, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software or + * otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE + * SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#if UNITY_2017_2_OR_NEWER +#define NEWPLAYMODECALLBACKS +#endif + +#define SPINE_OPTIONAL_ON_DEMAND_LOADING + +#if SPINE_OPTIONAL_ON_DEMAND_LOADING + +using System; +using System.Collections.Generic; +using UnityEditor; +using UnityEngine; + +namespace Spine.Unity.Editor { + + /// + /// Base class for GenericOnDemandTextureLoader Inspector subclasses. + /// For reference, see the class available + /// in the com.esotericsoftware.spine.addressables UPM package. + /// + /// The implementation struct which holds an on-demand loading reference + /// to the target texture to be loaded, derived from ITargetTextureReference. + /// The implementation struct covering a single texture loading request, + /// derived from IOnDemandRequest + [InitializeOnLoad] + [CustomEditor(typeof(GenericOnDemandTextureLoader<,>)), CanEditMultipleObjects] + public abstract class GenericOnDemandTextureLoaderInspector : UnityEditor.Editor + where TargetReference : Spine.Unity.ITargetTextureReference + where TextureRequest : Spine.Unity.IOnDemandRequest { + + protected SerializedProperty atlasAsset; + protected SerializedProperty maxPlaceholderSize; + protected SerializedProperty placeholderMap; + protected SerializedProperty unloadAfterSecondsUnused; + static protected bool placeholdersFoldout = true; + protected SerializedProperty loadedDataAtMaterial; + protected GenericOnDemandTextureLoader loader; + protected GUIContent placeholderTexturesLabel; + + /// + /// Called via InitializeOnLoad attribute upon Editor startup or compilation. + /// + static GenericOnDemandTextureLoaderInspector () { +#if NEWPLAYMODECALLBACKS + EditorApplication.playModeStateChanged += OnPlaymodeChanged; +#else + EditorApplication.playmodeStateChanged += OnPlaymodeChanged; +#endif + } + + /// + /// Derive your implementation subclass of this class and implement the respective abstract methods. + /// Note: Unfortunately the Unity menu entries are created via static methods, so this is a workaround + /// to provide virtual static functions in old C# versions. + /// + public abstract class StaticMethodImplementations { + + public abstract GenericOnDemandTextureLoader GetOrCreateLoader (string loaderPath); + + /// + /// Returns the on-demand loader asset's filename suffix. The filename + /// is determined by the AtlasAsset, while this suffix replaces the "_Atlas" suffix. + /// When set to e.g. "_Addressable", the loader asset created for + /// the "Skeleton_Atlas" asset is named "Skeleton_Addressable". + /// + public string LoaderSuffix { get; } + + public abstract bool SetupOnDemandLoadingReference ( + ref TargetReference targetTextureReference, Texture targetTexture); + + /// + /// Create a context menu wrapper in the main class for this generic implementation using the code below. + /// + /// [MenuItem("CONTEXT/AtlasAssetBase/Add YourSubclass Loader")] + /// static void AddYourSubclassLoader (MenuCommand cmd) { + /// if (staticMethods == null) + /// staticMethods = new YourSubclassMethodImplementations (); + /// staticMethods.AddOnDemandLoader(cmd); + /// } + /// + /// + public virtual void AddOnDemandLoader (MenuCommand cmd) { + AtlasAssetBase atlasAsset = cmd.context as AtlasAssetBase; + Debug.Log("Adding On-Demand Loader for " + atlasAsset.name, atlasAsset); + + if (atlasAsset.OnDemandTextureLoader != null) { + Debug.LogWarning("AtlasAsset On-Demand TextureLoader is already set. " + + "Please clear it if you want to assign a different one."); + return; + } + + atlasAsset.TextureLoadingMode = AtlasAssetBase.LoadingMode.OnDemand; + EditorUtility.SetDirty(atlasAsset); + + string atlasAssetPath = AssetDatabase.GetAssetPath(atlasAsset); + string loaderPath = atlasAssetPath.Replace(AssetUtility.AtlasSuffix, LoaderSuffix); + + GenericOnDemandTextureLoader loader = staticMethods.GetOrCreateLoader(loaderPath); + staticMethods.SetupForAtlasAsset(loader, atlasAsset); + + EditorUtility.SetDirty(loader); + AssetDatabase.SaveAssets(); + } + + public virtual void SetupForAtlasAsset (GenericOnDemandTextureLoader loader, AtlasAssetBase atlasAsset) { + if (loader.placeholderMap != null && loader.placeholderMap.Length > 0) { + IEnumerable modifiedMaterials; + loader.AssignTargetTextures(out modifiedMaterials); // start from normal textures + } + + if (atlasAsset == null) { + Debug.LogError("AddressableTextureLoader.SetupForAtlasAsset: atlasAsset was null, aborting setup.", atlasAsset); + return; + } + + int materialCount = atlasAsset.MaterialCount; + loader.placeholderMap = new GenericOnDemandTextureLoader.PlaceholderMaterialMapping[materialCount]; + GenericOnDemandTextureLoader.PlaceholderMaterialMapping[] materialMap = loader.placeholderMap; + + atlasAsset.OnDemandTextureLoader = loader; + int maxPlaceholderSize = loader.maxPlaceholderSize; + + int i = 0; + foreach (Material targetMaterial in atlasAsset.Materials) { + Texture targetTexture = targetMaterial.mainTexture; + materialMap[i].textures = new GenericOnDemandTextureLoader.PlaceholderTextureMapping[1]; // Todo: currently only main texture is supported. + int textureIndex = 0; + + GenericOnDemandTextureLoader.PlaceholderTextureMapping[] texturesMap = materialMap[i].textures; + if (texturesMap[textureIndex].placeholderTexture != targetTexture) { // otherwise already set to placeholder + SetupOnDemandLoadingReference(ref texturesMap[textureIndex].targetTextureReference, targetTexture); + texturesMap[textureIndex].placeholderTexture = CreatePlaceholderTextureFor(targetTexture, maxPlaceholderSize, loader); + } + ++i; + } + // assign late since CreatePlaceholderTextureFor(texture) method above might save assets and clear these values. + loader.placeholderMap = materialMap; + loader.atlasAsset = atlasAsset; + } + + public virtual Texture CreatePlaceholderTextureFor (Texture originalTexture, int maxPlaceholderSize, + GenericOnDemandTextureLoader loader) { + + const string AssetFolderName = "LoadingPlaceholderAssets"; + string originalPath = AssetDatabase.GetAssetPath(originalTexture); + string parentFolder = System.IO.Path.GetDirectoryName(originalPath); + string dataPath = parentFolder + "/" + AssetFolderName; + if (!AssetDatabase.IsValidFolder(dataPath)) { + AssetDatabase.CreateFolder(parentFolder, AssetFolderName); + } + + string originalTextureName = System.IO.Path.GetFileNameWithoutExtension(originalPath); + string texturePath = string.Format("{0}/{1}.png", dataPath, loader.GetPlaceholderTextureName(originalTextureName)); + Texture placeholderTexture = AssetDatabase.LoadAssetAtPath(texturePath); + if (placeholderTexture == null) { + AssetDatabase.CopyAsset(originalPath, texturePath); + + const bool resizePhysically = true; + + TextureImporter importer = (TextureImporter)TextureImporter.GetAtPath(texturePath); + const string defaultPlatform = "Default"; + TextureImporterPlatformSettings settings = importer.GetPlatformTextureSettings(defaultPlatform); + settings.maxTextureSize = maxPlaceholderSize; + importer.SetPlatformTextureSettings(settings); + importer.maxTextureSize = maxPlaceholderSize; + importer.isReadable = resizePhysically; + importer.SaveAndReimport(); + + if (resizePhysically) { + Texture2D texture2D = AssetDatabase.LoadAssetAtPath(texturePath); + if (texture2D) { + Color[] maxTextureSizePixels = texture2D.GetPixels(); + texture2D.SetPixels(maxTextureSizePixels); + + var bytes = texture2D.EncodeToPNG(); + string targetPath = Application.dataPath + "/../" + texturePath; + System.IO.File.WriteAllBytes(targetPath, bytes); + texture2D.Apply(updateMipmaps: true, makeNoLongerReadable: true); + EditorUtility.SetDirty(texture2D); + AssetDatabase.SaveAssets(); + } + } + placeholderTexture = AssetDatabase.LoadAssetAtPath(texturePath); + } + + UnityEngine.Object folderObject = AssetDatabase.LoadAssetAtPath(dataPath, typeof(UnityEngine.Object)); + if (folderObject != null) { + EditorGUIUtility.PingObject(folderObject); + } + + return placeholderTexture; + } + } + public static StaticMethodImplementations staticMethods; + + void OnEnable () { + atlasAsset = serializedObject.FindProperty("atlasAsset"); + maxPlaceholderSize = serializedObject.FindProperty("maxPlaceholderSize"); + placeholderMap = serializedObject.FindProperty("placeholderMap"); + unloadAfterSecondsUnused = serializedObject.FindProperty("unloadAfterSecondsUnused"); + loadedDataAtMaterial = serializedObject.FindProperty("loadedDataAtMaterial"); + placeholderTexturesLabel = new GUIContent("Placeholder Textures"); + loader = (GenericOnDemandTextureLoader)target; + + if (staticMethods == null) + staticMethods = CreateStaticMethodImplementations (); + } + +#if NEWPLAYMODECALLBACKS + static void OnPlaymodeChanged (PlayModeStateChange mode) { + bool assignTargetTextures = mode == PlayModeStateChange.ExitingPlayMode; +#else + static void OnPlaymodeChanged () { + bool assignTargetTextures = !Application.isPlaying; +#endif + if (assignTargetTextures) { + AssignTargetTexturesAtAllLoaders(); + } + } + + public static void AssignTargetTexturesAtAllLoaders () { + + string[] loaderAssets = AssetDatabase.FindAssets("t:OnDemandTextureLoader"); + foreach (string loaderAsset in loaderAssets) { + string assetPath = AssetDatabase.GUIDToAssetPath(loaderAsset); + OnDemandTextureLoader loader = AssetDatabase.LoadAssetAtPath(assetPath); + AssignTargetTexturesAtLoader(loader); + } + } + + public static void AssignTargetTexturesAtLoader (OnDemandTextureLoader loader) { + List placeholderMaterials; + bool anyPlaceholdersAssigned = loader.HasPlaceholderTexturesAssigned(out placeholderMaterials); + if (anyPlaceholdersAssigned) { + Debug.Log("OnDemandTextureLoader detected placeholders assigned at one or more materials. Resetting to target textures.", loader); + AssetDatabase.StartAssetEditing(); + IEnumerable modifiedMaterials; + loader.AssignTargetTextures(out modifiedMaterials); + foreach (Material placeholderMaterial in placeholderMaterials) { + EditorUtility.SetDirty(placeholderMaterial); + } + AssetDatabase.StopAssetEditing(); + AssetDatabase.SaveAssets(); + } + } + + /// + /// Override this method in your implementation subclass as follows. + /// + /// protected override StaticMethodImplementations CreateStaticMethodImplementations () { + /// return new YourStaticMethodImplementationsSubclass(); + /// } + /// + /// + protected abstract StaticMethodImplementations CreateStaticMethodImplementations (); + + /// Draws a single texture mapping entry in the Inspector. + /// Can be overridden in subclasses where needed. Note that DrawSingleLineTargetTextureProperty + /// can be overridden as well instead of overriding this method. + /// Note that for the sake of space it should be drawn as a single line if possible. + /// + /// SerializedProperty pointing to a + /// PlaceholderTextureMapping object of the placeholderMap array. + protected virtual void DrawPlaceholderMapping (SerializedProperty textureMapping) { + EditorGUILayout.BeginHorizontal(GUILayout.Height(EditorGUIUtility.singleLineHeight + 5)); + var placeholderTextureProp = textureMapping.FindPropertyRelative("placeholderTexture"); + var targetTextureProp = textureMapping.FindPropertyRelative("targetTextureReference"); + GUILayout.Space(16f); + EditorGUILayout.PropertyField(placeholderTextureProp, GUIContent.none); + EditorGUIUtility.labelWidth = 1; // workaround since GUIContent.none below seems to be ignored + + DrawSingleLineTargetTextureProperty(targetTextureProp); + EditorGUIUtility.labelWidth = 0; // change back to default + EditorGUILayout.EndHorizontal(); + } + + /// Draws a single texture mapping TargetReference in the Inspector. + /// Can be overridden in subclasses where needed. Note that this method is + /// called inside a horizontal Inspector line of a BeginHorizontal() / EndHorizontal() + /// pair, so it is limited to approximately half Inspector width. + /// + /// SerializedProperty pointing to a + /// TargetReference object of the PlaceholderTextureMapping entry. + protected virtual void DrawSingleLineTargetTextureProperty (SerializedProperty property) { + EditorGUILayout.PropertyField(property, GUIContent.none, true); + } + + public override void OnInspectorGUI () { + if (serializedObject.isEditingMultipleObjects) { + DrawDefaultInspector(); + return; + } + + serializedObject.Update(); + + EditorGUILayout.PropertyField(atlasAsset); + EditorGUILayout.PropertyField(maxPlaceholderSize); + EditorGUILayout.PropertyField(unloadAfterSecondsUnused); + + placeholdersFoldout = EditorGUILayout.Foldout(placeholdersFoldout, placeholderTexturesLabel, true); + if (placeholdersFoldout) { + for (int m = 0, materialCount = placeholderMap.arraySize; m < materialCount; ++m) { + // line below equals: PlaceholderTextureMapping[] materialTextures = placeholderMap[m].textures; + SerializedProperty materialTextures = placeholderMap.GetArrayElementAtIndex(m).FindPropertyRelative("textures"); + + for (int t = 0, textureCount = materialTextures.arraySize; t < textureCount; ++t) { + // line below equals: PlaceholderTextureMapping textureMapping = materialTextures[t]; + SerializedProperty textureMapping = materialTextures.GetArrayElementAtIndex(t); + DrawPlaceholderMapping(textureMapping); + } + } + } + + if (GUILayout.Button(new GUIContent("Regenerate", "Re-initialize the placeholder texture maps."), EditorStyles.miniButton, GUILayout.Width(160f))) + ReinitPlaceholderTextures(loader); + + GUILayout.Space(16f); + EditorGUILayout.LabelField("Testing", EditorStyles.boldLabel); + EditorGUILayout.BeginHorizontal(GUILayout.Height(EditorGUIUtility.singleLineHeight + 5)); + if (GUILayout.Button(new GUIContent("Assign Placeholders", "Assign placeholder textures (for testing)."), EditorStyles.miniButton, GUILayout.Width(160f))) + AssignPlaceholderTextures(loader); + if (GUILayout.Button(new GUIContent("Assign Normal Textures", "Re-assign target textures."), EditorStyles.miniButton, GUILayout.Width(160f))) + AssignTargetTextures(loader); + EditorGUILayout.EndHorizontal(); + + if (!Application.isPlaying) + serializedObject.ApplyModifiedProperties(); + } + + public void DeletePlaceholderTextures (GenericOnDemandTextureLoader loader) { + foreach (var materialMap in loader.placeholderMap) { + Texture texture = materialMap.textures[0].placeholderTexture; + if (texture) + AssetDatabase.DeleteAsset(AssetDatabase.GetAssetPath(texture)); + } + loader.Clear(clearAtlasAsset: false); + AssetDatabase.SaveAssets(); + } + + public void ReinitPlaceholderTextures (GenericOnDemandTextureLoader loader) { + AssignTargetTextures(loader); + DeletePlaceholderTextures(loader); + staticMethods.SetupForAtlasAsset(loader, loader.atlasAsset); + EditorUtility.SetDirty(loader); + AssetDatabase.SaveAssets(); + } + + public bool AssignPlaceholderTextures (GenericOnDemandTextureLoader loader) { + // re-setup placeholders to ensure the mapping is up to date. + staticMethods.SetupForAtlasAsset(loader, loader.atlasAsset); + IEnumerable modifiedMaterials; + return loader.AssignPlaceholderTextures(out modifiedMaterials); + } + + public bool AssignTargetTextures (GenericOnDemandTextureLoader loader) { + IEnumerable modifiedMaterials; + return loader.AssignTargetTextures(out modifiedMaterials); + } + } +} +#endif diff --git a/spine-unity/Modules/com.esotericsoftware.spine.on-demand-loading/Editor/GenericOnDemandTextureLoaderInspector.cs.meta b/spine-unity/Modules/com.esotericsoftware.spine.on-demand-loading/Editor/GenericOnDemandTextureLoaderInspector.cs.meta new file mode 100644 index 0000000000..76b816ab06 --- /dev/null +++ b/spine-unity/Modules/com.esotericsoftware.spine.on-demand-loading/Editor/GenericOnDemandTextureLoaderInspector.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c3eb807840ae1934b9346f35db2c8fac +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/spine-unity/Modules/com.esotericsoftware.spine.on-demand-loading/Editor/spine-on-demand-loading-editor.asmdef b/spine-unity/Modules/com.esotericsoftware.spine.on-demand-loading/Editor/spine-on-demand-loading-editor.asmdef new file mode 100644 index 0000000000..976379f110 --- /dev/null +++ b/spine-unity/Modules/com.esotericsoftware.spine.on-demand-loading/Editor/spine-on-demand-loading-editor.asmdef @@ -0,0 +1,20 @@ +{ + "name": "spine-on-demand-loading-editor", + "rootNamespace": "", + "references": [ + "spine-unity", + "spine-unity-editor", + "spine-on-demand-loading" + ], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/spine-unity/Modules/com.esotericsoftware.spine.on-demand-loading/Editor/spine-on-demand-loading-editor.asmdef.meta b/spine-unity/Modules/com.esotericsoftware.spine.on-demand-loading/Editor/spine-on-demand-loading-editor.asmdef.meta new file mode 100644 index 0000000000..a4d8df4e71 --- /dev/null +++ b/spine-unity/Modules/com.esotericsoftware.spine.on-demand-loading/Editor/spine-on-demand-loading-editor.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 469d9d10b699b6b42a8727851decc63e +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/spine-unity/Modules/com.esotericsoftware.spine.on-demand-loading/LICENSE.md b/spine-unity/Modules/com.esotericsoftware.spine.on-demand-loading/LICENSE.md new file mode 100644 index 0000000000..4425c61220 --- /dev/null +++ b/spine-unity/Modules/com.esotericsoftware.spine.on-demand-loading/LICENSE.md @@ -0,0 +1,26 @@ +# Spine Runtimes License Agreement +Last updated July 28, 2023. Replaces all prior versions. + +Copyright (c) 2013-2023, Esoteric Software LLC + +Integration of the Spine Runtimes into software or otherwise creating +derivative works of the Spine Runtimes is permitted under the terms and +conditions of Section 2 of the Spine Editor License Agreement: +http://esotericsoftware.com/spine-editor-license + +Otherwise, it is permitted to integrate the Spine Runtimes into software or +otherwise create derivative works of the Spine Runtimes (collectively, +"Products"), provided that each user of the Products must obtain their own +Spine Editor license and redistribution of the Products in any form must +include this license and copyright notice. + +THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, +BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE +SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/spine-unity/Modules/com.esotericsoftware.spine.on-demand-loading/LICENSE.md.meta b/spine-unity/Modules/com.esotericsoftware.spine.on-demand-loading/LICENSE.md.meta new file mode 100644 index 0000000000..485a08ecfd --- /dev/null +++ b/spine-unity/Modules/com.esotericsoftware.spine.on-demand-loading/LICENSE.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: efea0eae41e05cb438aabbfb99211f72 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/spine-unity/Modules/com.esotericsoftware.spine.on-demand-loading/Runtime.meta b/spine-unity/Modules/com.esotericsoftware.spine.on-demand-loading/Runtime.meta new file mode 100644 index 0000000000..3f58d7e84a --- /dev/null +++ b/spine-unity/Modules/com.esotericsoftware.spine.on-demand-loading/Runtime.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 8258ac8697ed60b46a52a3dca4f406e4 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/spine-unity/Modules/com.esotericsoftware.spine.on-demand-loading/Runtime/GenericOnDemandTextureLoader.cs b/spine-unity/Modules/com.esotericsoftware.spine.on-demand-loading/Runtime/GenericOnDemandTextureLoader.cs new file mode 100644 index 0000000000..e8a06b8d9a --- /dev/null +++ b/spine-unity/Modules/com.esotericsoftware.spine.on-demand-loading/Runtime/GenericOnDemandTextureLoader.cs @@ -0,0 +1,316 @@ +/****************************************************************************** + * Spine Runtimes License Agreement + * Last updated July 28, 2023. Replaces all prior versions. + * + * Copyright (c) 2013-2023, Esoteric Software LLC + * + * Integration of the Spine Runtimes into software or otherwise creating + * derivative works of the Spine Runtimes is permitted under the terms and + * conditions of Section 2 of the Spine Editor License Agreement: + * http://esotericsoftware.com/spine-editor-license + * + * Otherwise, it is permitted to integrate the Spine Runtimes into software or + * otherwise create derivative works of the Spine Runtimes (collectively, + * "Products"), provided that each user of the Products must obtain their own + * Spine Editor license and redistribution of the Products in any form must + * include this license and copyright notice. + * + * THE SPINE RUNTIMES ARE PROVIDED BY ESOTERIC SOFTWARE LLC "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL ESOTERIC SOFTWARE LLC BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, + * BUSINESS INTERRUPTION, OR LOSS OF USE, DATA, OR PROFITS) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THE + * SPINE RUNTIMES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + *****************************************************************************/ + +#define SPINE_OPTIONAL_ON_DEMAND_LOADING + +#if SPINE_OPTIONAL_ON_DEMAND_LOADING + +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace Spine.Unity { + + /// + /// Interface to derive a concrete target reference struct from which holds + /// an on-demand loading reference to the target texture to be loaded. + /// + public interface ITargetTextureReference { +#if UNITY_EDITOR + Texture EditorTexture { get; } +#endif + } + + /// + /// Interface to derive a concrete request handler struct from which covers + /// a single texture loading request. + /// + public interface IOnDemandRequest { + bool WasRequested { get; } + bool WasSuccessfullyLoaded { get; } + bool IsTarget (Texture texture); + void Release (); + } + + /// + /// Base class to derive your own OnDemandTextureLoader subclasses from which already provides + /// the general loading and unloading framework. + /// For reference, see the class available + /// in the com.esotericsoftware.spine.addressables UPM package. + /// + /// The implementation struct which holds an on-demand loading reference + /// to the target texture to be loaded, derived from ITargetTextureReference. + /// The implementation struct covering a single texture loading request, + /// derived from IOnDemandRequest + [System.Serializable] + public abstract class GenericOnDemandTextureLoader : OnDemandTextureLoader + where TargetReference : ITargetTextureReference + where TextureRequest : IOnDemandRequest { + + [System.Serializable] + public struct PlaceholderTextureMapping { + public Texture placeholderTexture; + public TargetReference targetTextureReference; + } + + /// + /// Unfortunately serialization of jagged arrays PlaceholderTextureMapping[][] is not supported, + /// so we need to use this class with a 1D-array PlaceholderMaterialMapping[] as a workaround. + /// + [System.Serializable] + public struct PlaceholderMaterialMapping { + + public PlaceholderTextureMapping[] textures; + } + + // Note: not System.Serializabe on purpose. Would be unnecessary and causes problems otherwise. + public struct MaterialOnDemandData { + public int lastFrameRequested; + public TextureRequest[] textureRequests; + } + + void Reset () { + Clear(clearAtlasAsset: true); + } + + public override void Clear (bool clearAtlasAsset = false) { + if (clearAtlasAsset) atlasAsset = null; + placeholderMap = null; + loadedDataAtMaterial = null; + } + + public override string GetPlaceholderTextureName (string originalTextureName) { + return originalTextureName + "_low"; + } + + public override bool AssignPlaceholderTextures (out IEnumerable modifiedMaterials) { + modifiedMaterials = null; + if (!atlasAsset) return false; + + int materialIndex = 0; + foreach (Material targetMaterial in atlasAsset.Materials) { + if (materialIndex >= placeholderMap.Length) { + Debug.LogError(string.Format("Failed to assign placeholder textures at {0}, material #{1} {2}. " + + "It seems like the GenericOnDemandTextureLoader asset was not setup accordingly for the AtlasAsset.", + atlasAsset, materialIndex+1, targetMaterial), this); + return false; + } + Texture activeTexture = targetMaterial.mainTexture; + int textureIndex = 0; // Todo: currently only main texture is supported. + + int mapIndex = materialIndex; +#if UNITY_EDITOR + if (!Application.isPlaying) { + int foundMapIndex = Array.FindIndex(placeholderMap, + entry => entry.textures[textureIndex].targetTextureReference.EditorTexture == activeTexture); + if (foundMapIndex >= 0) + mapIndex = foundMapIndex; + } +#endif + Texture placeholderTexture = placeholderMap[mapIndex].textures[textureIndex].placeholderTexture; + if (placeholderTexture == null) { + Debug.LogWarning(string.Format("Placeholder texture set to null at {0}, for material #{1} {2}. " + + "It seems like the GenericOnDemandTextureLoader asset was not setup accordingly for the AtlasAsset.", + atlasAsset, materialIndex + 1, targetMaterial), this); + } + else { + targetMaterial.mainTexture = placeholderTexture; + } + ++materialIndex; + } + modifiedMaterials = atlasAsset.Materials; + return true; + } + + public override bool HasPlaceholderTexturesAssigned (out List placeholderMaterials) { + placeholderMaterials = null; + if (!atlasAsset) return false; + + bool anyPlaceholderAssigned = false; + + int materialIndex = 0; + foreach (Material material in atlasAsset.Materials) { + if (materialIndex >= placeholderMap.Length) + return false; + bool hasPlaceholderAssigned = HasPlaceholderAssigned(material); + if (hasPlaceholderAssigned) { + anyPlaceholderAssigned = true; + if (placeholderMaterials == null) placeholderMaterials = new List(); + placeholderMaterials.Add(material); + } + } + return anyPlaceholderAssigned; + } + + public override bool AssignTargetTextures (out IEnumerable modifiedMaterials) { + modifiedMaterials = null; + if (!atlasAsset) return false; + BeginCustomTextureLoading(); + int i = 0; + foreach (Material targetMaterial in atlasAsset.Materials) { + if (i >= placeholderMap.Length) { + Debug.LogError(string.Format("Failed to assign target textures at {0}, material #{1} {2}. " + + "It seems like the OnDemandTextureLoader asset was not setup accordingly for the AtlasAsset.", + atlasAsset, i + 1, targetMaterial), this); + return false; + } + Material ignoredArgument = null; + RequestLoadMaterialTextures(targetMaterial, ref ignoredArgument); + ++i; + } + modifiedMaterials = atlasAsset.Materials; + EndCustomTextureLoading(); + return true; + } + + public override void BeginCustomTextureLoading () { + if (loadedDataAtMaterial == null || (loadedDataAtMaterial.Length == 0 && placeholderMap.Length > 0)) { + loadedDataAtMaterial = new MaterialOnDemandData[placeholderMap.Length]; + for (int i = 0, count = loadedDataAtMaterial.Length; i < count; ++i) { + loadedDataAtMaterial[i].lastFrameRequested = -1; + int texturesAtMaterial = placeholderMap[i].textures.Length; + loadedDataAtMaterial[i].textureRequests = new TextureRequest[texturesAtMaterial]; + } + } + } + + public override void EndCustomTextureLoading () { +#if UNITY_EDITOR + if (!Application.isPlaying) + return; +#endif + UnloadUnusedTextures(); + } + + public override bool HasPlaceholderAssigned (Material material) { + Texture currentTexture = material.mainTexture; + int textureIndex = 0; // Todo: currently only main texture is supported. + int foundMaterialIndex = Array.FindIndex(placeholderMap, entry => entry.textures[textureIndex].placeholderTexture == currentTexture); + return foundMaterialIndex >= 0; + } + + public override void RequestLoadMaterialTextures (Material material, ref Material overrideMaterial) { + if (!material || !material.mainTexture) return; + + Texture currentTexture = material.mainTexture; + int textureIndex = 0; // Todo: currently only main texture is supported. + + int foundMaterialIndex = Array.FindIndex(placeholderMap, entry => entry.textures[textureIndex].placeholderTexture == currentTexture); + if (foundMaterialIndex >= 0) + RequestLoadTexture(material, foundMaterialIndex, textureIndex); + + int loadedMaterialIndex = Array.FindIndex(loadedDataAtMaterial, entry => + entry.textureRequests[textureIndex].WasRequested && + entry.textureRequests[textureIndex].IsTarget(currentTexture)); + if (loadedMaterialIndex >= 0) + loadedDataAtMaterial[loadedMaterialIndex].lastFrameRequested = Time.frameCount; + } + + protected virtual void RequestLoadTexture (Material material, int materialIndex, int textureIndex) { + PlaceholderTextureMapping[] placeholderTextures = placeholderMap[materialIndex].textures; + TargetReference targetReference = placeholderTextures[textureIndex].targetTextureReference; + loadedDataAtMaterial[materialIndex].lastFrameRequested = Time.frameCount; + +#if UNITY_EDITOR + if (!Application.isPlaying) { + if (targetReference.EditorTexture != null) + material.mainTexture = targetReference.EditorTexture; + return; + } +#endif + MaterialOnDemandData materialData = loadedDataAtMaterial[materialIndex]; + if (materialData.textureRequests[textureIndex].WasRequested) { + Texture loadedTexture = GetAlreadyLoadedTexture(materialIndex, textureIndex); + if (loadedTexture != null) + material.mainTexture = loadedTexture; + return; + } + + CreateTextureRequest(targetReference, materialData, textureIndex, material); + } + + public abstract Texture GetAlreadyLoadedTexture (int materialIndex, int textureIndex); + + public abstract void CreateTextureRequest(TargetReference targetReference, + MaterialOnDemandData materialData, int textureIndex, Material materialToUpdate); + + public virtual void UnloadUnusedTextures () { + int currentFrameCount = Time.frameCount; + float timePerFrame = Time.smoothDeltaTime; + float deltaFramesToUnload = unloadAfterSecondsUnused / timePerFrame; + + for (int materialIndex = 0, materialCount = loadedDataAtMaterial.Length; materialIndex < materialCount; ++materialIndex) { + MaterialOnDemandData materialData = loadedDataAtMaterial[materialIndex]; + int textureCount = materialData.textureRequests.Length; + + for (int textureIndex = 0; textureIndex < textureCount; ++textureIndex) { + TextureRequest textureRequest = materialData.textureRequests[textureIndex]; + if (textureRequest.WasSuccessfullyLoaded && + currentFrameCount - materialData.lastFrameRequested > deltaFramesToUnload) { + RequestUnloadTexture(materialIndex, textureIndex); + } + } + } + } + + public virtual void RequestUnloadTexture (int materialIndex, int textureIndex) { + if (materialIndex >= loadedDataAtMaterial.Length) return; + + bool wasReleased = false; + PlaceholderTextureMapping[] placeholderTextures = placeholderMap[materialIndex].textures; + MaterialOnDemandData materialData = loadedDataAtMaterial[materialIndex]; + if (materialData.textureRequests[textureIndex].WasRequested) { + materialData.textureRequests[textureIndex].Release(); + wasReleased = true; + } + + // reset material textures to placeholder textures. + Material targetMaterial = atlasAsset.Materials.ElementAt(materialIndex); + if (targetMaterial) { + targetMaterial.mainTexture = placeholderTextures[textureIndex].placeholderTexture; + if (wasReleased) + OnTextureUnloaded(targetMaterial, textureIndex); + } + } + + public int maxPlaceholderSize = 128; + public float unloadAfterSecondsUnused = 60.0f; + + /// A map from placeholder to on-demand-loaded target textures. + /// This array holds PlaceholderMaterialMapping for each Material, + /// where each PlaceholderMaterialMapping.textures contains a Texture-to-TextureReference mapping + /// for each Texture at the Material. + public PlaceholderMaterialMapping[] placeholderMap; + + /// An array holding loaded data for each Material. + protected MaterialOnDemandData[] loadedDataAtMaterial; + } +} +#endif diff --git a/spine-unity/Modules/com.esotericsoftware.spine.on-demand-loading/Runtime/GenericOnDemandTextureLoader.cs.meta b/spine-unity/Modules/com.esotericsoftware.spine.on-demand-loading/Runtime/GenericOnDemandTextureLoader.cs.meta new file mode 100644 index 0000000000..54bfc1c152 --- /dev/null +++ b/spine-unity/Modules/com.esotericsoftware.spine.on-demand-loading/Runtime/GenericOnDemandTextureLoader.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5964cfa446b05ea4e8c3622d3a1b9430 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/spine-unity/Modules/com.esotericsoftware.spine.on-demand-loading/Runtime/spine-on-demand-loading.asmdef b/spine-unity/Modules/com.esotericsoftware.spine.on-demand-loading/Runtime/spine-on-demand-loading.asmdef new file mode 100644 index 0000000000..1faa583b7d --- /dev/null +++ b/spine-unity/Modules/com.esotericsoftware.spine.on-demand-loading/Runtime/spine-on-demand-loading.asmdef @@ -0,0 +1,17 @@ +{ + "name": "spine-on-demand-loading", + "rootNamespace": "", + "references": [ + "spine-unity", + "spine-csharp" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/spine-unity/Modules/com.esotericsoftware.spine.on-demand-loading/Runtime/spine-on-demand-loading.asmdef.meta b/spine-unity/Modules/com.esotericsoftware.spine.on-demand-loading/Runtime/spine-on-demand-loading.asmdef.meta new file mode 100644 index 0000000000..4f4fb18d98 --- /dev/null +++ b/spine-unity/Modules/com.esotericsoftware.spine.on-demand-loading/Runtime/spine-on-demand-loading.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 2eb352bed1bae98439c9adcbb05e6b90 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/spine-unity/Modules/com.esotericsoftware.spine.on-demand-loading/package.json b/spine-unity/Modules/com.esotericsoftware.spine.on-demand-loading/package.json new file mode 100644 index 0000000000..5682f34439 --- /dev/null +++ b/spine-unity/Modules/com.esotericsoftware.spine.on-demand-loading/package.json @@ -0,0 +1,21 @@ +{ + "name": "com.esotericsoftware.spine.on-demand-loading", + "displayName": "Spine On-Demand Loading Extensions [Experimental]", + "description": "This experimental plugin provides a generic basic implementation of on-demand texture loading for the spine-unity runtime. You might want to use the available com.esotericsoftware.spine.addressables package which depends on this package.\nPlease be sure to test this package first and create backups of your project before using.\n\nPrerequisites:\nIt requires a working installation of the spine-unity runtime (via the spine-unity unitypackage), version 4.1.\n(See http://esotericsoftware.com/git/spine-runtimes/spine-unity)", + "version": "4.1.0", + "unity": "2018.3", + "author": { + "name": "Esoteric Software", + "email": "contact@esotericsoftware.com", + "url": "http://esotericsoftware.com/" + }, + "dependencies": { + "com.esotericsoftware.spine.spine-unity": "4.1.21" + }, + "keywords": [ + "spine", + "on-demand", + "loading", + "preview" + ] +} diff --git a/spine-unity/Modules/com.esotericsoftware.spine.on-demand-loading/package.json.meta b/spine-unity/Modules/com.esotericsoftware.spine.on-demand-loading/package.json.meta new file mode 100644 index 0000000000..313694e540 --- /dev/null +++ b/spine-unity/Modules/com.esotericsoftware.spine.on-demand-loading/package.json.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 8b808d40cc8a54b4ab5b0dbf607e15a8 +PackageManifestImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: