AssetLoaderZip.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  1. #pragma warning disable 184
  2. using System;
  3. using System.IO;
  4. using ICSharpCode.SharpZipLib.Zip;
  5. using TriLibCore.General;
  6. using TriLibCore.Mappers;
  7. using TriLibCore.Utils;
  8. using UnityEngine;
  9. namespace TriLibCore
  10. {
  11. /// <summary>Represents an Asset Loader class which works with Zip files.</summary>
  12. //todo: unify async loading.
  13. public static class AssetLoaderZip
  14. {
  15. /// <summary>
  16. /// Buffer size used to copy the zip file entries content.
  17. /// </summary>
  18. private const int ZipBufferSize = 4096;
  19. /// <summary>
  20. /// Called when all model resources have been loaded.
  21. /// </summary>
  22. /// <param name="assetLoaderContext">The asset loading context, containing callbacks and model loading data.</param>
  23. private static void OnMaterialsLoad(AssetLoaderContext assetLoaderContext)
  24. {
  25. var zipLoadCustomContextData = CustomDataHelper.GetCustomData<ZipLoadCustomContextData>(assetLoaderContext.CustomData);
  26. if (zipLoadCustomContextData != null)
  27. {
  28. if (zipLoadCustomContextData.Stream != null)
  29. {
  30. zipLoadCustomContextData.Stream.Close();
  31. }
  32. if (zipLoadCustomContextData.OnMaterialsLoad != null)
  33. {
  34. zipLoadCustomContextData.OnMaterialsLoad(assetLoaderContext);
  35. }
  36. }
  37. }
  38. /// <summary>
  39. /// Called when any loading error occurs.
  40. /// </summary>
  41. /// <param name="contextualizedError">Thrown error containing an attached context.</param>
  42. private static void OnError(IContextualizedError contextualizedError)
  43. {
  44. if (contextualizedError?.GetContext() is AssetLoaderContext assetLoaderContext)
  45. {
  46. var zipLoadCustomContextData = CustomDataHelper.GetCustomData<ZipLoadCustomContextData>(assetLoaderContext.CustomData);
  47. if (zipLoadCustomContextData != null)
  48. {
  49. if (zipLoadCustomContextData.Stream != null)
  50. {
  51. zipLoadCustomContextData.Stream.Close();
  52. }
  53. if (zipLoadCustomContextData.OnError != null)
  54. {
  55. zipLoadCustomContextData.OnError.Invoke(contextualizedError);
  56. }
  57. }
  58. }
  59. }
  60. /// <summary>Loads a model from the given Zip file path asynchronously.</summary>
  61. /// <param name="path">The Zip file path.</param>
  62. /// <param name="onLoad">The Method to call on the Main Thread when the Model is loaded but resources may still pending.</param>
  63. /// <param name="onMaterialsLoad">The Method to call on the Main Thread when the Model and resources are loaded.</param>
  64. /// <param name="onProgress">The Method to call when the Model loading progress changes.</param>
  65. /// <param name="onError">The Method to call on the Main Thread when any error occurs.</param>
  66. /// <param name="wrapperGameObject">The Game Object that will be the parent of the loaded Game Object. Can be null.</param>
  67. /// <param name="assetLoaderOptions">The options to use when loading the Model.</param>
  68. /// <param name="customContextData">The Custom Data that will be passed along the Context.</param>
  69. /// <param name="fileExtension">The Model inside the Zip file extension. If <c>null</c> TriLib will try to find a suitable model format inside the Zip file.</param>
  70. /// <param name="haltTask">Turn on this field to avoid loading the model immediately and chain the Tasks.</param>
  71. /// <param name="onPreLoad">The method to call on the parallel Thread before the Unity objects are created.</param>
  72. /// <returns>The asset loader context, containing model loading information and the output game object.</returns>
  73. public static AssetLoaderContext LoadModelFromZipFile(string path,
  74. Action<AssetLoaderContext> onLoad,
  75. Action<AssetLoaderContext> onMaterialsLoad,
  76. Action<AssetLoaderContext, float> onProgress,
  77. Action<IContextualizedError> onError = null,
  78. GameObject wrapperGameObject = null,
  79. AssetLoaderOptions assetLoaderOptions = null,
  80. object customContextData = null,
  81. string fileExtension = null,
  82. bool haltTask = false,
  83. Action<AssetLoaderContext> onPreLoad = null)
  84. {
  85. Stream stream = null;
  86. var memoryStream = SetupZipModelLoading(onError, ref stream, path, ref assetLoaderOptions, ref fileExtension, out var zipFile, out var zipEntry);
  87. var customDataDic = (object)CustomDataHelper.CreateCustomDataDictionaryWithData(new ZipLoadCustomContextData
  88. {
  89. ZipFile = zipFile,
  90. ZipEntry = zipEntry,
  91. Stream = stream,
  92. OnError = onError,
  93. OnMaterialsLoad = onMaterialsLoad
  94. });
  95. if (customContextData != null)
  96. {
  97. CustomDataHelper.SetCustomData(ref customDataDic, customContextData);
  98. }
  99. return AssetLoader.LoadModelFromStream(memoryStream,
  100. path,
  101. fileExtension,
  102. onLoad,
  103. OnMaterialsLoad,
  104. onProgress,
  105. OnError,
  106. wrapperGameObject,
  107. assetLoaderOptions,
  108. customDataDic,
  109. haltTask,
  110. onPreLoad,
  111. true);
  112. }
  113. /// <summary>Loads a model from the given Zip file Stream asynchronously.</summary>
  114. /// <param name="stream">The Stream containing the Zip data.</param>
  115. /// <param name="onLoad">The Method to call on the Main Thread when the Model is loaded but resources may still pending.</param>
  116. /// <param name="onMaterialsLoad">The Method to call on the Main Thread when the Model and resources are loaded.</param>
  117. /// <param name="onProgress">The Method to call when the Model loading progress changes.</param>
  118. /// <param name="onError">The Method to call on the Main Thread when any error occurs.</param>
  119. /// <param name="wrapperGameObject">The Game Object that will be the parent of the loaded Game Object. Can be null.</param>
  120. /// <param name="assetLoaderOptions">The options to use when loading the Model.</param>
  121. /// <param name="customContextData">The Custom Data that will be passed along the Context.</param>
  122. /// <param name="fileExtension">The Model inside the Zip file extension. If <c>null</c> TriLib will try to find a suitable model format inside the Zip file.</param>
  123. /// <param name="haltTask">Turn on this field to avoid loading the model immediately and chain the Tasks.</param> ///
  124. /// <param name="modelFilename">The Zip file path.</param>
  125. /// <param name="onPreLoad">The method to call on the parallel Thread before the Unity objects are created.</param>
  126. /// <returns>The asset loader context, containing model loading information and the output game object.</returns>
  127. public static AssetLoaderContext LoadModelFromZipStream(Stream stream,
  128. Action<AssetLoaderContext> onLoad,
  129. Action<AssetLoaderContext> onMaterialsLoad,
  130. Action<AssetLoaderContext, float> onProgress,
  131. Action<IContextualizedError> onError = null,
  132. GameObject wrapperGameObject = null,
  133. AssetLoaderOptions assetLoaderOptions = null,
  134. object customContextData = null,
  135. string fileExtension = null,
  136. bool haltTask = false,
  137. string modelFilename = null,
  138. Action<AssetLoaderContext> onPreLoad = null)
  139. {
  140. var memoryStream = SetupZipModelLoading(onError, ref stream, null, ref assetLoaderOptions, ref fileExtension, out var zipFile, out var zipEntry);
  141. var customDataDic = (object)CustomDataHelper.CreateCustomDataDictionaryWithData(new ZipLoadCustomContextData
  142. {
  143. ZipFile = zipFile,
  144. ZipEntry = zipEntry,
  145. Stream = stream,
  146. OnError = onError,
  147. OnMaterialsLoad = onMaterialsLoad
  148. });
  149. if (customContextData != null)
  150. {
  151. CustomDataHelper.SetCustomData(ref customDataDic, customContextData);
  152. }
  153. return AssetLoader.LoadModelFromStream(memoryStream,
  154. modelFilename,
  155. fileExtension,
  156. onLoad,
  157. OnMaterialsLoad,
  158. onProgress,
  159. OnError,
  160. wrapperGameObject,
  161. assetLoaderOptions,
  162. customDataDic,
  163. haltTask,
  164. onPreLoad,
  165. true);
  166. }
  167. /// <summary>Loads a model from the given Zip file path synchronously.</summary>
  168. /// <param name="path">The Zip file path.</param>
  169. /// <param name="onError">The Method to call on the Main Thread when any error occurs.</param>
  170. /// <param name="wrapperGameObject">The Game Object that will be the parent of the loaded Game Object. Can be null.</param>
  171. /// <param name="assetLoaderOptions">The options to use when loading the Model.</param>
  172. /// <param name="customContextData">The Custom Data that will be passed along the Context.</param>
  173. /// <param name="fileExtension">The Model inside the Zip file extension. If <c>null</c> TriLib will try to find a suitable model format inside the Zip file.</param>
  174. /// <returns>The asset loader context, containing model loading information and the output game object.</returns>
  175. public static AssetLoaderContext LoadModelFromZipFileNoThread(string path,
  176. Action<IContextualizedError> onError = null,
  177. GameObject wrapperGameObject = null,
  178. AssetLoaderOptions assetLoaderOptions = null,
  179. object customContextData = null,
  180. string fileExtension = null)
  181. {
  182. Stream stream = null;
  183. var memoryStream = SetupZipModelLoading(onError, ref stream, path, ref assetLoaderOptions, ref fileExtension, out var zipFile, out var zipEntry);
  184. var customDataDic = (object)CustomDataHelper.CreateCustomDataDictionaryWithData(new ZipLoadCustomContextData
  185. {
  186. ZipFile = zipFile,
  187. ZipEntry = zipEntry,
  188. Stream = stream,
  189. OnError = onError
  190. });
  191. if (customContextData != null)
  192. {
  193. CustomDataHelper.SetCustomData(ref customDataDic, customContextData);
  194. }
  195. var assetLoaderContext = AssetLoader.LoadModelFromStreamNoThread(memoryStream,
  196. path,
  197. fileExtension,
  198. OnError,
  199. wrapperGameObject,
  200. assetLoaderOptions,
  201. customDataDic,
  202. true);
  203. stream.Close();
  204. return assetLoaderContext;
  205. }
  206. /// <summary>Loads a model from the given Zip file Stream synchronously.</summary>
  207. /// <param name="stream">The Stream containing the Zip data.</param>
  208. /// <param name="onError">The Method to call on the Main Thread when any error occurs.</param>
  209. /// <param name="wrapperGameObject">The Game Object that will be the parent of the loaded Game Object. Can be null.</param>
  210. /// <param name="assetLoaderOptions">The options to use when loading the Model.</param>
  211. /// <param name="customContextData">The Custom Data that will be passed along the Context.</param>
  212. /// <param name="fileExtension">The Model inside the Zip file extension. If <c>null</c> TriLib will try to find a suitable model format inside the Zip file.</param>
  213. /// <returns>The asset loader context, containing model loading information and the output game object.</returns>
  214. public static AssetLoaderContext LoadModelFromZipStreamNoThread(Stream stream,
  215. Action<IContextualizedError> onError,
  216. GameObject wrapperGameObject = null,
  217. AssetLoaderOptions assetLoaderOptions = null,
  218. object customContextData = null,
  219. string fileExtension = null)
  220. {
  221. var memoryStream = SetupZipModelLoading(onError, ref stream, null, ref assetLoaderOptions, ref fileExtension, out var zipFile, out var zipEntry);
  222. var customDataDic = (object)CustomDataHelper.CreateCustomDataDictionaryWithData(new ZipLoadCustomContextData
  223. {
  224. ZipFile = zipFile,
  225. ZipEntry = zipEntry,
  226. Stream = stream,
  227. OnError = onError
  228. });
  229. if (customContextData != null)
  230. {
  231. CustomDataHelper.SetCustomData(ref customDataDic, customContextData);
  232. }
  233. var assetLoaderContext = AssetLoader.LoadModelFromStreamNoThread(memoryStream,
  234. null,
  235. fileExtension,
  236. OnError,
  237. wrapperGameObject,
  238. assetLoaderOptions,
  239. customDataDic,
  240. true);
  241. stream.Close();
  242. return assetLoaderContext;
  243. }
  244. /// <summary>Configures the Zip Model loading, adding the Zip External Data/Texture Mappers.</summary>
  245. /// <param name="onError">The method to execute when any error occurs.</param>
  246. /// <param name="stream">The Stream containing the Zip data.</param>
  247. /// <param name="path">The Zip file path.</param>
  248. /// <param name="assetLoaderOptions">The options to use when loading the Model.</param>
  249. /// <param name="fileExtension">The Model inside the Zip file extension.</param>
  250. /// <param name="zipFile">The Zip file instance.</param>
  251. /// <param name="modelZipEntry">The model Zip entry inside the Zip file.</param>
  252. /// <returns>The model file data stream.</returns>
  253. private static Stream SetupZipModelLoading(Action<IContextualizedError> onError, ref Stream stream, string path, ref AssetLoaderOptions assetLoaderOptions, ref string fileExtension, out ZipFile zipFile, out ZipEntry modelZipEntry)
  254. {
  255. if (assetLoaderOptions == null)
  256. {
  257. assetLoaderOptions = AssetLoader.CreateDefaultLoaderOptions();
  258. }
  259. if (!ArrayUtils.ContainsType<ZipFileTextureMapper>(assetLoaderOptions.TextureMappers))
  260. {
  261. assetLoaderOptions.TextureMappers = new TextureMapper[] { ScriptableObject.CreateInstance<ZipFileTextureMapper>() };
  262. }
  263. if (!(assetLoaderOptions.ExternalDataMapper is ZipFileExternalDataMapper))
  264. {
  265. assetLoaderOptions.ExternalDataMapper = ScriptableObject.CreateInstance<ZipFileExternalDataMapper>();
  266. }
  267. if (stream == null)
  268. {
  269. stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
  270. }
  271. var validExtensions = Readers.Extensions;
  272. zipFile = new ZipFile(stream);
  273. Stream memoryStream = null;
  274. modelZipEntry = null;
  275. foreach (ZipEntry zipEntry in zipFile)
  276. {
  277. if (!zipEntry.IsFile)
  278. {
  279. continue;
  280. }
  281. var checkingFileExtension = FileUtils.GetFileExtension(zipEntry.Name, false);
  282. if (fileExtension != null && checkingFileExtension == fileExtension)
  283. {
  284. memoryStream = ZipFileEntryToStream(out fileExtension, zipEntry, zipFile);
  285. modelZipEntry = zipEntry;
  286. }
  287. else if (validExtensions.Contains(checkingFileExtension))
  288. {
  289. memoryStream = ZipFileEntryToStream(out fileExtension, zipEntry, zipFile);
  290. modelZipEntry = zipEntry;
  291. break;
  292. }
  293. }
  294. if (memoryStream == null)
  295. {
  296. var exception = new Exception("Unable to find a suitable model on the Zip file. Please inform a valid model file extension.");
  297. onError?.Invoke(new ContextualizedError<string>(exception, "Error"));
  298. }
  299. return memoryStream;
  300. }
  301. /// <summary>Copies the contents of a Zip file Entry into a Memory Stream.</summary>
  302. /// <param name="fileExtension">The Model inside the Zip file extension.</param>
  303. /// <param name="zipEntry">The Zip file Entry.</param>
  304. /// <param name="zipFile">The Zip file instance.</param>
  305. /// <returns>A memory stream with the zip file entry contents.</returns>
  306. public static Stream ZipFileEntryToStream(out string fileExtension, ZipEntry zipEntry, ZipFile zipFile)
  307. {
  308. var buffer = new byte[ZipBufferSize];
  309. var zipFileStream = zipFile.GetInputStream(zipEntry);
  310. var memoryStream = new MemoryStream(ZipBufferSize);
  311. ICSharpCode.SharpZipLib.Core.StreamUtils.Copy(zipFileStream, memoryStream, buffer);
  312. memoryStream.Seek(0, SeekOrigin.Begin);
  313. zipFileStream.Close();
  314. fileExtension = FileUtils.GetFileExtension(zipEntry.Name, false);
  315. return memoryStream;
  316. }
  317. }
  318. }