Skip to content

Instantly share code, notes, and snippets.

@AndyMUnity
Last active October 6, 2023 20:55
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save AndyMUnity/2338dd468315c26e295e8014545afc22 to your computer and use it in GitHub Desktop.
Save AndyMUnity/2338dd468315c26e295e8014545afc22 to your computer and use it in GitHub Desktop.
BuildWithSBPReport.cs
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