Files
2026-09-21 12:15:57 +03:00

239 lines
10 KiB
C#

#if UNITY_2022_2_OR_NEWER
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using UnityEditor.Build.Content;
using UnityEditor.Build.Pipeline.Injector;
using UnityEditor.Build.Pipeline.Interfaces;
using UnityEditor.Build.Pipeline.Utilities;
using UnityEditor.Build.Pipeline.WriteTypes;
using UnityEditor.Build.Utilities;
using UnityEngine;
namespace UnityEditor.Build.Pipeline.Tasks
{
public interface IClusterOutput : IContextObject
{
Dictionary<ObjectIdentifier, Hash128> ObjectToCluster { get; }
Dictionary<ObjectIdentifier, long> ObjectToLocalID { get; }
}
public class ClusterOutput : IClusterOutput
{
private Dictionary<ObjectIdentifier, Hash128> m_ObjectToCluster = new Dictionary<ObjectIdentifier, Hash128>();
private Dictionary<ObjectIdentifier, long> m_ObjectToLocalID = new Dictionary<ObjectIdentifier, long>();
public Dictionary<ObjectIdentifier, Hash128> ObjectToCluster { get { return m_ObjectToCluster; } }
public Dictionary<ObjectIdentifier, long> ObjectToLocalID { get { return m_ObjectToLocalID; } }
}
/// <summary>
/// Build task for creating content archives based asset co-location.
/// </summary>
public class ClusterBuildLayout : IBuildTask
{
private static void GetOrAdd<TKey, TValue>(IDictionary<TKey, TValue> dictionary, TKey key, out TValue value) where TValue : new()
{
if (dictionary.TryGetValue(key, out value))
return;
value = new TValue();
dictionary.Add(key, value);
}
public ClusterBuildLayout()
{
m_useContentIdsForClusterName = false;
}
public ClusterBuildLayout(bool useContentIdsForClusterName)
{
m_useContentIdsForClusterName = useContentIdsForClusterName;
}
bool m_useContentIdsForClusterName;
/// <inheritdoc />
public int Version { get { return 2; } }
#pragma warning disable 649
[InjectContext(ContextUsage.In)]
IBundleBuildParameters m_Parameters;
[InjectContext(ContextUsage.In)]
IDependencyData m_DependencyData;
[InjectContext]
IBundleWriteData m_WriteData;
[InjectContext(ContextUsage.In)]
IDeterministicIdentifiers m_PackingMethod;
[InjectContext]
IBuildResults m_Results;
[InjectContext]
IClusterOutput m_ClusterResult;
#pragma warning restore 649
/// <inheritdoc />
public ReturnCode Run()
{
return Run(m_Parameters, m_DependencyData, m_WriteData, m_PackingMethod, m_ClusterResult, m_useContentIdsForClusterName);
}
internal static ReturnCode Run(IBundleBuildParameters buildParams, IDependencyData dependencyData, IBundleWriteData writeData, IDeterministicIdentifiers packingMethod, IClusterOutput clusterResult, bool useContentIdsForClusterName)
{
// Create mapping of objects to all assets that depend on them
var objectToAssets = new Dictionary<ObjectIdentifier, HashSet<GUID>>();
foreach (var pair in dependencyData.AssetInfo)
{
ExtractAssets(objectToAssets, pair.Key, pair.Value.includedObjects);
ExtractAssets(objectToAssets, pair.Key, pair.Value.referencedObjects);
}
foreach (var pair in dependencyData.SceneInfo)
{
ExtractAssets(objectToAssets, pair.Key, pair.Value.referencedObjects);
}
//create clusters of object ids based on having the same assets referencing them
//the cluster ids here are created from the asset ids
var tempClusterToObjects = new Dictionary<Hash128, HashSet<ObjectIdentifier>>();
foreach (var pair in objectToAssets)
{
HashSet<GUID> assets = pair.Value;
Hash128 cluster = HashingMethods.Calculate(assets.OrderBy(x => x)).ToHash128();
GetOrAdd(tempClusterToObjects, cluster, out var objectIds);
objectIds.Add(pair.Key);
}
//create the final clusters with names based on the ids of the objects contained
var finalClusterToObjects = new Dictionary<Hash128, List<ObjectIdentifier>>();
foreach (var pair in tempClusterToObjects)
{
var objectsInCluster = pair.Value.ToList();
#if UNITY_6000_0_OR_NEWER
objectsInCluster.Sort();
#else
objectsInCluster.Sort((a, b) => a.GetHashCode().CompareTo(b.GetHashCode()));
#endif
var clusterId = useContentIdsForClusterName ? ComputeClusterId(objectsInCluster) : pair.Key;
finalClusterToObjects.Add(clusterId, objectsInCluster);
foreach (var o in objectsInCluster)
clusterResult.ObjectToCluster.TryAdd(o, clusterId);
}
// From clusters, create the final write data
BuildUsageTagSet usageSet = new BuildUsageTagSet();
foreach (var pair in dependencyData.AssetUsage)
usageSet.UnionWith(pair.Value);
foreach (var pair in dependencyData.SceneUsage)
usageSet.UnionWith(pair.Value);
var builtInResourcesGUID = new GUID("0000000000000000e000000000000000");
// Generates Content Archive Files from Clusters
foreach (var pair in finalClusterToObjects)
{
var objectsInCluster = pair.Value;
var clusterName = pair.Key.ToString();
writeData.FileToObjects.Add(clusterName, objectsInCluster);
#pragma warning disable CS0618 // Type or member is obsolete
var op = new RawWriteOperation();
#pragma warning restore CS0618 // Type or member is obsolete
writeData.WriteOperations.Add(op);
op.ReferenceMap = new BuildReferenceMap();
op.Command = new WriteCommand();
op.Command.fileName = clusterName;
op.Command.internalName = clusterName;
op.Command.serializeObjects = new List<SerializationInfo>();
foreach (var objectId in objectsInCluster)
{
var lfid = packingMethod.SerializationIndexFromObjectIdentifier(objectId);
op.Command.serializeObjects.Add(new SerializationInfo { serializationObject = objectId, serializationIndex = lfid });
op.ReferenceMap.AddMapping(clusterName, lfid, objectId);
clusterResult.ObjectToLocalID.Add(objectId, lfid);
}
var deps = ContentBuildInterface.GetPlayerDependenciesForObjects(objectsInCluster.ToArray(), buildParams.Target, buildParams.ScriptInfo, DependencyType.ValidReferences);
foreach (var d in deps)
{
if (d.m_GUID != builtInResourcesGUID)
{
var depCluster = clusterResult.ObjectToCluster[d].ToString();
op.ReferenceMap.AddMapping(depCluster, packingMethod.SerializationIndexFromObjectIdentifier(d), d);
}
}
op.UsageSet = usageSet;
writeData.FileToBundle.Add(clusterName, clusterName);
}
// Generates Content Scene Archive Files from Scene Input
foreach (var pair in dependencyData.SceneInfo)
{
#pragma warning disable CS0618 // Type or member is obsolete
var op = new SceneRawWriteOperation();
#pragma warning restore CS0618 // Type or member is obsolete
writeData.WriteOperations.Add(op);
op.ReferenceMap = new BuildReferenceMap();
op.Command = new WriteCommand();
op.Command.fileName = pair.Key.ToString();
op.Command.internalName = pair.Key.ToString();
op.Command.serializeObjects = new List<SerializationInfo>();
foreach (var d in pair.Value.m_ReferencedObjects)
{
if (d.m_GUID != builtInResourcesGUID)
{
var depCluster = clusterResult.ObjectToCluster[d].ToString();
op.ReferenceMap.AddMapping(depCluster, packingMethod.SerializationIndexFromObjectIdentifier(d), d);
}
}
op.UsageSet = usageSet;
op.Scene = pair.Value.scene;
writeData.FileToBundle.Add(pair.Key.ToString(), pair.Key.ToString());
}
return ReturnCode.Success;
}
[StructLayout(LayoutKind.Sequential)]
struct ObjectIdBufferData
{
public GUID guid;
public long lfid;
public FileType fileType;
public Hash128 pathHash;
}
internal static Hash128 ComputeClusterId(List<ObjectIdentifier> objIds)
{
//since ObjectIdentifer is not blittable, copy to a struct that is and compute the hash for the entire buffer
var seqObjectIdBuffer = new ObjectIdBufferData[objIds.Count];
for (int i = 0; i < seqObjectIdBuffer.Length; i++)
{
var pathHash = !string.IsNullOrEmpty(objIds[i].filePath) ? Hash128.Compute(objIds[i].filePath) : default;
seqObjectIdBuffer[i] = new ObjectIdBufferData { guid = objIds[i].guid, fileType = objIds[i].fileType, lfid = objIds[i].localIdentifierInFile, pathHash = pathHash };
}
return Hash128.Compute(seqObjectIdBuffer);
}
private static void ExtractAssets(Dictionary<ObjectIdentifier, HashSet<GUID>> objectToAssets, GUID asset, IEnumerable<ObjectIdentifier> objectIds)
{
foreach (var objectId in objectIds)
{
if (objectId.filePath.Equals(CommonStrings.UnityDefaultResourcePath, StringComparison.OrdinalIgnoreCase))
continue;
GetOrAdd(objectToAssets, objectId, out var assets);
assets.Add(asset);
}
}
}
}
#endif