AssetBundleInspectTab.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514
  1. using UnityEditor;
  2. using UnityEngine;
  3. using UnityEditor.IMGUI.Controls;
  4. using System.Collections.Generic;
  5. using System.IO;
  6. using System.Runtime.Serialization.Formatters.Binary;
  7. using System.Linq;
  8. namespace AssetBundleBrowser
  9. {
  10. [System.Serializable]
  11. internal class AssetBundleInspectTab
  12. {
  13. Rect m_Position;
  14. [SerializeField]
  15. private InspectTabData m_Data;
  16. private Dictionary<string, List<string> > m_BundleList;
  17. private InspectBundleTree m_BundleTreeView;
  18. [SerializeField]
  19. private TreeViewState m_BundleTreeState;
  20. internal Editor m_Editor = null;
  21. private SingleBundleInspector m_SingleInspector;
  22. /// <summary>
  23. /// Collection of loaded asset bundle records indexed by bundle name
  24. /// </summary>
  25. private Dictionary<string, AssetBundleRecord> m_loadedAssetBundles;
  26. /// <summary>
  27. /// Returns the record for a loaded asset bundle by name if it exists in our container.
  28. /// </summary>
  29. /// <returns>Asset bundle record instance if loaded, otherwise null.</returns>
  30. /// <param name="bundleName">Name of the loaded asset bundle, excluding the variant extension</param>
  31. private AssetBundleRecord GetLoadedBundleRecordByName(string bundleName)
  32. {
  33. if (string.IsNullOrEmpty(bundleName))
  34. {
  35. return null;
  36. }
  37. if (!m_loadedAssetBundles.ContainsKey(bundleName))
  38. {
  39. return null;
  40. }
  41. return m_loadedAssetBundles[bundleName];
  42. }
  43. internal AssetBundleInspectTab()
  44. {
  45. m_BundleList = new Dictionary<string, List<string>>();
  46. m_SingleInspector = new SingleBundleInspector();
  47. m_loadedAssetBundles = new Dictionary<string, AssetBundleRecord>();
  48. }
  49. internal void OnEnable(Rect pos)
  50. {
  51. m_Position = pos;
  52. if (m_Data == null)
  53. m_Data = new InspectTabData();
  54. //LoadData...
  55. var dataPath = System.IO.Path.GetFullPath(".");
  56. dataPath = dataPath.Replace("\\", "/");
  57. dataPath += "/Library/AssetBundleBrowserInspect.dat";
  58. if (File.Exists(dataPath))
  59. {
  60. BinaryFormatter bf = new BinaryFormatter();
  61. FileStream file = File.Open(dataPath, FileMode.Open);
  62. var data = bf.Deserialize(file) as InspectTabData;
  63. if (data != null)
  64. m_Data = data;
  65. file.Close();
  66. }
  67. if (m_BundleList == null)
  68. m_BundleList = new Dictionary<string, List<string>>();
  69. if (m_BundleTreeState == null)
  70. m_BundleTreeState = new TreeViewState();
  71. m_BundleTreeView = new InspectBundleTree(m_BundleTreeState, this);
  72. RefreshBundles();
  73. }
  74. internal void OnDisable()
  75. {
  76. ClearData();
  77. var dataPath = System.IO.Path.GetFullPath(".");
  78. dataPath = dataPath.Replace("\\", "/");
  79. dataPath += "/Library/AssetBundleBrowserInspect.dat";
  80. BinaryFormatter bf = new BinaryFormatter();
  81. FileStream file = File.Create(dataPath);
  82. bf.Serialize(file, m_Data);
  83. file.Close();
  84. }
  85. internal void OnGUI(Rect pos)
  86. {
  87. m_Position = pos;
  88. if (Application.isPlaying)
  89. {
  90. var style = new GUIStyle(GUI.skin.label);
  91. style.alignment = TextAnchor.MiddleCenter;
  92. style.wordWrap = true;
  93. GUI.Label(
  94. new Rect(m_Position.x + 1f, m_Position.y + 1f, m_Position.width - 2f, m_Position.height - 2f),
  95. new GUIContent("Inspector unavailable while in PLAY mode"),
  96. style);
  97. }
  98. else
  99. {
  100. OnGUIEditor();
  101. }
  102. }
  103. private void OnGUIEditor()
  104. {
  105. EditorGUILayout.Space();
  106. GUILayout.BeginHorizontal();
  107. if (GUILayout.Button("Add File", GUILayout.MaxWidth(75f)))
  108. {
  109. BrowseForFile();
  110. }
  111. if (GUILayout.Button("Add Folder", GUILayout.MaxWidth(75f)))
  112. {
  113. BrowseForFolder();
  114. }
  115. GUILayout.EndHorizontal();
  116. EditorGUILayout.Space();
  117. if (m_BundleList.Count > 0)
  118. {
  119. int halfWidth = (int)(m_Position.width / 2.0f);
  120. m_BundleTreeView.OnGUI(new Rect(m_Position.x, m_Position.y + 30, halfWidth, m_Position.height - 30));
  121. m_SingleInspector.OnGUI(new Rect(m_Position.x + halfWidth, m_Position.y + 30, halfWidth, m_Position.height - 30));
  122. }
  123. }
  124. internal void RemoveBundlePath(string pathToRemove)
  125. {
  126. UnloadBundle(pathToRemove);
  127. m_Data.RemovePath(pathToRemove);
  128. }
  129. internal void RemoveBundleFolder(string pathToRemove)
  130. {
  131. List<string> paths = null;
  132. if(m_BundleList.TryGetValue(pathToRemove, out paths))
  133. {
  134. foreach(var p in paths)
  135. {
  136. UnloadBundle(p);
  137. }
  138. }
  139. m_Data.RemoveFolder(pathToRemove);
  140. }
  141. private void BrowseForFile()
  142. {
  143. var newPath = EditorUtility.OpenFilePanelWithFilters("Bundle Folder", string.Empty, new string[] { });
  144. if (!string.IsNullOrEmpty(newPath))
  145. {
  146. var gamePath = System.IO.Path.GetFullPath(".");//TODO - FileUtil.GetProjectRelativePath??
  147. gamePath = gamePath.Replace("\\", "/");
  148. if (newPath.StartsWith(gamePath))
  149. newPath = newPath.Remove(0, gamePath.Length + 1);
  150. m_Data.AddPath(newPath);
  151. RefreshBundles();
  152. }
  153. }
  154. //TODO - this is largely copied from BuildTab, should maybe be shared code.
  155. private void BrowseForFolder(string folderPath = null)
  156. {
  157. folderPath = EditorUtility.OpenFolderPanel("Bundle Folder", string.Empty, string.Empty);
  158. if (!string.IsNullOrEmpty(folderPath))
  159. {
  160. var gamePath = System.IO.Path.GetFullPath(".");//TODO - FileUtil.GetProjectRelativePath??
  161. gamePath = gamePath.Replace("\\", "/");
  162. if (folderPath.StartsWith(gamePath))
  163. folderPath = folderPath.Remove(0, gamePath.Length + 1);
  164. AddBundleFolder(folderPath);
  165. RefreshBundles();
  166. }
  167. }
  168. internal void AddBundleFolder(string folderPath)
  169. {
  170. m_Data.AddFolder(folderPath);
  171. }
  172. private void ClearData()
  173. {
  174. m_SingleInspector.SetBundle(null);
  175. if (null != m_loadedAssetBundles)
  176. {
  177. List<AssetBundleRecord> records = new List<AssetBundleRecord>(m_loadedAssetBundles.Values);
  178. foreach (AssetBundleRecord record in records)
  179. {
  180. record.bundle.Unload(true);
  181. }
  182. m_loadedAssetBundles.Clear();
  183. }
  184. }
  185. internal void RefreshBundles()
  186. {
  187. ClearData();
  188. if (m_Data.BundlePaths == null)
  189. return;
  190. //find assets
  191. if (m_BundleList == null)
  192. m_BundleList = new Dictionary<string, List<string>>();
  193. m_BundleList.Clear();
  194. var pathsToRemove = new List<string>();
  195. foreach(var filePath in m_Data.BundlePaths)
  196. {
  197. if(File.Exists(filePath))
  198. {
  199. AddBundleToList(string.Empty, filePath);
  200. }
  201. else
  202. {
  203. Debug.Log("Expected bundle not found: " + filePath);
  204. pathsToRemove.Add(filePath);
  205. }
  206. }
  207. foreach(var path in pathsToRemove)
  208. {
  209. m_Data.RemovePath(path);
  210. }
  211. pathsToRemove.Clear();
  212. foreach(var folder in m_Data.BundleFolders)
  213. {
  214. if(Directory.Exists(folder.path))
  215. {
  216. AddFilePathToList(folder.path, folder.path);
  217. }
  218. else
  219. {
  220. Debug.Log("Expected folder not found: " + folder);
  221. pathsToRemove.Add(folder.path);
  222. }
  223. }
  224. foreach (var path in pathsToRemove)
  225. {
  226. m_Data.RemoveFolder(path);
  227. }
  228. m_BundleTreeView.Reload();
  229. }
  230. private void AddBundleToList(string parent, string bundlePath)
  231. {
  232. List<string> bundles = null;
  233. m_BundleList.TryGetValue(parent, out bundles);
  234. if(bundles == null)
  235. {
  236. bundles = new List<string>();
  237. m_BundleList.Add(parent, bundles);
  238. }
  239. bundles.Add(bundlePath);
  240. }
  241. private void AddFilePathToList(string rootPath, string path)
  242. {
  243. var notAllowedExtensions = new string[] { ".meta", ".manifest", ".dll", ".cs", ".exe", ".js" };
  244. foreach (var file in Directory.GetFiles(path))
  245. {
  246. var ext = Path.GetExtension(file);
  247. if(!notAllowedExtensions.Contains(ext))
  248. {
  249. var f = file.Replace('\\', '/');
  250. if (File.Exists(file) && !m_Data.FolderIgnoresFile(rootPath, f))
  251. {
  252. AddBundleToList(rootPath, f);
  253. }
  254. }
  255. }
  256. foreach (var dir in Directory.GetDirectories(path))
  257. {
  258. AddFilePathToList(rootPath, dir);
  259. }
  260. }
  261. internal Dictionary<string, List<string>> BundleList
  262. { get { return m_BundleList; } }
  263. internal void SetBundleItem(IList<InspectTreeItem> selected)
  264. {
  265. //m_SelectedBundleTreeItems = selected;
  266. if (selected == null || selected.Count == 0 || selected[0] == null)
  267. {
  268. m_SingleInspector.SetBundle(null);
  269. }
  270. else if(selected.Count == 1)
  271. {
  272. AssetBundle bundle = LoadBundle(selected[0].bundlePath);
  273. m_SingleInspector.SetBundle(bundle, selected[0].bundlePath, m_Data, this);
  274. }
  275. else
  276. {
  277. m_SingleInspector.SetBundle(null);
  278. //perhaps there should be a way to set a message in the inspector, to tell it...
  279. //var style = GUI.skin.label;
  280. //style.alignment = TextAnchor.MiddleCenter;
  281. //style.wordWrap = true;
  282. //GUI.Label(
  283. // inspectorRect,
  284. // new GUIContent("Multi-select inspection not supported"),
  285. // style);
  286. }
  287. }
  288. [System.Serializable]
  289. internal class InspectTabData
  290. {
  291. [SerializeField]
  292. private List<string> m_BundlePaths = new List<string>();
  293. [SerializeField]
  294. private List<BundleFolderData> m_BundleFolders = new List<BundleFolderData>();
  295. internal IList<string> BundlePaths { get { return m_BundlePaths.AsReadOnly(); } }
  296. internal IList<BundleFolderData> BundleFolders { get { return m_BundleFolders.AsReadOnly(); } }
  297. internal void AddPath(string newPath)
  298. {
  299. if (!m_BundlePaths.Contains(newPath))
  300. {
  301. var possibleFolderData = FolderDataContainingFilePath(newPath);
  302. if(possibleFolderData == null)
  303. {
  304. m_BundlePaths.Add(newPath);
  305. }
  306. else
  307. {
  308. possibleFolderData.ignoredFiles.Remove(newPath);
  309. }
  310. }
  311. }
  312. internal void AddFolder(string newPath)
  313. {
  314. if (!BundleFolderContains(newPath))
  315. m_BundleFolders.Add(new BundleFolderData(newPath));
  316. }
  317. internal void RemovePath(string pathToRemove)
  318. {
  319. m_BundlePaths.Remove(pathToRemove);
  320. }
  321. internal void RemoveFolder(string pathToRemove)
  322. {
  323. m_BundleFolders.Remove(BundleFolders.FirstOrDefault(bfd => bfd.path == pathToRemove));
  324. }
  325. internal bool FolderIgnoresFile(string folderPath, string filePath)
  326. {
  327. if (BundleFolders == null)
  328. return false;
  329. var bundleFolderData = BundleFolders.FirstOrDefault(bfd => bfd.path == folderPath);
  330. return bundleFolderData != null && bundleFolderData.ignoredFiles.Contains(filePath);
  331. }
  332. internal BundleFolderData FolderDataContainingFilePath(string filePath)
  333. {
  334. foreach (var bundleFolderData in BundleFolders)
  335. {
  336. if (Path.GetFullPath(filePath).StartsWith(Path.GetFullPath(bundleFolderData.path)))
  337. {
  338. return bundleFolderData;
  339. }
  340. }
  341. return null;
  342. }
  343. private bool BundleFolderContains(string folderPath)
  344. {
  345. foreach(var bundleFolderData in BundleFolders)
  346. {
  347. if(Path.GetFullPath(bundleFolderData.path) == Path.GetFullPath(folderPath))
  348. {
  349. return true;
  350. }
  351. }
  352. return false;
  353. }
  354. [System.Serializable]
  355. internal class BundleFolderData
  356. {
  357. [SerializeField]
  358. internal string path;
  359. [SerializeField]
  360. private List<string> m_ignoredFiles;
  361. internal List<string> ignoredFiles
  362. {
  363. get
  364. {
  365. if (m_ignoredFiles == null)
  366. m_ignoredFiles = new List<string>();
  367. return m_ignoredFiles;
  368. }
  369. }
  370. internal BundleFolderData(string p)
  371. {
  372. path = p;
  373. }
  374. }
  375. }
  376. /// <summary>
  377. /// Returns the bundle at the specified path, loading it if necessary.
  378. /// Unloads previously loaded bundles if necessary when dealing with variants.
  379. /// </summary>
  380. /// <returns>Returns the loaded bundle, null if it could not be loaded.</returns>
  381. /// <param name="path">Path of bundle to get</param>
  382. private AssetBundle LoadBundle(string path)
  383. {
  384. if (string.IsNullOrEmpty(path))
  385. {
  386. return null;
  387. }
  388. string extension = Path.GetExtension(path);
  389. string bundleName = path.Substring(0, path.Length - extension.Length);
  390. // Check if we have a record for this bundle
  391. AssetBundleRecord record = GetLoadedBundleRecordByName(bundleName);
  392. AssetBundle bundle = null;
  393. if (null != record)
  394. {
  395. // Unload existing bundle if variant names differ, otherwise use existing bundle
  396. if (!record.path.Equals(path))
  397. {
  398. UnloadBundle(bundleName);
  399. }
  400. else
  401. {
  402. bundle = record.bundle;
  403. }
  404. }
  405. if (null == bundle)
  406. {
  407. // Load the bundle
  408. bundle = AssetBundle.LoadFromFile(path);
  409. if (null == bundle)
  410. {
  411. return null;
  412. }
  413. m_loadedAssetBundles[bundleName] = new AssetBundleRecord(path, bundle);
  414. // Load the bundle's assets
  415. string[] assetNames = bundle.GetAllAssetNames();
  416. foreach (string name in assetNames)
  417. {
  418. bundle.LoadAsset(name);
  419. }
  420. }
  421. return bundle;
  422. }
  423. /// <summary>
  424. /// Unloads the bundle with the given name.
  425. /// </summary>
  426. /// <param name="bundleName">Name of the bundle to unload without variant extension</param>
  427. private void UnloadBundle(string bundleName)
  428. {
  429. AssetBundleRecord record = this.GetLoadedBundleRecordByName(bundleName);
  430. if (null == record)
  431. {
  432. return;
  433. }
  434. record.bundle.Unload(true);
  435. m_loadedAssetBundles.Remove(bundleName);
  436. }
  437. }
  438. }