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