Last active
October 6, 2023 20:55
-
-
Save AndyMUnity/2338dd468315c26e295e8014545afc22 to your computer and use it in GitHub Desktop.
BuildWithSBPReport.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.IO; | |
using UnityEditor; | |
using UnityEditor.Build.Content; | |
using UnityEditor.Build.Pipeline; | |
using UnityEditor.Build.Pipeline.Interfaces; | |
using UnityEditor.Build.Pipeline.Utilities; | |
using UnityEngine; | |
public class BuildWithSBPReport | |
{ | |
[MenuItem(("Assets/Build AssetBundles"))] | |
public static void Build() | |
{ | |
BuildAssetBundles( "Bundles", EditorUserBuildSettings.activeBuildTarget, EditorUserBuildSettings.selectedBuildTargetGroup ); | |
} | |
private static void BuildAssetBundles(string outputPath, BuildTarget buildTarget, BuildTargetGroup buildGroup) | |
{ | |
var buildContent = new BundleBuildContent(ContentBuildInterface.GenerateAssetBundleBuilds()); | |
var buildParams = new BundleBuildParameters( buildTarget, buildGroup, outputPath ); | |
// needs to use the Cache to get the built files from cache post build. To Generate hashes and file sizes. | |
buildParams.UseCache = true; | |
ReturnCode exitCode = ContentPipeline.BuildAssetBundles(buildParams, buildContent, out var results); | |
if( exitCode == ReturnCode.Success ) | |
WriteReport( results, buildContent ); | |
else | |
Debug.LogError( "Could not build AssetBundles : " + exitCode ); | |
} | |
[Serializable] | |
private class BasicBundleObject | |
{ | |
public string type; | |
public long fileID; | |
public ulong size; | |
public string hash; | |
} | |
[Serializable] | |
private class BundleSubObject | |
{ | |
public string path; | |
public string type; | |
public long fileID; | |
public ulong size; | |
public string hash; | |
} | |
[Serializable] | |
private class BundleObject | |
{ | |
public string path; | |
public string type; | |
public string hash; | |
public string guid; | |
public long fileID; | |
public ulong sizeSelf; | |
public ulong sizeTotal; | |
public List<BundleSubObject> subObjects = new List<BundleSubObject>(); | |
[NonSerialized] public FileType fileType; | |
[NonSerialized] public bool isMainObject = true; | |
private Type m_RuntimeType; | |
public Type RuntimeType | |
{ | |
get { return m_RuntimeType; } | |
set | |
{ | |
m_RuntimeType = value; | |
type = m_RuntimeType.ToString(); | |
} | |
} | |
private GUID m_Guid; | |
public GUID Guid | |
{ | |
get { return m_Guid; } | |
set { m_Guid = value; | |
guid = m_Guid.ToString(); | |
} | |
} | |
public void GetAssetObjectData() | |
{ | |
string assetPath = AssetDatabase.GUIDToAssetPath( guid ); | |
if( string.IsNullOrEmpty( assetPath ) ) | |
{ | |
Debug.LogError( "Not a valid Project Asset." ); | |
return; | |
} | |
UnityEngine.Object[] objs = AssetDatabase.LoadAllAssetsAtPath( assetPath ); | |
UnityEngine.Object correct = null; | |
foreach( UnityEngine.Object o in objs ) | |
{ | |
if( !AssetDatabase.TryGetGUIDAndLocalFileIdentifier( o, out string g, out long localId ) || guid != g || fileID != localId ) | |
continue; | |
correct = o; | |
break; | |
} | |
if( correct != null ) | |
{ | |
if( AssetDatabase.IsMainAsset( correct ) ) | |
{ | |
path = assetPath; | |
} | |
else | |
{ | |
path = assetPath + "[" + correct.name + "]"; | |
isMainObject = false; | |
} | |
RuntimeType = correct.GetType(); | |
} | |
else | |
{ | |
Debug.LogError( "Asset Error, unknown error" ); | |
} | |
} | |
} | |
[Serializable] | |
private class AssetBundleSizeSummary | |
{ | |
public ulong totalUncompressedSize = 0; | |
public ulong SerialisedHeadersSize = 0; | |
public ulong bundleMetaSize = 0; | |
public ulong MonoScriptSize = 0; | |
public ulong sceneSize = 0; | |
public ulong totalObjectsSize = 0; | |
public ulong explicitObjectsSize = 0; | |
public ulong implicitObjectsSize = 0; | |
public ulong otherObjectsSize = 0; | |
// public ulong textures; | |
// public ulong materials; | |
// public ulong shaders; | |
} | |
[Serializable] | |
private class AssetBundleReportData | |
{ | |
public string Name; | |
public bool isSceneBundle = false; | |
public List<BasicBundleObject> bundleMetaObjects = new List<BasicBundleObject>(); | |
public List<BundleObject> explicitObjects = new List<BundleObject>(); | |
public List<BundleObject> implicitObjects = new List<BundleObject>(); | |
public List<BundleObject> otherObjects = new List<BundleObject>(); | |
public List<BasicBundleObject> sceneObjects = new List<BasicBundleObject>(); | |
public AssetBundleSizeSummary sizeSummary = new AssetBundleSizeSummary(); | |
public void Summarize() | |
{ | |
foreach( BasicBundleObject o in bundleMetaObjects ) | |
sizeSummary.bundleMetaSize += o.size; | |
foreach( BundleObject o in explicitObjects ) | |
{ | |
o.sizeTotal += o.sizeSelf; | |
foreach( BundleSubObject subObject in o.subObjects ) | |
o.sizeTotal += subObject.size; | |
sizeSummary.explicitObjectsSize += o.sizeTotal; | |
} | |
foreach( BundleObject o in implicitObjects ) | |
{ | |
o.sizeTotal += o.sizeSelf; | |
foreach( BundleSubObject subObject in o.subObjects ) | |
o.sizeTotal += subObject.size; | |
sizeSummary.implicitObjectsSize += o.sizeTotal; | |
} | |
foreach( BundleObject o in otherObjects ) | |
{ | |
o.sizeTotal += o.sizeSelf; | |
foreach( BundleSubObject subObject in o.subObjects ) | |
o.sizeTotal += subObject.size; | |
sizeSummary.otherObjectsSize += o.sizeTotal; | |
} | |
foreach( BasicBundleObject o in sceneObjects ) | |
sizeSummary.sceneSize += o.size; | |
sizeSummary.totalObjectsSize += sizeSummary.sceneSize | |
+ sizeSummary.otherObjectsSize | |
+ sizeSummary.implicitObjectsSize | |
+ sizeSummary.explicitObjectsSize; | |
} | |
public void OrganiseSubObjects() | |
{ | |
List<BundleObject> mainExplicit = new List<BundleObject>(); | |
foreach( BundleObject o in explicitObjects ) | |
{ | |
if( o.isMainObject ) | |
mainExplicit.Add( o ); | |
} | |
for( int i = explicitObjects.Count - 1; i >= 0; --i ) | |
{ | |
if( explicitObjects[i].isMainObject == false ) | |
{ | |
BundleObject main = mainExplicit.Find( o => o.guid == explicitObjects[i].guid ); | |
if( main != null ) | |
{ | |
main.subObjects.Add( new BundleSubObject | |
{ | |
// TODO replace main[sub] to just sub | |
path = explicitObjects[i].path, | |
type = explicitObjects[i].type, | |
fileID = explicitObjects[i].fileID, | |
size = explicitObjects[i].sizeSelf, | |
hash = explicitObjects[i].hash | |
} ); | |
explicitObjects.RemoveAt( i ); | |
} | |
} | |
} | |
foreach( BundleObject o in explicitObjects ) | |
o.subObjects.Sort( ( s1, s2 ) => string.Compare( s1.path, s2.path, StringComparison.CurrentCulture ) ); | |
List<BundleObject> mainImplicit = new List<BundleObject>(); | |
foreach( BundleObject o in implicitObjects ) | |
{ | |
if( o.isMainObject ) | |
mainImplicit.Add( o ); | |
} | |
for( int i = implicitObjects.Count - 1; i >= 0; --i ) | |
{ | |
if( implicitObjects[i].isMainObject == false ) | |
{ | |
BundleObject main = mainImplicit.Find( o => o.guid == implicitObjects[i].guid ); | |
if( main != null ) | |
{ | |
main.subObjects.Add( new BundleSubObject | |
{ | |
path = implicitObjects[i].path, | |
type = implicitObjects[i].type, | |
fileID = implicitObjects[i].fileID, | |
size = implicitObjects[i].sizeSelf, | |
hash = implicitObjects[i].hash | |
} ); | |
implicitObjects.RemoveAt( i ); | |
} | |
} | |
} | |
foreach( BundleObject o in implicitObjects ) | |
o.subObjects.Sort( ( s1, s2 ) => string.Compare( s1.path, s2.path, StringComparison.CurrentCulture ) ); | |
} | |
} | |
static void WriteReport( IBundleBuildResults results, IBundleBuildContent contents ) | |
{ | |
List<AssetBundleReportData> Bundles = new List<AssetBundleReportData>(); | |
Dictionary<GUID, string> GuidToBundleName = new Dictionary<GUID, string>(); | |
foreach( var content in contents.BundleLayout ) | |
{ | |
string bundleName = content.Key; | |
foreach( GUID guid in content.Value ) | |
GuidToBundleName.Add( guid, bundleName ); | |
} | |
foreach( var res in results.WriteResults ) | |
{ | |
AssetBundleReportData bundle = new AssetBundleReportData {Name = res.Key}; | |
if( bundle.Name.EndsWith( "sharedAssets" ) ) | |
bundle.isSceneBundle = true; | |
foreach( ResourceFile resourceFile in res.Value.resourceFiles ) | |
{ | |
FileInfo fi = new FileInfo( resourceFile.fileName ); | |
if (!fi.Exists) | |
continue; | |
bundle.sizeSummary.totalUncompressedSize += (ulong)fi.Length; | |
} | |
HashSet<string> serialisedFilesSet = new HashSet<string>(); | |
foreach( var ob in res.Value.serializedObjects ) | |
{ | |
BundleObject f = new BundleObject | |
{ | |
Guid = ob.serializedObject.guid, | |
fileID = ob.serializedObject.localIdentifierInFile, | |
sizeSelf = ob.rawData.size + ob.header.size, | |
fileType = ob.serializedObject.fileType, | |
path = ob.serializedObject.filePath | |
}; | |
var data = new byte[ob.header.size + ob.rawData.size]; | |
var read = 0; | |
if( string.IsNullOrEmpty( ob.header.fileName ) == false ) | |
{ | |
foreach( ResourceFile resourceFile in res.Value.resourceFiles ) | |
{ | |
if( ob.header.fileName != resourceFile.fileAlias || !resourceFile.fileName.StartsWith("Library/BuildCache")) | |
continue; | |
using (FileStream fs = new FileStream(resourceFile.fileName, FileMode.Open)) | |
{ | |
int readSize = (int)ob.header.size; | |
fs.Position = (int)ob.header.offset; | |
do read += fs.Read(data, read, readSize-read); | |
while (read != readSize && fs.Position < fs.Length); | |
} | |
break; | |
} | |
} | |
read = 0; | |
if( string.IsNullOrEmpty( ob.rawData.fileName ) == false ) | |
{ | |
foreach( ResourceFile resourceFile in res.Value.resourceFiles ) | |
{ | |
if( ob.rawData.fileName != resourceFile.fileAlias || !resourceFile.fileName.StartsWith("Library/BuildCache")) | |
continue; | |
using (FileStream fs = new FileStream(resourceFile.fileName, FileMode.Open)) | |
{ | |
int readSize = (int)ob.rawData.size; | |
fs.Position = (int)ob.rawData.offset; | |
do read += fs.Read(data, read + (int)ob.header.size, readSize-read); | |
while (read != readSize && fs.Position < fs.Length); | |
} | |
break; | |
} | |
} | |
f.hash = HashingMethods.Calculate( data ).ToString(); | |
if( !serialisedFilesSet.Contains( ob.header.fileName ) ) | |
{ | |
bundle.sizeSummary.SerialisedHeadersSize += ob.header.offset; | |
serialisedFilesSet.Add( ob.header.fileName ); | |
} | |
if( f.fileType == FileType.MetaAssetType || f.fileType == FileType.SerializedAssetType ) | |
{ | |
// Asset in project, check if the guid is in the layout | |
if( GuidToBundleName.TryGetValue( f.Guid, out string val ) ) | |
{ | |
bundle.Name = val; | |
f.GetAssetObjectData(); | |
bundle.explicitObjects.Add( f ); | |
} | |
else | |
{ | |
// not in layout, so implicit | |
f.GetAssetObjectData(); | |
if( f.RuntimeType == typeof(MonoScript) ) | |
bundle.sizeSummary.MonoScriptSize += f.sizeSelf; | |
else | |
bundle.implicitObjects.Add( f ); | |
} | |
} | |
else | |
{ | |
if( f.RuntimeType != null ) | |
{ | |
bundle.otherObjects.Add( f ); | |
} | |
else if( f.path == "temp:/AssetBundle" || f.path == "temp:/PreloadData" ) | |
{ | |
bundle.bundleMetaObjects.Add( new BasicBundleObject | |
{ | |
type = f.path.Substring( 6 ), | |
fileID = f.fileID, | |
size = f.sizeSelf, | |
hash = f.hash | |
} ); | |
} | |
else if( bundle.isSceneBundle && f.path.StartsWith( "temp:/" ) ) | |
{ | |
bundle.sceneObjects.Add( new BasicBundleObject | |
{ | |
type = f.path.Substring( 6 ), | |
fileID = f.fileID, | |
size = f.sizeSelf, | |
hash = f.hash | |
} ); | |
} | |
else if( f.path.StartsWith( "resources/" ) ) | |
{ | |
bundle.otherObjects.Add( f ); | |
} | |
else | |
{ | |
Debug.LogWarning( "Unknown AssetBundle Object type " + f.guid ); | |
bundle.otherObjects.Add( f ); | |
} | |
} | |
} | |
Bundles.Add( bundle ); | |
} | |
foreach( AssetBundleReportData bundle in Bundles ) | |
{ | |
bundle.OrganiseSubObjects(); | |
bundle.Summarize(); | |
} | |
foreach( AssetBundleReportData bundle in Bundles ) | |
{ | |
Debug.Log(JsonUtility.ToJson( bundle, true )); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment