2025-05-01 01:48:08 -07:00

798 lines
33 KiB
C#

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Text;
using UnityEditor.Localization.Addressables;
using UnityEditor.Localization.UI;
using UnityEngine;
using UnityEngine.Localization;
using UnityEngine.Localization.Tables;
using UnityEngine.Pool;
namespace UnityEditor.Localization
{
/// <summary>
/// Editor interface to a collection of tables which all share the same <see cref="SharedTableData"/>.
/// </summary>
public abstract class LocalizationTableCollection : ScriptableObject, ISerializationCallbackReceiver
{
/// <summary>
/// Represents a single Key and its localized values when using GetRowEnumerator.
/// </summary>
/// <typeparam name="TEntry"></typeparam>
public class Row<TEntry> where TEntry : TableEntry
{
/// <summary>
/// The <see cref="LocaleIdentifier"/> for each table value in <see cref="TableEntries"/>.
/// The order of the tables is guaranteed not to change.
/// </summary>
public LocaleIdentifier[] TableEntriesReference { get; internal set; }
/// <summary>
/// The Key for the current row.
/// </summary>
public SharedTableData.SharedTableEntry KeyEntry { get; internal set; }
/// <summary>
/// The entries taken from all the tables for the current <see cref="KeyEntry"/>.
/// The value may be null, such as when the table does not have a value for the current key.
/// </summary>
public TEntry[] TableEntries { get; internal set; }
}
[SerializeField]
LazyLoadReference<SharedTableData> m_SharedTableData;
[SerializeField]
List<LazyLoadReference<LocalizationTable>> m_Tables = new List<LazyLoadReference<LocalizationTable>>();
[SerializeReference]
List<CollectionExtension> m_Extensions = new List<CollectionExtension>();
[SerializeField]
string m_Group;
ReadOnlyCollection<LazyLoadReference<LocalizationTable>> m_ReadOnlyTables;
ReadOnlyCollection<CollectionExtension> m_ReadOnlyExtensions;
/// <summary>
/// The type of table stored in the collection.
/// </summary>
protected internal abstract Type TableType { get; }
/// <summary>
/// The required attribute for an extension to be added to this collection through the Editor.
/// </summary>
protected internal abstract Type RequiredExtensionAttribute { get; }
/// <summary>
/// Removes the entry from the <see cref="SharedTableData"/> and all tables that are part of this collection.
/// </summary>
/// <param name="entryReference"></param>
public abstract void RemoveEntry(TableEntryReference entryReference);
/// <summary>
/// The default value to use for <see cref="Group"/>.
/// </summary>
protected internal abstract string DefaultGroupName { get; }
internal (bool valid, string error) IsValid
{
get
{
if (SharedData != null)
return (true, null);
return (false, "SharedTableData is null");
}
}
/// <summary>
/// All tables that are part of this collection.
/// Tables are stored as LazyLoadReferences so that they only load when required and not when the collection loads.
/// </summary>
public ReadOnlyCollection<LazyLoadReference<LocalizationTable>> Tables
{
get
{
if (m_ReadOnlyTables == null)
{
RemoveBrokenTables();
m_ReadOnlyTables = m_Tables.AsReadOnly();
}
return m_ReadOnlyTables;
}
}
/// <summary>
/// Extensions attached to the collection. Extensions can be used to attach additional data or functionality to a collection.
/// </summary>
public virtual ReadOnlyCollection<CollectionExtension> Extensions
{
get
{
if (m_ReadOnlyExtensions == null)
m_ReadOnlyExtensions = m_Extensions.AsReadOnly();
return m_ReadOnlyExtensions;
}
}
/// <summary>
/// The name of this collection of Tables.
/// </summary>
public virtual string TableCollectionName { get => SharedData.TableCollectionName; set => SharedData.TableCollectionName = value; }
/// <summary>
/// Reference to use to refer to this table collection.
/// </summary>
public TableReference TableCollectionNameReference => SharedData.TableCollectionNameGuid;
/// <summary>
/// The <see cref="SharedTableData"/> that is used by all tables in this collection.
/// </summary>
public virtual SharedTableData SharedData { get => m_SharedTableData.asset; internal set => m_SharedTableData.asset = value; }
/// <summary>
/// Collections can be added to groups which will be used when showing the list of collections in the Localization Window.
/// For example all collections with the Group "UI" would be shown under a UI menu in the `Selected Table Collection` field.
/// </summary>
public string Group { get => m_Group; set => m_Group = value; }
protected virtual void OnEnable()
{
if (string.IsNullOrEmpty(m_Group))
{
// Default group if one is not set
m_Group = DefaultGroupName;
}
}
/// <summary>
/// Changes the table collection name.
/// This will change <see cref="SharedTableData.TableCollectionName"/> and update the Addressables data for all tables within the collection.
/// </summary>
/// <param name="name"></param>
/// <param name="createUndo"></param>
public void SetTableCollectionName(string name, bool createUndo = false)
{
SetTableCollectionNameInternal(name, createUndo);
}
// virtual for testing
internal virtual void SetTableCollectionNameInternal(string name, bool createUndo)
{
if (name == TableCollectionName)
return;
var tableNameError = LocalizationEditorSettings.Instance.IsTableNameValid(GetType(), name);
if (tableNameError != null)
{
throw new ArgumentException(tableNameError, nameof(name));
}
var undoGroup = Undo.GetCurrentGroup();
if (createUndo)
Undo.RecordObject(SharedData, "Change Table Collection Name");
EditorUtility.SetDirty(SharedData);
var OldTableCollectionName = SharedData.TableCollectionName;
SharedData.TableCollectionName = name;
RefreshAddressables(createUndo);
RefreshAssetNames(OldTableCollectionName);
if (createUndo)
Undo.CollapseUndoOperations(undoGroup);
}
void RefreshAssetNames(string OldTableCollectionName)
{
if (SharedData == null || SharedData.TableCollectionName == OldTableCollectionName)
return;
//Change Table Name
foreach (var table in m_Tables)
{
ObjectNames.SetNameSmart(table.asset, AddressHelper.GetTableAddress(table.asset.TableCollectionName, table.asset.LocaleIdentifier));
}
//change Localization TableCollection Name
ObjectNames.SetNameSmart(this, SharedData.TableCollectionName);
//change SharedData TableCollection name.
ObjectNames.SetNameSmart(SharedData, AddressHelper.GetSharedTableAddress(SharedData.TableCollectionName));
EditorUtility.SetDirty(SharedData);
}
/// <summary>
/// Sets the preload flag for all tables in this collection.
/// </summary>
/// <param name="preload">Should the tables be preloaded? True for preloading or false to load on demand.</param>
/// <param name="createUndo">Create an undo point?</param>
public virtual void SetPreloadTableFlag(bool preload, bool createUndo = false)
{
RemoveBrokenTables();
foreach (var table in m_Tables)
{
LocalizationEditorSettings.SetPreloadTableFlag(table.asset, preload, createUndo);
}
}
/// <summary>
/// Are the tables in the collection set to preload?
/// </summary>
/// <returns>True if <b>all</b> tables are set to preload else false.</returns>
public virtual bool IsPreloadTableFlagSet()
{
RemoveBrokenTables();
return m_Tables.Count > 0 && m_Tables.TrueForAll(tbl => LocalizationEditorSettings.GetPreloadTableFlag(tbl.asset));
}
/// <summary>
/// Adds the table to the collection and updates Addressable assets.
/// The table will not be added if it is not using the same <see cref="SharedTableData"/> as the collection.
/// </summary>
/// <param name="table">The table to add to the collection.</param>
/// <param name="createUndo">Should an Undo operation be created?</param>
/// <param name="postEvent">Should the <see cref="LocalizationEditorEvents.TableAddedToCollection"/> event be sent after the table was added?</param>
public virtual void AddTable(LocalizationTable table, bool createUndo = false, bool postEvent = true)
{
if (table == null)
throw new ArgumentNullException(nameof(table));
if (!EditorUtility.IsPersistent(table))
throw new AssetNotPersistentException(table);
if (table.SharedData != SharedData)
throw new Exception($"Can not add table {table}, it has different Shared Data. Table uses {table.SharedData.name} but collection uses {SharedData.name}");
if (!table.GetType().IsAssignableFrom(TableType))
return;
using (new UndoScope("Add table to collection", createUndo))
{
if (createUndo)
Undo.RegisterCompleteObjectUndo(this, "Add table to collection");
AddTableToAddressables(table, createUndo);
// We always run to this point in case we need to fix Addressable issues.
// We only send the event if the table has been added for the first time though.
if (!m_Tables.Any(tbl => tbl.asset == table || tbl.asset?.LocaleIdentifier == table.LocaleIdentifier))
{
//Setting the PreloadTableFlag true if PreladAll is set true
LocalizationEditorSettings.SetPreloadTableFlag(table, IsPreloadTableFlagSet());
m_Tables.Add(new LazyLoadReference<LocalizationTable> { asset = table });
// We need to SetDirty after AddTableToAddressables as AddTableToAddressables may call
// SaveAssets which would reset the dirty state before we have finished making changes.
EditorUtility.SetDirty(this);
if (postEvent)
LocalizationEditorSettings.EditorEvents.RaiseTableAddedToCollection(this, table);
}
if (postEvent)
LocalizationEditorSettings.EditorEvents.RaiseCollectionModified(this, this);
}
}
/// <summary>
/// Creates a table in the collection.
/// </summary>
/// <param name="localeIdentifier"></param>
/// <returns>>The newly created table.</returns>
public virtual LocalizationTable AddNewTable(LocaleIdentifier localeIdentifier)
{
var defaultDirectory = Path.GetDirectoryName(AssetDatabase.GetAssetPath(this));
var relativePath = PathHelper.MakePathRelative(defaultDirectory);
var tableName = AddressHelper.GetTableAddress(TableCollectionName, localeIdentifier);
var path = Path.Combine(relativePath, tableName + ".asset");
return AddNewTable(localeIdentifier, path);
}
/// <summary>
/// Creates a table in the collection.
/// </summary>
/// <param name="localeIdentifier"></param>
/// <param name="path"></param>
/// <returns>>The newly created table.</returns>
public virtual LocalizationTable AddNewTable(LocaleIdentifier localeIdentifier, string path)
{
if (ContainsTable(localeIdentifier))
throw new Exception("Can not add new table. The same LocaleIdentifier is already in use.");
LocalizationTable table;
if (File.Exists(path))
table = AssetDatabase.LoadAssetAtPath<LocalizationTable>(path);
else
{
table = CreateInstance(TableType) as LocalizationTable;
table.LocaleIdentifier = localeIdentifier;
table.SharedData = SharedData;
LocalizationEditorSettings.Instance.CreateAsset(table, path);
}
AddTable(table);
return table;
}
/// <summary>
/// Removes the table from the collection and updates Addressables assets.
/// </summary>
/// <param name="table"></param>
/// <param name="createUndo"></param>
/// <param name="postEvent">Should the <see cref="LocalizationEditorEvents.TableRemovedFromCollection"/> event be sent after the table has been removed?</param>
public virtual void RemoveTable(LocalizationTable table, bool createUndo = false, bool postEvent = true)
{
if (table == null)
throw new ArgumentNullException(nameof(table));
// We use the instance id so as not to force the tables to be loaded.
var tableInstanceID = table.GetInstanceID();
var index = m_Tables.FindIndex(t => t.GetInstanceId() == tableInstanceID);
if (index == -1)
return;
if (createUndo)
Undo.RecordObject(this, "Remove table from collection");
RemoveTableFromAddressables(table, false);
m_Tables.RemoveAt(index);
EditorUtility.SetDirty(this);
if (postEvent)
LocalizationEditorSettings.EditorEvents.RaiseTableRemovedFromCollection(this, table);
}
/// <summary>
/// Removes all the entries from <see cref="SharedTableData"/> and all <see cref="Tables"/> that are part of this collection.
/// </summary>
public virtual void ClearAllEntries()
{
// Clear all keys
if (SharedData != null)
{
SharedData.Clear();
EditorUtility.SetDirty(SharedData);
}
EditorUtility.SetDirty(this);
LocalizationEditorSettings.EditorEvents.RaiseCollectionModified(this, this);
}
/// <summary>
/// Returns the table with the matching <see cref="LocaleIdentifier"/>.
/// </summary>
/// <param name="localeIdentifier">The locale identifier that the table must have.</param>
/// <returns>The table with the matching <see cref="LocaleIdentifier"/> or null if one does not exist in the collection.</returns>
public virtual LocalizationTable GetTable(LocaleIdentifier localeIdentifier)
{
foreach (var tbl in m_Tables)
{
if (tbl.asset?.LocaleIdentifier == localeIdentifier)
return tbl.asset;
}
return null;
}
// We use this so we can enumerate all the tables and mock it in tests.
// LazyLoadReference only works with perssistent assets which makes testing temporary assets hard so we use this instead.
internal virtual IEnumerable<LocalizationTable> GetTableEnumerator()
{
foreach (var table in m_Tables)
{
yield return table.asset;
}
}
/// <summary>
/// Forces Addressables data to be updated for this collection.
/// This will ensure that <see cref="SharedData"/> and <see cref="Tables"/> are both part of Addressables and correctly labeled.
/// </summary>
/// <param name="createUndo"></param>
public void RefreshAddressables(bool createUndo = false)
{
RemoveBrokenTables();
AddSharedTableDataToAddressables();
using (new UndoScope("Add table to collection", createUndo))
{
foreach (var table in m_Tables)
{
AddTableToAddressables(table.asset, createUndo);
}
}
}
/// <summary>
/// Checks if a table with the same instance Id exists in the collection.
/// This check should be fast as a table does not need to be loaded to have its instance Id compared.
/// </summary>
/// <param name="table">The table to look for.</param>
/// <returns></returns>
public virtual bool ContainsTable(LocalizationTable table)
{
// We use the instance id so as not to force the tables to be loaded.
var tableInstanceID = table.GetInstanceID();
return m_Tables.Any(t => t.GetInstanceId() == tableInstanceID);
}
/// <summary>
/// Checks if a table with the same <see cref="LocaleIdentifier"/> exists in the collection.
/// </summary>
/// <param name="localeIdentifier">The Id to match against.</param>
/// <returns></returns>
public bool ContainsTable(LocaleIdentifier localeIdentifier) => GetTable(localeIdentifier) != null;
/// <summary>
/// Attaches the provided extension to the collection.
/// </summary>
/// <param name="extension">The extension to add to the collection.</param>
/// <exception cref="ArgumentException">Thrown if the extension does not have correct attribute.</exception>
public void AddExtension(CollectionExtension extension)
{
if (extension == null)
throw new ArgumentNullException(nameof(extension));
if (!Attribute.IsDefined(extension.GetType(), RequiredExtensionAttribute))
throw new ArgumentException($"Can not add extension. It requires the Attribute {RequiredExtensionAttribute}.", nameof(extension));
extension.TargetCollection = this;
m_Extensions.Add(extension);
extension.Initialize();
LocalizationEditorSettings.EditorEvents.RaiseExtensionAddedToCollection(this, extension);
}
/// <summary>
/// Removes the extension from <see cref="Extensions"/>.
/// </summary>
/// <param name="extension">The extension to remove from the collection.</param>
/// <exception cref="ArgumentNullException">Thrown if the extension is null.</exception>
public void RemoveExtension(CollectionExtension extension)
{
if (extension == null)
throw new ArgumentNullException(nameof(extension));
m_Extensions.Remove(extension);
extension.Destroy();
extension.TargetCollection = null;
LocalizationEditorSettings.EditorEvents.RaiseExtensionRemovedFromCollection(this, extension);
}
internal void SaveChangesToDisk()
{
#if ENABLE_SAVE_ASSET_IF_DIRTY // Added in 2020.3.16 and 2021.2
foreach (var tbl in m_Tables)
{
AssetDatabase.SaveAssetIfDirty(tbl.asset);
}
AssetDatabase.SaveAssetIfDirty(SharedData);
#else
AssetDatabase.SaveAssets();
#endif
}
/// <summary>
/// Returns an enumerable for stepping through the rows of the collection. Sorted by the <see cref="SharedData"/> entry Ids.
/// </summary>
/// <typeparam name="TTable"></typeparam>
/// <typeparam name="TEntry"></typeparam>
/// <param name="tables"></param>
/// <returns></returns>
protected static IEnumerable<Row<TEntry>> GetRowEnumerator<TTable, TEntry>(IEnumerable<TTable> tables)
where TTable : DetailedLocalizationTable<TEntry>
where TEntry : TableEntry
{
if (tables == null)
throw new ArgumentNullException(nameof(tables));
SharedTableData sharedTableData = null;
// Prepare the tables - Sort the keys and table entries
var sortedTableEntries = new List<IOrderedEnumerable<TEntry>>();
foreach (var table in tables)
{
if (sharedTableData == null)
{
sharedTableData = table.SharedData;
}
else if (sharedTableData != table.SharedData)
{
throw new Exception("All tables must share the same SharedData.");
}
if (table != null)
{
var s = table.Values.OrderBy(e => e.KeyId);
sortedTableEntries.Add(s);
}
}
var sortedKeyEntries = sharedTableData.Entries.OrderBy(e => e.Id);
var currentTableRowIterator = sortedTableEntries.Select(o =>
{
var itr = o.GetEnumerator();
itr.MoveNext();
return itr;
}).ToArray();
var currentRow = new Row<TEntry>
{
TableEntriesReference = tables.Select(t => t.LocaleIdentifier).ToArray(),
TableEntries = new TEntry[sortedTableEntries.Count]
};
using (StringBuilderPool.Get(out var warningMessage))
{
// Extract the table row values for this key.
// If the table has a key value then add it to currentTableRow otherwise use null.
foreach (var keyRow in sortedKeyEntries)
{
currentRow.KeyEntry = keyRow;
// Extract the string table entries for this row
for (int i = 0; i < currentRow.TableEntries.Length; ++i)
{
var tableRowItr = currentTableRowIterator[i];
// Skip any table entries that may not not exist in Shared Data
while (tableRowItr != null && tableRowItr.Current?.KeyId < keyRow.Id)
{
warningMessage.AppendLine($"{tableRowItr.Current.Table.name} - {tableRowItr.Current.KeyId} - {tableRowItr.Current.Data.Localized}");
if (!tableRowItr.MoveNext())
{
currentTableRowIterator[i] = null;
break;
}
}
if (tableRowItr?.Current?.KeyId == keyRow.Id)
{
currentRow.TableEntries[i] = tableRowItr.Current;
if (!tableRowItr.MoveNext())
{
currentTableRowIterator[i] = null;
}
}
else
{
currentRow.TableEntries[i] = null;
}
}
yield return currentRow;
}
// Any warning messages?
if (warningMessage.Length > 0)
{
warningMessage.Insert(0, "Found entries in Tables that were missing a Shared Table Data Entry. These entries were ignored:\n");
Debug.LogWarning(warningMessage.ToString(), sharedTableData);
}
}
}
/// <summary>
/// Returns an enumerable for stepping through the rows of the collection. Unlike <see cref="GetRowEnumerator"/>,
/// the items are not sorted by Id and will be returned in the same order as they are stored in <see cref="SharedData"/>.
/// </summary>
/// <typeparam name="TTable"></typeparam>
/// <typeparam name="TEntry"></typeparam>
/// <param name="tables"></param>
/// <returns></returns>
protected static IEnumerable<Row<TEntry>> GetRowEnumeratorUnsorted<TTable, TEntry>(IList<TTable> tables)
where TTable : DetailedLocalizationTable<TEntry>
where TEntry : TableEntry
{
if (tables == null)
throw new ArgumentNullException(nameof(tables));
SharedTableData sharedTableData = null;
// Prepare the tables - Sort the keys and table entries
foreach (var table in tables)
{
if (sharedTableData == null)
{
sharedTableData = table.SharedData;
}
else if (sharedTableData != table.SharedData)
{
throw new Exception("All tables must share the same SharedData.");
}
}
var currentRow = new Row<TEntry>
{
TableEntriesReference = tables.Select(t => t.LocaleIdentifier).ToArray(),
TableEntries = new TEntry[tables.Count]
};
foreach (var keyEntry in sharedTableData.Entries)
{
currentRow.KeyEntry = keyEntry;
for (int i = 0; i < tables.Count; ++i)
{
currentRow.TableEntries[i] = tables[i].GetEntry(keyEntry.Id);
}
yield return currentRow;
}
}
/// <summary>
/// Adds <see cref="SharedData"/> to Addressables.
/// </summary>
protected internal virtual void AddSharedTableDataToAddressables()
{
var aaSettings = LocalizationEditorSettings.Instance.GetAddressableAssetSettings(true);
if (aaSettings == null)
return;
if (TableType == typeof(StringTable))
AddressableGroupRules.AddStringTableSharedAsset(SharedData, aaSettings, false);
else
AddressableGroupRules.AddAssetTableSharedAsset(SharedData, aaSettings, false);
}
/// <summary>
/// Add the table to the Addressable assets system.
/// </summary>
/// <param name="table">The table to add.</param>
/// <param name="createUndo">Should an Undo operation be recorded?</param>
protected virtual void AddTableToAddressables(LocalizationTable table, bool createUndo)
{
if (table == null)
throw new ArgumentNullException(nameof(table), "Can add a null table to Addressables");
var aaSettings = LocalizationEditorSettings.Instance.GetAddressableAssetSettings(true);
if (aaSettings == null)
return;
var tableEntry = TableType == typeof(StringTable) ? AddressableGroupRules.AddStringTableAsset(table, aaSettings, createUndo) : AddressableGroupRules.AddAssetTableAsset(table, aaSettings, createUndo);
if (createUndo)
Undo.RecordObjects(new UnityEngine.Object[] { aaSettings, tableEntry.parentGroup }, "Update table");
tableEntry.address = AddressHelper.GetTableAddress(table.TableCollectionName, table.LocaleIdentifier);
tableEntry.labels.RemoveWhere(AddressHelper.IsLocaleLabel); // Locale may have changed so clear the old ones.
// Label the locale
var localeLabel = AddressHelper.FormatAssetLabel(table.LocaleIdentifier);
tableEntry.SetLabel(localeLabel, true, true);
}
/// <summary>
/// Remove the table from the Addressables system.
/// </summary>
/// <param name="table">The table to remove.</param>
/// <param name="createUndo">Should an Undo operation be recorded?</param>
protected virtual void RemoveTableFromAddressables(LocalizationTable table, bool createUndo)
{
if (table == null)
return;
var settings = LocalizationEditorSettings.Instance.GetAddressableAssetSettings(false);
if (settings == null)
return;
var tableEntry = LocalizationEditorSettings.Instance.GetAssetEntry(table);
if (tableEntry == null)
return;
if (createUndo)
Undo.RecordObjects(new UnityEngine.Object[] { settings, tableEntry.parentGroup }, "Remove table");
settings.RemoveAssetEntry(tableEntry.guid);
}
/// <summary>
/// Called when the asset is created or imported into a project(via OnPostprocessAllAssets).
/// </summary>
protected internal virtual void ImportCollectionIntoProject()
{
RefreshAddressables();
var missingLocales = new List<LocaleIdentifier>();
foreach (var table in Tables)
{
var locale = LocalizationEditorSettings.GetLocale(table.asset.LocaleIdentifier.Code);
if (locale == null)
{
missingLocales.Add(new LocaleIdentifier(table.asset.LocaleIdentifier.Code));
}
}
if (missingLocales.Count > 0)
{
// First check that the Locale does not exist in the project but is not marked as Addressable.
using (DictionaryPool<LocaleIdentifier, Locale>.Get(out var projectLocales))
{
var allLocales = AssetDatabase.FindAssets("t:Locale");
foreach (var loc in allLocales)
{
var loadedLocale = AssetDatabase.LoadAssetAtPath<Locale>(AssetDatabase.GUIDToAssetPath(loc));
projectLocales[loadedLocale.Identifier] = loadedLocale;
}
for (int i = 0; i < missingLocales.Count; ++i)
{
if (projectLocales.TryGetValue(missingLocales[i], out var foundLocale))
{
missingLocales.RemoveAt(i);
i--;
LocalizationEditorSettings.AddLocale(foundLocale);
}
}
}
if (missingLocales.Count > 0)
{
var defaultDirectory = Path.GetDirectoryName(AssetDatabase.GetAssetPath(this));
var relativePath = PathHelper.MakePathRelative(defaultDirectory);
LocaleGeneratorWindow.ExportSelectedLocales(relativePath, missingLocales);
var sb = new StringBuilder();
sb.AppendLine($"The following missing Locales have been added to the project because they are used by the Collection {TableCollectionName}:");
missingLocales.ForEach(l => sb.AppendLine(l.ToString()));
Debug.Log(sb.ToString());
}
}
var isInProject = TableType == typeof(StringTable) ? LocalizationEditorSettings.GetStringTableCollection(TableCollectionName) != null : LocalizationEditorSettings.GetAssetTableCollection(TableCollectionName) != null;
if (!isInProject)
LocalizationEditorSettings.EditorEvents.RaiseCollectionAdded(this);
else
LocalizationEditorSettings.EditorEvents.RaiseCollectionModified(this, this);
}
/// <summary>
/// Called to remove the asset from a project, such as when it is about to be deleted.
/// </summary>
protected internal virtual void RemoveCollectionFromProject()
{
foreach (var tbl in m_Tables)
{
if (tbl.asset != null)
RemoveTableFromAddressables(tbl.asset, false);
}
LocalizationEditorSettings.EditorEvents.RaiseCollectionRemoved(this);
}
void RemoveBrokenTables()
{
// We cant do this in OnBeforeSerialize or OnAfterDeserialize as it uses ForceLoadFromInstanceID and this is not allowed to be called during serialization.
int brokenCount = 0;
for (int i = 0; i < m_Tables.Count; ++i)
{
if (m_Tables[i].isBroken)
{
m_Tables.RemoveAt(i);
--i;
++brokenCount;
}
}
if (brokenCount > 0)
{
Debug.LogWarning($"{brokenCount} Broken table reference was found and removed for {TableCollectionName} collection. References to this table or its assets may have not been cleaned up.", this);
EditorUtility.SetDirty(this);
}
}
public void OnBeforeSerialize()
{
}
public void OnAfterDeserialize()
{
m_ReadOnlyTables = null;
m_ReadOnlyExtensions = null;
}
public override string ToString() => $"{TableCollectionName}({TableType.Name})";
}
}