LoadModule.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.IO;
  5. using System.Text;
  6. //using LitJson;
  7. using UnityEngine;
  8. using Object = UnityEngine.Object;
  9. public delegate void LoadedCallback(AssetRequest assetRequest);
  10. public sealed class LoadModule : ModuleBase
  11. {
  12. #region Instance
  13. private static LoadModule instance;
  14. public static LoadModule Instance
  15. {
  16. get
  17. {
  18. if (instance == null)
  19. {
  20. instance = new LoadModule();
  21. }
  22. return instance;
  23. }
  24. }
  25. #endregion
  26. public static readonly string ManifestAsset = "Assets/Manifest.asset";
  27. public static readonly string Extension = ".unity3d";
  28. public static bool runtimeMode = false;
  29. public static Func<string, Type, Object> loadDelegate = null;
  30. private const string TAG = "[Assets]";
  31. #region API
  32. public override void Init()
  33. {
  34. base.Init();
  35. runtimeMode = ConfigDate.Instancecfg.UseAssetBundle;
  36. }
  37. /// <summary>
  38. /// 读取所有资源路径
  39. /// </summary>
  40. /// <returns></returns>
  41. public static string[] GetAllAssetPaths()
  42. {
  43. var assets = new List<string>();
  44. assets.AddRange(_assetToBundles.Keys);
  45. return assets.ToArray();
  46. }
  47. public static string basePath { get; set; }
  48. public static string updatePath { get; set; }
  49. public static void AddSearchPath(string path)
  50. {
  51. searchPaths.Add(path);
  52. }
  53. public static ManifestRequest Initialize()
  54. {
  55. //改成不依赖MonoBehavior
  56. //var instance = FindObjectOfType<Assets>();
  57. //if(instance == null)
  58. //{
  59. // instance = new GameObject("Assets").AddComponent<Assets>();
  60. // DontDestroyOnLoad(instance.gameObject);
  61. //}
  62. if (string.IsNullOrEmpty(basePath))
  63. basePath = Application.streamingAssetsPath + Path.DirectorySeparatorChar;
  64. if (string.IsNullOrEmpty(updatePath))
  65. updatePath = Application.persistentDataPath + Path.DirectorySeparatorChar;
  66. Clear();
  67. Debug.Log(string.Format(
  68. "Initialize with: runtimeMode={0}\nbasePath:{1}\nupdatePath={2}",
  69. runtimeMode, basePath, updatePath));
  70. var request = new ManifestRequest { name = ManifestAsset };
  71. AddAssetRequest(request);
  72. return request;
  73. }
  74. public static void Clear()
  75. {
  76. searchPaths.Clear();
  77. _activeVariants.Clear();
  78. _assetToBundles.Clear();
  79. _bundleToDependencies.Clear();
  80. }
  81. private static SceneAssetRequest _runningScene;
  82. //public static SceneAssetRequest LoadSceneAsync(string path, bool additive, LoadedCallback loadedCallback = null)
  83. //{
  84. // if (string.IsNullOrEmpty(path))
  85. // {
  86. // Debug.LogError("invalid path");
  87. // return null;
  88. // }
  89. // path = GetExistPath(path);
  90. // var asset = new SceneAssetRequestAsync(path, additive);
  91. // if (!additive)
  92. // {
  93. // if (_runningScene != null)
  94. // {
  95. // _runningScene.Release(); ;
  96. // _runningScene = null;
  97. // }
  98. // _runningScene = asset;
  99. // }
  100. // if (loadedCallback != null)
  101. // asset.completed += loadedCallback;
  102. // asset.Load();
  103. // asset.AddReferance();
  104. // _scenes.Add(asset);
  105. // Debug.Log(string.Format("LoadScene:{0}", path));
  106. // return asset;
  107. //}
  108. //public static void UnloadScene(SceneAssetRequest scene)
  109. //{
  110. // scene.Release();
  111. //}
  112. public static AssetRequest LoadAssetAsync(string path, Type type, LoadedCallback loadedCallback = null)
  113. {
  114. return LoadAsset(path, type, true, loadedCallback);
  115. }
  116. public static AssetRequest LoadAsset(string path, Type type)
  117. {
  118. return LoadAsset(path, type, false);
  119. }
  120. public static void UnloadAsset(AssetRequest asset)
  121. {
  122. asset.Release();
  123. }
  124. #region 业务
  125. #if UNITY_EDITOR
  126. public const string BULLET_PATH_PREFIX = "Assets/Data/bullet/";
  127. public const string MODEL_PATH_PREFIX = "Assets/Data/character/";
  128. //private static string BundlePathPrefix = "Assets/Data/";
  129. private static string UIPathPrefix = "Assets/Rec/ui/panel/";
  130. // private static string JsonPathPrefix = "Assets/Scripts/DataTable/json/";
  131. private static StringBuilder stringBuilder = new StringBuilder();
  132. #else
  133. public const string BULLET_PATH_PREFIX = "Assets/Data/bullet/";
  134. public const string MODEL_PATH_PREFIX = "Assets/Data/character/";
  135. private static string BundlePathPrefix = "Assets/Data/";
  136. private static string UIPathPrefix = Application.streamingAssetsPath+"/";
  137. private static string JsonPathPrefix = "Assets/Scripts/DataTable/json/";
  138. private static StringBuilder stringBuilder = new StringBuilder();
  139. #endif
  140. public static AssetRequest LoadModel(string assetName, LoadedCallback loadedCallback = null)
  141. {
  142. Type type = typeof(GameObject);
  143. stringBuilder.Clear();
  144. stringBuilder.Append(MODEL_PATH_PREFIX);
  145. stringBuilder.Append(assetName);
  146. stringBuilder.Append(GetAssetPostfix(type));
  147. return LoadAssetAsync(stringBuilder.ToString(), type, loadedCallback);
  148. }
  149. public static AssetRequest LoadUI(string path, LoadedCallback loadedCallback = null)
  150. {
  151. Type type = typeof(GameObject);
  152. stringBuilder.Clear();
  153. stringBuilder.Append(UIPathPrefix);
  154. stringBuilder.Append(path);
  155. stringBuilder.Append(GetAssetPostfix(type));
  156. return LoadAssetAsync(stringBuilder.ToString(), type, loadedCallback);
  157. }
  158. //public static JsonData LoadJson(string path)
  159. //{
  160. // stringBuilder.Clear();
  161. // stringBuilder.Append(JsonPathPrefix);
  162. // stringBuilder.Append(path);
  163. // path = stringBuilder.ToString();
  164. // //Debug.Log("LoadJson: " + path);
  165. // if (FileHelper.IsFileExist(path))
  166. // {
  167. // using (StreamReader streamReader = new StreamReader(path))
  168. // {
  169. // string jsonText = streamReader.ReadToEnd();
  170. // JsonData jsonData = JsonMapper.ToObject(jsonText);
  171. // return jsonData;
  172. // }
  173. // }
  174. // else
  175. // {
  176. // GameLog.LogError(path + "不存在!");
  177. // return null;
  178. // }
  179. //}
  180. #endregion
  181. #endregion
  182. #region Private
  183. /// <summary>
  184. /// Manifest加载结束回调
  185. /// </summary>
  186. /// <param name="manifest"></param>
  187. internal static void OnManifestLoaded(Manifest manifest)
  188. {
  189. _activeVariants.AddRange(manifest.activeVariants);
  190. AssetRef[] assets = manifest.assets;
  191. string[] dirs = manifest.dirs;
  192. BundleRef[] bundles = manifest.bundles;
  193. foreach (var item in bundles)
  194. _bundleToDependencies[item.name] = Array.ConvertAll(item.deps, id => bundles[id].name);
  195. foreach (AssetRef item in assets)
  196. {
  197. string path = string.Format("{0}/{1}", dirs[item.dir], item.name);
  198. if (item.bundle >= 0 && item.bundle < bundles.Length)
  199. {
  200. // 初始化路径映射表:路径==》bundle名字
  201. _assetToBundles[path] = bundles[item.bundle].name;
  202. }
  203. else
  204. {
  205. Debug.LogError(string.Format("{0} bundle {1} not exist.", path, item.bundle));
  206. }
  207. }
  208. }
  209. /// <summary>
  210. /// 每帧最大加载bundle数
  211. /// </summary>
  212. private static List<AssetRequest> _unusedAssets = new List<AssetRequest>();
  213. private static List<AssetRequest> _loadingAssets = new List<AssetRequest>();
  214. private static List<SceneAssetRequest> _scenes = new List<SceneAssetRequest>();
  215. private static Dictionary<string, AssetRequest> _assets = new Dictionary<string, AssetRequest>();
  216. // update 驱动
  217. public override void Update(float dt)
  218. {
  219. UpdateAssets();
  220. UpdateBundles();
  221. }
  222. /// <summary>
  223. /// 更新加载请求
  224. /// </summary>
  225. private static void UpdateAssets()
  226. {
  227. for (int i = 0; i < _loadingAssets.Count; i++)
  228. {
  229. var request = _loadingAssets[i];
  230. if (request.Update())
  231. continue;
  232. _loadingAssets.RemoveAt(i);
  233. --i;
  234. }
  235. foreach (var item in _assets)
  236. {
  237. if (item.Value.isDone && item.Value.IsUnused())
  238. {
  239. _unusedAssets.Add(item.Value);
  240. }
  241. }
  242. //TODO 惰性GC
  243. //之所以叫惰性GC,是因为和上一个版本相比,上一个版本是每帧都会检查和清理未使用的资源,
  244. //这个版本底层只会在切换场景或者主动调用Assets.RemoveUnusedAssets();
  245. //的时候才会清理未使用的资源,这样用户可以按需调整资源回收的频率,在没有内存压力的时候,不回收可以获得更好的性能。
  246. if (_unusedAssets.Count > 0)
  247. {
  248. for (int i = 0; i < _unusedAssets.Count; ++i)
  249. {
  250. AssetRequest request = _unusedAssets[i];
  251. Debug.Log(string.Format("UnloadAsset:{0}", request.name));
  252. _assets.Remove(request.name);
  253. request.Unload();
  254. }
  255. _unusedAssets.Clear();
  256. }
  257. for (var i = 0; i < _scenes.Count; ++i)
  258. {
  259. var request = _scenes[i];
  260. if (request.Update() || !request.IsUnused())
  261. continue;
  262. _scenes.RemoveAt(i);
  263. Debug.Log(string.Format("UnloadScene:{0}", request.name));
  264. request.Unload();
  265. --i;
  266. }
  267. }
  268. private static void UpdateBundles()
  269. {
  270. int max = MAX_BUNDLES_PERFRAME;
  271. // 正在加载的bundle数量小于每帧可加载数量
  272. if (_toloadBundles.Count > 0 && max > 0 && _loadingBundles.Count < max)
  273. // 把能加载的bundle加进加载队列中
  274. for (int i = 0; i < Math.Min(max - _loadingBundles.Count, _toloadBundles.Count); ++i)
  275. {
  276. BundleRequest item = _toloadBundles[i];
  277. if (item.LoadState == AssetLoadState.Init)
  278. {
  279. item.Load();
  280. _loadingBundles.Add(item);
  281. _toloadBundles.RemoveAt(i);
  282. --i;
  283. }
  284. }
  285. // 加载中bundleUpdate,加载完就移除
  286. for (var i = 0; i < _loadingBundles.Count; i++)
  287. {
  288. var item = _loadingBundles[i];
  289. if (item.Update())
  290. continue;
  291. _loadingBundles.RemoveAt(i);
  292. --i;
  293. }
  294. // 加载完的bundle,并且没有引用,就加进无用bundle列表
  295. foreach (var item in _bundles)
  296. {
  297. if (item.Value.isDone && item.Value.IsUnused())
  298. {
  299. _unusedBundles.Add(item.Value);
  300. }
  301. }
  302. if (_unusedBundles.Count <= 0)
  303. return;
  304. // 清理无用bundle列表
  305. for (int i = 0; i < _unusedBundles.Count; i++)
  306. {
  307. BundleRequest item = _unusedBundles[i];
  308. if (item.isDone)
  309. {
  310. item.Unload();
  311. _bundles.Remove(item.name);
  312. Debug.Log("UnloadBundle: " + item.name);
  313. }
  314. }
  315. _unusedBundles.Clear();
  316. }
  317. private static void AddAssetRequest(AssetRequest request)
  318. {
  319. _assets.Add(request.name, request);
  320. _loadingAssets.Add(request);
  321. }
  322. private static AssetRequest LoadAsset(string path, Type type, bool async, LoadedCallback loadedCallback = null)
  323. {
  324. if (string.IsNullOrEmpty(path))
  325. {
  326. Debug.LogError("empty path!");
  327. return null;
  328. }
  329. path = GetExistPath(path);
  330. AssetRequest request;
  331. if (_assets.TryGetValue(path, out request))
  332. {
  333. request.AddReferance();
  334. _loadingAssets.Add(request);
  335. if (loadedCallback != null)
  336. {
  337. loadedCallback(request);
  338. }
  339. return request;
  340. }
  341. string assetBundleName;
  342. if (GetAssetBundleName(path, out assetBundleName))
  343. {
  344. request = async ? new BundleAssetRequestAsync(assetBundleName) : new BundleAssetRequest(assetBundleName);
  345. }
  346. else
  347. {
  348. if (path.StartsWith("http://", StringComparison.Ordinal) ||
  349. path.StartsWith("https://", StringComparison.Ordinal) ||
  350. path.StartsWith("file://", StringComparison.Ordinal) ||
  351. path.StartsWith("ftp://", StringComparison.Ordinal) ||
  352. path.StartsWith("jar:file://", StringComparison.Ordinal))
  353. {
  354. request = new WebAssetRequest();
  355. }
  356. else
  357. request = new AssetRequest();
  358. }
  359. request.name = path;
  360. request.assetType = type;
  361. AddAssetRequest(request);
  362. if (loadedCallback != null)
  363. {
  364. request.completed += loadedCallback;
  365. }
  366. request.Load();
  367. request.AddReferance();
  368. Debug.Log($"LoadAsset:{path}");
  369. return request;
  370. }
  371. #endregion
  372. #region Paths
  373. private static List<string> searchPaths = new List<string>();
  374. private static string GetExistPath(string path)
  375. {
  376. #if UNITY_EDITOR
  377. if (runtimeMode == false)
  378. {
  379. // 编辑器模式
  380. if (File.Exists(path))
  381. return path;
  382. foreach (var item in searchPaths)
  383. {
  384. var existPath = string.Format("{0}/{1}", item, path);
  385. if (File.Exists(existPath))
  386. return existPath;
  387. }
  388. Debug.LogError("【找不到资源路径】" + path);
  389. return path;
  390. }
  391. #endif
  392. if (_assetToBundles.ContainsKey(path))
  393. return path;
  394. foreach (var item in searchPaths)
  395. {
  396. var existPath = string.Format("{0}/{1}", item, path);
  397. if (_assetToBundles.ContainsKey(existPath))
  398. return existPath;
  399. }
  400. //Debug.LogError("资源没有收集打包" + path);
  401. return path;
  402. }
  403. // 获取资源在编辑器模式下的后缀
  404. public static string GetAssetPostfix(Type type)
  405. {
  406. if (type == typeof(GameObject))
  407. return ".prefab";
  408. if (type == typeof(TextAsset))
  409. return ".bytes";
  410. if (type == typeof(Texture2D) || type == typeof(Sprite))
  411. return ".png";
  412. if (type == typeof(Material))
  413. return ".mat";
  414. if (type == typeof(ScriptableObject))
  415. return ".asset";
  416. return "";
  417. }
  418. #endregion
  419. #region Bundles
  420. private static readonly int MAX_BUNDLES_PERFRAME = 0;
  421. private static List<string> _activeVariants = new List<string>();
  422. private static List<BundleRequest> _loadingBundles = new List<BundleRequest>();
  423. private static List<BundleRequest> _toloadBundles = new List<BundleRequest>();
  424. private static List<BundleRequest> _unusedBundles = new List<BundleRequest>();
  425. private static Dictionary<string, BundleRequest> _bundles = new Dictionary<string, BundleRequest>();
  426. private static Dictionary<string, string> _assetToBundles = new Dictionary<string, string>();
  427. private static Dictionary<string, string[]> _bundleToDependencies = new Dictionary<string, string[]>();
  428. private static Dictionary<string, AssetBundle> AssetBundle_dic = new Dictionary<string, AssetBundle>();
  429. public static AssetBundle GetAssetBundle(string key)
  430. {
  431. if (AssetBundle_dic.ContainsKey(key))
  432. {
  433. return AssetBundle_dic[key];
  434. }
  435. return null;
  436. }
  437. public static void AddAssetBundle(string key,AssetBundle ab)
  438. {
  439. if (!AssetBundle_dic.ContainsKey(key))
  440. {
  441. AssetBundle_dic.Add(key, ab);
  442. }
  443. }
  444. internal static bool GetAssetBundleName(string path, out string assetBundleName)
  445. {
  446. return _assetToBundles.TryGetValue(path, out assetBundleName);
  447. }
  448. internal static string[] GetAllDependencies(string bundle)
  449. {
  450. string[] deps;
  451. if (_bundleToDependencies.TryGetValue(bundle, out deps))
  452. return deps;
  453. return new string[0];
  454. }
  455. internal static BundleRequest LoadBundle(string assetBundleName)
  456. {
  457. return LoadBundle(assetBundleName, false);
  458. }
  459. internal static BundleRequest LoadBundleAsync(string assetBundleName)
  460. {
  461. return LoadBundle(assetBundleName, true);
  462. }
  463. internal static void UnloadBundle(BundleRequest bundle)
  464. {
  465. bundle.Release();
  466. }
  467. internal static BundleRequest LoadBundle(string assetBundleName, bool asyncMode)
  468. {
  469. if (string.IsNullOrEmpty(assetBundleName))
  470. {
  471. Debug.LogError("assetBundleName == null");
  472. return null;
  473. }
  474. assetBundleName = RemapVariantName(assetBundleName);
  475. var url = GetDataPath(assetBundleName) + assetBundleName;
  476. BundleRequest bundle;
  477. if (_bundles.TryGetValue(url, out bundle))
  478. {
  479. bundle.AddReferance();
  480. _loadingBundles.Add(bundle);
  481. return bundle;
  482. }
  483. if (url.StartsWith("http://", StringComparison.Ordinal) ||
  484. url.StartsWith("https://", StringComparison.Ordinal) ||
  485. url.StartsWith("file://", StringComparison.Ordinal) ||
  486. url.StartsWith("ftp://", StringComparison.Ordinal))
  487. {
  488. bundle = new WebBundleRequest();
  489. }
  490. else
  491. bundle = asyncMode ? new BundleRequestAsync() : new BundleRequest();
  492. bundle.name = url;
  493. _bundles.Add(url, bundle);
  494. if (MAX_BUNDLES_PERFRAME > 0 && (bundle is BundleRequestAsync || bundle is WebBundleRequest))
  495. {
  496. _toloadBundles.Add(bundle);
  497. }
  498. else
  499. {
  500. bundle.Load();
  501. _loadingBundles.Add(bundle);
  502. Debug.Log("LoadBundle: " + url);
  503. }
  504. bundle.AddReferance();
  505. return bundle;
  506. }
  507. // 这个函数没看懂
  508. private static string RemapVariantName(string assetBundleName)
  509. {
  510. var bundlesWithVariant = _activeVariants;
  511. // Get base bundle path
  512. var baseName = assetBundleName.Split('.')[0];
  513. var bestFit = int.MaxValue;
  514. var bestFitIndex = -1;
  515. // Loop all the assetBundles with variant to find the best fit variant assetBundle.
  516. for (var i = 0; i < bundlesWithVariant.Count; i++)
  517. {
  518. var curSplit = bundlesWithVariant[i].Split('.');
  519. var curBaseName = curSplit[0];
  520. var curVariant = curSplit[1];
  521. if (curBaseName != baseName)
  522. continue;
  523. var found = bundlesWithVariant.IndexOf(curVariant);
  524. // If there is no active variant found. We still want to use the first
  525. if (found == -1)
  526. found = int.MaxValue - 1;
  527. if (found >= bestFit)
  528. continue;
  529. bestFit = found;
  530. bestFitIndex = i;
  531. }
  532. if (bestFit == int.MaxValue - 1)
  533. Debug.LogWarning(
  534. "Ambiguous asset bundle variant chosen because there was no matching active variant: " +
  535. bundlesWithVariant[bestFitIndex]);
  536. return bestFitIndex != -1 ? bundlesWithVariant[bestFitIndex] : assetBundleName;
  537. }
  538. private static string GetDataPath(string bundleName)
  539. {
  540. if (string.IsNullOrEmpty(updatePath))
  541. return basePath;
  542. if (File.Exists(updatePath + bundleName))
  543. return updatePath;
  544. return basePath;
  545. }
  546. #endregion
  547. }