using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor.AddressableAssets.Build.DataBuilders;
using UnityEditor.AddressableAssets.Settings;
using UnityEditor.AddressableAssets.Settings.GroupSchemas;
using UnityEditor.Build.Pipeline;
using UnityEditor.SceneManagement;
using UnityEngine;
namespace UnityEditor.AddressableAssets.Build.AnalyzeRules
{
///
/// Rule class to check for duplicate bundle dependencies
///
public class CheckBundleDupeDependencies : BundleRuleBase
{
///
/// Result for checking for duplicates
///
protected internal struct CheckDupeResult
{
public AddressableAssetGroup Group;
public string DuplicatedFile;
public string AssetPath;
public GUID DuplicatedGroupGuid;
}
///
/// Results for duplicate results inverted check
///
protected internal struct ExtraCheckBundleDupeData
{
public bool ResultsInverted;
}
///
public override bool CanFix
{
get { return true; }
}
///
public override string ruleName
{ get { return "Check Duplicate Bundle Dependencies"; } }
[NonSerialized]
internal readonly Dictionary>> m_AllIssues = new Dictionary>>();
[SerializeField]
internal HashSet m_ImplicitAssets;
///
/// Clear current analysis and rerun check for duplicates
///
/// The current Addressables settings object
/// List of the analysis results
public override List RefreshAnalysis(AddressableAssetSettings settings)
{
ClearAnalysis();
return CheckForDuplicateDependencies(settings);
}
void RefreshDisplay()
{
var savedData = AnalyzeSystem.GetDataForRule(this);
if (!savedData.ResultsInverted)
{
m_Results = (from issueGroup in m_AllIssues
from bundle in issueGroup.Value
from item in bundle.Value
select new AnalyzeResult
{
resultName = issueGroup.Key + kDelimiter +
ConvertBundleName(bundle.Key, issueGroup.Key) + kDelimiter +
item,
severity = MessageType.Warning
}).ToList();
}
else
{
m_Results = (from issueGroup in m_AllIssues
from bundle in issueGroup.Value
from item in bundle.Value
select new AnalyzeResult
{
resultName = item + kDelimiter +
ConvertBundleName(bundle.Key, issueGroup.Key) + kDelimiter +
issueGroup.Key,
severity = MessageType.Warning
}).ToList();
}
if (m_Results.Count == 0)
m_Results.Add(noErrors);
}
internal override IList GetCustomContextMenuItems()
{
IList customItems = new List();
customItems.Add(new CustomContextMenu("Organize by Asset",
() => InvertDisplay(),
AnalyzeSystem.AnalyzeData.Data[ruleName].Any(),
AnalyzeSystem.GetDataForRule(this).ResultsInverted));
return customItems;
}
void InvertDisplay()
{
List updatedResults = new List();
foreach (var result in AnalyzeSystem.AnalyzeData.Data[ruleName])
{
updatedResults.Add(new AnalyzeResult()
{
//start at index 1 because the first result is going to be the rule name which we want to remain where it is.
resultName = ReverseStringFromIndex(result.resultName, 1, kDelimiter),
severity = result.severity
});
}
AnalyzeSystem.ReplaceAnalyzeData(this, updatedResults);
var savedData = AnalyzeSystem.GetDataForRule(this);
savedData.ResultsInverted = !savedData.ResultsInverted;
AnalyzeSystem.SaveDataForRule(this, savedData);
AnalyzeSystem.SerializeData();
AnalyzeSystem.ReloadUI();
}
private string ReverseStringFromIndex(string data, int startingIndex, char delimiter)
{
string[] splitData = data.Split(delimiter);
int i = startingIndex;
int k = splitData.Length - 1;
while (i < k)
{
string temp = splitData[i];
splitData[i] = splitData[k];
splitData[k] = temp;
i++;
k--;
}
return String.Join(kDelimiter.ToString(), splitData);
}
///
/// Check for duplicates among the dependencies and build implicit duplicates
///
/// The current Addressables settings object
/// List of results from analysis
protected List CheckForDuplicateDependencies(AddressableAssetSettings settings)
{
if (!BuildUtility.CheckModifiedScenesAndAskToSave())
{
Debug.LogError("Cannot run Analyze with unsaved scenes");
m_Results.Add(new AnalyzeResult { resultName = ruleName + "Cannot run Analyze with unsaved scenes" });
return m_Results;
}
CalculateInputDefinitions(settings);
if (m_AllBundleInputDefs.Count > 0)
{
var context = GetBuildContext(settings);
ReturnCode exitCode = RefreshBuild(context);
if (exitCode < ReturnCode.Success)
{
Debug.LogError("Analyze build failed. " + exitCode);
m_Results.Add(new AnalyzeResult { resultName = ruleName + "Analyze build failed. " + exitCode });
return m_Results;
}
var implicitGuids = GetImplicitGuidToFilesMap();
var checkDupeResults = CalculateDuplicates(implicitGuids, context);
BuildImplicitDuplicatedAssetsSet(checkDupeResults);
}
RefreshDisplay();
return m_Results;
}
///
/// Calculate duplicate dependencies
///
/// Map of implicit guids to their bundle files
/// The build context information
/// Enumerable of results from duplicates check
protected internal IEnumerable CalculateDuplicates(Dictionary> implicitGuids, AddressableAssetsBuildContext aaContext)
{
//Get all guids that have more than one bundle referencing them
IEnumerable>> validGuids =
from dupeGuid in implicitGuids
where dupeGuid.Value.Distinct().Count() > 1
where IsValidPath(AssetDatabase.GUIDToAssetPath(dupeGuid.Key.ToString()))
select dupeGuid;
return
from guidToFile in validGuids
from file in guidToFile.Value
//Get the files that belong to those guids
let fileToBundle = m_ExtractData.WriteData.FileToBundle[file]
//Get the bundles that belong to those files
let bundleToGroup = aaContext.bundleToAssetGroup[fileToBundle]
//Get the asset groups that belong to those bundles
let selectedGroup = aaContext.Settings.FindGroup(findGroup => findGroup != null && findGroup.Guid == bundleToGroup)
select new CheckDupeResult
{
Group = selectedGroup,
DuplicatedFile = file,
AssetPath = AssetDatabase.GUIDToAssetPath(guidToFile.Key.ToString()),
DuplicatedGroupGuid = guidToFile.Key
};
}
internal void BuildImplicitDuplicatedAssetsSet(IEnumerable checkDupeResults)
{
m_ImplicitAssets = new HashSet();
foreach (var checkDupeResult in checkDupeResults)
{
Dictionary> groupData;
if (!m_AllIssues.TryGetValue(checkDupeResult.Group.Name, out groupData))
{
groupData = new Dictionary>();
m_AllIssues.Add(checkDupeResult.Group.Name, groupData);
}
List assets;
if (!groupData.TryGetValue(m_ExtractData.WriteData.FileToBundle[checkDupeResult.DuplicatedFile], out assets))
{
assets = new List();
groupData.Add(m_ExtractData.WriteData.FileToBundle[checkDupeResult.DuplicatedFile], assets);
}
assets.Add(checkDupeResult.AssetPath);
m_ImplicitAssets.Add(checkDupeResult.DuplicatedGroupGuid);
}
}
///
/// Fix duplicates by moving to a new group
///
/// The current Addressables settings object
public override void FixIssues(AddressableAssetSettings settings)
{
if (m_ImplicitAssets == null)
CheckForDuplicateDependencies(settings);
if (m_ImplicitAssets.Count == 0)
return;
var group = settings.CreateGroup("Duplicate Asset Isolation", false, false, false, null, typeof(BundledAssetGroupSchema), typeof(ContentUpdateGroupSchema));
group.GetSchema().StaticContent = true;
foreach (var asset in m_ImplicitAssets)
settings.CreateOrMoveEntry(asset.ToString(), group, false, false);
settings.SetDirty(AddressableAssetSettings.ModificationEvent.BatchModification, null, true, true);
}
///
public override void ClearAnalysis()
{
m_AllIssues.Clear();
m_ImplicitAssets = null;
base.ClearAnalysis();
}
}
[InitializeOnLoad]
class RegisterCheckBundleDupeDependencies
{
static RegisterCheckBundleDupeDependencies()
{
AnalyzeSystem.RegisterNewRule();
}
}
}