using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization.Formatters.Binary;
using UnityEditor.AddressableAssets;
using UnityEditor.AddressableAssets.Settings.GroupSchemas;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.Serialization;
namespace UnityEditor.AddressableAssets.Settings
{
///
/// Contains the collection of asset entries associated with this group.
///
[Serializable]
public class AddressableAssetGroup : ScriptableObject, IComparer, ISerializationCallbackReceiver
{
[FormerlySerializedAs("m_name")]
[SerializeField]
string m_GroupName;
[FormerlySerializedAs("m_data")]
[SerializeField]
KeyDataStore m_Data;
[FormerlySerializedAs("m_guid")]
[SerializeField]
string m_GUID;
[FormerlySerializedAs("m_serializeEntries")]
[SerializeField]
List m_SerializeEntries = new List();
[FormerlySerializedAs("m_readOnly")]
[SerializeField]
internal bool m_ReadOnly;
[FormerlySerializedAs("m_settings")]
[SerializeField]
AddressableAssetSettings m_Settings;
[FormerlySerializedAs("m_schemaSet")]
[SerializeField]
AddressableAssetGroupSchemaSet m_SchemaSet = new AddressableAssetGroupSchemaSet();
Dictionary m_EntryMap = new Dictionary();
///
/// The group name.
///
public virtual string Name
{
get
{
if (string.IsNullOrEmpty(m_GroupName))
m_GroupName = Guid;
return m_GroupName;
}
set
{
m_GroupName = value;
m_GroupName = m_GroupName.Replace('/', '-');
m_GroupName = m_GroupName.Replace('\\', '-');
if(m_GroupName != value)
Debug.Log("Group names cannot include '\\' or '/'. Replacing with '-'. " + m_GroupName);
if (m_GroupName != name)
{
string guid;
long localId;
if (AssetDatabase.TryGetGUIDAndLocalFileIdentifier(this, out guid, out localId))
{
var path = AssetDatabase.GUIDToAssetPath(guid);
if (!string.IsNullOrEmpty(path))
{
var newPath = path.Replace(name, m_GroupName);
if (path != newPath)
{
var setPath = AssetDatabase.MoveAsset(path, newPath);
if (!string.IsNullOrEmpty(setPath))
{
//unable to rename group due to invalid file name
Debug.LogError("Rename of Group failed. " + setPath);
}
m_GroupName = name;
}
}
}
else
{
//this isn't a valid asset, which means it wasn't persisted, so just set the object name to the desired display name.
name = m_GroupName;
}
SetDirty(AddressableAssetSettings.ModificationEvent.GroupRenamed, this, true, true);
}
}
}
///
/// The group GUID.
///
public virtual string Guid
{
get
{
if (string.IsNullOrEmpty(m_GUID))
m_GUID = GUID.Generate().ToString();
return m_GUID;
}
}
///
/// List of schemas for this group.
///
public List Schemas { get { return m_SchemaSet.Schemas; } }
///
/// Get the types of added schema for this group.
///
public List SchemaTypes { get { return m_SchemaSet.Types; } }
string GetSchemaAssetPath(Type type)
{
return Settings.IsPersisted ? (Settings.GroupSchemaFolder + "/" + m_GUID + "_" + type.Name + ".asset") : string.Empty;
}
///
/// Adds a copy of the provided schema object.
///
/// The schema to add. A copy will be made and saved in a folder relative to the main Addressables settings asset.
/// Determines if this method call will post an event to the internal addressables event system
/// The created schema object.
public AddressableAssetGroupSchema AddSchema(AddressableAssetGroupSchema schema, bool postEvent = true)
{
var added = m_SchemaSet.AddSchema(schema, GetSchemaAssetPath);
if (added != null)
{
added.Group = this;
SetDirty(AddressableAssetSettings.ModificationEvent.GroupSchemaAdded, this, postEvent, true);
}
return added;
}
///
/// Creates and adds a schema of a given type to this group. The schema asset will be created in the GroupSchemas directory relative to the settings asset.
///
/// The schema type. This type must not already be added.
/// Determines if this method call will post an event to the internal addressables event system
/// The created schema object.
public AddressableAssetGroupSchema AddSchema(Type type, bool postEvent = true)
{
var added = m_SchemaSet.AddSchema(type, GetSchemaAssetPath);
if (added != null)
{
added.Group = this;
SetDirty(AddressableAssetSettings.ModificationEvent.GroupSchemaAdded, this, postEvent, true);
}
return added;
}
///
/// Creates and adds a schema of a given type to this group.
///
/// Determines if this method call will post an event to the internal addressables event system
/// The schema type. This type must not already be added.
/// The created schema object.
public TSchema AddSchema(bool postEvent = true) where TSchema : AddressableAssetGroupSchema
{
return AddSchema(typeof(TSchema), postEvent) as TSchema;
}
///
/// Remove a given schema from this group.
///
/// The schema type.
/// Determines if this method call will post an event to the internal addressables event system
/// True if the schema was found and removed, false otherwise.
public bool RemoveSchema(Type type, bool postEvent = true)
{
if (!m_SchemaSet.RemoveSchema(type))
return false;
SetDirty(AddressableAssetSettings.ModificationEvent.GroupSchemaRemoved, this, postEvent, true);
return true;
}
///
/// Remove a given schema from this group.
///
/// Determines if this method call will post an event to the internal addressables event system
/// The schema type.
/// True if the schema was found and removed, false otherwise.
public bool RemoveSchema(bool postEvent = true)
{
return RemoveSchema(typeof(TSchema), postEvent);
}
///
/// Gets an added schema of the specified type.
///
/// The schema type.
/// The schema if found, otherwise null.
public TSchema GetSchema() where TSchema : AddressableAssetGroupSchema
{
return GetSchema(typeof(TSchema)) as TSchema;
}
///
/// Gets an added schema of the specified type.
///
/// The schema type.
/// The schema if found, otherwise null.
public AddressableAssetGroupSchema GetSchema(Type type)
{
return m_SchemaSet.GetSchema(type);
}
///
/// Checks if the group contains a schema of a given type.
///
/// The schema type.
/// True if the schema type or subclass has been added to this group.
public bool HasSchema()
{
return HasSchema(typeof(TSchema));
}
///
/// Removes all schemas and optionally deletes the assets associated with them.
///
/// If true, the schema assets will also be deleted.
/// Determines if this method call will post an event to the internal addressables event system
public void ClearSchemas(bool deleteAssets, bool postEvent = true)
{
m_SchemaSet.ClearSchemas(deleteAssets);
SetDirty(AddressableAssetSettings.ModificationEvent.GroupRemoved, this, postEvent, true);
}
///
/// Checks if the group contains a schema of a given type.
///
/// The schema type.
/// True if the schema type or subclass has been added to this group.
public bool HasSchema(Type type)
{
return GetSchema(type) != null;
}
///
/// Is this group read only. This is normally false. Built in resources (resource folders and the scene list) are put into a special read only group.
///
public virtual bool ReadOnly
{
get { return m_ReadOnly; }
}
///
/// The AddressableAssetSettings that this group belongs to.
///
public AddressableAssetSettings Settings
{
get
{
if (m_Settings == null)
m_Settings = AddressableAssetSettingsDefaultObject.Settings;
return m_Settings;
}
}
///
/// The collection of asset entries.
///
public virtual ICollection entries
{
get
{
return m_EntryMap.Values;
}
}
///
/// Is the default group.
///
public virtual bool Default
{
get { return Guid == Settings.DefaultGroup.Guid; }
}
///
public virtual int Compare(AddressableAssetEntry x, AddressableAssetEntry y)
{
if (x == null && y == null)
return 0;
if (x == null)
return -1;
if (y == null)
return 1;
return x.guid.CompareTo(y.guid);
}
internal void SerializeForHash(BinaryFormatter formatter, Stream stream)
{
formatter.Serialize(stream, m_GroupName);
formatter.Serialize(stream, m_GUID);
formatter.Serialize(stream, entries.Count);
foreach (var e in entries)
e.SerializeForHash(formatter, stream);
formatter.Serialize(stream, m_ReadOnly);
//TODO: serialize group data
}
///
/// Converts data to serializable format.
///
public void OnBeforeSerialize()
{
m_SerializeEntries.Clear();
foreach (var e in entries)
m_SerializeEntries.Add(e);
m_SerializeEntries.Sort(this);
}
///
/// Converts data from serializable format.
///
public void OnAfterDeserialize()
{
ResetEntryMap();
m_SerializeEntries.Clear();
}
internal void ResetEntryMap()
{
m_EntryMap.Clear();
foreach (var e in m_SerializeEntries)
{
try
{
e.parentGroup = this;
e.IsSubAsset = false;
m_EntryMap.Add(e.guid, e);
}
catch (Exception ex)
{
Addressables.Log(e.address);
Debug.LogException(ex);
}
}
}
void OnEnable()
{
Validate();
}
internal void Validate()
{
bool allValid = false;
while (!allValid)
{
allValid = true;
for (int i = 0; i < m_SchemaSet.Schemas.Count; i++)
{
if (m_SchemaSet.Schemas[i] == null)
{
m_SchemaSet.Schemas.RemoveAt(i);
allValid = false;
break;
}
if(m_SchemaSet.Schemas[i].Group == null)
m_SchemaSet.Schemas[i].Group = this;
}
}
var editorList = GetAssetEntry(AddressableAssetEntry.EditorSceneListName);
if (editorList != null)
{
if (m_GroupName == null)
m_GroupName = AddressableAssetSettings.PlayerDataGroupName;
if (m_Data != null)
{
if(!HasSchema())
AddSchema();
m_Data = null;
}
}
else if(Settings != null)
{
if (m_GroupName == null)
m_GroupName = Settings.FindUniqueGroupName("Packed Content Group");
m_Data = null;
}
}
internal void DedupeEnteries()
{
List removeEntries = new List();
foreach (AddressableAssetEntry e in m_EntryMap.Values)
{
AddressableAssetEntry lookedUpEntry = m_Settings.FindAssetEntry(e.guid);
if (lookedUpEntry.parentGroup != this)
{
Debug.LogWarning( e.address
+ " is already a member of group "
+ lookedUpEntry.parentGroup
+ " but group "
+ m_GroupName
+ " contained a reference to it. Removing referece.");
removeEntries.Add(e);
}
}
if(removeEntries.Count > 0)
RemoveAssetEntries(removeEntries);
}
internal void Initialize(AddressableAssetSettings settings, string groupName, string guid, bool readOnly)
{
m_Settings = settings;
m_GroupName = groupName;
m_ReadOnly = readOnly;
m_GUID = guid;
}
///
/// Gathers all asset entries. Each explicit entry may contain multiple sub entries. For example, addressable folders create entries for each asset contained within.
///
/// The generated list of entries. For simple entries, this will contain just the entry itself if specified.
/// Determines if the entry should be contained in the result list or just sub entries.
/// Determines if full recursion should be done when gathering entries.
/// Optional predicate to run against each entry, only returning those that pass. A null filter will return all entries
public virtual void GatherAllAssets(List results, bool includeSelf, bool recurseAll, Func entryFilter = null)
{
foreach (var e in entries)
if(entryFilter == null || entryFilter(e))
e.GatherAllAssets(results, includeSelf, recurseAll, entryFilter);
}
internal void AddAssetEntry(AddressableAssetEntry e, bool postEvent = true)
{
e.IsSubAsset = false;
e.parentGroup = this;
m_EntryMap[e.guid] = e;
SetDirty(AddressableAssetSettings.ModificationEvent.EntryAdded, e, postEvent, true);
}
///
/// Get an entry via the asset guid.
///
/// The asset guid.
///
public virtual AddressableAssetEntry GetAssetEntry(string guid)
{
if (m_EntryMap.ContainsKey(guid))
return m_EntryMap[guid];
return null;
}
///
/// Marks the object as modified.
///
/// The event type that is changed.
/// The object data that corresponds to the event.
/// If true, the event is propagated to callbacks.
/// If true, the group asset will be marked as dirty.
public void SetDirty(AddressableAssetSettings.ModificationEvent modificationEvent, object eventData, bool postEvent, bool groupModified = false)
{
if (Settings != null)
{
if (groupModified && Settings.IsPersisted && this != null)
EditorUtility.SetDirty(this);
Settings.SetDirty(modificationEvent, eventData, postEvent, false);
}
}
///
/// Remove an entry.
///
/// The entry to remove.
/// If true, post the event to callbacks.
public void RemoveAssetEntry(AddressableAssetEntry entry, bool postEvent = true)
{
m_EntryMap.Remove(entry.guid);
entry.parentGroup = null;
SetDirty(AddressableAssetSettings.ModificationEvent.EntryRemoved, entry, postEvent, true);
}
internal void RemoveAssetEntries(IEnumerable removeEntries, bool postEvent = true)
{
foreach (AddressableAssetEntry entry in removeEntries)
{
m_EntryMap.Remove(entry.guid);
entry.parentGroup = null;
}
if(removeEntries.Count() > 0)
SetDirty(AddressableAssetSettings.ModificationEvent.EntryRemoved, removeEntries.ToArray(), postEvent, true);
}
///
/// Check to see if a group is the Default Group.
///
///
public bool IsDefaultGroup()
{
return Guid == m_Settings.DefaultGroup.Guid;
}
///
/// Check if a group has the appropriate schemas and attributes that the Default Group requires.
///
///
public bool CanBeSetAsDefault()
{
return !m_ReadOnly;
}
}
}