using System;
using System.Collections.Generic;
using UnityEngine.Localization.Settings;
using UnityEngine.Serialization;
namespace UnityEngine.Localization.Tables
{
///
/// It is possible to reference a table via either the table collection name or the table collection name guid.
/// The TableReference provides a flexible way to reference via either of these methods and also includes editor functionality.
///
[Serializable]
public struct TableReference : ISerializationCallbackReceiver, IEquatable
{
///
/// The type of reference.
///
public enum Type
{
///
/// No table is referenced.
///
Empty,
///
/// A table is referenced by its table collection name guid.
///
Guid,
///
/// A table is referenced by its name.
///
Name
}
// Cached values to reduce GC
static readonly Dictionary s_GuidToStringCache = new Dictionary();
static readonly Dictionary s_StringToGuidCache = new Dictionary();
[SerializeField]
[FormerlySerializedAs("m_TableName")]
string m_TableCollectionName;
bool m_Valid;
const string k_GuidTag = "GUID:";
///
/// The type of reference.
///
public Type ReferenceType { get; private set; }
///
/// The table collection name guid when is .
///
public Guid TableCollectionNameGuid { get; private set; }
///
/// The table collection name when is .
/// If the is not then an attempt will be made to extract the Table Collection Name, for debugging purposes, through
/// the AssetDatabase(in Editor) or by checking the to see if the has been loaded by the
/// or , if the name can not be resolved then null will be returned.
///
public string TableCollectionName
{
get => ReferenceType == Type.Name ? m_TableCollectionName : SharedTableData?.TableCollectionName;
private set => m_TableCollectionName = value;
}
internal SharedTableData SharedTableData
{
get
{
if (ReferenceType == Type.Empty || !LocalizationSettings.HasSettings)
return null;
if (ReferenceType == Type.Guid)
{
#if UNITY_EDITOR
// We can extract the name through the shared table data GUID
var guid = StringFromGuid(TableCollectionNameGuid);
var path = UnityEditor.AssetDatabase.GUIDToAssetPath(guid);
if (!string.IsNullOrEmpty(path))
{
var sharedTableData = UnityEditor.AssetDatabase.LoadAssetAtPath(path);
return sharedTableData;
}
#endif
// We don't actually know what type of table we refer to so we will need to check both.
// We only check shared table data that is loaded, we don't want to trigger any new operations as
// the asset may not exist and we don't want to trigger errors when creating debug info.
if (LocalizationSettings.StringDatabase != null && LocalizationSettings.StringDatabase.SharedTableDataOperations.TryGetValue(TableCollectionNameGuid, out var async))
return async.Result;
if (LocalizationSettings.AssetDatabase != null && LocalizationSettings.AssetDatabase.SharedTableDataOperations.TryGetValue(TableCollectionNameGuid, out async))
return async.Result;
}
else if (ReferenceType == Type.Name)
{
// We don't actually know what type of table we refer to so we will need to check both.
// We only check shared table data that is loaded, we don't want to trigger any new operations as
// the asset may not exist and we don't want to trigger errors when creating debug info.
foreach (var sharedTableOperation in LocalizationSettings.StringDatabase?.SharedTableDataOperations)
{
if (sharedTableOperation.Value.Result?.TableCollectionName == m_TableCollectionName)
return sharedTableOperation.Value.Result;
}
foreach (var sharedTableOperation in LocalizationSettings.AssetDatabase?.SharedTableDataOperations)
{
if (sharedTableOperation.Value.Result?.TableCollectionName == m_TableCollectionName)
return sharedTableOperation.Value.Result;
}
}
return null;
}
}
#pragma warning disable CA2225 // CA2225: Operator overloads have named alternates
///
/// Convert a table collection name into a .
///
/// The name of the table.
///
public static implicit operator TableReference(string tableCollectionName)
{
return new TableReference { TableCollectionName = tableCollectionName, ReferenceType = string.IsNullOrWhiteSpace(tableCollectionName) ? Type.Empty : Type.Name };
}
///
/// Convert a table collection name guid into a .
///
/// The table collection name guid.
///
public static implicit operator TableReference(Guid tableCollectionNameGuid)
{
return new TableReference { TableCollectionNameGuid = tableCollectionNameGuid, ReferenceType = tableCollectionNameGuid == Guid.Empty ? Type.Empty : Type.Guid };
}
///
/// Returns .
///
///
///
public static implicit operator string(TableReference tableReference)
{
return tableReference.TableCollectionName;
}
///
/// Returns .
///
///
///
public static implicit operator Guid(TableReference tableReference)
{
return tableReference.TableCollectionNameGuid;
}
#pragma warning restore CA2225
internal void Validate()
{
if (m_Valid)
return;
switch (ReferenceType)
{
case Type.Empty:
throw new ArgumentException("Empty Table Reference. Must contain a Guid or Table Collection Name");
case Type.Guid:
if (TableCollectionNameGuid == Guid.Empty)
throw new ArgumentException("Must use a valid Table Collection Name Guid, can not be Empty.");
break;
case Type.Name:
if (string.IsNullOrWhiteSpace(TableCollectionName))
throw new ArgumentException($"Table Collection Name can not be null or empty.");
break;
}
m_Valid = true;
}
internal string GetSerializedString()
{
switch (ReferenceType)
{
case Type.Guid:
return $"{k_GuidTag}{StringFromGuid(TableCollectionNameGuid)}";
case Type.Name:
return TableCollectionName;
default:
return string.Empty;
}
}
///
/// Returns a string representation.
///
///
public override string ToString()
{
if (ReferenceType == Type.Guid)
return $"{nameof(TableReference)}({TableCollectionNameGuid} - {TableCollectionName})";
if (ReferenceType == Type.Name)
return $"{nameof(TableReference)}({TableCollectionName})";
return $"{nameof(TableReference)}(Empty)";
}
///
/// Compare the TableReference to another TableReference.
///
///
///
public override bool Equals(object obj)
{
if (obj is null)
return false;
return obj is TableReference ter && Equals(ter);
}
///
/// Returns the hash code of or .
///
///
public override int GetHashCode()
{
if (ReferenceType == Type.Guid)
return TableCollectionNameGuid.GetHashCode();
if (ReferenceType == Type.Name)
return TableCollectionName.GetHashCode();
return base.GetHashCode();
}
///
/// Compare 2 TableReferences.
///
///
///
public bool Equals(TableReference other)
{
if (ReferenceType != other.ReferenceType)
return false;
if (ReferenceType == Type.Guid)
{
return TableCollectionNameGuid == other.TableCollectionNameGuid;
}
if (ReferenceType == Type.Name)
{
return TableCollectionName == other.TableCollectionName;
}
return true;
}
///
/// Parse a string that contains uses the tag.
///
///
///
internal static Guid GuidFromString(string value)
{
if (s_StringToGuidCache.TryGetValue(value, out var result))
return result;
if (Guid.TryParse(value.Substring(k_GuidTag.Length, value.Length - k_GuidTag.Length), out var guid))
{
s_StringToGuidCache[value] = guid;
return guid;
}
return Guid.Empty;
}
///
/// Returns a string version of the GUID which works with Addressables, it uses the "N" format(32 digits).
///
///
///
internal static string StringFromGuid(Guid value)
{
if (s_GuidToStringCache.TryGetValue(value, out var guid))
return guid.ToString();
var stringValue = value.ToString("N");
s_GuidToStringCache[value] = stringValue;
return stringValue;
}
///
/// Converts a string into a a .
///
/// The string to convert. The string can either be a table collection name or a GUID identified by prepending the tag.
///
internal static TableReference TableReferenceFromString(string value)
{
if (IsGuid(value))
return GuidFromString(value);
return value;
}
///
/// Is the string identified as a string.
/// Strings that start with are considered a Guid.
///
///
///
internal static bool IsGuid(string value)
{
if (string.IsNullOrEmpty(value))
return false;
return value.StartsWith(k_GuidTag, StringComparison.OrdinalIgnoreCase);
}
///
/// Converts the reference into a serializable string.
///
public void OnBeforeSerialize()
{
m_TableCollectionName = GetSerializedString();
}
///
/// Converts the serializable string into the correct reference type.
///
public void OnAfterDeserialize()
{
if (string.IsNullOrEmpty(m_TableCollectionName))
{
ReferenceType = Type.Empty;
}
else if (IsGuid(m_TableCollectionName))
{
TableCollectionNameGuid = GuidFromString(m_TableCollectionName);
ReferenceType = Type.Guid;
}
else
{
ReferenceType = Type.Name;
}
}
}
///
/// Allows for referencing a table entry via key or key id.
///
[Serializable]
public struct TableEntryReference : ISerializationCallbackReceiver, IEquatable
{
///
/// The type of reference.
///
public enum Type
{
///
/// No table entry is referenced.
///
Empty,
///
/// The key name is referenced.
///
Name,
///
/// The Key Id is referenced
///
Id
}
[SerializeField]
long m_KeyId;
[SerializeField]
string m_Key;
bool m_Valid;
///
/// The type of reference.
///
public Type ReferenceType { get; private set; }
///
/// The Key Id when is .
///
public long KeyId { get => m_KeyId; private set => m_KeyId = value; }
///
/// The key name when is .
///
public string Key { get => m_Key; private set => m_Key = value; }
#pragma warning disable CA2225 // CA2225: Operator overloads have named alternates
///
/// Converts a string name into a reference.
///
///
///
public static implicit operator TableEntryReference(string key)
{
if (!string.IsNullOrWhiteSpace(key))
return new TableEntryReference() { Key = key, ReferenceType = Type.Name };
return new TableEntryReference(); // Empty
}
///
/// Converts a key id into a reference.
///
///
///
public static implicit operator TableEntryReference(long keyId)
{
if (keyId != SharedTableData.EmptyId)
return new TableEntryReference() { KeyId = keyId, ReferenceType = Type.Id };
return new TableEntryReference(); // Empty
}
///
/// Returns .
///
///
///
public static implicit operator string(TableEntryReference tableEntryReference)
{
return tableEntryReference.Key;
}
///
/// Returns
///
///
///
public static implicit operator long(TableEntryReference tableEntryReference)
{
return tableEntryReference.KeyId;
}
#pragma warning restore CA2225
internal void Validate()
{
if (m_Valid)
return;
switch (ReferenceType)
{
case Type.Empty:
throw new ArgumentException("Empty Table Entry Reference. Must contain a Name or Key Id");
case Type.Name:
if (string.IsNullOrWhiteSpace(Key))
throw new ArgumentException("Must use a valid Key, can not be null or Empty.");
break;
case Type.Id:
if (KeyId == SharedTableData.EmptyId)
throw new ArgumentException("Key Id can not be empty.");
break;
}
m_Valid = true;
}
///
/// Returns the key name.
/// If is then will be returned.
/// If is then will be used to extract the name.
///
/// The to use if the key name is not stored in the reference or null if it could not br resolbved.
///
public string ResolveKeyName(SharedTableData sharedData)
{
if (ReferenceType == Type.Name)
return Key;
if (ReferenceType == Type.Id)
return sharedData != null ? sharedData.GetKey(KeyId) : $"Key Id {KeyId}";
return null;
}
///
/// Returns a string representation.
///
///
public override string ToString()
{
switch (ReferenceType)
{
case Type.Name:
return $"{nameof(TableEntryReference)}({Key})";
case Type.Id:
return $"{nameof(TableEntryReference)}({KeyId})";
}
return $"{nameof(TableEntryReference)}(Empty)";
}
///
/// Returns a string representation.
///
/// The that this entry is part of. This is used to extract the or
///
public string ToString(TableReference tableReference)
{
var sharedTableData = tableReference.SharedTableData;
if (sharedTableData != null)
{
long id;
string key;
if (ReferenceType == Type.Name)
{
key = Key;
id = sharedTableData.GetId(key);
}
else if (ReferenceType == Type.Id)
{
id = KeyId;
key = sharedTableData.GetKey(id);
}
else
{
return ToString();
}
return $"{nameof(TableEntryReference)}({id} - {key})";
}
return ToString();
}
///
/// Compare the TableEntryReference to another TableEntryReference.
///
///
///
public override bool Equals(object obj)
{
if (obj is null)
return false;
return obj is TableEntryReference ter && Equals(ter);
}
///
/// Compare the TableEntryReference to another TableEntryReference.
///
///
///
public bool Equals(TableEntryReference other)
{
if (ReferenceType != other.ReferenceType)
return false;
if (ReferenceType == Type.Name)
{
return Key == other.Key;
}
if (ReferenceType == Type.Id)
{
return KeyId == other.KeyId;
}
return true;
}
///
/// Returns the hash code of or .
///
///
public override int GetHashCode()
{
if (ReferenceType == Type.Name)
{
return Key.GetHashCode();
}
if (ReferenceType == Type.Id)
{
return KeyId.GetHashCode();
}
return base.GetHashCode();
}
///
/// Does nothing but is required for .
///
public void OnBeforeSerialize()
{
}
///
/// Determines the .
///
public void OnAfterDeserialize()
{
if (KeyId != SharedTableData.EmptyId)
ReferenceType = Type.Id;
else if (string.IsNullOrEmpty(m_Key))
ReferenceType = Type.Empty;
else
ReferenceType = Type.Name;
}
}
}