AssetLoaderZip.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. using System;
  2. using System.Collections.Generic;
  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. using Object = UnityEngine.Object;
  10. namespace TriLibCore
  11. {
  12. /// <summary>Represents an Asset Loader class which works with Zip files.</summary>
  13. //todo: unify async loading.
  14. public static class AssetLoaderZip
  15. {
  16. /// <summary>
  17. /// Buffer size used to copy the zip file entries content.
  18. /// </summary>
  19. private const int ZipBufferSize = 4096;
  20. /// <summary>Loads a model from the given Zip file path asynchronously.</summary>
  21. /// <param name="path">The Zip file path.</param>
  22. /// <param name="onLoad">The Method to call on the Main Thread when the Model Meshes and hierarchy are loaded.</param>
  23. /// <param name="onMaterialsLoad">The Method to call on the Main Thread when the Model (including Textures and Materials) has been fully loaded.</param>
  24. /// <param name="onProgress">The Method to call when the Model loading progress changes.</param>
  25. /// <param name="onError">The Method to call on the Main Thread when any error occurs.</param>
  26. /// <param name="wrapperGameObject">The Game Object that will be the parent of the loaded Game Object. Can be null.</param>
  27. /// <param name="assetLoaderOptions">The Asset Loader Options reference. Asset Loader Options contains various options used during the Model loading process.</param>
  28. /// <param name="customContextData">The Custom Data that will be passed along the AssetLoaderContext.</param>
  29. /// <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>
  30. /// <returns>The asset loader context, containing model loading information and the output game object.</returns>
  31. public static AssetLoaderContext LoadModelFromZipFile(string path, Action<AssetLoaderContext> onLoad, Action<AssetLoaderContext> onMaterialsLoad, Action<AssetLoaderContext, float> onProgress, Action<IContextualizedError> onError = null, GameObject wrapperGameObject = null, AssetLoaderOptions assetLoaderOptions = null, object customContextData = null, string fileExtension = null)
  32. {
  33. Stream stream = null;
  34. var memoryStream = SetupZipModelLoading(ref stream, path, onError, assetLoaderOptions, ref fileExtension, out var zipFile);
  35. return AssetLoader.LoadModelFromStream(memoryStream, path, fileExtension, onLoad, delegate (AssetLoaderContext assetLoaderContext)
  36. {
  37. var zipLoadCustomContextData = assetLoaderContext.CustomData as ZipLoadCustomContextData;
  38. zipLoadCustomContextData?.Stream.Close();
  39. onMaterialsLoad?.Invoke(assetLoaderContext);
  40. }, onProgress, OnError, wrapperGameObject, assetLoaderOptions, new ZipLoadCustomContextData
  41. {
  42. ZipFile = zipFile,
  43. Stream = stream,
  44. CustomData = customContextData
  45. });
  46. }
  47. /// <summary>Loads a model from the given Zip file Stream asynchronously.</summary>
  48. /// <param name="stream">The Stream containing the Zip data.</param>
  49. /// <param name="onLoad">The Method to call on the Main Thread when the Model Meshes and hierarchy are loaded.</param>
  50. /// <param name="onMaterialsLoad">The Method to call on the Main Thread when the Model (including Textures and Materials) has been fully loaded.</param>
  51. /// <param name="onProgress">The Method to call when the Model loading progress changes.</param>
  52. /// <param name="onError">The Method to call on the Main Thread when any error occurs.</param>
  53. /// <param name="wrapperGameObject">The Game Object that will be the parent of the loaded Game Object. Can be null.</param>
  54. /// <param name="assetLoaderOptions">The Asset Loader Options reference. Asset Loader Options contains various options used during the Model loading process.</param>
  55. /// <param name="customContextData">The Custom Data that will be passed along the AssetLoaderContext.</param>
  56. /// <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>
  57. /// <returns>The asset loader context, containing model loading information and the output game object.</returns>
  58. public static AssetLoaderContext LoadModelFromZipStream(Stream stream, Action<AssetLoaderContext> onLoad, Action<AssetLoaderContext> onMaterialsLoad, Action<AssetLoaderContext, float> onProgress, Action<IContextualizedError> onError = null, GameObject wrapperGameObject = null, AssetLoaderOptions assetLoaderOptions = null, object customContextData = null, string fileExtension = null)
  59. {
  60. var memoryStream = SetupZipModelLoading(ref stream, null, onError, assetLoaderOptions, ref fileExtension, out var zipFile);
  61. return AssetLoader.LoadModelFromStream(memoryStream, null, fileExtension, onLoad, delegate (AssetLoaderContext assetLoaderContext)
  62. {
  63. var zipLoadCustomContextData = assetLoaderContext.CustomData as ZipLoadCustomContextData;
  64. zipLoadCustomContextData?.Stream.Close();
  65. onMaterialsLoad?.Invoke(assetLoaderContext);
  66. }, onProgress, OnError, wrapperGameObject, assetLoaderOptions, new ZipLoadCustomContextData
  67. {
  68. ZipFile = zipFile,
  69. Stream = stream,
  70. CustomData = customContextData
  71. });
  72. }
  73. /// <summary>Loads a model from the given Zip file path synchronously.</summary>
  74. /// <param name="path">The Zip file path.</param>
  75. /// <param name="onError">The Method to call on the Main Thread when any error occurs.</param>
  76. /// <param name="wrapperGameObject">The Game Object that will be the parent of the loaded Game Object. Can be null.</param>
  77. /// <param name="assetLoaderOptions">The Asset Loader Options reference. Asset Loader Options contains various options used during the Model loading process.</param>
  78. /// <param name="customContextData">The Custom Data that will be passed along the AssetLoaderContext.</param>
  79. /// <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>
  80. /// <returns>The asset loader context, containing model loading information and the output game object.</returns>
  81. public static AssetLoaderContext LoadModelFromZipFileNoThread(string path, Action<IContextualizedError> onError = null, GameObject wrapperGameObject = null, AssetLoaderOptions assetLoaderOptions = null, object customContextData = null, string fileExtension = null)
  82. {
  83. Stream stream = null;
  84. var memoryStream = SetupZipModelLoading(ref stream, path, onError, assetLoaderOptions, ref fileExtension, out var zipFile);
  85. var assetLoaderContext = AssetLoader.LoadModelFromStreamNoThread(memoryStream, path, fileExtension, OnError, wrapperGameObject, assetLoaderOptions, new ZipLoadCustomContextData
  86. {
  87. ZipFile = zipFile,
  88. CustomData = customContextData
  89. });
  90. stream.Close();
  91. return assetLoaderContext;
  92. }
  93. /// <summary>Loads a model from the given Zip file Stream synchronously.</summary>
  94. /// <param name="stream">The Stream containing the Zip data.</param>
  95. /// <param name="onError">The Method to call on the Main Thread when any error occurs.</param>
  96. /// <param name="wrapperGameObject">The Game Object that will be the parent of the loaded Game Object. Can be null.</param>
  97. /// <param name="assetLoaderOptions">The Asset Loader Options reference. Asset Loader Options contains various options used during the Model loading process.</param>
  98. /// <param name="customContextData">The Custom Data that will be passed along the AssetLoaderContext.</param>
  99. /// <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>
  100. /// <returns>The asset loader context, containing model loading information and the output game object.</returns>
  101. public static AssetLoaderContext LoadModelFromZipStreamNoThread(Stream stream, Action<IContextualizedError> onError, GameObject wrapperGameObject = null, AssetLoaderOptions assetLoaderOptions = null, object customContextData = null, string fileExtension = null)
  102. {
  103. var memoryStream = SetupZipModelLoading(ref stream, null, onError, assetLoaderOptions, ref fileExtension, out var zipFile);
  104. var assetLoaderContext = AssetLoader.LoadModelFromStreamNoThread(memoryStream, null, fileExtension, OnError, wrapperGameObject, assetLoaderOptions, new ZipLoadCustomContextData
  105. {
  106. ZipFile = zipFile,
  107. CustomData = customContextData
  108. });
  109. stream.Close();
  110. return assetLoaderContext;
  111. }
  112. /// <summary>
  113. /// Called when any loading error occurs.
  114. /// </summary>
  115. /// <param name="contextualizedError">Thrown error containing an attached context.</param>
  116. private static void OnError(IContextualizedError contextualizedError)
  117. {
  118. var assetLoaderContext = contextualizedError?.GetContext() as AssetLoaderContext;
  119. var zipLoadCustomContextData = assetLoaderContext?.CustomData as ZipLoadCustomContextData;
  120. zipLoadCustomContextData?.Stream.Close();
  121. assetLoaderContext?.OnError?.Invoke(contextualizedError);
  122. }
  123. /// <summary>Configures the Zip Model loading, adding the Zip External Data/Texture Mappers.</summary>
  124. /// <param name="stream">The Stream containing the Zip data.</param>
  125. /// <param name="path">The Zip file path.</param>
  126. /// <param name="onError">The Method to call on the Main Thread when any error occurs.</param>
  127. /// <param name="assetLoaderOptions">The Asset Loader Options reference. Asset Loader Options contains various options used during the Model loading process.</param>
  128. /// <param name="fileExtension">The Model inside the Zip file extension.</param>
  129. /// <param name="zipFile">The Zip file instance.</param>
  130. /// <returns>The model file data stream.</returns>
  131. private static Stream SetupZipModelLoading(ref Stream stream, string path, Action<IContextualizedError> onError, AssetLoaderOptions assetLoaderOptions, ref string fileExtension, out ZipFile zipFile)
  132. {
  133. if (assetLoaderOptions == null)
  134. {
  135. assetLoaderOptions = AssetLoader.CreateDefaultLoaderOptions();
  136. }
  137. assetLoaderOptions.TextureMapper = ScriptableObject.CreateInstance<ZipFileTextureMapper>();
  138. assetLoaderOptions.ExternalDataMapper = ScriptableObject.CreateInstance<ZipFileExternalDataMapper>();
  139. assetLoaderOptions.FixedAllocations.Add(assetLoaderOptions.TextureMapper);
  140. assetLoaderOptions.FixedAllocations.Add(assetLoaderOptions.ExternalDataMapper);
  141. if (stream == null)
  142. {
  143. stream = new FileStream(path, FileMode.Open);
  144. }
  145. var validExtensions = Readers.Extensions;
  146. zipFile = new ZipFile(stream);
  147. Stream memoryStream = null;
  148. foreach (ZipEntry zipEntry in zipFile)
  149. {
  150. if (!zipEntry.IsFile)
  151. {
  152. continue;
  153. }
  154. var checkingFileExtension = FileUtils.GetFileExtension(zipEntry.Name, false);
  155. if (fileExtension != null && checkingFileExtension == fileExtension)
  156. {
  157. memoryStream = ZipFileEntryToStream(out fileExtension, zipEntry, zipFile);
  158. }
  159. else if (validExtensions.Contains(checkingFileExtension))
  160. {
  161. memoryStream = ZipFileEntryToStream(out fileExtension, zipEntry, zipFile);
  162. break;
  163. }
  164. }
  165. if (memoryStream == null)
  166. {
  167. var exception = new Exception("Unable to find a suitable model on the Zip file. Please inform a valid model file extension.");
  168. if (onError != null)
  169. {
  170. var error = exception as IContextualizedError;
  171. onError(error ?? new ContextualizedError<AssetLoaderOptions>(exception, assetLoaderOptions));
  172. }
  173. else
  174. {
  175. throw exception;
  176. }
  177. }
  178. return memoryStream;
  179. }
  180. /// <summary>Copies the contents of a Zip file Entry into a Memory Stream.</summary>
  181. /// <param name="fileExtension">The Model inside the Zip file extension</param>
  182. /// <param name="zipEntry">The Zip file Entry.</param>
  183. /// <param name="zipFile">The Zip file instance.</param>
  184. /// <returns>A memory stream with the zip file entry contents.</returns>
  185. public static Stream ZipFileEntryToStream(out string fileExtension, ZipEntry zipEntry, ZipFile zipFile)
  186. {
  187. var buffer = new byte[ZipBufferSize];
  188. var zipFileStream = zipFile.GetInputStream(zipEntry);
  189. var memoryStream = new MemoryStream(ZipBufferSize);
  190. ICSharpCode.SharpZipLib.Core.StreamUtils.Copy(zipFileStream, memoryStream, buffer);
  191. memoryStream.Seek(0, SeekOrigin.Begin);
  192. zipFileStream.Close();
  193. fileExtension = FileUtils.GetFileExtension(zipEntry.Name, false);
  194. return memoryStream;
  195. }
  196. }
  197. }