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
}
}