using System; using UnityEngine.Localization.Settings; using UnityEngine.Localization.Tables; using UnityEngine.ResourceManagement.AsyncOperations; namespace UnityEngine.Localization { /// /// Provides a way to access a at runtime. /// See and for implementations. /// /// The type of Table. /// The type of entry that is part of the table. [Serializable] public abstract partial class LocalizedTable #if UNITY_EDITOR : ISerializationCallbackReceiver #endif where TTable : DetailedLocalizationTable where TEntry : TableEntry { [SerializeField] TableReference m_TableReference; CallbackArray m_ChangeHandler; Action m_SelectedLocaleChanged; #if UNITY_EDITOR // This is so we can detect when a change is made via the inspector. protected TableReference m_CurrentTable; #endif /// /// The database to request the table from. /// protected abstract LocalizedDatabase Database { get; } /// /// The current loading operation for the table when using or if one is not available. /// public AsyncOperationHandle CurrentLoadingOperationHandle { get; internal set; } /// /// Delegate used by . /// /// The localized table. public delegate void ChangeHandler(TTable value); /// /// Provides a reference to the . /// A table reference can be either the name of the table or the table collection name Guid. /// /// /// Note: Changing this value will trigger an update to any subscribers. /// public TableReference TableReference { get => m_TableReference; set { if (value.Equals(m_TableReference)) return; m_TableReference = value; ForceUpdate(); } } /// /// Does contain a valid reference? /// public bool IsEmpty => TableReference.ReferenceType == TableReference.Type.Empty; /// /// Provides a callback that will be invoked when the table is available or has changed. /// /// /// The following events will trigger an update: /// - The first time the action is added to the event. /// - The changing. /// - The changing. /// /// When the first is added, a loading operation (see ) automatically starts. /// When the operation completes, the localized table is sent to the subscriber. /// If you add any additional subscribers added after loading has completed, they are also sent the latest localized table. /// This ensures that a subscriber will always have the correct localized value regardless of when it was added. /// /// /// This example shows how the event can be used to print out the contents of the table. /// /// public event ChangeHandler TableChanged { add { if (value == null) throw new ArgumentNullException(); m_ChangeHandler.Add(value); if (m_ChangeHandler.Length == 1) { LocalizationSettings.ValidateSettingsExist(); LocalizationSettings.SelectedLocaleChanged += m_SelectedLocaleChanged; ForceUpdate(); } else if (CurrentLoadingOperationHandle.IsValid() && CurrentLoadingOperationHandle.IsDone) { // Call the event with the latest value. value(CurrentLoadingOperationHandle.Result); } } remove { m_ChangeHandler.RemoveByMovingTail(value); if (m_ChangeHandler.Length == 0) { LocalizationSettings.SelectedLocaleChanged -= m_SelectedLocaleChanged; ClearLoadingOperation(); } } } /// /// Initializes and returns an empty instance of a . /// public LocalizedTable() { m_SelectedLocaleChanged = HandleLocaleChange; } /// /// Provides the table with the . /// /// /// The event provides notification once the operation has finished and the table has been found or an error has occurred. /// A table may have already been loaded during a previous operation or when using Preload mode. /// Check the property to see if the table is already loaded and immediately available. /// See [Async operation handling](https://docs.unity3d.com/Packages/com.unity.addressables@latest/index.html?subfolder=/manual/AddressableAssetsAsyncOperationHandle.html) for further details. /// /// Returns the loading operation for the requested table. public AsyncOperationHandle GetTableAsync() => Database.GetTableAsync(TableReference); /// /// Provides the table with the . /// Uses [WaitForCompletion](xref:UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationHandle.WaitForCompletion) to force the loading to complete synchronously. /// Please note that [WaitForCompletion](xref:UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationHandle.WaitForCompletion) is not supported on /// [WebGL](https://docs.unity3d.com/Packages/com.unity.addressables@latest/index.html?subfolder=/manual/SynchronousAddressables.html#webgl). /// /// public TTable GetTable() => GetTableAsync().WaitForCompletion(); /// /// Force an update as if the had changed. /// protected void ForceUpdate() { if (m_ChangeHandler.Length != 0) { HandleLocaleChange(null); } } void InvokeChangeHandler(TTable value) { try { m_ChangeHandler.LockForChanges(); var len = m_ChangeHandler.Length; if (len == 1) { m_ChangeHandler.SingleDelegate(value); } else if (len > 1) { var array = m_ChangeHandler.MultiDelegates; for (int i = 0; i < len; ++i) array[i](value); } } catch (Exception ex) { Debug.LogException(ex); } m_ChangeHandler.UnlockForChanges(); } void HandleLocaleChange(Locale _) { // Cancel any previous loading operations. ClearLoadingOperation(); // Don't try and load empty references. if (IsEmpty) return; CurrentLoadingOperationHandle = GetTableAsync(); if (CurrentLoadingOperationHandle.IsDone) AutomaticLoadingCompleted(CurrentLoadingOperationHandle); else CurrentLoadingOperationHandle.Completed += AutomaticLoadingCompleted; } void AutomaticLoadingCompleted(AsyncOperationHandle loadOperation) { if (loadOperation.Status != AsyncOperationStatus.Succeeded) { CurrentLoadingOperationHandle = default; return; } InvokeChangeHandler(loadOperation.Result); } void ClearLoadingOperation() { if (CurrentLoadingOperationHandle.IsValid()) { // We should only call this if we are not done as its possible that the internal list is null if its not been used. if (!CurrentLoadingOperationHandle.IsDone) CurrentLoadingOperationHandle.Completed -= AutomaticLoadingCompleted; CurrentLoadingOperationHandle = default; } } /// /// Returns a string representation including the . /// /// public override string ToString() => TableReference; #if UNITY_EDITOR void ChangedThroughSerialization() { ClearLoadingOperation(); ForceUpdate(); } public void OnBeforeSerialize() => UpdateIfChangedThroughSerialization(); public void OnAfterDeserialize() => UpdateIfChangedThroughSerialization(); void UpdateIfChangedThroughSerialization() { if (!m_CurrentTable.Equals(TableReference)) { m_CurrentTable = TableReference; // We must defer as we can not call certain parts of Unity during serialization UnityEditor.EditorApplication.delayCall += ChangedThroughSerialization; } } #endif } }