using System.Linq; using UnityEngine; namespace Crosstales.RTVoice { /// Global cache for wrappers. [ExecuteInEditMode] [DisallowMultipleComponent] [HelpURL("https://crosstales.com/media/data/assets/rtvoice/api/class_crosstales_1_1_r_t_voice_1_1_global_cache.html")] public class GlobalCache : Crosstales.Common.Util.Singleton { #region Variables [UnityEngine.Serialization.FormerlySerializedAsAttribute("ClipCacheSize")] [Header("Cache Settings"), Tooltip("Size of the clip cache in MB (default: 256)"), Range(16, 1024), SerializeField] private int clipCacheSize = Crosstales.RTVoice.Util.Constants.DEFAULT_CACHE_SIZE_CLIPS; [Tooltip("Automatically loads and saves the cache (default: false)"), SerializeField] private bool persistCache; ///Dictionary with all cached clips. public readonly System.Collections.Generic.Dictionary Clips = new System.Collections.Generic.Dictionary(); private readonly System.Collections.Generic.List clipKeys = new System.Collections.Generic.List(); private Transform tf; private static string dataStorePath; #endregion #region Properties ///Size of the clip cache in Bytes. public int ClipCacheSize { get => clipCacheSize * Crosstales.RTVoice.Util.Constants.FACTOR_MB; set => clipCacheSize = Mathf.Clamp(value / Crosstales.RTVoice.Util.Constants.FACTOR_MB, 1, Crosstales.RTVoice.Util.Constants.DEFAULT_MAX_CACHE_SIZE_CLIPS); } /// Current size of the clip cache in Bytes. public int CurrentClipCacheSize => Clips.Sum(pair => pair.Value.samples * 2 * 4); /// Automatically loads and saves the cache. public bool PersistCache { get => persistCache; set => persistCache = value; } #endregion #region MonoBehaviour methods private void Start() { dataStorePath = $"{Application.persistentDataPath}/rtvoice_datastore.xml"; if (persistCache) LoadCache(); } private void OnValidate() { if (clipCacheSize <= 16) { clipCacheSize = 16; } else if (clipCacheSize > Crosstales.RTVoice.Util.Constants.DEFAULT_MAX_CACHE_SIZE_CLIPS) { clipCacheSize = Crosstales.RTVoice.Util.Constants.DEFAULT_MAX_CACHE_SIZE_CLIPS; } } protected override void OnApplicationQuit() { if (persistCache) SaveCache(); ClearCache(); base.OnApplicationQuit(); } #endregion #region Public methods /// Resets this object. //[RuntimeInitializeOnLoadMethod] public static void ResetObject() { DeleteInstance(); } /// Returns the AudioClip for a given key. /// Key for the AudioClip. /// AudioClip for the given key. public AudioClip GetClip(Crosstales.RTVoice.Model.Wrapper key) { if (key != null) { Clips.TryGetValue(key, out AudioClip data); //Debug.LogWarning("DATA: " + data); return data; } return null; } /// Removes an AudioClip for a given key. /// Key for the AudioClip. public void RemoveClip(Crosstales.RTVoice.Model.Wrapper key) { if (key != null && Clips.ContainsKey(key)) { Destroy(Clips[key]); Clips.Remove(key); clipKeys.Remove(key); } } /// Adds an AudioClip for a given key. /// Key for the AudioClip. /// AudioClip for the key. public void AddClip(Crosstales.RTVoice.Model.Wrapper key, AudioClip data) { if (key != null && data != null && !Clips.ContainsKey(key)) { while (CurrentClipCacheSize >= ClipCacheSize) { RemoveClip(clipKeys[0]); } Clips.Add(key, data); clipKeys.Add(key); } } /// Clears the clips cache. public void ClearClipCache() { Crosstales.RTVoice.Util.Context.NumberOfCachedSpeeches = 0; Crosstales.RTVoice.Util.Context.NumberOfNonCachedSpeeches = 0; foreach (System.Collections.Generic.KeyValuePair kvp in Clips) { if (Crosstales.RTVoice.Util.Helper.isEditorMode) { DestroyImmediate(kvp.Value); } else { Destroy(kvp.Value); } } Clips.Clear(); clipKeys.Clear(); } /// Clears the complete cache. public void ClearCache() { ClearClipCache(); } /// Clears and deletes the complete cache. public void ClearAndDeleteCache() { ClearCache(); if (System.IO.File.Exists(dataStorePath)) System.IO.File.Delete(dataStorePath); } /// Saves the complete cache. public void SaveCache() { System.Collections.Generic.List data = Clips.Select(kvp => new DataStore(kvp.Key, Crosstales.Common.Audio.WavMaster.FromAudioClip(kvp.Value))).ToList(); Crosstales.Common.Util.XmlHelper.SerializeToFile(data, dataStorePath); //Debug.Log("SaveCache: " + data.Count); } /// Loads the complete cache. public void LoadCache() { if (System.IO.File.Exists(dataStorePath)) { System.Collections.Generic.List data = Crosstales.Common.Util.XmlHelper.DeserializeFromFile>(dataStorePath); if (data != null) { foreach (DataStore ds in data) { AddClip(ds.wrapper, Crosstales.Common.Audio.WavMaster.ToAudioClip(ds.Data)); } } } //Debug.Log("LoadCache: " + Clips.Count); } #endregion } /// Model for a voice. [System.Serializable] public class DataStore { public Crosstales.RTVoice.Model.Wrapper wrapper; public byte[] Data; /// Default. public DataStore() { } /// Instantiate the class. /// Wrapper of the speech. /// Data of the speech. public DataStore(Crosstales.RTVoice.Model.Wrapper wrapper, byte[] data) { this.wrapper = wrapper; Data = data; } } } // © 2020-2023 crosstales LLC (https://www.crosstales.com)