mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2025-12-20 17:26:01 +08:00
316 lines
13 KiB
C#
316 lines
13 KiB
C#
/******************************************************************************
|
|
* 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 {
|
|
|
|
/// <summary>
|
|
/// Interface to derive a concrete target reference struct from which holds
|
|
/// an on-demand loading reference to the target texture to be loaded.
|
|
/// </summary>
|
|
public interface ITargetTextureReference {
|
|
#if UNITY_EDITOR
|
|
Texture EditorTexture { get; }
|
|
#endif
|
|
}
|
|
|
|
/// <summary>
|
|
/// Interface to derive a concrete request handler struct from which covers
|
|
/// a single texture loading request.
|
|
/// </summary>
|
|
public interface IOnDemandRequest {
|
|
bool WasRequested { get; }
|
|
bool WasSuccessfullyLoaded { get; }
|
|
bool IsTarget (Texture texture);
|
|
void Release ();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Base class to derive your own OnDemandTextureLoader subclasses from which already provides
|
|
/// the general loading and unloading framework.
|
|
/// For reference, see the <see cref="AddressablesTextureLoader"/> class available
|
|
/// in the com.esotericsoftware.spine.addressables UPM package.
|
|
/// </summary>
|
|
/// <typeparam name="TargetReference">The implementation struct which holds an on-demand loading reference
|
|
/// to the target texture to be loaded, derived from ITargetTextureReference.</typeparam>
|
|
/// <typeparam name="TextureRequest">The implementation struct covering a single texture loading request,
|
|
/// derived from IOnDemandRequest</typeparam>
|
|
[System.Serializable]
|
|
public abstract class GenericOnDemandTextureLoader<TargetReference, TextureRequest> : OnDemandTextureLoader
|
|
where TargetReference : ITargetTextureReference
|
|
where TextureRequest : IOnDemandRequest {
|
|
|
|
[System.Serializable]
|
|
public struct PlaceholderTextureMapping {
|
|
public Texture placeholderTexture;
|
|
public TargetReference targetTextureReference;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Unfortunately serialization of jagged arrays PlaceholderTextureMapping[][] is not supported,
|
|
/// so we need to use this class with a 1D-array PlaceholderMaterialMapping[] as a workaround.
|
|
/// </summary>
|
|
[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<Material> 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<Material> 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<Material>();
|
|
placeholderMaterials.Add(material);
|
|
}
|
|
}
|
|
return anyPlaceholderAssigned;
|
|
}
|
|
|
|
public override bool AssignTargetTextures (out IEnumerable<Material> 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;
|
|
|
|
/// <summary>A map from placeholder to on-demand-loaded target textures.
|
|
/// This array holds PlaceholderMaterialMapping for each Material,
|
|
/// where each <c>PlaceholderMaterialMapping.textures</c> contains a Texture-to-TextureReference mapping
|
|
/// for each Texture at the Material.</summary>
|
|
public PlaceholderMaterialMapping[] placeholderMap;
|
|
|
|
/// <summary>An array holding loaded data for each Material.</summary>
|
|
protected MaterialOnDemandData[] loadedDataAtMaterial;
|
|
}
|
|
}
|
|
#endif
|