├── .gitattributes
├── .idea
└── .idea.BundleMaster.dir
│ └── .idea
│ ├── .gitignore
│ ├── encodings.xml
│ ├── indexLayout.xml
│ └── vcs.xml
├── BMWebGLAdapter
├── AssetComponentInit_WebGL.cs
├── AssetComponentTools_WebGL.cs
├── AssetComponentUpdate_WebGL.cs
└── LoadBaseRuntimeLoad_WebGL.cs
├── BundleMasterRuntime
├── AssetComponent.cs
├── AssetComponentCheck.cs
├── AssetComponentConfig.cs
├── AssetComponentInit.cs
├── AssetComponentLife.cs
├── AssetComponentTools.cs
├── AssetComponentUnLoad.cs
├── AssetComponentUpdate.cs
├── Base
│ ├── LoadBase.cs
│ ├── LoadBaseRuntimeLoad.cs
│ ├── LoadBaseUnLoad.cs
│ ├── LoadDepend.cs
│ ├── LoadFile.cs
│ ├── LoadGroup.cs
│ ├── LoadHandlerFactory.cs
│ └── WebLoadProgress.cs
├── BundleMasterRuntimeConfig.cs
├── BundleRuntimeInfo.cs
├── DownLoadTask.cs
├── Helper
│ ├── AssetLogHelper.cs
│ ├── DeleteHelper.cs
│ ├── DownloadBundleHelper.cs
│ ├── GroupAssetHelper.cs
│ ├── HandlerIdHelper.cs
│ ├── PathUnifiedHelper.cs
│ ├── TimeAwaitHelper.cs
│ ├── ValueHelper.cs
│ └── VerifyHelper.cs
├── LoadHandler.cs
├── LoadHandlerBase.cs
├── LoadSceneHandler.cs
└── UpdateBundleDataInfo.cs
├── CoroutineLock
├── CoroutineLock.cs
├── CoroutineLockComponent.cs
├── CoroutineLockQueue.cs
├── CoroutineLockType.cs
└── LoadPathConvertHelper.cs
├── Editor
└── BundleMasterEditor
│ ├── AssetLoadTable.cs
│ ├── AssetsLoadSetting.cs
│ ├── AssetsOriginSetting.cs
│ ├── AssetsSetting.cs
│ ├── BuildAssets.cs
│ ├── BuildAssetsTools.cs
│ ├── BundleMasterInterface
│ ├── BundleMasterAssetsLoadWindow.cs
│ ├── BundleMasterMainWindow.cs
│ ├── BundleMasterOriginLoadWindow.cs
│ └── BundleMasterWindow.cs
│ ├── BundleUsefulTool.cs
│ └── DevelopSceneChange.cs
├── LICENSE
├── LiteMultiThreadDownLoad
├── BundleMaster.LMTD.asmdef
├── BundleMaster.LMTD.asmdef.meta
├── ILiteThreadAction.cs
├── LMTDProxy.cs
├── LMTDownLoad.cs
├── LiteThread.cs
├── LmtdTable.cs
└── ThreadFactory.cs
├── NintendoAdapter
├── AssetComponentInit_Nintendo.cs
├── AssetComponentTools_Nintendo.cs
├── AssetComponentUpdate_Nintendo.cs
└── LoadBaseRuntimeLoad_Nintendo.cs
└── README.md
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/.idea/.idea.BundleMaster.dir/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # 默认忽略的文件
2 | /shelf/
3 | /workspace.xml
4 | # Rider 忽略的文件
5 | /modules.xml
6 | /projectSettingsUpdater.xml
7 | /contentModel.xml
8 | /.idea.BundleMaster.iml
9 | # 基于编辑器的 HTTP 客户端请求
10 | /httpRequests/
11 | # Datasource local storage ignored files
12 | /dataSources/
13 | /dataSources.local.xml
14 |
--------------------------------------------------------------------------------
/.idea/.idea.BundleMaster.dir/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/.idea/.idea.BundleMaster.dir/.idea/indexLayout.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/.idea.BundleMaster.dir/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/BMWebGLAdapter/AssetComponentInit_WebGL.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text.RegularExpressions;
4 | using UnityEngine;
5 | using ET;
6 | using UnityEngine.Networking;
7 |
8 | namespace BM
9 | {
10 | public static partial class AssetComponent
11 | {
12 | #if BMWebGL
13 | ///
14 | /// 初始化
15 | ///
16 | /// WebGL模式下 secretKey 参数失效,仅作占位使用
17 | public static async ETTask Initialize(string bundlePackageName, string secretKey = null)
18 | {
19 | if (AssetComponentConfig.AssetLoadMode == AssetLoadMode.Develop)
20 | {
21 | AssetLogHelper.Log("AssetLoadMode = Develop 不需要初始化Bundle配置文件");
22 | return false;
23 | }
24 | if (BundleNameToRuntimeInfo.ContainsKey(bundlePackageName))
25 | {
26 | AssetLogHelper.LogError(bundlePackageName + " 重复初始化");
27 | return false;
28 | }
29 | BundleRuntimeInfo bundleRuntimeInfo = new BundleRuntimeInfo(bundlePackageName);
30 | BundleNameToRuntimeInfo.Add(bundlePackageName, bundleRuntimeInfo);
31 |
32 | string filePath = BundleFileExistPath_WebGL(bundlePackageName, "FileLogs.txt");
33 | string fileLogs = await LoadWebGLFileText(filePath);
34 | {
35 | Regex reg = new Regex(@"\<(.+?)>");
36 | MatchCollection matchCollection = reg.Matches(fileLogs);
37 | List dependFileName = new List();
38 | foreach (Match m in matchCollection)
39 | {
40 | string[] fileLog = m.Groups[1].Value.Split('|');
41 | LoadFile loadFile = new LoadFile();
42 | loadFile.FilePath = fileLog[0];
43 | loadFile.AssetBundleName = fileLog[1];
44 |
45 | if (fileLog.Length > 2)
46 | {
47 | for (int i = 2; i < fileLog.Length; i++)
48 | {
49 | dependFileName.Add(fileLog[i]);
50 | }
51 | }
52 | loadFile.DependFileName = dependFileName.ToArray();
53 | dependFileName.Clear();
54 | bundleRuntimeInfo.LoadFileDic.Add(loadFile.FilePath, loadFile);
55 | }
56 | }
57 |
58 | string dependPath = BundleFileExistPath_WebGL(bundlePackageName, "DependLogs.txt");
59 | string dependLogs = await LoadWebGLFileText(dependPath);
60 | {
61 | Regex reg = new Regex(@"\<(.+?)>");
62 | MatchCollection matchCollection = reg.Matches(dependLogs);
63 | foreach (Match m in matchCollection)
64 | {
65 | string[] dependLog = m.Groups[1].Value.Split('|');
66 | LoadDepend loadDepend = new LoadDepend();
67 | loadDepend.FilePath = dependLog[0];
68 | loadDepend.AssetBundleName = dependLog[1];
69 | bundleRuntimeInfo.LoadDependDic.Add(loadDepend.FilePath, loadDepend);
70 | }
71 | }
72 |
73 | ETTask groupTcs = ETTask.Create();
74 | string groupPath = BundleFileExistPath_WebGL(bundlePackageName, "GroupLogs.txt");
75 | string groupLogs = await LoadWebGLFileText(groupPath);
76 | {
77 | Regex reg = new Regex(@"\<(.+?)>");
78 | MatchCollection matchCollection = reg.Matches(groupLogs);
79 | foreach (Match m in matchCollection)
80 | {
81 | string[] groupLog = m.Groups[1].Value.Split('|');
82 | LoadGroup loadGroup = new LoadGroup();
83 | loadGroup.FilePath = groupLog[0];
84 | loadGroup.AssetBundleName = groupLog[1];
85 | if (groupLog.Length > 2)
86 | {
87 | for (int i = 2; i < groupLog.Length; i++)
88 | {
89 | loadGroup.DependFileName.Add(groupLog[i]);
90 | }
91 | }
92 | bundleRuntimeInfo.LoadGroupDic.Add(loadGroup.FilePath, loadGroup);
93 | bundleRuntimeInfo.LoadGroupDicKey.Add(loadGroup.FilePath);
94 | }
95 | }
96 |
97 | //加载当前分包的shader
98 | await LoadShader_WebGL(bundlePackageName);
99 | return true;
100 | }
101 |
102 | private static async ETTask LoadShader_WebGL(string bundlePackageName)
103 | {
104 | ETTask tcs = ETTask.Create();
105 | string shaderPath = BundleFileExistPath_WebGL(bundlePackageName, "shader_" + bundlePackageName.ToLower());
106 | //判断文件是否存在
107 | using (UnityWebRequest webRequest = UnityWebRequestAssetBundle.GetAssetBundle(shaderPath))
108 | {
109 | UnityWebRequestAsyncOperation weq = webRequest.SendWebRequest();
110 | weq.completed += (o) => { tcs.SetResult(); };
111 | await tcs;
112 | #if UNITY_2020_1_OR_NEWER
113 | if (webRequest.result == UnityWebRequest.Result.Success)
114 | #else
115 | if (string.IsNullOrEmpty(webRequest.error))
116 | #endif
117 | {
118 | AssetBundle assetBundle = DownloadHandlerAssetBundle.GetContent(webRequest);
119 | BundleNameToRuntimeInfo[bundlePackageName].Shader = assetBundle;
120 | }
121 | else
122 | {
123 | return;
124 | }
125 | }
126 | }
127 | #endif
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/BMWebGLAdapter/AssetComponentTools_WebGL.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using System.Text;
3 | using ET;
4 | using UnityEngine.Networking;
5 |
6 | namespace BM
7 | {
8 | #if BMWebGL
9 | public static partial class AssetComponent
10 | {
11 | ///
12 | /// WebGL获取Bundle信息文件的路径
13 | ///
14 | internal static string BundleFileExistPath_WebGL(string bundlePackageName, string fileName)
15 | {
16 | string path = Path.Combine(AssetComponentConfig.LocalBundlePath, bundlePackageName, fileName);
17 | return path;
18 | }
19 |
20 | internal static async ETTask LoadWebGLFileText(string filePath)
21 | {
22 | ETTask tcs = ETTask.Create();
23 | using (UnityWebRequest webRequest = UnityWebRequest.Get(filePath))
24 | {
25 | UnityWebRequestAsyncOperation weq = webRequest.SendWebRequest();
26 | weq.completed += (o) =>
27 | {
28 | tcs.SetResult();
29 | };
30 | await tcs;
31 | #if UNITY_2020_1_OR_NEWER
32 | if (webRequest.result == UnityWebRequest.Result.Success)
33 | #else
34 | if (string.IsNullOrEmpty(webRequest.error))
35 | #endif
36 | {
37 | string str = webRequest.downloadHandler.text;
38 | return str;
39 | }
40 | else
41 | {
42 | AssetLogHelper.LogError("WebGL初始化分包未找到要读取的文件: \t" + filePath);
43 | return "";
44 | }
45 |
46 | }
47 | }
48 |
49 |
50 | }
51 | #endif
52 | }
53 |
--------------------------------------------------------------------------------
/BMWebGLAdapter/AssetComponentUpdate_WebGL.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using ET;
3 |
4 | namespace BM
5 | {
6 | public static partial class AssetComponent
7 | {
8 | #if BMWebGL
9 | ///
10 | /// 检查分包是否需要更新
11 | ///
12 | /// 所有分包的名称以及是否验证文件CRC
13 | #pragma warning disable CS1998 // 异步方法缺少 "await" 运算符,将以同步方式运行
14 | public static async ETTask CheckAllBundlePackageUpdate(Dictionary bundlePackageNames)
15 | #pragma warning restore CS1998 // 异步方法缺少 "await" 运算符,将以同步方式运行
16 | {
17 | UpdateBundleDataInfo updateBundleDataInfo = new UpdateBundleDataInfo();
18 | updateBundleDataInfo.NeedUpdate = false;
19 |
20 | if (AssetComponentConfig.AssetLoadMode != AssetLoadMode.Build)
21 | {
22 | if (AssetComponentConfig.AssetLoadMode == AssetLoadMode.Local)
23 | {
24 | AssetComponentConfig.HotfixPath = AssetComponentConfig.LocalBundlePath;
25 | }
26 | else
27 | {
28 | #if !UNITY_EDITOR
29 | AssetLogHelper.LogError("AssetLoadMode = AssetLoadMode.Develop 只能在编辑器下运行");
30 | #endif
31 | }
32 | }
33 | else
34 | {
35 | AssetLogHelper.LogError("AssetLoadMode = AssetLoadMode.Build WebGL无需更新,请用Local模式");
36 | }
37 | return updateBundleDataInfo;
38 | }
39 |
40 | ///
41 | /// 下载更新
42 | ///
43 | #pragma warning disable CS1998 // 异步方法缺少 "await" 运算符,将以同步方式运行
44 | public static async ETTask DownLoadUpdate(UpdateBundleDataInfo updateBundleDataInfo)
45 | #pragma warning restore CS1998 // 异步方法缺少 "await" 运算符,将以同步方式运行
46 | {
47 | AssetLogHelper.LogError("WebGL无需更新");
48 | }
49 |
50 | #endif
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/BMWebGLAdapter/LoadBaseRuntimeLoad_WebGL.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using UnityEngine;
3 | using ET;
4 | using UnityEngine.Networking;
5 |
6 | namespace BM
7 | {
8 | public partial class LoadBase
9 | {
10 | #if BMWebGL
11 | internal void LoadAssetBundle(string bundlePackageName)
12 | {
13 | AddRefCount();
14 | if (_loadState == LoadState.Finish)
15 | {
16 | return;
17 | }
18 | //资源没有加载过也没有正在加载就同步加载出来
19 | string assetBundlePath = AssetComponent.BundleFileExistPath_WebGL(bundlePackageName, AssetBundleName);
20 |
21 | if (_loadState == LoadState.Loading)
22 | {
23 | AssetLogHelper.LogError("同步加载了正在异步加载的资源, WebGL出现此错误请检查资源加载是否冲突, 否则会引起异步加载失败。资源名: " + FilePath +
24 | "\nAssetBundle包名: " + AssetBundleName);
25 | using (UnityWebRequest webRequest = UnityWebRequestAssetBundle.GetAssetBundle(assetBundlePath))
26 | {
27 | webRequest.SendWebRequest();
28 | while (!webRequest.isDone) { }
29 | #if UNITY_2020_1_OR_NEWER
30 | if (webRequest.result == UnityWebRequest.Result.Success)
31 | #else
32 | if (string.IsNullOrEmpty(webRequest.error))
33 | #endif
34 | {
35 | AssetBundle assetBundle = DownloadHandlerAssetBundle.GetContent(webRequest);
36 | if (AssetBundle == null)
37 | {
38 | AssetBundle = assetBundle;
39 | }
40 | else
41 | {
42 | assetBundle.Unload(true);
43 | }
44 | }
45 | else
46 | {
47 | AssetLogHelper.LogError("UnityWebRequest同步加载正在异步加载的资源 AssetBundle失败: \t" + assetBundlePath);
48 | }
49 | }
50 | return;
51 | }
52 |
53 | using (UnityWebRequest webRequest = UnityWebRequestAssetBundle.GetAssetBundle(assetBundlePath))
54 | {
55 | webRequest.SendWebRequest();
56 | while (!webRequest.isDone) { }
57 | #if UNITY_2020_1_OR_NEWER
58 | if (webRequest.result == UnityWebRequest.Result.Success)
59 | #else
60 | if (string.IsNullOrEmpty(webRequest.error))
61 | #endif
62 | {
63 | AssetBundle assetBundle = DownloadHandlerAssetBundle.GetContent(webRequest);
64 | if (AssetBundle == null)
65 | {
66 | AssetBundle = assetBundle;
67 | }
68 | else
69 | {
70 | assetBundle.Unload(true);
71 | }
72 | }
73 | else
74 | {
75 | AssetLogHelper.LogError("UnityWebRequest加载AssetBundle失败: \t" + assetBundlePath);
76 | }
77 | }
78 | _loadState = LoadState.Finish;
79 | if (_loadProgress != null)
80 | {
81 | _loadProgress.WeqOperation = null;
82 | }
83 | }
84 |
85 | ///
86 | /// 异步加载LoadBase的AssetBundle
87 | ///
88 | internal async ETTask LoadAssetBundleAsync(string bundlePackageName)
89 | {
90 | AddRefCount();
91 | if (_loadState == LoadState.Finish)
92 | {
93 | return;
94 | }
95 | BundleRuntimeInfo bundleRuntimeInfo = AssetComponent.BundleNameToRuntimeInfo[bundlePackageName];
96 | string assetBundlePath = AssetComponent.BundleFileExistPath_WebGL(bundlePackageName, AssetBundleName);
97 | //获取一个协程锁
98 | CoroutineLock coroutineLock = await CoroutineLockComponent.Wait(CoroutineLockType.BundleMaster, LoadPathConvertHelper.LoadPathConvert(assetBundlePath));
99 | if (_loadState == LoadState.NoLoad)
100 | {
101 | _loadState = LoadState.Loading;
102 | await LoadBundleFinish(assetBundlePath);
103 | _loadState = LoadState.Finish;
104 | if (_loadProgress != null)
105 | {
106 | _loadProgress.WeqOperation = null;
107 | }
108 | }
109 | //协程锁解锁
110 | coroutineLock.Dispose();
111 | }
112 |
113 | ///
114 | /// 通过路径直接加载硬盘上的AssetBundle
115 | ///
116 | private async ETTask LoadBundleFinish(string assetBundlePath)
117 | {
118 | if (_loadState == LoadState.Finish)
119 | {
120 | return;
121 | }
122 | ETTask tcs = ETTask.Create(true);
123 |
124 | using (UnityWebRequest webRequest = UnityWebRequestAssetBundle.GetAssetBundle(assetBundlePath))
125 | {
126 | UnityWebRequestAsyncOperation weq = webRequest.SendWebRequest();
127 | if (_loadProgress != null)
128 | {
129 | _loadProgress.WeqOperation = weq;
130 | }
131 | weq.completed += (o) =>
132 | {
133 | tcs.SetResult();
134 | };
135 | await tcs;
136 | #if UNITY_2020_1_OR_NEWER
137 | if (webRequest.result == UnityWebRequest.Result.Success)
138 | #else
139 | if (string.IsNullOrEmpty(webRequest.error))
140 | #endif
141 | {
142 | AssetBundle assetBundle = DownloadHandlerAssetBundle.GetContent(webRequest);
143 | //判断是否还需要
144 | if (_refCount > 0)
145 | {
146 |
147 | if (AssetBundle == null)
148 | {
149 | AssetBundle = assetBundle;
150 | }
151 | else
152 | {
153 | assetBundle.Unload(true);
154 | }
155 | }
156 | else
157 | {
158 | assetBundle.Unload(true);
159 | AssetComponent.AddPreUnLoadPool(this);
160 | }
161 | }
162 | else
163 | {
164 | AssetLogHelper.LogError("UnityWebRequest加载AssetBundle失败: \t" + assetBundlePath);
165 | }
166 | }
167 |
168 | }
169 |
170 | ///
171 | /// 强制加载完成
172 | ///
173 | internal void ForceLoadFinish(string bundlePackageName)
174 | {
175 | if (_loadState == LoadState.Finish)
176 | {
177 | return;
178 | }
179 | string assetBundlePath = AssetComponent.BundleFileExistPath_WebGL(bundlePackageName, AssetBundleName);
180 | if (AssetBundle == null)
181 | {
182 | using (UnityWebRequest webRequest = UnityWebRequestAssetBundle.GetAssetBundle(assetBundlePath))
183 | {
184 | webRequest.SendWebRequest();
185 | while (!webRequest.isDone){}
186 | #if UNITY_2020_1_OR_NEWER
187 | if (webRequest.result == UnityWebRequest.Result.Success)
188 | #else
189 | if (string.IsNullOrEmpty(webRequest.error))
190 | #endif
191 | {
192 | AssetBundle assetBundle = DownloadHandlerAssetBundle.GetContent(webRequest);
193 | if (AssetBundle == null)
194 | {
195 | AssetBundle = assetBundle;
196 | _loadState = LoadState.Finish;
197 | if (_loadProgress != null)
198 | {
199 | _loadProgress.WeqOperation = null;
200 | }
201 | if (_refCount <= 0)
202 | {
203 | AssetComponent.AddPreUnLoadPool(this);
204 | }
205 | }
206 | else
207 | {
208 | AssetLogHelper.LogError("同步加载了正在异步加载的资源, WebGL出现此错误请检查资源加载是否冲突, 否则会引起异步加载失败。资源名: " + FilePath +
209 | "\nAssetBundle包名: " + AssetBundleName);
210 | assetBundle.Unload(true);
211 | }
212 | }
213 | else
214 | {
215 | AssetLogHelper.LogError("UnityWebRequest同步加载正在异步加载的资源 AssetBundle失败: \t" + assetBundlePath);
216 | }
217 | }
218 | return;
219 | }
220 |
221 | }
222 |
223 | ///
224 | /// 打开进度统计
225 | ///
226 | internal void OpenProgress()
227 | {
228 | _loadProgress = new WebLoadProgress();
229 | }
230 |
231 | ///
232 | /// 获取当前资源加载进度
233 | ///
234 | internal float GetProgress()
235 | {
236 | if (_loadProgress == null)
237 | {
238 | AssetLogHelper.LogError("未打开进度统计无法获取进度");
239 | return 0;
240 | }
241 | if (_loadState == LoadState.Finish)
242 | {
243 | return 1;
244 | }
245 | if (_loadState == LoadState.NoLoad)
246 | {
247 | return 0;
248 | }
249 | return _loadProgress.GetWebProgress();
250 | }
251 |
252 | #endif
253 |
254 | }
255 | }
256 |
--------------------------------------------------------------------------------
/BundleMasterRuntime/AssetComponentCheck.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using UnityEditor;
3 | using UnityEngine;
4 |
5 | namespace BM
6 | {
7 | public static partial class AssetComponent
8 | {
9 | ///
10 | /// 判定一个资源是否存在,如果这个资源是组里的资源,那么只能检测到这个资源所在的组是否存在
11 | ///
12 | public static bool CheckAssetExist(string assetPath, string bundlePackageName = null)
13 | {
14 | if (bundlePackageName == null)
15 | {
16 | bundlePackageName = AssetComponentConfig.DefaultBundlePackageName;
17 | }
18 | if (AssetComponentConfig.AssetLoadMode == AssetLoadMode.Develop)
19 | {
20 | #if UNITY_EDITOR
21 | assetPath = Path.Combine(Application.dataPath + "/../", assetPath);
22 | return File.Exists(assetPath);
23 | #else
24 | AssetLogHelper.LogError("检查资源: " + assetPath + " 失败(资源检查Develop模式只能在编辑器下运行)");
25 | return false;
26 | #endif
27 | }
28 | if (!BundleNameToRuntimeInfo.TryGetValue(bundlePackageName, out BundleRuntimeInfo bundleRuntimeInfo))
29 | {
30 | AssetLogHelper.LogError(bundlePackageName + "检查资源时分包没有初始化");
31 | return false;
32 | }
33 | string groupPath = GroupAssetHelper.IsGroupAsset(assetPath, bundleRuntimeInfo.LoadGroupDicKey);
34 | if (groupPath != null)
35 | {
36 | return true;
37 | }
38 | if (bundleRuntimeInfo.LoadFileDic.ContainsKey(assetPath))
39 | {
40 | return true;
41 | }
42 | return false;
43 | }
44 | }
45 | }
--------------------------------------------------------------------------------
/BundleMasterRuntime/AssetComponentConfig.cs:
--------------------------------------------------------------------------------
1 | using UnityEngine;
2 |
3 | namespace BM
4 | {
5 | ///
6 | /// 资源加载初始化所需配置信息
7 | ///
8 | public class AssetComponentConfig
9 | {
10 | ///
11 | /// 运行时配置
12 | ///
13 | private static BundleMasterRuntimeConfig _bmRuntimeConfig = null;
14 |
15 | ///
16 | /// 资源更新目录 Application.dataPath + "/../HotfixBundles/"
17 | ///
18 | public static string HotfixPath = "";
19 |
20 | ///
21 | /// 存放本地Bundle的位置 Application.streamingAssetsPath;
22 | ///
23 | public static string LocalBundlePath = Application.streamingAssetsPath;
24 |
25 | ///
26 | /// 资源服务器的地址 http://192.168.50.157/BundleData/
27 | ///
28 | public static string BundleServerUrl = "http://192.168.50.157/BundleData/";
29 |
30 | ///
31 | /// 默认加载的Bundle名
32 | ///
33 | public static string DefaultBundlePackageName = "";
34 |
35 | private static void InitRuntimeConfig()
36 | {
37 | if (_bmRuntimeConfig == null)
38 | {
39 | _bmRuntimeConfig = Resources.Load("BMConfig");
40 | }
41 | }
42 |
43 | ///
44 | /// 加载模式
45 | ///
46 | public static AssetLoadMode AssetLoadMode
47 | {
48 | get
49 | {
50 | InitRuntimeConfig();
51 | return _bmRuntimeConfig.AssetLoadMode;
52 | }
53 | }
54 |
55 | ///
56 | /// 最大同时下载的资源数量
57 | ///
58 | public static int MaxDownLoadCount
59 | {
60 | get
61 | {
62 | InitRuntimeConfig();
63 | return _bmRuntimeConfig.MaxDownLoadCount;
64 | }
65 | }
66 |
67 | ///
68 | /// 下载失败最多重试次数
69 | ///
70 | public static int ReDownLoadCount
71 | {
72 | get
73 | {
74 | InitRuntimeConfig();
75 | return _bmRuntimeConfig.ReDownLoadCount;
76 | }
77 | }
78 |
79 | }
80 | }
--------------------------------------------------------------------------------
/BundleMasterRuntime/AssetComponentInit.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text.RegularExpressions;
4 | using UnityEngine;
5 | using ET;
6 | using UnityEngine.Networking;
7 |
8 | namespace BM
9 | {
10 | public static partial class AssetComponent
11 | {
12 | ///
13 | /// Bundle初始化的信息
14 | ///
15 | internal static readonly Dictionary BundleNameToRuntimeInfo = new Dictionary();
16 |
17 | #if !(Nintendo_Switch || BMWebGL)
18 | ///
19 | /// 初始化
20 | ///
21 | public static async ETTask Initialize(string bundlePackageName, string secretKey = null)
22 | {
23 | if (AssetComponentConfig.AssetLoadMode == AssetLoadMode.Develop)
24 | {
25 | AssetLogHelper.Log("AssetLoadMode = Develop 不需要初始化Bundle配置文件");
26 | return false;
27 | }
28 | if (BundleNameToRuntimeInfo.ContainsKey(bundlePackageName))
29 | {
30 | AssetLogHelper.LogError(bundlePackageName + " 重复初始化");
31 | return false;
32 | }
33 | BundleRuntimeInfo bundleRuntimeInfo = new BundleRuntimeInfo(bundlePackageName, secretKey);
34 | BundleNameToRuntimeInfo.Add(bundlePackageName, bundleRuntimeInfo);
35 |
36 | ETTask fileTcs= ETTask.Create();
37 | string filePath = BundleFileExistPath(bundlePackageName, "FileLogs.txt", true);
38 | using (UnityWebRequest webRequest = UnityWebRequest.Get(filePath))
39 | {
40 | UnityWebRequestAsyncOperation weq = webRequest.SendWebRequest();
41 | weq.completed += (o) =>
42 | {
43 | fileTcs.SetResult();
44 | };
45 | await fileTcs;
46 | #if UNITY_2020_1_OR_NEWER
47 | if (webRequest.result != UnityWebRequest.Result.Success)
48 | #else
49 | if (!string.IsNullOrEmpty(webRequest.error))
50 | #endif
51 | {
52 | AssetLogHelper.LogError("初始化分包未找到FileLogs 分包名: " + bundlePackageName + "\t" + filePath);
53 | return false;
54 | }
55 | string fileLogs = webRequest.downloadHandler.text;
56 | Regex reg = new Regex(@"\<(.+?)>");
57 | MatchCollection matchCollection = reg.Matches(fileLogs);
58 | List dependFileName = new List();
59 | foreach (Match m in matchCollection)
60 | {
61 | string[] fileLog = m.Groups[1].Value.Split('|');
62 | LoadFile loadFile = new LoadFile();
63 | loadFile.FilePath = fileLog[0];
64 | loadFile.AssetBundleName = fileLog[1];
65 |
66 | if (fileLog.Length > 2)
67 | {
68 | for (int i = 2; i < fileLog.Length; i++)
69 | {
70 | dependFileName.Add(fileLog[i]);
71 | }
72 | }
73 | loadFile.DependFileName = dependFileName.ToArray();
74 | dependFileName.Clear();
75 | bundleRuntimeInfo.LoadFileDic.Add(loadFile.FilePath, loadFile);
76 | }
77 | }
78 | ETTask dependTcs = ETTask.Create();
79 | string dependPath = BundleFileExistPath(bundlePackageName, "DependLogs.txt", true);
80 | using (UnityWebRequest webRequest = UnityWebRequest.Get(dependPath))
81 | {
82 | UnityWebRequestAsyncOperation weq = webRequest.SendWebRequest();
83 | weq.completed += (o) =>
84 | {
85 | dependTcs.SetResult();
86 | };
87 | await dependTcs;
88 | #if UNITY_2020_1_OR_NEWER
89 | if (webRequest.result != UnityWebRequest.Result.Success)
90 | #else
91 | if (!string.IsNullOrEmpty(webRequest.error))
92 | #endif
93 | {
94 | AssetLogHelper.LogError("初始化分包未找到DependLogs 分包名: " + bundlePackageName + "\t" + dependPath);
95 | return false;
96 | }
97 | string dependLogs = webRequest.downloadHandler.text;
98 | Regex reg = new Regex(@"\<(.+?)>");
99 | MatchCollection matchCollection = reg.Matches(dependLogs);
100 | foreach (Match m in matchCollection)
101 | {
102 | string[] dependLog = m.Groups[1].Value.Split('|');
103 | LoadDepend loadDepend = new LoadDepend();
104 | loadDepend.FilePath = dependLog[0];
105 | loadDepend.AssetBundleName = dependLog[1];
106 | bundleRuntimeInfo.LoadDependDic.Add(loadDepend.FilePath, loadDepend);
107 | }
108 | }
109 | ETTask groupTcs = ETTask.Create();
110 | string groupPath = BundleFileExistPath(bundlePackageName, "GroupLogs.txt", true);
111 | using (UnityWebRequest webRequest = UnityWebRequest.Get(groupPath))
112 | {
113 | UnityWebRequestAsyncOperation weq = webRequest.SendWebRequest();
114 | weq.completed += (o) =>
115 | {
116 | groupTcs.SetResult();
117 | };
118 | await groupTcs;
119 | #if UNITY_2020_1_OR_NEWER
120 | if (webRequest.result != UnityWebRequest.Result.Success)
121 | #else
122 | if (!string.IsNullOrEmpty(webRequest.error))
123 | #endif
124 | {
125 | AssetLogHelper.LogError("初始化分包未找到GroupLogs 分包名: " + bundlePackageName+ "\t" + groupPath);
126 | return false;
127 | }
128 | string groupLogs = webRequest.downloadHandler.text;
129 | Regex reg = new Regex(@"\<(.+?)>");
130 | MatchCollection matchCollection = reg.Matches(groupLogs);
131 | foreach (Match m in matchCollection)
132 | {
133 | string[] groupLog = m.Groups[1].Value.Split('|');
134 | LoadGroup loadGroup = new LoadGroup();
135 | loadGroup.FilePath = groupLog[0];
136 | loadGroup.AssetBundleName = groupLog[1];
137 | if (groupLog.Length > 2)
138 | {
139 | for (int i = 2; i < groupLog.Length; i++)
140 | {
141 | loadGroup.DependFileName.Add(groupLog[i]);
142 | }
143 | }
144 | bundleRuntimeInfo.LoadGroupDic.Add(loadGroup.FilePath, loadGroup);
145 | bundleRuntimeInfo.LoadGroupDicKey.Add(loadGroup.FilePath);
146 | }
147 | }
148 | //加载当前分包的shader
149 | await LoadShader(bundlePackageName);
150 | return true;
151 | }
152 |
153 | ///
154 | /// 加载Shader文件
155 | ///
156 | private static async ETTask LoadShader(string bundlePackageName)
157 | {
158 | ETTask tcs = ETTask.Create();
159 | string shaderPath = BundleFileExistPath(bundlePackageName, "shader_" + bundlePackageName.ToLower(), true);
160 | if (BundleNameToRuntimeInfo[bundlePackageName].Encrypt)
161 | {
162 | byte[] shaderData = await VerifyHelper.GetDecryptDataAsync(shaderPath, null, BundleNameToRuntimeInfo[bundlePackageName].SecretKey);
163 | if (shaderData == null)
164 | {
165 | tcs.SetResult();
166 | }
167 | else
168 | {
169 | AssetBundleCreateRequest request = AssetBundle.LoadFromMemoryAsync(shaderData);
170 | request.completed += operation =>
171 | {
172 | BundleNameToRuntimeInfo[bundlePackageName].Shader = request.assetBundle;
173 | tcs.SetResult();
174 | };
175 | }
176 | }
177 | else
178 | {
179 | byte[] shaderData = await VerifyHelper.GetDecryptDataAsync(shaderPath, null, BundleNameToRuntimeInfo[bundlePackageName].SecretKey);
180 | if (shaderData == null)
181 | {
182 | tcs.SetResult();
183 | }
184 | else
185 | {
186 | AssetBundleCreateRequest request = AssetBundle.LoadFromFileAsync(BundleFileExistPath(bundlePackageName, "shader_" + bundlePackageName.ToLower(), false));
187 | request.completed += operation =>
188 | {
189 | BundleNameToRuntimeInfo[bundlePackageName].Shader = request.assetBundle;
190 | tcs.SetResult();
191 | };
192 | }
193 | }
194 | await tcs;
195 | }
196 | #endif
197 |
198 | }
199 | }
--------------------------------------------------------------------------------
/BundleMasterRuntime/AssetComponentLife.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using UnityEngine;
4 |
5 | namespace BM
6 | {
7 | public static partial class AssetComponent
8 | {
9 | ///
10 | /// 计时
11 | ///
12 | private static float _timer = 0;
13 |
14 | ///
15 | /// 下载进度更新器
16 | ///
17 | internal static Action DownLoadAction = null;
18 |
19 | ///
20 | /// 等待计时队列
21 | ///
22 | internal static Queue TimerAwaitQueue = new Queue();
23 |
24 | ///
25 | /// 卸载周期计时循环
26 | ///
27 | public static void Update()
28 | {
29 | float nowTime = Time.deltaTime;
30 | int awaitQueueCount = TimerAwaitQueue.Count;
31 | for (int i = 0; i < awaitQueueCount; i++)
32 | {
33 | TimerAwait timerAwait = TimerAwaitQueue.Dequeue();
34 | if (!timerAwait.CalcSubTime(nowTime))
35 | {
36 | TimerAwaitQueue.Enqueue(timerAwait);
37 | }
38 | }
39 | _timer += nowTime;
40 | if (_timer >= _unLoadCirculateTime)
41 | {
42 | _timer = 0;
43 | AutoAddToTrueUnLoadPool();
44 | }
45 |
46 | //更新下载完成任务
47 | lock (DownloadBundleHelper.DownLoadFinishQueue)
48 | {
49 | if (DownloadBundleHelper.DownLoadFinishQueue.Count > 0)
50 | {
51 | int downLoadFinishCount = DownloadBundleHelper.DownLoadFinishQueue.Count;
52 | for (int i = 0; i < downLoadFinishCount; i++)
53 | {
54 | DownloadBundleHelper.DownLoadFinishQueue.Dequeue().SetResult();
55 | }
56 | }
57 | }
58 | DownLoadAction?.Invoke(nowTime);
59 |
60 |
61 | CoroutineLockComponent.UpDate();
62 | }
63 |
64 | ///
65 | /// 游戏关闭时调用
66 | ///
67 | public static void Destroy()
68 | {
69 | #if !BMWebGL
70 | LMTD.ThreadFactory.Destroy();
71 | #endif
72 | }
73 | }
74 | }
--------------------------------------------------------------------------------
/BundleMasterRuntime/AssetComponentTools.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using System.Text;
3 | using ET;
4 |
5 | namespace BM
6 | {
7 | public static partial class AssetComponent
8 | {
9 |
10 | ///
11 | /// 获取Bundle信息文件的路径
12 | ///
13 | internal static string BundleFileExistPath(string bundlePackageName, string fileName, bool isWebLoad)
14 | {
15 | string path = GetBasePath(bundlePackageName, fileName);
16 | if (isWebLoad)
17 | {
18 | //通过webReq加载
19 | #if UNITY_ANDROID && !UNITY_EDITOR
20 | if (!path.Contains("file:///"))
21 | {
22 | path = "file://" + path;
23 | }
24 | #elif UNITY_IOS && !UNITY_EDITOR
25 | path = "file://" + path;
26 | #elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
27 | path = "file://" + path;
28 | #elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
29 | #else
30 | #endif
31 | return path;
32 | }
33 | else
34 | {
35 | //直接加载
36 | #if UNITY_ANDROID && !UNITY_EDITOR
37 | #elif UNITY_IOS && !UNITY_EDITOR
38 | #elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
39 | #elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
40 | #else
41 | #endif
42 | return path;
43 | }
44 | }
45 |
46 | ///
47 | /// 得到基础的路径
48 | ///
49 | private static string GetBasePath(string bundlePackageName, string fileName)
50 | {
51 | if (AssetComponentConfig.AssetLoadMode == AssetLoadMode.Local)
52 | {
53 | string path = Path.Combine(AssetComponentConfig.LocalBundlePath, bundlePackageName, fileName);
54 | return path;
55 | }
56 | else
57 | {
58 | string path = Path.Combine(AssetComponentConfig.HotfixPath, bundlePackageName, fileName);
59 | if (!File.Exists(path))
60 | {
61 | //热更目录不存在,返回streaming目录
62 | path = Path.Combine(AssetComponentConfig.LocalBundlePath, bundlePackageName, fileName);
63 | }
64 | //热更目录存在,返回热更目录
65 | return path;
66 | }
67 | }
68 |
69 | ///
70 | /// 获取分包的更新索引列表
71 | ///
72 | private static async ETTask GetRemoteBundlePackageVersionLog(string bundlePackageName)
73 | {
74 | byte[] data = await DownloadBundleHelper.DownloadDataByUrl(Path.Combine(AssetComponentConfig.BundleServerUrl, bundlePackageName, "VersionLogs.txt"));
75 | if (data == null)
76 | {
77 | AssetLogHelper.LogError(bundlePackageName + "获取更新索引列表失败");
78 | return null;
79 | }
80 | return System.Text.Encoding.UTF8.GetString(data);
81 | }
82 |
83 | ///
84 | /// 创建更新后的Log文件
85 | ///
86 | /// 文件的全路径
87 | /// 文件的内容
88 | private static void CreateUpdateLogFile(string filePath, string fileData)
89 | {
90 | using (StreamWriter sw = new StreamWriter(filePath))
91 | {
92 | StringBuilder sb = new StringBuilder();
93 | sb.Append(fileData);
94 | sw.WriteLine(sb.ToString());
95 | }
96 | }
97 |
98 | }
99 |
100 | }
--------------------------------------------------------------------------------
/BundleMasterRuntime/Base/LoadBase.cs:
--------------------------------------------------------------------------------
1 | namespace BM
2 | {
3 | ///
4 | /// 加载类型的基类
5 | ///
6 | public partial class LoadBase
7 | {
8 | public string FilePath;
9 | public string AssetBundleName;
10 | protected LoadType LoadType = LoadType.Null;
11 | }
12 |
13 | public enum LoadType
14 | {
15 | Null = 0,
16 | File = 1,
17 | Depend = 2,
18 | Group = 3
19 | }
20 | }
--------------------------------------------------------------------------------
/BundleMasterRuntime/Base/LoadBaseRuntimeLoad.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using UnityEngine;
3 | using ET;
4 |
5 | namespace BM
6 | {
7 | public partial class LoadBase
8 | {
9 | ///
10 | /// 引用计数
11 | ///
12 | private int _refCount = 0;
13 |
14 | ///
15 | /// AssetBundle加载的状态
16 | ///
17 | private LoadState _loadState = LoadState.NoLoad;
18 |
19 | ///
20 | /// 加载请求索引
21 | ///
22 | private AssetBundleCreateRequest _assetBundleCreateRequest;
23 |
24 | ///
25 | /// AssetBundle的引用
26 | ///
27 | public AssetBundle AssetBundle = null;
28 |
29 | #if !Nintendo_Switch
30 | ///
31 | /// 需要统计进度
32 | ///
33 | private WebLoadProgress _loadProgress = null;
34 | #endif
35 |
36 | private void AddRefCount()
37 | {
38 | _refCount++;
39 | if (_refCount == 1 && _loadState == LoadState.Finish)
40 | {
41 | AssetComponent.SubPreUnLoadPool(this);
42 | }
43 | }
44 |
45 | internal void SubRefCount()
46 | {
47 | _refCount--;
48 | if (_loadState == LoadState.NoLoad)
49 | {
50 | AssetLogHelper.LogError("资源未被加载,引用不可能减少\n" + FilePath);
51 | return;
52 | }
53 | if (_loadState == LoadState.Loading)
54 | {
55 | AssetLogHelper.Log("资源加载中,等加载完成后再进入卸载逻辑\n" + FilePath);
56 | return;
57 | }
58 | if (_refCount <= 0)
59 | {
60 | //需要进入预卸载池等待卸载
61 | AssetComponent.AddPreUnLoadPool(this);
62 | }
63 | }
64 |
65 | #if !(BMWebGL || Nintendo_Switch)
66 |
67 | internal void LoadAssetBundle(string bundlePackageName)
68 | {
69 | AddRefCount();
70 | if (_loadState == LoadState.Finish)
71 | {
72 | return;
73 | }
74 | if (_loadState == LoadState.Loading)
75 | {
76 | AssetLogHelper.LogError("同步加载了正在异步加载的资源, 打断异步加载资源会导致所有异步加载的资源都立刻同步加载出来。资源名: " + FilePath +
77 | "\nAssetBundle包名: " + AssetBundleName);
78 | if (_assetBundleCreateRequest != null)
79 | {
80 | AssetBundle = _assetBundleCreateRequest.assetBundle;
81 | return;
82 | }
83 | }
84 | //资源没有加载过也没有正在加载就同步加载出来
85 | if (AssetComponent.BundleNameToRuntimeInfo[bundlePackageName].Encrypt)
86 | {
87 | string assetBundlePath = AssetComponent.BundleFileExistPath(bundlePackageName, AssetBundleName, true);
88 | byte[] data = VerifyHelper.GetDecryptData(assetBundlePath, AssetComponent.BundleNameToRuntimeInfo[bundlePackageName].SecretKey);
89 | AssetBundle = AssetBundle.LoadFromMemory(data);
90 | }
91 | else
92 | {
93 | string assetBundlePath = AssetComponent.BundleFileExistPath(bundlePackageName, AssetBundleName, false);
94 | AssetBundle = AssetBundle.LoadFromFile(assetBundlePath);
95 | }
96 | _loadState = LoadState.Finish;
97 | }
98 |
99 | ///
100 | /// 异步加载LoadBase的AssetBundle
101 | ///
102 | internal async ETTask LoadAssetBundleAsync(string bundlePackageName)
103 | {
104 | AddRefCount();
105 | if (_loadState == LoadState.Finish)
106 | {
107 | return;
108 | }
109 | BundleRuntimeInfo bundleRuntimeInfo = AssetComponent.BundleNameToRuntimeInfo[bundlePackageName];
110 | string assetBundlePath = AssetComponent.BundleFileExistPath(bundlePackageName, AssetBundleName, bundleRuntimeInfo.Encrypt);
111 | //获取一个协程锁
112 | CoroutineLock coroutineLock = await CoroutineLockComponent.Wait(CoroutineLockType.BundleMaster, LoadPathConvertHelper.LoadPathConvert(assetBundlePath));
113 | if (_loadState == LoadState.NoLoad)
114 | {
115 | _loadState = LoadState.Loading;
116 | if (bundleRuntimeInfo.Encrypt)
117 | {
118 | await LoadDataFinish(assetBundlePath, bundleRuntimeInfo.SecretKey);
119 | }
120 | else
121 | {
122 | await LoadBundleFinish(assetBundlePath);
123 | }
124 | _loadState = LoadState.Finish;
125 | }
126 | //协程锁解锁
127 | coroutineLock.Dispose();
128 | }
129 |
130 | ///
131 | /// 通过Byte加载完成(只有启用了异或加密才使用此加载方式)
132 | ///
133 | private async ETTask LoadDataFinish(string assetBundlePath, char[] bundlePackageSecretKey)
134 | {
135 | byte[] data = await VerifyHelper.GetDecryptDataAsync(assetBundlePath, _loadProgress, bundlePackageSecretKey);
136 | if (_loadState == LoadState.Finish)
137 | {
138 | return;
139 | }
140 | ETTask tcs = ETTask.Create(true);
141 | _assetBundleCreateRequest = AssetBundle.LoadFromMemoryAsync(data);
142 | _assetBundleCreateRequest.completed += operation =>
143 | {
144 | AssetBundle = _assetBundleCreateRequest.assetBundle;
145 | tcs.SetResult();
146 | //判断是否还需要
147 | if (_refCount <= 0)
148 | {
149 | AssetComponent.AddPreUnLoadPool(this);
150 | }
151 | };
152 | await tcs;
153 | }
154 |
155 | ///
156 | /// 通过路径直接加载硬盘上的AssetBundle
157 | ///
158 | private async ETTask LoadBundleFinish(string assetBundlePath)
159 | {
160 | if (_loadState == LoadState.Finish)
161 | {
162 | return;
163 | }
164 | ETTask tcs = ETTask.Create(true);
165 | _assetBundleCreateRequest = AssetBundle.LoadFromFileAsync(assetBundlePath);
166 | _assetBundleCreateRequest.completed += operation =>
167 | {
168 | AssetBundle = _assetBundleCreateRequest.assetBundle;
169 | tcs.SetResult();
170 | //判断是否还需要
171 | if (_refCount <= 0)
172 | {
173 | AssetComponent.AddPreUnLoadPool(this);
174 | }
175 | };
176 | await tcs;
177 | }
178 |
179 | ///
180 | /// 强制加载完成
181 | ///
182 | internal void ForceLoadFinish(string bundlePackageName)
183 | {
184 | if (_loadState == LoadState.Finish)
185 | {
186 | return;
187 | }
188 | if (_assetBundleCreateRequest != null)
189 | {
190 | AssetLogHelper.LogError("触发强制加载, 打断异步加载资源会导致所有异步加载的资源都立刻同步加载出来。资源名: " + FilePath +
191 | "\nAssetBundle包名: " + AssetBundleName);
192 | AssetBundle = _assetBundleCreateRequest.assetBundle;
193 | return;
194 | }
195 |
196 | if (AssetComponent.BundleNameToRuntimeInfo[bundlePackageName].Encrypt)
197 | {
198 | string assetBundlePath = AssetComponent.BundleFileExistPath(bundlePackageName, AssetBundleName, AssetComponent.BundleNameToRuntimeInfo[bundlePackageName].Encrypt);
199 | byte[] data = VerifyHelper.GetDecryptData(assetBundlePath, AssetComponent.BundleNameToRuntimeInfo[bundlePackageName].SecretKey);
200 | AssetBundle = AssetBundle.LoadFromMemory(data);
201 | }
202 | else
203 | {
204 | string assetBundlePath = AssetComponent.BundleFileExistPath(bundlePackageName, AssetBundleName, false);
205 | AssetBundle = AssetBundle.LoadFromFile(assetBundlePath);
206 | }
207 | _loadState = LoadState.Finish;
208 | //判断是否还需要
209 | if (_refCount <= 0)
210 | {
211 | AssetComponent.AddPreUnLoadPool(this);
212 | }
213 | }
214 |
215 | ///
216 | /// 打开进度统计
217 | ///
218 | internal void OpenProgress()
219 | {
220 | _loadProgress = new WebLoadProgress();
221 | }
222 |
223 | internal float GetProgress()
224 | {
225 | if (_loadProgress == null)
226 | {
227 | AssetLogHelper.LogError("未打开进度统计无法获取进度");
228 | return 0;
229 | }
230 | if (_loadState == LoadState.Finish)
231 | {
232 | return 1;
233 | }
234 | if (_loadState == LoadState.NoLoad)
235 | {
236 | return 0;
237 | }
238 |
239 | if (_loadProgress.WeqOperation == null)
240 | {
241 | if (_assetBundleCreateRequest == null)
242 | {
243 | return _loadProgress.GetWebProgress() / 2;
244 | }
245 | return _assetBundleCreateRequest.progress;
246 | }
247 | if (_assetBundleCreateRequest == null)
248 | {
249 | return _loadProgress.GetWebProgress() / 2;
250 | }
251 | return (_assetBundleCreateRequest.progress + 1.0f) / 2;
252 | }
253 |
254 | #endif
255 |
256 |
257 | }
258 |
259 | ///
260 | /// AssetBundle加载的状态
261 | ///
262 | internal enum LoadState
263 | {
264 | NoLoad = 0,
265 | Loading = 1,
266 | Finish = 2
267 | }
268 | }
269 |
--------------------------------------------------------------------------------
/BundleMasterRuntime/Base/LoadBaseUnLoad.cs:
--------------------------------------------------------------------------------
1 | namespace BM
2 | {
3 | public partial class LoadBase
4 | {
5 | ///
6 | /// 卸载AssetBundle
7 | ///
8 | public void UnLoad()
9 | {
10 | _assetBundleCreateRequest = null;
11 | AssetBundle.Unload(true);
12 | _loadState = LoadState.NoLoad;
13 | }
14 | }
15 | }
--------------------------------------------------------------------------------
/BundleMasterRuntime/Base/LoadDepend.cs:
--------------------------------------------------------------------------------
1 | namespace BM
2 | {
3 | ///
4 | /// 一个文件的依赖项
5 | ///
6 | public class LoadDepend : LoadBase
7 | {
8 | public LoadDepend()
9 | {
10 | base.LoadType = LoadType.Depend;
11 | }
12 | }
13 | }
--------------------------------------------------------------------------------
/BundleMasterRuntime/Base/LoadFile.cs:
--------------------------------------------------------------------------------
1 | namespace BM
2 | {
3 | ///
4 | /// 一个文件以及它的bundle包信息
5 | ///
6 | public class LoadFile : LoadBase
7 | {
8 | ///
9 | /// 依赖的文件的名字
10 | ///
11 | public string[] DependFileName;
12 |
13 | public LoadFile()
14 | {
15 | base.LoadType = LoadType.File;
16 | }
17 |
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/BundleMasterRuntime/Base/LoadGroup.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace BM
4 | {
5 | public class LoadGroup : LoadBase
6 | {
7 | ///
8 | /// 组Bundle里所有资源
9 | ///
10 | public List FilePathList = new List();
11 |
12 | ///
13 | /// 依赖的文件的名字
14 | ///
15 | public List DependFileName = new List();
16 |
17 | public LoadGroup()
18 | {
19 | base.LoadType = LoadType.Group;
20 | }
21 | }
22 | }
--------------------------------------------------------------------------------
/BundleMasterRuntime/Base/LoadHandlerFactory.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace BM
4 | {
5 | internal static class LoadHandlerFactory
6 | {
7 | private static Queue _loadHandlerPool = new Queue();
8 |
9 | internal static LoadHandler GetLoadHandler(string assetPath, string bundlePackageName, bool haveHandler, bool isPool)
10 | {
11 | LoadHandler loadHandler;
12 | if (_loadHandlerPool.Count > 0 && isPool)
13 | {
14 | loadHandler = _loadHandlerPool.Dequeue();
15 | loadHandler.Init(assetPath, bundlePackageName, haveHandler);
16 | return loadHandler;
17 | }
18 | loadHandler = CreateLoadHandler(assetPath, bundlePackageName, haveHandler, isPool);
19 | return loadHandler;
20 | }
21 |
22 | private static LoadHandler CreateLoadHandler(string assetPath, string bundlePackageName, bool haveHandler, bool isPool)
23 | {
24 | LoadHandler loadHandler = new LoadHandler(isPool);
25 | loadHandler.Init(assetPath, bundlePackageName, haveHandler);
26 | return loadHandler;
27 | }
28 |
29 | internal static void EnterPool(LoadHandler loadHandler)
30 | {
31 | _loadHandlerPool.Enqueue(loadHandler);
32 | }
33 | }
34 | }
--------------------------------------------------------------------------------
/BundleMasterRuntime/Base/WebLoadProgress.cs:
--------------------------------------------------------------------------------
1 | using UnityEngine.Networking;
2 |
3 | namespace BM
4 | {
5 | public class WebLoadProgress
6 | {
7 | internal UnityWebRequestAsyncOperation WeqOperation;
8 |
9 | internal float GetWebProgress()
10 | {
11 | if (WeqOperation != null)
12 | {
13 | return WeqOperation.progress;
14 | }
15 | return 1;
16 | }
17 | }
18 | }
--------------------------------------------------------------------------------
/BundleMasterRuntime/BundleMasterRuntimeConfig.cs:
--------------------------------------------------------------------------------
1 | using UnityEngine;
2 |
3 | namespace BM
4 | {
5 | public class BundleMasterRuntimeConfig : ScriptableObject
6 | {
7 | ///
8 | /// 加载模式
9 | ///
10 | public AssetLoadMode AssetLoadMode;
11 |
12 | ///
13 | /// 最大同时下载的资源数量
14 | ///
15 | public int MaxDownLoadCount;
16 |
17 | ///
18 | /// 下载失败最多重试次数
19 | ///
20 | public int ReDownLoadCount;
21 | }
22 | }
--------------------------------------------------------------------------------
/BundleMasterRuntime/BundleRuntimeInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using ET;
3 | using UnityEngine;
4 |
5 | namespace BM
6 | {
7 | ///
8 | /// 存放一个Bundle分包的初始化信息
9 | ///
10 | public class BundleRuntimeInfo
11 | {
12 | ///
13 | /// 主动加载的文件
14 | ///
15 | internal readonly Dictionary LoadFileDic = new Dictionary();
16 |
17 | ///
18 | /// 依赖加载的文件
19 | ///
20 | internal readonly Dictionary LoadDependDic = new Dictionary();
21 |
22 | ///
23 | /// 细分组的文件
24 | ///
25 | internal readonly Dictionary LoadGroupDic = new Dictionary();
26 |
27 | ///
28 | /// 细分组的路径
29 | ///
30 | internal readonly List LoadGroupDicKey = new List();
31 |
32 | ///
33 | /// 资源路径对应的资源LoadHandler
34 | ///
35 | internal readonly Dictionary AllAssetLoadHandler = new Dictionary();
36 |
37 | ///
38 | /// 所有没有卸载的LoadHandler
39 | ///
40 | internal readonly Dictionary UnLoadHandler = new Dictionary();
41 |
42 | ///
43 | /// 分包的名称
44 | ///
45 | private string BundlePackageName;
46 |
47 | ///
48 | /// 分包是否加密
49 | ///
50 | internal bool Encrypt = false;
51 |
52 | ///
53 | /// 分包的加密Key
54 | ///
55 | internal char[] SecretKey = null;
56 |
57 | ///
58 | /// Shader的AssetBundle
59 | ///
60 | internal AssetBundle Shader = null;
61 |
62 | public BundleRuntimeInfo(string bundlePackageName, string secretKey = null)
63 | {
64 | BundlePackageName = bundlePackageName;
65 | if (secretKey != null)
66 | {
67 | SecretKey = secretKey.ToCharArray();
68 | Encrypt = true;
69 | }
70 | }
71 |
72 | public T Load(string assetPath) where T : UnityEngine.Object => AssetComponent.Load(assetPath, BundlePackageName);
73 | public T Load(out LoadHandler loadHandler, string assetPath, bool isPool = false) where T : UnityEngine.Object => AssetComponent.Load(out loadHandler, assetPath, isPool, BundlePackageName);
74 | public UnityEngine.Object Load(string assetPath) => AssetComponent.Load(assetPath, BundlePackageName);
75 | public UnityEngine.Object Load(out LoadHandler loadHandler, string assetPath, bool isPool = false) => AssetComponent.Load(out loadHandler, assetPath, isPool, BundlePackageName);
76 | public async ETTask LoadAsync(string assetPath) where T : UnityEngine.Object => await AssetComponent.LoadAsync(assetPath, BundlePackageName);
77 | public ETTask LoadAsync(out LoadHandler loadHandler, string assetPath, bool isPool = false) where T : UnityEngine.Object => AssetComponent.LoadAsync(out loadHandler, assetPath, isPool, BundlePackageName);
78 | public async ETTask LoadAsync(string assetPath) => await AssetComponent.LoadAsync(assetPath, BundlePackageName);
79 | public ETTask LoadAsync(out LoadHandler loadHandler, string assetPath, bool isPool = false) => AssetComponent.LoadAsync(out loadHandler,assetPath, isPool, BundlePackageName);
80 | public LoadSceneHandler LoadScene(string scenePath) => AssetComponent.LoadScene(scenePath, BundlePackageName);
81 | public async ETTask LoadSceneAsync(string scenePath) => await AssetComponent.LoadSceneAsync(scenePath, BundlePackageName);
82 | public ETTask LoadSceneAsync(out LoadSceneHandler loadSceneHandler, string scenePath) => AssetComponent.LoadSceneAsync(out loadSceneHandler, scenePath, BundlePackageName);
83 |
84 | ///
85 | /// 通过路径卸载(场景资源不可以通过路径卸载)
86 | ///
87 | public void UnLoadByPath(string assetPath) => AssetComponent.UnLoadByPath(assetPath, BundlePackageName);
88 | }
89 | }
--------------------------------------------------------------------------------
/BundleMasterRuntime/DownLoadTask.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using ET;
3 | using System.Collections.Generic;
4 | using System.IO;
5 | using UnityEngine.Networking;
6 | using UnityEngine;
7 |
8 | #if !BMWebGL
9 | using LMTD;
10 | #endif
11 |
12 | namespace BM
13 | {
14 | #if !BMWebGL
15 | public class DownLoadTask
16 | {
17 | public UpdateBundleDataInfo UpdateBundleDataInfo;
18 |
19 | ///
20 | /// 资源更新下载完成回调位
21 | ///
22 | public ETTask DownLoadingKey;
23 |
24 | public Dictionary> PackageDownLoadTask;
25 |
26 | ///
27 | /// 下载的资源分包名称
28 | ///
29 | public string PackegName;
30 |
31 | ///
32 | /// 分包所在路径
33 | ///
34 | public string DownLoadPackagePath;
35 |
36 | ///
37 | /// 下载的文件的名称
38 | ///
39 | public string FileName;
40 |
41 | ///
42 | /// 下载的文件的大小
43 | ///
44 | public long FileSize;
45 |
46 | public async ETTask DownLoad()
47 | {
48 | string url = Path.Combine(AssetComponentConfig.BundleServerUrl, PackegName, UnityWebRequest.EscapeURL(FileName));
49 | if (FileName.Contains("\\"))
50 | {
51 | string[] pathSplits = FileName.Split('\\');
52 | string filePath = "";
53 | string fileUrls = "";
54 | for (int i = 0; i < pathSplits.Length - 1; i++)
55 | {
56 | filePath += (pathSplits[i] + "/");
57 | fileUrls += (UnityWebRequest.EscapeURL(pathSplits[i]) + "/");
58 | }
59 | fileUrls += (UnityWebRequest.EscapeURL(pathSplits[pathSplits.Length - 1]));
60 | Directory.CreateDirectory(Path.Combine(AssetComponentConfig.HotfixPath, PackegName, filePath));
61 | url = Path.Combine(AssetComponentConfig.BundleServerUrl, PackegName, fileUrls);
62 | }
63 | float startDownLoadTime = Time.realtimeSinceStartup;
64 | DownLoadData downLoadData = await DownloadBundleHelper.DownloadRefDataByUrl(url);
65 | if (downLoadData.Data == null)
66 | {
67 | UpdateBundleDataInfo.CancelUpdate();
68 | }
69 | //说明下载更新已经被取消
70 | if (UpdateBundleDataInfo.Cancel)
71 | {
72 | DownLoadData.Recovery(downLoadData);
73 | return;
74 | }
75 | Debug.Assert(downLoadData.Data != null, "downLoadData.Data != null");
76 | int dataLength = downLoadData.Data.Length;
77 |
78 | string fileCreatePath = PathUnifiedHelper.UnifiedPath(Path.Combine(DownLoadPackagePath, FileName));
79 | using (FileStream fs = new FileStream(fileCreatePath, FileMode.Create))
80 | {
81 | //大于2M用异步
82 | if (dataLength > 2097152)
83 | {
84 | await fs.WriteAsync(downLoadData.Data, 0, downLoadData.Data.Length);
85 | }
86 | else
87 | {
88 | fs.Write(downLoadData.Data, 0, downLoadData.Data.Length);
89 | }
90 | fs.Close();
91 | }
92 | UpdateBundleDataInfo.AddCRCFileInfo(PackegName, FileName, VerifyHelper.GetCRC32(downLoadData));
93 | UpdateBundleDataInfo.FinishUpdateSize += downLoadData.Data.Length;
94 | UpdateBundleDataInfo.FinishDownLoadBundleCount++;
95 | DownLoadData.Recovery(downLoadData);
96 | foreach (Queue downLoadTaskQueue in PackageDownLoadTask.Values)
97 | {
98 | if (downLoadTaskQueue.Count > 0)
99 | {
100 | downLoadTaskQueue.Dequeue().DownLoad().Coroutine();
101 | return;
102 | }
103 | }
104 | //说明下载完成了
105 | if (UpdateBundleDataInfo.FinishDownLoadBundleCount < UpdateBundleDataInfo.NeedDownLoadBundleCount)
106 | {
107 | DownLoadData.ClearPool();
108 | return;
109 | }
110 | UpdateBundleDataInfo.FinishUpdate = true;
111 | DownLoadingKey.SetResult();
112 | }
113 |
114 |
115 | public async ETTask ThreadDownLoad()
116 | {
117 | //计算URL
118 | string url = Path.Combine(AssetComponentConfig.BundleServerUrl, PackegName, UnityWebRequest.EscapeURL(FileName));
119 | if (FileName.Contains("\\"))
120 | {
121 | string[] pathSplits = FileName.Split('\\');
122 | string filePath = "";
123 | string fileUrls = "";
124 | for (int i = 0; i < pathSplits.Length - 1; i++)
125 | {
126 | filePath += (pathSplits[i] + "/");
127 | fileUrls += (UnityWebRequest.EscapeURL(pathSplits[i]) + "/");
128 | }
129 | fileUrls += (UnityWebRequest.EscapeURL(pathSplits[pathSplits.Length - 1]));
130 | Directory.CreateDirectory(Path.Combine(AssetComponentConfig.HotfixPath, PackegName, filePath));
131 | url = Path.Combine(AssetComponentConfig.BundleServerUrl, PackegName, fileUrls);
132 | }
133 | //计算文件存储路径
134 | string fileCreatePath = PathUnifiedHelper.UnifiedPath(Path.Combine(DownLoadPackagePath, FileName));
135 | //开始下载
136 | LmtDownloadInfo lmtDownloadInfo = await DownloadBundleHelper.DownloadData(url, fileCreatePath, UpdateBundleDataInfo);
137 | //说明下载更新已经被取消
138 | if (UpdateBundleDataInfo.Cancel)
139 | {
140 | return;
141 | }
142 | UpdateBundleDataInfo.AddCRCFileInfo(PackegName, FileName, lmtDownloadInfo.DownLoadFileCRC);
143 | UpdateBundleDataInfo.FinishDownLoadBundleCount++;
144 | //检查新的需要下载的资源
145 | foreach (Queue downLoadTaskQueue in PackageDownLoadTask.Values)
146 | {
147 | if (downLoadTaskQueue.Count > 0)
148 | {
149 | downLoadTaskQueue.Dequeue().ThreadDownLoad().Coroutine();
150 | return;
151 | }
152 | }
153 | //说明下载完成了
154 | if (UpdateBundleDataInfo.FinishDownLoadBundleCount < UpdateBundleDataInfo.NeedDownLoadBundleCount)
155 | {
156 | DownLoadData.ClearPool();
157 | return;
158 | }
159 | UpdateBundleDataInfo.FinishUpdate = true;
160 | DownLoadingKey.SetResult();
161 | }
162 |
163 | }
164 | #endif
165 | }
--------------------------------------------------------------------------------
/BundleMasterRuntime/Helper/AssetLogHelper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using UnityEngine;
3 |
4 | namespace BM
5 | {
6 | public static class AssetLogHelper
7 | {
8 | public static void Log(string message)
9 | {
10 | Debug.Log(message);
11 | }
12 |
13 | public static void LogError(string message)
14 | {
15 | Debug.LogError(message);
16 | }
17 |
18 | public static void LogError(Exception e)
19 | {
20 | if (e.Data.Contains("StackTrace"))
21 | {
22 | Debug.LogError($"{e.Data["StackTrace"]}\n{e}");
23 | return;
24 | }
25 | string str = e.ToString();
26 | Debug.LogError(str);
27 | }
28 | }
29 | }
--------------------------------------------------------------------------------
/BundleMasterRuntime/Helper/DeleteHelper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 |
4 | namespace BM
5 | {
6 | public static class DeleteHelper
7 | {
8 |
9 | }
10 | }
--------------------------------------------------------------------------------
/BundleMasterRuntime/Helper/DownloadBundleHelper.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Threading.Tasks;
3 | using UnityEngine.Networking;
4 | using ET;
5 |
6 | #if !BMWebGL
7 | using LMTD;
8 | #endif
9 |
10 | namespace BM
11 | {
12 | public static class DownloadBundleHelper
13 | {
14 | public static async ETTask DownloadDataByUrl(string url)
15 | {
16 | for (int i = 0; i < AssetComponentConfig.ReDownLoadCount; i++)
17 | {
18 | byte[] data = await DownloadData(url);
19 | if (data != null)
20 | {
21 | return data;
22 | }
23 | }
24 | AssetLogHelper.LogError("下载资源失败: " + url);
25 | return null;
26 | }
27 |
28 | private static async ETTask DownloadData(string url)
29 | {
30 | using (UnityWebRequest webRequest = UnityWebRequest.Get(url))
31 | {
32 | UnityWebRequestAsyncOperation webRequestAsync = webRequest.SendWebRequest();
33 | ETTask waitDown = ETTask.Create(true);
34 | webRequestAsync.completed += (asyncOperation) =>
35 | {
36 | waitDown.SetResult();
37 | };
38 | await waitDown;
39 | #if UNITY_2020_1_OR_NEWER
40 | if (webRequest.result != UnityWebRequest.Result.Success)
41 | #else
42 | if (!string.IsNullOrEmpty(webRequest.error))
43 | #endif
44 | {
45 | AssetLogHelper.Log("下载Bundle失败 重试\n" + webRequest.error + "\nURL:" + url);
46 | return null;
47 | }
48 | return webRequest.downloadHandler.data;
49 | }
50 | }
51 |
52 |
53 | public static async ETTask DownloadRefDataByUrl(string url)
54 | {
55 | DownLoadData downLoadData = DownLoadData.Get();
56 | for (int i = 0; i < AssetComponentConfig.ReDownLoadCount; i++)
57 | {
58 | await DownloadData(url, downLoadData);
59 | if (downLoadData.Data != null)
60 | {
61 | return downLoadData;
62 | }
63 | }
64 | AssetLogHelper.LogError("下载资源失败: " + url);
65 | return null;
66 | }
67 |
68 | private static async ETTask DownloadData(string url, DownLoadData downLoadData)
69 | {
70 | using (UnityWebRequest webRequest = UnityWebRequest.Get(url))
71 | {
72 | UnityWebRequestAsyncOperation webRequestAsync = webRequest.SendWebRequest();
73 | ETTask waitDown = ETTask.Create(true);
74 | webRequestAsync.completed += (asyncOperation) =>
75 | {
76 | waitDown.SetResult();
77 | };
78 | await waitDown;
79 | #if UNITY_2020_1_OR_NEWER
80 | if (webRequest.result != UnityWebRequest.Result.Success)
81 | #else
82 | if (!string.IsNullOrEmpty(webRequest.error))
83 | #endif
84 | {
85 | AssetLogHelper.Log("下载Bundle失败 重试\n" + webRequest.error + "\nURL:" + url);
86 | return;
87 | }
88 | downLoadData.Data = webRequest.downloadHandler.data;
89 | webRequest.downloadHandler.Dispose();
90 | }
91 | }
92 |
93 | ///
94 | /// 下载完成回调位
95 | ///
96 | internal static readonly Queue DownLoadFinishQueue = new Queue();
97 |
98 | #if !BMWebGL
99 |
100 | ///
101 | /// 多线程下载资源直存
102 | ///
103 | public static async ETTask DownloadData(string url, string filePath, UpdateBundleDataInfo updateBundleDataInfo)
104 | {
105 | LmtDownloadInfo lmtDownloadInfo = new LmtDownloadInfo();
106 | for (int i = 0; i < AssetComponentConfig.ReDownLoadCount; i++)
107 | {
108 | //说明下载更新已经被取消
109 | if (updateBundleDataInfo.Cancel)
110 | {
111 | return lmtDownloadInfo;
112 | }
113 |
114 | lmtDownloadInfo = await DownloadOneData(url, filePath, updateBundleDataInfo);
115 | if (lmtDownloadInfo.LmtDownloadResult == LmtDownloadResult.Success)
116 | {
117 | return lmtDownloadInfo;
118 | }
119 | }
120 | //多次下载失败直接取消下载
121 | updateBundleDataInfo.CancelUpdate();
122 | return lmtDownloadInfo;
123 | }
124 |
125 | private static async ETTask DownloadOneData(string url, string filePath, UpdateBundleDataInfo updateBundleDataInfo)
126 | {
127 | ETTask tcs = ETTask.Create(true);
128 | LMTDownLoad lmtDownLoad = LMTDownLoad.Create(url, filePath);
129 | long lastDownSize = 0;
130 | lmtDownLoad.UpDateInfo += () =>
131 | {
132 | // ReSharper disable AccessToDisposedClosure
133 | updateBundleDataInfo.FinishUpdateSize += lmtDownLoad.LmtDownloadInfo.DownLoadSize - lastDownSize;
134 | lastDownSize = lmtDownLoad.LmtDownloadInfo.DownLoadSize;
135 | // ReSharper restore AccessToDisposedClosure
136 | lock (updateBundleDataInfo)
137 | {
138 | if (updateBundleDataInfo.Cancel)
139 | {
140 | // ReSharper disable once AccessToDisposedClosure
141 | lmtDownLoad.CancelLock = true;
142 | }
143 | }
144 | };
145 | lmtDownLoad.Completed += (info) =>
146 | {
147 | lock (DownLoadFinishQueue)
148 | {
149 | DownLoadFinishQueue.Enqueue(tcs);
150 | }
151 | };
152 | ThreadFactory.ThreadAction(lmtDownLoad);
153 | await tcs;
154 | LmtDownloadInfo lmtDownloadInfo = lmtDownLoad.LmtDownloadInfo;
155 | if (lmtDownloadInfo.LmtDownloadResult != LmtDownloadResult.Success)
156 | {
157 | updateBundleDataInfo.FinishUpdateSize -= lmtDownLoad.LmtDownloadInfo.DownLoadSize;
158 | //主动取消下载就没必要再输出log了
159 | if (lmtDownloadInfo.LmtDownloadResult != LmtDownloadResult.CancelDownLoad)
160 | {
161 | AssetLogHelper.LogError("下载资源失败: " + lmtDownloadInfo.LmtDownloadResult);
162 | }
163 | }
164 | lmtDownLoad.Dispose();
165 | return lmtDownloadInfo;
166 | }
167 |
168 | ///
169 | /// 另一种多线程下载的方式,不建议使用
170 | ///
171 | public static async Task DownloadDataTask(string url, string filePath)
172 | {
173 | LMTDownLoad lmtDownLoad = LMTDownLoad.Create(url, filePath);
174 | Task tcs = new Task(lmtDownLoad.DownLoad);
175 | tcs.Start();
176 | await tcs;
177 | lmtDownLoad.Dispose();
178 | return lmtDownLoad.LmtDownloadInfo;
179 | }
180 | #endif
181 |
182 | }
183 |
184 | public class DownLoadData
185 | {
186 | public byte[] Data;
187 |
188 | private static readonly Queue DownLoadDataPool = new Queue();
189 |
190 | public static DownLoadData Get()
191 | {
192 | if (DownLoadDataPool.Count > 0)
193 | {
194 | return DownLoadDataPool.Dequeue();
195 | }
196 | else
197 | {
198 | return new DownLoadData();
199 | }
200 | }
201 |
202 | public static void Recovery(DownLoadData downLoadData)
203 | {
204 | downLoadData.Data = null;
205 | DownLoadDataPool.Enqueue(downLoadData);
206 | }
207 |
208 | public static void ClearPool()
209 | {
210 | DownLoadDataPool.Clear();
211 | }
212 |
213 | }
214 | }
--------------------------------------------------------------------------------
/BundleMasterRuntime/Helper/GroupAssetHelper.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace BM
4 | {
5 | public static class GroupAssetHelper
6 | {
7 | ///
8 | /// 判断是否是颗粒化组的文件
9 | ///
10 | /// 组路径
11 | public static string IsGroupAsset(string assetPath, List loadGroupKey)
12 | {
13 | for (int i = 0; i < loadGroupKey.Count; i++)
14 | {
15 | if (assetPath.Contains(loadGroupKey[i]))
16 | {
17 | return loadGroupKey[i];
18 | }
19 | }
20 | return null;
21 | }
22 |
23 | }
24 | }
--------------------------------------------------------------------------------
/BundleMasterRuntime/Helper/HandlerIdHelper.cs:
--------------------------------------------------------------------------------
1 | namespace BM
2 | {
3 | public static class HandlerIdHelper
4 | {
5 | private static uint _handlerIdCounter = 0;
6 |
7 | ///
8 | /// 获取唯一ID
9 | ///
10 | ///
11 | public static uint GetUniqueId()
12 | {
13 | _handlerIdCounter++;
14 | return _handlerIdCounter;
15 | }
16 |
17 | }
18 | }
--------------------------------------------------------------------------------
/BundleMasterRuntime/Helper/PathUnifiedHelper.cs:
--------------------------------------------------------------------------------
1 | namespace BM
2 | {
3 | public static class PathUnifiedHelper
4 | {
5 | ///
6 | /// 解决路径资源是否存在的判定问题
7 | ///
8 | public static string UnifiedPath(string path)
9 | {
10 |
11 | #if UNITY_ANDROID && !UNITY_EDITOR
12 | path = path.Replace("\\", "/");
13 | #elif UNITY_IOS && !UNITY_EDITOR
14 | path = path.Replace("\\", "/");
15 | #elif UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
16 |
17 | #elif UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
18 |
19 | #endif
20 | return path;
21 | }
22 |
23 |
24 | }
25 | }
--------------------------------------------------------------------------------
/BundleMasterRuntime/Helper/TimeAwaitHelper.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using ET;
3 |
4 | namespace BM
5 | {
6 | public static class TimeAwaitHelper
7 | {
8 | internal static readonly Queue TimerFactoryQueue = new Queue();
9 |
10 | ///
11 | /// 等待一定时间
12 | ///
13 | /// 单位 秒
14 | /// 异步取消锁
15 | public static ETTask AwaitTime(float time, ETCancellationToken cancellationToken = null)
16 | {
17 | TimerAwait timerAwait;
18 | if (TimerFactoryQueue.Count > 0)
19 | {
20 | timerAwait = TimerFactoryQueue.Dequeue();
21 | }
22 | else
23 | {
24 | timerAwait = new TimerAwait();
25 | }
26 | ETTask tcs = ETTask.Create(true);
27 | timerAwait.Init(time, tcs);
28 | cancellationToken?.Add(() =>
29 | {
30 | timerAwait.Cancel();
31 | tcs.SetResult();
32 | });
33 | return tcs;
34 | }
35 | }
36 |
37 |
38 | internal class TimerAwait
39 | {
40 | private float remainingTime = 0;
41 | private ETTask tcs;
42 | private bool cancelTimer = false;
43 |
44 | internal void Init(float time, ETTask task)
45 | {
46 | this.remainingTime = time;
47 | this.tcs = task;
48 | AssetComponent.TimerAwaitQueue.Enqueue(this);
49 | cancelTimer = false;
50 | }
51 |
52 | internal bool CalcSubTime(float time)
53 | {
54 | remainingTime -= time;
55 | if (remainingTime > 0)
56 | {
57 | return false;
58 | }
59 | if (!cancelTimer)
60 | {
61 | tcs.SetResult();
62 | }
63 | tcs = null;
64 | remainingTime = 0;
65 | TimeAwaitHelper.TimerFactoryQueue.Enqueue(this);
66 | return true;
67 | }
68 |
69 | internal void Cancel()
70 | {
71 | cancelTimer = true;
72 | }
73 | }
74 | }
--------------------------------------------------------------------------------
/BundleMasterRuntime/Helper/ValueHelper.cs:
--------------------------------------------------------------------------------
1 | namespace BM
2 | {
3 | public static class ValueHelper
4 | {
5 | public static float GetMinValue(float value1, float value2)
6 | {
7 | if (value1 > value2)
8 | {
9 | return value2;
10 | }
11 | return value1;
12 | }
13 | }
14 | }
--------------------------------------------------------------------------------
/BundleMasterRuntime/Helper/VerifyHelper.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Security.Cryptography;
4 | using System.Text;
5 | using ET;
6 | using UnityEngine.Networking;
7 |
8 | namespace BM
9 | {
10 | ///
11 | /// 验证脚本
12 | ///
13 | public static class VerifyHelper
14 | {
15 | public static string GetMd5Hash(string filePath)
16 | {
17 | byte[] data = MD5.Create().ComputeHash(Encoding.UTF8.GetBytes(filePath));
18 | StringBuilder sb = new StringBuilder();
19 | foreach (byte t in data)
20 | {
21 | sb.Append(t.ToString("x2"));
22 | }
23 | return sb.ToString();
24 | }
25 |
26 | ///
27 | /// 得到一个路径下文件的CRC32
28 | ///
29 | internal static async ETTask GetFileCRC32(string filePath)
30 | {
31 | uint fileCRC32;
32 | ETTask tcs = ETTask.Create(true);
33 | using (UnityWebRequest webRequest = UnityWebRequest.Get(filePath))
34 | {
35 | webRequest.timeout = 0;
36 | UnityWebRequestAsyncOperation weq = webRequest.SendWebRequest();
37 | weq.completed += o =>
38 | {
39 | tcs.SetResult();
40 | };
41 | await tcs;
42 | #if UNITY_2020_1_OR_NEWER
43 | if (webRequest.result == UnityWebRequest.Result.Success)
44 | #else
45 | if (string.IsNullOrEmpty(webRequest.error))
46 | #endif
47 | {
48 | byte[] data = webRequest.downloadHandler.data;
49 | fileCRC32 = VerifyHelper.GetCRC32(data);
50 | }
51 | else
52 | {
53 | fileCRC32 = 0;
54 | }
55 | }
56 | return fileCRC32;
57 | }
58 | public static uint GetCRC32(byte[] bytes)
59 | {
60 | uint iCount = (uint)bytes.Length;
61 | uint crc = 0xFFFFFFFF;
62 | for (uint i = 0; i < iCount; i++)
63 | {
64 | crc = (crc << 8) ^ CRCTable[(crc >> 24) ^ bytes[i]];
65 | }
66 | return crc;
67 | }
68 |
69 | internal static uint GetCRC32(DownLoadData downLoadData)
70 | {
71 | uint iCount = (uint)downLoadData.Data.Length;
72 | uint crc = 0xFFFFFFFF;
73 | for (uint i = 0; i < iCount; i++)
74 | {
75 | crc = (crc << 8) ^ CRCTable[(crc >> 24) ^ downLoadData.Data[i]];
76 | }
77 | return crc;
78 | }
79 |
80 | ///
81 | /// 异步获取解密的数据(无密钥的情况下直接获取数据)
82 | ///
83 | internal static async ETTask GetDecryptDataAsync(string filePath, WebLoadProgress loadProgress = null, char[] secretKey = null)
84 | {
85 | byte[] encryptData;
86 | ETTask tcs = ETTask.Create();
87 | using (UnityWebRequest webRequest = UnityWebRequest.Get(filePath))
88 | {
89 | UnityWebRequestAsyncOperation weq = webRequest.SendWebRequest();
90 | if (loadProgress != null)
91 | {
92 | loadProgress.WeqOperation = weq;
93 | }
94 | weq.completed += (o) =>
95 | {
96 | tcs.SetResult();
97 | };
98 | await tcs;
99 | #if UNITY_2020_1_OR_NEWER
100 | if (webRequest.result == UnityWebRequest.Result.Success)
101 | #else
102 | if (string.IsNullOrEmpty(webRequest.error))
103 | #endif
104 | {
105 | encryptData = webRequest.downloadHandler.data;
106 | if (secretKey != null)
107 | {
108 | for (int i = 0; i < encryptData.Length; i++)
109 | {
110 | encryptData[i] = (byte)(encryptData[i] ^ secretKey[i % secretKey.Length]);
111 | }
112 | }
113 | }
114 | else
115 | {
116 | encryptData = null;
117 | }
118 | }
119 | return encryptData;
120 | }
121 |
122 | ///
123 | /// 获取解密的数据(无密钥的情况下直接获取数据)
124 | ///
125 | internal static byte[] GetDecryptData(string filePath, char[] secretKey = null)
126 | {
127 | byte[] encryptData;
128 | using (UnityWebRequest webRequest = UnityWebRequest.Get(filePath))
129 | {
130 | webRequest.SendWebRequest();
131 | while (!webRequest.isDone) { }
132 | #if UNITY_2020_1_OR_NEWER
133 | if (webRequest.result == UnityWebRequest.Result.Success)
134 | #else
135 | if (string.IsNullOrEmpty(webRequest.error))
136 | #endif
137 | {
138 | encryptData = webRequest.downloadHandler.data;
139 | if (secretKey != null)
140 | {
141 | for (int i = 0; i < encryptData.Length; i++)
142 | {
143 | encryptData[i] = (byte)(encryptData[i] ^ secretKey[i % secretKey.Length]);
144 | }
145 | }
146 | }
147 | else
148 | {
149 | encryptData = null;
150 | }
151 | }
152 | return encryptData;
153 | }
154 |
155 | ///
156 | /// 注意此Table和多线程下载的Table以及打AssetBundle加密用的Table需要一样,其中在多线程下载的Table里还有一份拷贝
157 | ///
158 | private static readonly UInt32[] CRCTable =
159 | {
160 | 0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b, 0x1a864db2, 0x1e475005,
161 | 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61, 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd,
162 | 0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9, 0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75,
163 | 0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3, 0x709f7b7a, 0x745e66cd,
164 | 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039, 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5,
165 | 0xbe2b5b58, 0xbaea46ef, 0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d,
166 | 0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49, 0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95,
167 | 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, 0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d,
168 | 0x34867077, 0x30476dc0, 0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072,
169 | 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, 0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca,
170 | 0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde, 0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02,
171 | 0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1, 0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba,
172 | 0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b, 0xbb60adfc, 0xb6238b25, 0xb2e29692,
173 | 0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6, 0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a,
174 | 0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e, 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2,
175 | 0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34, 0xdc3abded, 0xd8fba05a,
176 | 0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637, 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb,
177 | 0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f, 0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53,
178 | 0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5, 0x3f9b762c, 0x3b5a6b9b,
179 | 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff, 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623,
180 | 0xf12f560e, 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b,
181 | 0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f, 0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3,
182 | 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, 0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b,
183 | 0x9b3660c6, 0x9ff77d71, 0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3,
184 | 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, 0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c,
185 | 0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8, 0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24,
186 | 0x119b4be9, 0x155a565e, 0x18197087, 0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec,
187 | 0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d, 0x2056cd3a, 0x2d15ebe3, 0x29d4f654,
188 | 0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0, 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c,
189 | 0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18, 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4,
190 | 0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662, 0x933eb0bb, 0x97ffad0c,
191 | 0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668, 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4
192 | };
193 |
194 | }
195 | }
--------------------------------------------------------------------------------
/BundleMasterRuntime/LoadHandler.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using ET;
4 |
5 | namespace BM
6 | {
7 | public class LoadHandler : LoadHandlerBase
8 | {
9 | internal Action CompleteCallback;
10 | public event Action Completed
11 | {
12 | add
13 | {
14 | if (Asset != null)
15 | {
16 | value(this);
17 | }
18 | else
19 | {
20 | this.CompleteCallback += value;
21 | }
22 | }
23 | remove => this.CompleteCallback -= value;
24 | }
25 |
26 | ///
27 | /// 是否进池
28 | ///
29 | private bool isPool;
30 |
31 | ///
32 | /// 加载出来的资源
33 | ///
34 | public UnityEngine.Object Asset = null;
35 |
36 | ///
37 | /// 加载的状态
38 | ///
39 | internal LoadState LoadState = LoadState.NoLoad;
40 |
41 | internal LoadHandler(bool isPool)
42 | {
43 | this.isPool = isPool;
44 | }
45 |
46 | internal void Init(string assetPath, string bundlePackageName, bool haveHandler)
47 | {
48 | AssetPath = assetPath;
49 | UniqueId = HandlerIdHelper.GetUniqueId();
50 | BundlePackageName = bundlePackageName;
51 | LoadState = LoadState.NoLoad;
52 | UnloadFinish = false;
53 | HaveHandler = haveHandler;
54 | if (AssetComponentConfig.AssetLoadMode == AssetLoadMode.Develop)
55 | {
56 | //Develop模式直接返回就行
57 | return;
58 | }
59 | //说明是组里的资源
60 | string groupPath = GroupAssetHelper.IsGroupAsset(AssetPath, AssetComponent.BundleNameToRuntimeInfo[BundlePackageName].LoadGroupDicKey);
61 | if (groupPath != null)
62 | {
63 | //先找到对应加载的LoadGroup类
64 | if (!AssetComponent.BundleNameToRuntimeInfo[BundlePackageName].LoadGroupDic.TryGetValue(groupPath, out LoadGroup loadGroup))
65 | {
66 | AssetLogHelper.LogError("没有找到资源组: " + groupPath);
67 | return;
68 | }
69 | LoadBase = loadGroup;
70 | //需要记录loadGroup的依赖
71 | for (int i = 0; i < loadGroup.DependFileName.Count; i++)
72 | {
73 | string dependFile = loadGroup.DependFileName[i];
74 | if (AssetComponent.BundleNameToRuntimeInfo[BundlePackageName].LoadDependDic.TryGetValue(dependFile, out LoadDepend loadDepend))
75 | {
76 | LoadDepends.Add(loadDepend);
77 | continue;
78 | }
79 | if (AssetComponent.BundleNameToRuntimeInfo[BundlePackageName].LoadFileDic.TryGetValue(dependFile, out LoadFile loadDependFile))
80 | {
81 | LoadDependFiles.Add(loadDependFile);
82 | continue;
83 | }
84 | if (AssetComponent.BundleNameToRuntimeInfo[BundlePackageName].LoadGroupDic.TryGetValue(dependFile, out LoadGroup loadDependGroup))
85 | {
86 | LoadDependGroups.Add(loadDependGroup);
87 | continue;
88 | }
89 | AssetLogHelper.LogError("依赖的资源没有找到对应的类: " + dependFile);
90 | }
91 | return;
92 | }
93 | //先找到对应加载的LoadFile类
94 | if (!AssetComponent.BundleNameToRuntimeInfo[BundlePackageName].LoadFileDic.TryGetValue(AssetPath, out LoadFile loadFile))
95 | {
96 | AssetLogHelper.LogError("没有找到资源: " + AssetPath);
97 | return;
98 | }
99 | LoadBase = loadFile;
100 | //需要记录loadFile的依赖
101 | for (int i = 0; i < loadFile.DependFileName.Length; i++)
102 | {
103 | string dependFile = loadFile.DependFileName[i];
104 | if (AssetComponent.BundleNameToRuntimeInfo[BundlePackageName].LoadDependDic.TryGetValue(dependFile, out LoadDepend loadDepend))
105 | {
106 | LoadDepends.Add(loadDepend);
107 | continue;
108 | }
109 | if (AssetComponent.BundleNameToRuntimeInfo[BundlePackageName].LoadFileDic.TryGetValue(dependFile, out LoadFile loadDependFile))
110 | {
111 | LoadDependFiles.Add(loadDependFile);
112 | continue;
113 | }
114 | if (AssetComponent.BundleNameToRuntimeInfo[BundlePackageName].LoadGroupDic.TryGetValue(dependFile, out LoadGroup loadDependGroup))
115 | {
116 | LoadDependGroups.Add(loadDependGroup);
117 | continue;
118 | }
119 | AssetLogHelper.LogError("依赖的资源没有找到对应的类: " + dependFile);
120 | }
121 | }
122 |
123 | ///
124 | /// 同步加载所有的Bundle
125 | ///
126 | internal void Load()
127 | {
128 | LoadBase.LoadAssetBundle(BundlePackageName);
129 | for (int i = 0; i < LoadDepends.Count; i++)
130 | {
131 | LoadDepends[i].LoadAssetBundle(BundlePackageName);
132 | }
133 | for (int i = 0; i < LoadDependFiles.Count; i++)
134 | {
135 | LoadDependFiles[i].LoadAssetBundle(BundlePackageName);
136 | }
137 | for (int i = 0; i < LoadDependGroups.Count; i++)
138 | {
139 | LoadDependGroups[i].LoadAssetBundle(BundlePackageName);
140 | }
141 | FileAssetBundle = LoadBase.AssetBundle;
142 | LoadState = LoadState.Finish;
143 | }
144 |
145 | ///
146 | /// 异步加载所有的Bundle
147 | ///
148 | internal async ETTask LoadAsync()
149 | {
150 | LoadState = LoadState.Loading;
151 | //计算出所有需要加载的Bundle包的总数
152 | RefLoadFinishCount = LoadDepends.Count + LoadDependFiles.Count + LoadDependGroups.Count + 1;
153 | ETTask tcs = ETTask.Create(true);
154 | LoadAsyncLoader(LoadBase, tcs).Coroutine();
155 | for (int i = 0; i < LoadDepends.Count; i++)
156 | {
157 | LoadAsyncLoader(LoadDepends[i], tcs).Coroutine();
158 | }
159 | for (int i = 0; i < LoadDependFiles.Count; i++)
160 | {
161 | LoadAsyncLoader(LoadDependFiles[i], tcs).Coroutine();
162 | }
163 | for (int i = 0; i < LoadDependGroups.Count; i++)
164 | {
165 | LoadAsyncLoader(LoadDependGroups[i], tcs).Coroutine();
166 | }
167 | await tcs;
168 | if (LoadState == LoadState.Finish)
169 | {
170 | AssetLogHelper.Log("此资源异步加载时触发了强制加载: " + AssetPath);
171 | }
172 | LoadState = LoadState.Finish;
173 | FileAssetBundle = LoadBase.AssetBundle;
174 | }
175 |
176 | ///
177 | /// 强制异步加载完成
178 | ///
179 | internal void ForceAsyncLoadFinish()
180 | {
181 | LoadBase.ForceLoadFinish(BundlePackageName);
182 | for (int i = 0; i < LoadDepends.Count; i++)
183 | {
184 | LoadDepends[i].ForceLoadFinish(BundlePackageName);
185 | }
186 | for (int i = 0; i < LoadDependFiles.Count; i++)
187 | {
188 | LoadDependFiles[i].ForceLoadFinish(BundlePackageName);
189 | }
190 | for (int i = 0; i < LoadDependGroups.Count; i++)
191 | {
192 | LoadDependGroups[i].ForceLoadFinish(BundlePackageName);
193 | }
194 | FileAssetBundle = LoadBase.AssetBundle;
195 | LoadState = LoadState.Finish;
196 | }
197 |
198 | ///
199 | /// 清理引用
200 | ///
201 | protected override void ClearAsset()
202 | {
203 | Asset = null;
204 | FileAssetBundle = null;
205 | LoadState = LoadState.NoLoad;
206 | foreach (LoadDepend loadDepends in LoadDepends)
207 | {
208 | loadDepends.SubRefCount();
209 | }
210 | LoadDepends.Clear();
211 | foreach (LoadFile loadDependFiles in LoadDependFiles)
212 | {
213 | loadDependFiles.SubRefCount();
214 | }
215 | LoadDependFiles.Clear();
216 | foreach (LoadGroup loadDependGroups in LoadDependGroups)
217 | {
218 | loadDependGroups.SubRefCount();
219 | }
220 | LoadDependGroups.Clear();
221 | LoadBase.SubRefCount();
222 | LoadBase = null;
223 | //从缓存里取出进池
224 | if (!AssetComponent.BundleNameToRuntimeInfo.TryGetValue(BundlePackageName, out BundleRuntimeInfo bundleRuntimeInfo))
225 | {
226 | AssetLogHelper.LogError("没要找到分包的信息: " + BundlePackageName);
227 | return;
228 | }
229 | if (HaveHandler)
230 | {
231 | if (!bundleRuntimeInfo.AllAssetLoadHandler.ContainsKey(AssetPath))
232 | {
233 | AssetLogHelper.LogError("没要找到缓存的LoadHandler: " + AssetPath);
234 | return;
235 | }
236 | bundleRuntimeInfo.AllAssetLoadHandler.Remove(AssetPath);
237 | }
238 | CompleteCallback = null;
239 | if (isPool)
240 | {
241 | //进池
242 | LoadHandlerFactory.EnterPool(this);
243 | }
244 | }
245 |
246 | }
247 | }
--------------------------------------------------------------------------------
/BundleMasterRuntime/LoadHandlerBase.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using ET;
3 | using UnityEngine;
4 |
5 | namespace BM
6 | {
7 | public abstract class LoadHandlerBase
8 | {
9 | ///
10 | /// 是否是通过路径加载的
11 | ///
12 | internal bool HaveHandler = true;
13 |
14 | ///
15 | /// 对应的加载的资源的路径
16 | ///
17 | protected string AssetPath;
18 |
19 | ///
20 | /// 唯一ID
21 | ///
22 | public uint UniqueId = 0;
23 |
24 | ///
25 | /// 所属分包的名称
26 | ///
27 | protected string BundlePackageName;
28 |
29 | ///
30 | /// 加载完成的计数
31 | ///
32 | protected int RefLoadFinishCount = 0;
33 |
34 | ///
35 | /// 是否卸载标记位
36 | ///
37 | protected bool UnloadFinish = false;
38 |
39 | ///
40 | /// File文件AssetBundle的引用
41 | ///
42 | public AssetBundle FileAssetBundle;
43 |
44 | ///
45 | /// 资源所在的LoadBase包
46 | ///
47 | protected LoadBase LoadBase = null;
48 |
49 | ///
50 | /// 依赖的Bundle包
51 | ///
52 | protected readonly List LoadDepends = new List();
53 |
54 | ///
55 | /// 依赖的其它File包
56 | ///
57 | protected readonly List LoadDependFiles = new List();
58 |
59 | ///
60 | /// 依赖的其它Group包
61 | ///
62 | protected readonly List LoadDependGroups = new List();
63 |
64 | ///
65 | /// 加载计数器(负责完成所有依赖的Bundle加载完成)
66 | ///
67 | protected async ETTask LoadAsyncLoader(LoadBase loadBase, ETTask baseTcs)
68 | {
69 | await loadBase.LoadAssetBundleAsync(BundlePackageName);
70 | RefLoadFinishCount--;
71 | if (RefLoadFinishCount == 0)
72 | {
73 | baseTcs.SetResult();
74 | }
75 | if (RefLoadFinishCount < 0)
76 | {
77 | AssetLogHelper.LogError("资源加载引用计数不正确: " + RefLoadFinishCount);
78 | }
79 | }
80 |
81 | public void UnLoad()
82 | {
83 | if (AssetComponentConfig.AssetLoadMode == AssetLoadMode.Develop)
84 | {
85 | return;
86 | }
87 | if (UnloadFinish)
88 | {
89 | AssetLogHelper.LogError(AssetPath + "已经卸载完了");
90 | return;
91 | }
92 | AssetComponent.BundleNameToRuntimeInfo[BundlePackageName].UnLoadHandler.Remove(UniqueId);
93 | //减少引用数量
94 | ClearAsset();
95 | UnloadFinish = true;
96 | }
97 |
98 | ///
99 | /// 子类需要实现清理资源引用的逻辑
100 | ///
101 | protected abstract void ClearAsset();
102 | }
103 | }
--------------------------------------------------------------------------------
/BundleMasterRuntime/LoadSceneHandler.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using UnityEngine;
3 | using ET;
4 |
5 | namespace BM
6 | {
7 | public partial class LoadSceneHandler : LoadHandlerBase
8 | {
9 | public LoadSceneHandler(string scenePath, string bundlePackageName)
10 | {
11 | AssetPath = scenePath;
12 | UniqueId = HandlerIdHelper.GetUniqueId();
13 | BundlePackageName = bundlePackageName;
14 | if (AssetComponentConfig.AssetLoadMode == AssetLoadMode.Develop)
15 | {
16 | //Develop模式直接返回
17 | return;
18 | }
19 | //说明是组里的资源
20 | string groupPath = GroupAssetHelper.IsGroupAsset(AssetPath, AssetComponent.BundleNameToRuntimeInfo[BundlePackageName].LoadGroupDicKey);
21 | if (groupPath != null)
22 | {
23 | //先找到对应加载的LoadGroup类
24 | if (!AssetComponent.BundleNameToRuntimeInfo[BundlePackageName].LoadGroupDic.TryGetValue(groupPath, out LoadGroup loadGroup))
25 | {
26 | AssetLogHelper.LogError("没有找到资源组: " + groupPath);
27 | return;
28 | }
29 | LoadBase = loadGroup;
30 | //需要记录loadGroup的依赖
31 | for (int i = 0; i < loadGroup.DependFileName.Count; i++)
32 | {
33 | string dependFile = loadGroup.DependFileName[i];
34 | if (AssetComponent.BundleNameToRuntimeInfo[BundlePackageName].LoadDependDic.TryGetValue(dependFile, out LoadDepend loadDepend))
35 | {
36 | LoadDepends.Add(loadDepend);
37 | continue;
38 | }
39 | if (AssetComponent.BundleNameToRuntimeInfo[BundlePackageName].LoadFileDic.TryGetValue(dependFile, out LoadFile loadDependFile))
40 | {
41 | LoadDependFiles.Add(loadDependFile);
42 | continue;
43 | }
44 | if (AssetComponent.BundleNameToRuntimeInfo[BundlePackageName].LoadGroupDic.TryGetValue(dependFile, out LoadGroup loadDependGroup))
45 | {
46 | LoadDependGroups.Add(loadDependGroup);
47 | continue;
48 | }
49 | AssetLogHelper.LogError("场景依赖的资源没有找到对应的类: " + dependFile);
50 | }
51 | return;
52 | }
53 | //先找到对应加载的LoadFile类
54 | if (!AssetComponent.BundleNameToRuntimeInfo[BundlePackageName].LoadFileDic.TryGetValue(AssetPath, out LoadFile loadFile))
55 | {
56 | AssetLogHelper.LogError("没有找到资源: " + AssetPath);
57 | return;
58 | }
59 | LoadBase = loadFile;
60 | //需要记录loadFile的依赖
61 | for (int i = 0; i < loadFile.DependFileName.Length; i++)
62 | {
63 | string dependFile = loadFile.DependFileName[i];
64 | if (AssetComponent.BundleNameToRuntimeInfo[BundlePackageName].LoadDependDic.TryGetValue(dependFile, out LoadDepend loadDepend))
65 | {
66 | LoadDepends.Add(loadDepend);
67 | continue;
68 | }
69 | if (AssetComponent.BundleNameToRuntimeInfo[BundlePackageName].LoadFileDic.TryGetValue(dependFile, out LoadFile loadDependFile))
70 | {
71 | LoadDependFiles.Add(loadDependFile);
72 | continue;
73 | }
74 | if (AssetComponent.BundleNameToRuntimeInfo[BundlePackageName].LoadGroupDic.TryGetValue(dependFile, out LoadGroup loadDependGroup))
75 | {
76 | LoadDependGroups.Add(loadDependGroup);
77 | continue;
78 | }
79 | AssetLogHelper.LogError("场景依赖的资源没有找到对应的类: " + dependFile);
80 | }
81 |
82 | }
83 |
84 | ///
85 | /// 同步加载场景资源所需的的AssetBundle包
86 | ///
87 | public void LoadSceneBundle()
88 | {
89 | LoadBase.LoadAssetBundle(BundlePackageName);
90 | for (int i = 0; i < LoadDepends.Count; i++)
91 | {
92 | LoadDepends[i].LoadAssetBundle(BundlePackageName);
93 | }
94 | for (int i = 0; i < LoadDependFiles.Count; i++)
95 | {
96 | LoadDependFiles[i].LoadAssetBundle(BundlePackageName);
97 | }
98 | for (int i = 0; i < LoadDependGroups.Count; i++)
99 | {
100 | LoadDependGroups[i].LoadAssetBundle(BundlePackageName);
101 | }
102 | FileAssetBundle = LoadBase.AssetBundle;
103 | }
104 |
105 | ///
106 | /// 异步加载场景的Bundle
107 | ///
108 | public async ETTask LoadSceneBundleAsync(ETTask finishTask)
109 | {
110 | //计算出所有需要加载的Bundle包的总数
111 | RefLoadFinishCount = LoadDepends.Count + LoadDependFiles.Count + LoadDependGroups.Count + 1;
112 | LoadBase.OpenProgress();
113 | LoadAsyncLoader(LoadBase, finishTask).Coroutine();
114 | for (int i = 0; i < LoadDepends.Count; i++)
115 | {
116 | LoadDepends[i].OpenProgress();
117 | LoadAsyncLoader(LoadDepends[i], finishTask).Coroutine();
118 | }
119 | for (int i = 0; i < LoadDependFiles.Count; i++)
120 | {
121 | LoadDependFiles[i].OpenProgress();
122 | LoadAsyncLoader(LoadDependFiles[i], finishTask).Coroutine();
123 | }
124 | for (int i = 0; i < LoadDependGroups.Count; i++)
125 | {
126 | LoadDependGroups[i].OpenProgress();
127 | LoadAsyncLoader(LoadDependGroups[i], finishTask).Coroutine();
128 | }
129 | await finishTask;
130 | FileAssetBundle = LoadBase.AssetBundle;
131 | }
132 |
133 | ///
134 | /// 获取场景AssetBundle加载的进度
135 | ///
136 | public float GetProgress()
137 | {
138 | if (AssetComponentConfig.AssetLoadMode == AssetLoadMode.Develop)
139 | {
140 | //Develop模式无法异步加载
141 | return 100;
142 | }
143 | if (UnloadFinish)
144 | {
145 | return 0;
146 | }
147 | float progress = 0;
148 | int loadCount = 1;
149 | progress += LoadBase.GetProgress();
150 | for (int i = 0; i < LoadDepends.Count; i++)
151 | {
152 | progress += LoadDepends[i].GetProgress();
153 | loadCount++;
154 | }
155 | for (int i = 0; i < LoadDependFiles.Count; i++)
156 | {
157 | progress += LoadDependFiles[i].GetProgress();
158 | loadCount++;
159 | }
160 | for (int i = 0; i < LoadDependGroups.Count; i++)
161 | {
162 | progress += LoadDependGroups[i].GetProgress();
163 | loadCount++;
164 | }
165 | return progress / loadCount;
166 | }
167 |
168 | ///
169 | /// 卸载场景的AssetBundle包
170 | ///
171 | protected override void ClearAsset()
172 | {
173 | FileAssetBundle = null;
174 | foreach (LoadDepend loadDepends in LoadDepends)
175 | {
176 | loadDepends.SubRefCount();
177 | }
178 | LoadDepends.Clear();
179 | foreach (LoadFile loadDependFiles in LoadDependFiles)
180 | {
181 | loadDependFiles.SubRefCount();
182 | }
183 | LoadDependFiles.Clear();
184 | foreach (LoadGroup loadDependGroups in LoadDependGroups)
185 | {
186 | loadDependGroups.SubRefCount();
187 | }
188 | LoadDependGroups.Clear();
189 | LoadBase.SubRefCount();
190 | LoadBase = null;
191 | }
192 | }
193 | }
--------------------------------------------------------------------------------
/BundleMasterRuntime/UpdateBundleDataInfo.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using UnityEngine;
5 |
6 | namespace BM
7 | {
8 | public class UpdateBundleDataInfo
9 | {
10 | ///
11 | /// 是否需要更新
12 | ///
13 | public bool NeedUpdate = false;
14 |
15 | ///
16 | /// 需要更新的总大小
17 | ///
18 | public long NeedUpdateSize = 0;
19 |
20 | ///
21 | /// 是否取消
22 | ///
23 | internal bool Cancel = false;
24 |
25 | ///
26 | /// 需要更新的Bundle的信息
27 | ///
28 | internal readonly Dictionary> PackageNeedUpdateBundlesInfos = new Dictionary>();
29 |
30 | ///
31 | /// 分包对应的版本号 int[本地版本, 远程版本] 仅Build模式可用
32 | ///
33 | internal readonly Dictionary PackageToVersion = new Dictionary();
34 |
35 | ///
36 | /// 分包以及对于的类型
37 | ///
38 | internal readonly Dictionary PackageToType = new Dictionary();
39 |
40 | ///
41 | /// 客户端更新时间
42 | ///
43 | internal string UpdateTime = "";
44 |
45 | ///
46 | /// CRC信息字典
47 | ///
48 | internal readonly Dictionary> PackageCRCDictionary = new Dictionary>();
49 |
50 | ///
51 | /// CRC对应的写入流
52 | ///
53 | internal readonly Dictionary PackageCRCFile = new Dictionary();
54 |
55 | ///
56 | /// 更新完成的大小
57 | ///
58 | public long FinishUpdateSize = 0;
59 |
60 | ///
61 | /// 是否更新完成
62 | ///
63 | public bool FinishUpdate = false;
64 |
65 | ///
66 | /// 更新进度(1 - 100)
67 | ///
68 | public float Progress
69 | {
70 | get
71 | {
72 | float progress = ((float)FinishUpdateSize / NeedUpdateSize) * 100.0f;
73 | return progress;
74 | }
75 | }
76 |
77 | ///
78 | /// 总共需要下载的Bundle的数量
79 | ///
80 | public int NeedDownLoadBundleCount = 0;
81 |
82 | ///
83 | /// 下载完成的Bundle的数量
84 | ///
85 | public int FinishDownLoadBundleCount = 0;
86 |
87 | ///
88 | /// 平滑进度
89 | ///
90 | internal float SmoothProgress = 0.0f;
91 | private Action progressCallback;
92 | ///
93 | /// 下载进度回调,每帧刷新,有插值
94 | ///
95 | public event Action ProgressCallback
96 | {
97 | add => progressCallback += value;
98 | remove => this.progressCallback -= value;
99 | }
100 |
101 | internal Action FinishCallback;
102 |
103 | ///
104 | /// 下载更新完成回调
105 | ///
106 | public event Action DownLoadFinishCallback
107 | {
108 | add => FinishCallback += value;
109 | remove => this.FinishCallback -= value;
110 | }
111 |
112 | ///
113 | /// 下载速度平均个数
114 | ///
115 | private int speedAvgCount = 5;
116 | ///
117 | /// 下载速度更新频率
118 | ///
119 | private long speedRate = 1000;
120 | ///
121 | /// 下载速度缓存队列
122 | ///
123 | private readonly Queue downLoadSpeedQueue = new Queue();
124 |
125 | ///
126 | /// 下载速度
127 | ///
128 | public int DownLoadSpeed
129 | {
130 | get
131 | {
132 | int addSpeed = 0;
133 | int speedQueueCount = downLoadSpeedQueue.Count;
134 | if (speedQueueCount == 0)
135 | {
136 | return 0;
137 | }
138 | for (int i = 0; i < speedQueueCount; i++)
139 | {
140 | int tempSpeed = downLoadSpeedQueue.Dequeue();
141 | addSpeed += tempSpeed;
142 | downLoadSpeedQueue.Enqueue(tempSpeed);
143 | }
144 | return addSpeed / speedQueueCount;
145 | }
146 | }
147 |
148 | private Action downLoadSpeedCallback;
149 |
150 | ///
151 | /// 下载速度改变回调,非每帧都刷新 单位byte每秒
152 | ///
153 | public event Action DownLoadSpeedCallback
154 | {
155 | add => downLoadSpeedCallback += value;
156 | remove => this.downLoadSpeedCallback -= value;
157 | }
158 |
159 | private void AddSpeedQueue(int speed)
160 | {
161 | if (downLoadSpeedQueue.Count >= speedAvgCount)
162 | {
163 | downLoadSpeedQueue.Dequeue();
164 | AddSpeedQueue(speed);
165 | return;
166 | }
167 | downLoadSpeedQueue.Enqueue(speed);
168 | }
169 |
170 | ///
171 | /// 上一帧下载大小
172 | ///
173 | private long lastDownSize = 0;
174 |
175 | ///
176 | /// 上一次下载速度变化时间
177 | ///
178 | private long lastTime = 0;
179 |
180 | ///
181 | /// 更新下载进度和下载速度以及执行回调
182 | ///
183 | internal void UpdateProgressAndSpeedCallBack(float deltaTime)
184 | {
185 | //计算下载进度
186 | if (FinishUpdate)
187 | {
188 | SmoothProgress = 100;
189 | }
190 | else
191 | {
192 | SmoothProgress += (Progress - SmoothProgress) * ValueHelper.GetMinValue(deltaTime / 0.1f, 1.0f);
193 | }
194 | progressCallback?.Invoke(SmoothProgress);
195 |
196 | long nowTime = DateTime.Now.Ticks;
197 | long continueTime = nowTime - lastTime;
198 | //下载速度一秒才更新一次
199 | if (continueTime > 10000 * speedRate)
200 | {
201 | //计算下载速度(非UnityWebReq下载), 下载量有改变
202 | if (FinishUpdateSize != lastDownSize)
203 | {
204 | long blockTimeDownSize = FinishUpdateSize - lastDownSize;
205 | lastDownSize = FinishUpdateSize;
206 | if (lastTime != 0)
207 | {
208 | AddSpeedQueue((int)(blockTimeDownSize / ((nowTime - lastTime) / 10000000.0f)));
209 | }
210 | lastTime = nowTime;
211 | }
212 | downLoadSpeedCallback?.Invoke(DownLoadSpeed);
213 | }
214 | }
215 |
216 | ///
217 | /// 获取更新的分包的版本索引 int[本地版本, 远程版本]
218 | ///
219 | public int[] GetVersion(string bundlePackageName)
220 | {
221 | if (AssetComponentConfig.AssetLoadMode != AssetLoadMode.Build)
222 | {
223 | AssetLogHelper.LogError("仅Build模式可用获取版本索引");
224 | return null;
225 | }
226 | if (!PackageToVersion.TryGetValue(bundlePackageName, out int[] versionData))
227 | {
228 | AssetLogHelper.LogError("获取索引号没有找到分包: " + bundlePackageName);
229 | return null;
230 | }
231 | return versionData;
232 | }
233 |
234 | ///
235 | /// 添加一个更新好的CRC文件信息
236 | ///
237 | internal void AddCRCFileInfo(string bundlePackageName, string fileName, uint crc)
238 | {
239 | if (AssetComponentConfig.AssetLoadMode != AssetLoadMode.Build)
240 | {
241 | AssetLogHelper.LogError("AssetLoadMode != Build 不涉及更新");
242 | return;
243 | }
244 | if (!PackageCRCDictionary.TryGetValue(bundlePackageName, out Dictionary crcDictionary))
245 | {
246 | AssetLogHelper.LogError("获取索引号没有找到分包: " + bundlePackageName);
247 | return;
248 | }
249 | if (Cancel)
250 | {
251 | return;
252 | }
253 | if (!crcDictionary.ContainsKey(fileName))
254 | {
255 | crcDictionary.Add(fileName, crc);
256 | }
257 | PackageCRCFile[bundlePackageName].WriteLine(fileName + "|" + crc.ToString() + "|" + UpdateTime);
258 | PackageCRCFile[bundlePackageName].Flush();
259 | }
260 |
261 | private Action errorCancelCallback;
262 |
263 | ///
264 | /// 下载失败回调
265 | ///
266 | public event Action ErrorCancelCallback
267 | {
268 | add => errorCancelCallback += value;
269 | remove => this.errorCancelCallback -= value;
270 | }
271 |
272 | ///
273 | /// 取消更新
274 | ///
275 | public void CancelUpdate()
276 | {
277 | if (Cancel)
278 | {
279 | return;
280 | }
281 | Cancel = true;
282 | errorCancelCallback?.Invoke();
283 | DestroySelf();
284 | }
285 |
286 | private void DestroySelf()
287 | {
288 | foreach (StreamWriter sw in PackageCRCFile.Values)
289 | {
290 | sw.Close();
291 | sw.Dispose();
292 | }
293 | PackageCRCFile.Clear();
294 | AssetComponent.DownLoadAction -= UpdateProgressAndSpeedCallBack;
295 | progressCallback = null;
296 | FinishCallback = null;
297 | downLoadSpeedCallback = null;
298 | errorCancelCallback = null;
299 | }
300 |
301 | ///
302 | /// 分包类型
303 | ///
304 | internal enum PackageType
305 | {
306 | ///
307 | /// 未加密
308 | ///
309 | Normal,
310 | ///
311 | /// 加密
312 | ///
313 | Encrypt,
314 | ///
315 | /// 原始资源
316 | ///
317 | Origin,
318 | }
319 | }
320 | }
--------------------------------------------------------------------------------
/CoroutineLock/CoroutineLock.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using ET;
3 |
4 | namespace BM
5 | {
6 | public class CoroutineLock : IDisposable
7 | {
8 | private bool _isDispose = false;
9 | private long _key;
10 | private CoroutineLockQueue _coroutineLockQueue;
11 | private ETTask _waitTask;
12 |
13 | internal void Init(long key, CoroutineLockQueue coroutineLockQueue)
14 | {
15 | _isDispose = false;
16 | this._key = key;
17 | this._coroutineLockQueue = coroutineLockQueue;
18 | _waitTask = ETTask.Create(true);
19 | }
20 |
21 | internal void Enable()
22 | {
23 | _waitTask.SetResult();
24 | }
25 |
26 | internal ETTask Wait()
27 | {
28 | return _waitTask;
29 | }
30 |
31 |
32 | public void Dispose()
33 | {
34 | if (_isDispose)
35 | {
36 | //AssetLogHelper.LogError("协程锁重复释放");
37 | return;
38 | }
39 | _waitTask = null;
40 | _isDispose = true;
41 | _coroutineLockQueue.CoroutineLockDispose(_key).Coroutine();
42 | _coroutineLockQueue = null;
43 | CoroutineLockComponent.CoroutineLockQueue.Enqueue(this);
44 |
45 | }
46 | }
47 | }
--------------------------------------------------------------------------------
/CoroutineLock/CoroutineLockComponent.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using ET;
3 |
4 | namespace BM
5 | {
6 | public static class CoroutineLockComponent
7 | {
8 | ///
9 | /// 协程锁类型以及对应的协程锁队列
10 | ///
11 | private static readonly Dictionary CoroutineLockTypeToQueue = new Dictionary();
12 |
13 | ///
14 | /// 没有用到的CoroutineLock池
15 | ///
16 | internal static readonly Queue CoroutineLockQueue = new Queue();
17 |
18 | ///
19 | /// 缓存池的池
20 | ///
21 | internal static readonly Queue> CoroutineLockQueuePool = new Queue>();
22 |
23 | public static async ETTask Wait(CoroutineLockType coroutineLockType, long key)
24 | {
25 | if (!CoroutineLockTypeToQueue.TryGetValue(coroutineLockType, out CoroutineLockQueue coroutineLockQueue))
26 | {
27 | coroutineLockQueue = new CoroutineLockQueue(coroutineLockType);
28 | CoroutineLockTypeToQueue.Add(coroutineLockType, coroutineLockQueue);
29 | }
30 | //取一个 CoroutineLock
31 | CoroutineLock coroutineLock = coroutineLockQueue.GetCoroutineLock(key);
32 | await coroutineLock.Wait();
33 | return coroutineLock;
34 | }
35 |
36 | public static void UpDate()
37 | {
38 | for (int i = 0; i < TaskDic.Count; i++)
39 | {
40 | TaskDic[i].SetResult();
41 | }
42 | TaskDic.Clear();
43 | }
44 |
45 | private static readonly List TaskDic = new List();
46 |
47 | ///
48 | /// 等待一个帧循环执行
49 | ///
50 | internal static ETTask WaitTask()
51 | {
52 | ETTask tcs = ETTask.Create(true);
53 | TaskDic.Add(tcs);
54 | return tcs;
55 | }
56 |
57 | }
58 | }
59 |
60 |
--------------------------------------------------------------------------------
/CoroutineLock/CoroutineLockQueue.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using ET;
3 |
4 | namespace BM
5 | {
6 | ///
7 | /// 每个协程锁类型与其对应的队列
8 | ///
9 | internal class CoroutineLockQueue
10 | {
11 | private CoroutineLockType _coroutineLockType;
12 | ///
13 | /// 每个key的以及其对应的队列
14 | ///
15 | private readonly Dictionary> _coroutineLockKeyToQueue = new Dictionary>();
16 |
17 | ///
18 | /// 可以瞬间完成的锁循环的数量(与设备栈大小有关)
19 | ///
20 | private const int LoopCount = 200;
21 |
22 | ///
23 | /// 递归循环次数
24 | ///
25 | private readonly Dictionary _coroutineLockKeyToLoopCount = new Dictionary();
26 |
27 | internal CoroutineLockQueue(CoroutineLockType coroutineLockType)
28 | {
29 | this._coroutineLockType = coroutineLockType;
30 | }
31 |
32 | internal async ETTask CoroutineLockDispose(long key)
33 | {
34 | Queue keyToQueue = _coroutineLockKeyToQueue[key];
35 | if (keyToQueue.Count > 0)
36 | {
37 | if (_coroutineLockKeyToLoopCount[key] > 0)
38 | {
39 | _coroutineLockKeyToLoopCount[key]--;
40 | keyToQueue.Dequeue().Enable();
41 | return;
42 | }
43 | _coroutineLockKeyToLoopCount[key] = LoopCount;
44 | await CoroutineLockComponent.WaitTask();
45 | keyToQueue.Dequeue().Enable();
46 | return;
47 | }
48 | keyToQueue.Clear();
49 | CoroutineLockComponent.CoroutineLockQueuePool.Enqueue(keyToQueue);
50 | _coroutineLockKeyToQueue.Remove(key);
51 | _coroutineLockKeyToLoopCount.Remove(key);
52 | }
53 |
54 | ///
55 | /// 获取一个锁头
56 | ///
57 | internal CoroutineLock GetCoroutineLock(long key)
58 | {
59 | CoroutineLock coroutineLock;
60 | if (CoroutineLockComponent.CoroutineLockQueue.Count > 0)
61 | {
62 | coroutineLock = CoroutineLockComponent.CoroutineLockQueue.Dequeue();
63 | }
64 | else
65 | {
66 | coroutineLock = new CoroutineLock();
67 | }
68 | coroutineLock.Init(key, this);
69 | if (!_coroutineLockKeyToQueue.ContainsKey(key))
70 | {
71 | Queue coroutineLockQueue;
72 | if (CoroutineLockComponent.CoroutineLockQueuePool.Count > 0)
73 | {
74 | coroutineLockQueue = CoroutineLockComponent.CoroutineLockQueuePool.Dequeue();
75 | }
76 | else
77 | {
78 | coroutineLockQueue = new Queue();
79 | }
80 | _coroutineLockKeyToLoopCount.Add(key, LoopCount);
81 | _coroutineLockKeyToQueue.Add(key, coroutineLockQueue);
82 | coroutineLock.Enable();
83 | }
84 | else
85 | {
86 | _coroutineLockKeyToQueue[key].Enqueue(coroutineLock);
87 | }
88 | return coroutineLock;
89 | }
90 |
91 | }
92 | }
--------------------------------------------------------------------------------
/CoroutineLock/CoroutineLockType.cs:
--------------------------------------------------------------------------------
1 | namespace BM
2 | {
3 | public enum CoroutineLockType
4 | {
5 | ///
6 | /// BundleMaster资源加载用的标识符
7 | ///
8 | BundleMaster = 0,
9 |
10 | }
11 | }
--------------------------------------------------------------------------------
/CoroutineLock/LoadPathConvertHelper.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace BM
4 | {
5 | ///
6 | /// 负责将加载路径转化为唯一的long key
7 | ///
8 | public class LoadPathConvertHelper
9 | {
10 | ///
11 | /// 初始值
12 | ///
13 | private static long _originKey = 0;
14 |
15 | private static readonly Dictionary LoadPathToKey = new Dictionary();
16 |
17 | public static long LoadPathConvert(string loadPath)
18 | {
19 | if (LoadPathToKey.TryGetValue(loadPath, out long key))
20 | {
21 | return key;
22 | }
23 | _originKey++;
24 | key = _originKey;
25 | LoadPathToKey.Add(loadPath, key);
26 | return key;
27 | }
28 | }
29 | }
--------------------------------------------------------------------------------
/Editor/BundleMasterEditor/AssetLoadTable.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.IO;
3 | using UnityEditor;
4 | using UnityEngine;
5 | using UnityEngine.Serialization;
6 |
7 | namespace BM
8 | {
9 | ///
10 | /// 用于配置所有分包的构建信息
11 | ///
12 | public class AssetLoadTable : ScriptableObject
13 | {
14 | [Header("相对构建路径文件夹名称")]
15 | [Tooltip("构建的资源的相对路径(Assets同级目录下的路径)")]
16 | public string BundlePath = "BuildBundles";
17 |
18 | [Header("是否启用绝对构建路径")]
19 | [Tooltip("启用后使用绝对路径")]
20 | public bool EnableRelativePath = false;
21 |
22 | [Header("绝对路径")]
23 | [Tooltip("自己填的绝对路径,替换掉Assets同级路径")]
24 | public string RelativePath = "";
25 |
26 | [Header("加密资源文件夹名称")]
27 | [Tooltip("加密的资源所在的和普通资源同级路径的文件夹名称")]
28 | public string EncryptPathFolder = "EncryptAssets";
29 |
30 | [Header("初始场景")]
31 | [Tooltip("最后不打Bundle直接打进包体里的场景(Scene In Build 里填的场景)")]
32 | public List InitScene = new List();
33 |
34 | [Header("是否生成路径字段代码脚本")]
35 | [Tooltip("字段匹配路径")]
36 | public bool GeneratePathCode = false;
37 |
38 | [Header("相对构建路径文件夹名称")]
39 | [Tooltip("构建的资源的相对路径(Assets同级目录下的路径)")]
40 | public string GenerateCodeScriptPath = "";
41 |
42 | ///
43 | /// 返回打包路径
44 | ///
45 | public string BuildBundlePath
46 | {
47 | get
48 | {
49 | if (EnableRelativePath)
50 | {
51 | return Path.Combine(RelativePath, BundlePath);
52 | }
53 | string path = Path.Combine(Application.dataPath + "/../", BundlePath);
54 | DirectoryInfo info;
55 | if (!Directory.Exists(path))
56 | {
57 | info = Directory.CreateDirectory(path);
58 | }
59 | else
60 | {
61 | info = new DirectoryInfo(path);
62 | }
63 | return info.FullName;
64 | }
65 | }
66 |
67 | [FormerlySerializedAs("AssetsLoadSettings")]
68 | [Header("所有分包配置信息")]
69 | [Tooltip("每一个分包的配置信息")]
70 | public List AssetsSettings = new List();
71 | }
72 | }
--------------------------------------------------------------------------------
/Editor/BundleMasterEditor/AssetsLoadSetting.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using UnityEditor;
3 | using UnityEngine;
4 |
5 | namespace BM
6 | {
7 | ///
8 | /// 用于配置单个Bundle包的构建信息
9 | ///
10 | public class AssetsLoadSetting : AssetsSetting
11 | {
12 | [Header("分包名字")]
13 | [Tooltip("当前分包的包名(建议英文)")] public string BuildName;
14 |
15 | [Header("版本索引")]
16 | [Tooltip("表示当前Bundle的索引")] public int BuildIndex;
17 |
18 | [Header("AssetBundle的后缀")]
19 | [Tooltip("AssetBundle资源的的后缀名(如'bundle')")] public string BundleVariant;
20 |
21 | [Header("是否启用Hash名")]
22 | [Tooltip("是否使用Hash名替换Bundle名称")] public bool NameByHash;
23 |
24 | [Header("构建选项")]
25 | public BuildAssetBundleOptions BuildAssetBundleOptions = BuildAssetBundleOptions.UncompressedAssetBundle;
26 |
27 | #if !(Nintendo_Switch || BMWebGL)
28 | [Header("是否加密资源")]
29 | [Tooltip("加密启用后会多一步异或操作")] public bool EncryptAssets;
30 |
31 | [Header("加密密钥")]
32 | [Tooltip("进行异或操作的密钥")] public string SecretKey;
33 | #endif
34 |
35 | [Header("资源路径")]
36 | [Tooltip("需要打包的资源所在的路径(不需要包含依赖, 只包括需要主动加载的资源)")]
37 | public List AssetPath = new List();
38 |
39 | [Header("一组资源路径")]
40 | [Tooltip("资源颗粒控制")]
41 | public List AssetGroupPaths = new List();
42 |
43 | [Header("场景资源")]
44 | [Tooltip("需要通过Bundle加载的场景")]
45 | public List Scene = new List();
46 |
47 | }
48 | }
49 |
50 |
51 |
--------------------------------------------------------------------------------
/Editor/BundleMasterEditor/AssetsOriginSetting.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using UnityEditor;
3 | using UnityEngine;
4 |
5 | namespace BM
6 | {
7 | ///
8 | /// 用于配置原生资源的更新
9 | ///
10 | public class AssetsOriginSetting : AssetsSetting
11 | {
12 | [Header("原生资源分包名字")]
13 | [Tooltip("原生资源的分包名(建议英文)")] public string BuildName;
14 |
15 | [Header("版本索引")]
16 | [Tooltip("表示当前原生的索引")] public int BuildIndex;
17 |
18 | [Header("资源路径")]
19 | [Tooltip("需要打包的资源所在的路径(不需要包含依赖, 只包括需要主动加载的资源)")]
20 | public string OriginFilePath = "";
21 | }
22 | }
--------------------------------------------------------------------------------
/Editor/BundleMasterEditor/AssetsSetting.cs:
--------------------------------------------------------------------------------
1 | using UnityEngine;
2 |
3 | namespace BM
4 | {
5 | public class AssetsSetting : ScriptableObject
6 | {
7 |
8 | }
9 | }
--------------------------------------------------------------------------------
/Editor/BundleMasterEditor/BuildAssetsTools.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Text;
5 | using UnityEditor;
6 | using UnityEngine;
7 | using Directory = UnityEngine.Windows.Directory;
8 |
9 | namespace BM
10 | {
11 | public static class BuildAssetsTools
12 | {
13 | ///
14 | /// 获取一个目录下所有的子文件
15 | ///
16 | public static void GetChildFiles(string basePath, HashSet files)
17 | {
18 | DirectoryInfo basefolder = new DirectoryInfo(basePath);
19 | FileInfo[] basefil = basefolder.GetFiles();
20 | for (int i = 0; i < basefil.Length; i++)
21 | {
22 |
23 | if (CantLoadType(basefil[i].FullName))
24 | {
25 | files.Add(basePath + "/" + basefil[i].Name);
26 | }
27 | }
28 | Er(basePath);
29 | void Er(string subPath)
30 | {
31 | string[] subfolders = AssetDatabase.GetSubFolders(subPath);
32 | for (int i = 0; i < subfolders.Length; i++)
33 | {
34 | DirectoryInfo subfolder = new DirectoryInfo(subfolders[i]);
35 | FileInfo[] fil = subfolder.GetFiles();
36 | for (int j = 0; j < fil.Length; j++)
37 | {
38 |
39 | if (CantLoadType(fil[j].FullName))
40 | {
41 | files.Add(subfolders[i] + "/" + fil[j].Name);
42 | }
43 | }
44 | Er(subfolders[i]);
45 | }
46 | }
47 | }
48 |
49 | ///
50 | /// 获取一个文件目录下的所有资源以及路径
51 | ///
52 | public static void GetOriginsPath(string originPath, HashSet files, HashSet dirs)
53 | {
54 | DirectoryInfo buildBundlePath = new DirectoryInfo(originPath);
55 | FileSystemInfo[] fileSystemInfos = buildBundlePath.GetFileSystemInfos();
56 | foreach (FileSystemInfo fileSystemInfo in fileSystemInfos)
57 | {
58 | if (fileSystemInfo is DirectoryInfo)
59 | {
60 | dirs.Add(fileSystemInfo.FullName);
61 | GetOriginsPath(fileSystemInfo.FullName, files, dirs);
62 | }
63 | else
64 | {
65 | files.Add(fileSystemInfo.FullName);
66 | }
67 | }
68 | }
69 |
70 | ///
71 | /// 创建加密的AssetBundle
72 | ///
73 | public static void CreateEncryptAssets(string bundlePackagePath, string encryptAssetPath, AssetBundleManifest manifest, string secretKey)
74 | {
75 | string[] assetBundles = manifest.GetAllAssetBundles();
76 | foreach (string assetBundle in assetBundles)
77 | {
78 | string bundlePath = Path.Combine(bundlePackagePath, assetBundle);
79 | if (!Directory.Exists(encryptAssetPath))
80 | {
81 | Directory.CreateDirectory(encryptAssetPath);
82 | }
83 | using (FileStream fs = new FileStream(Path.Combine(encryptAssetPath, assetBundle), FileMode.OpenOrCreate))
84 | {
85 | byte[] encryptBytes = CreateEncryptData(bundlePath, secretKey);
86 | fs.Write(encryptBytes, 0, encryptBytes.Length);
87 | }
88 | }
89 | }
90 |
91 | ///
92 | /// 创建加密过的数据
93 | ///
94 | private static byte[] CreateEncryptData(string filePath, string secretKey)
95 | {
96 | byte[] encryptData;
97 | char[] key = secretKey.ToCharArray();
98 | using (FileStream fs = new FileStream(filePath, FileMode.Open))
99 | {
100 | encryptData = new byte[fs.Length];
101 | fs.Read(encryptData, 0, encryptData.Length);
102 | for (int i = 0; i < encryptData.Length; i++)
103 | {
104 | encryptData[i] = (byte)(encryptData[i] ^ key[i % key.Length]);
105 | }
106 | }
107 | return encryptData;
108 | }
109 |
110 | ///
111 | /// 需要忽略加载的格式
112 | ///
113 | public static bool CantLoadType(string fileFullName)
114 | {
115 | if (!CantLoadFile(fileFullName))
116 | {
117 | return false;
118 | }
119 | string suffix = Path.GetExtension(fileFullName);
120 | switch (suffix)
121 | {
122 | case ".dll":
123 | return false;
124 | case ".cs":
125 | return false;
126 | case ".meta":
127 | return false;
128 | case ".js":
129 | return false;
130 | case ".boo":
131 | return false;
132 | }
133 | return true;
134 | }
135 |
136 | ///
137 | /// 不能加载的文件
138 | ///
139 | public static bool CantLoadFile(string fillPathOrName)
140 | {
141 | if (fillPathOrName.Contains("LightingData.asset"))
142 | {
143 | return false;
144 | }
145 | // if (fillPathOrName.Contains("Lightmap-"))
146 | // {
147 | // return false;
148 | // }
149 | // if (fillPathOrName.Contains("ReflectionProbe-"))
150 | // {
151 | // return false;
152 | // }
153 | return true;
154 | }
155 |
156 | ///
157 | /// 是Shader资源
158 | ///
159 | public static bool IsShaderAsset(string fileFullName)
160 | {
161 | string suffix = Path.GetExtension(fileFullName);
162 | switch (suffix)
163 | {
164 | case ".shader":
165 | return true;
166 | case ".shadervariants":
167 | return true;
168 | }
169 | return false;
170 | }
171 |
172 | public static bool IsGroupAsset(string fileFullName, AssetsLoadSetting assetsLoadSetting)
173 | {
174 | foreach (string assetGroupPath in assetsLoadSetting.AssetGroupPaths)
175 | {
176 | if (fileFullName.Contains(assetGroupPath))
177 | {
178 | return true;
179 | }
180 | }
181 | return false;
182 | }
183 |
184 | public static string GetGroupAssetPath(string fileFullName, AssetsLoadSetting assetsLoadSetting)
185 | {
186 | foreach (string assetGroupPath in assetsLoadSetting.AssetGroupPaths)
187 | {
188 | if (fileFullName.Contains(assetGroupPath))
189 | {
190 | return assetGroupPath;
191 | }
192 | }
193 | return null;
194 | }
195 |
196 | ///
197 | /// 是否生成路径字段代码脚本
198 | ///
199 | public static void GeneratePathCode(HashSet allAssetPaths, string scriptFilePaths)
200 | {
201 | using (StreamWriter sw = new StreamWriter(Path.Combine(Application.dataPath, scriptFilePaths, "BPath.cs")))
202 | {
203 | StringBuilder sb = new StringBuilder();
204 | sb.Append("// ReSharper disable All\n");
205 | sb.Append("namespace BM\n");
206 | sb.Append("{\n");
207 | sb.Append("\tpublic class BPath\n");
208 | sb.Append("\t{\n");
209 | foreach (string assetPath in allAssetPaths)
210 | {
211 | string name = assetPath.Replace("/", "_");
212 | name = name.Replace(".", "__");
213 | sb.Append("\t\tpublic const string " + name + " = \"" + assetPath + "\";\n");
214 | }
215 | sb.Append("\t}\n");
216 | sb.Append("}");
217 | sw.WriteLine(sb.ToString());
218 | }
219 | }
220 |
221 | ///
222 | /// 得到一个路径下文件的大小
223 | ///
224 | public static long GetFileLength(string filePath)
225 | {
226 | long fileLength;
227 | using (FileStream fs = new FileStream(filePath, FileMode.Open))
228 | {
229 | fileLength = fs.Length;
230 | }
231 | return fileLength;
232 | }
233 |
234 | public static void DeleteDir(string srcPath)
235 | {
236 | try
237 | {
238 | DirectoryInfo buildBundlePath = new DirectoryInfo(srcPath);
239 | FileSystemInfo[] fileSystemInfos = buildBundlePath.GetFileSystemInfos();
240 | foreach (FileSystemInfo fileSystemInfo in fileSystemInfos)
241 | {
242 | if (fileSystemInfo is DirectoryInfo)
243 | {
244 | DirectoryInfo subDir = new DirectoryInfo(fileSystemInfo.FullName);
245 | subDir.Delete(true);
246 | }
247 | else
248 | {
249 | File.Delete(fileSystemInfo.FullName);
250 | }
251 | }
252 | }
253 | catch (Exception e)
254 | {
255 | AssetLogHelper.LogError(e.ToString());
256 | throw;
257 | }
258 | }
259 | }
260 | }
--------------------------------------------------------------------------------
/Editor/BundleMasterEditor/BundleMasterInterface/BundleMasterOriginLoadWindow.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Diagnostics.CodeAnalysis;
4 | using System.IO;
5 | using UnityEditor;
6 | using UnityEngine;
7 |
8 | namespace BM
9 | {
10 | public partial class BundleMasterWindow
11 | {
12 | private void OriginLoadSettingRender()
13 | {
14 | GUILayout.BeginHorizontal();
15 | GUILayout.Label("--- <选中的原生资源配置信息> --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------", GUILayout.ExpandWidth(false));
16 | GUILayout.EndHorizontal();
17 | GUILayout.BeginHorizontal();
18 | GUILayout.Label("当前选择的分包配置信息文件: ", GUILayout.ExpandWidth(false));
19 | AssetsOriginSetting selectAssetsOriginSetting = _selectAssetsSetting as AssetsOriginSetting;
20 | selectAssetsOriginSetting = (AssetsOriginSetting)EditorGUILayout.ObjectField(selectAssetsOriginSetting, typeof(AssetsOriginSetting), true, GUILayout.Width(_w / 3),GUILayout.ExpandHeight(false));
21 | bool noLoadSetting = selectAssetsOriginSetting == null;
22 | EditorGUI.BeginDisabledGroup(noLoadSetting);
23 | GUILayout.Label("分包名: ", GUILayout.Width(_w / 20), GUILayout.ExpandWidth(false));
24 | string buildName = "";
25 | if (!noLoadSetting)
26 | {
27 | buildName = selectAssetsOriginSetting.BuildName;
28 | }
29 | buildName = EditorGUILayout.TextField(buildName, GUILayout.Width(_w / 8), GUILayout.ExpandWidth(false));
30 | if (!noLoadSetting)
31 | {
32 | if (!string.Equals(selectAssetsOriginSetting.BuildName, buildName, StringComparison.Ordinal))
33 | {
34 | selectAssetsOriginSetting.BuildName = buildName;
35 | needFlush = true;
36 | }
37 | }
38 | GUILayout.Label("版本索引: ", GUILayout.Width(_w / 17), GUILayout.ExpandWidth(false));
39 | int buildIndex = 0;
40 | if (!noLoadSetting)
41 | {
42 | buildIndex = selectAssetsOriginSetting.BuildIndex;
43 | }
44 | buildIndex = EditorGUILayout.IntField(buildIndex, GUILayout.Width(_w / 20), GUILayout.ExpandWidth(false));
45 | if (!noLoadSetting)
46 | {
47 | if (buildIndex != selectAssetsOriginSetting.BuildIndex)
48 | {
49 | selectAssetsOriginSetting.BuildIndex = buildIndex;
50 | needFlush = true;
51 | }
52 | }
53 | GUI.color = new Color(0.9921569F, 0.2745098F, 0.282353F);
54 | if (GUILayout.Button("删除当前选择的分包配置"))
55 | {
56 | _assetLoadTable.AssetsSettings.Remove(selectAssetsOriginSetting);
57 | AssetDatabase.DeleteAsset(AssetDatabase.GetAssetPath(selectAssetsOriginSetting));
58 | _viewSub = false;
59 | needFlush = true;
60 | }
61 | GUI.color = Color.white;
62 | GUILayout.EndHorizontal();
63 |
64 | GUILayout.Space(_h / 15);
65 |
66 | GUILayout.BeginHorizontal();
67 | string originAssetPath = selectAssetsOriginSetting.OriginFilePath;
68 | originAssetPath = EditorGUILayout.TextField(originAssetPath);
69 | if (!string.Equals(selectAssetsOriginSetting.OriginFilePath, originAssetPath, StringComparison.Ordinal))
70 | {
71 | selectAssetsOriginSetting.OriginFilePath = originAssetPath;
72 | needFlush = true;
73 | }
74 | GUILayout.EndHorizontal();
75 |
76 | GUILayout.Space(_h / 8);
77 |
78 | GUILayout.BeginHorizontal();
79 | GUI.color = new Color(0.9921569F, 0.7960784F, 0.509804F);
80 | if (GUILayout.Button("分包编辑完成", GUILayout.Height(_h / 10)))
81 | {
82 | _viewSub = false;
83 | }
84 | GUI.color = Color.white;
85 | GUILayout.EndHorizontal();
86 | }
87 | }
88 | }
--------------------------------------------------------------------------------
/Editor/BundleMasterEditor/BundleMasterInterface/BundleMasterWindow.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics.CodeAnalysis;
2 | using System.IO;
3 | using UnityEditor;
4 | using UnityEngine;
5 |
6 | namespace BM
7 | {
8 | [SuppressMessage("ReSharper", "PossibleLossOfFraction")]
9 | public partial class BundleMasterWindow : EditorWindow
10 | {
11 | private static BundleMasterWindow _instance = null;
12 |
13 | ///
14 | /// 自动刷新配置界面
15 | ///
16 | private static bool _autoFlush = true;
17 |
18 | ///
19 | /// 需要刷新
20 | ///
21 | private bool needFlush = false;
22 |
23 | ///
24 | /// 运行时配置文件
25 | ///
26 | private static BundleMasterRuntimeConfig _bundleMasterRuntimeConfig = null;
27 |
28 | private static bool _runtimeConfigLoad = false;
29 |
30 | private static int _w = 960;
31 | private static int _h = 540;
32 |
33 | ///
34 | /// 运行时配置文件的路径
35 | ///
36 | public static string RuntimeConfigPath = "Assets/Resources/BMConfig.asset";
37 |
38 | ///
39 | /// 分包文件资源索引配置
40 | ///
41 | public static string AssetLoadTablePath = "Assets/Editor/BundleMasterEditor/BuildSettings/AssetLoadTable.asset";
42 |
43 | ///
44 | /// 分包配置信息
45 | ///
46 | public static string AssetsLoadSettingPath = "Assets/Editor/BundleMasterEditor/BuildSettings/AssetsLoadSetting";
47 |
48 | ///
49 | /// 原生资源包配置信息
50 | ///
51 | public static string AssetsOriginSettingPath = "Assets/Editor/BundleMasterEditor/BuildSettings/AssetsOriginSetting";
52 |
53 | private static AssetLoadTable _assetLoadTable = null;
54 |
55 | ///
56 | /// 选中查看的分包信息
57 | ///
58 | private static AssetsSetting _selectAssetsSetting = null;
59 |
60 | ///
61 | /// 是否查看子页面
62 | ///
63 | private static bool _viewSub = false;
64 |
65 | [MenuItem("Tools/BuildAsset/打开配置界面")]
66 | public static void Init()
67 | {
68 | Open(true);
69 | }
70 |
71 | Vector2 scrollScenePos = Vector2.zero;
72 | Vector2 scrollPos = Vector2.zero;
73 | Vector2 scrollBundleScenePos = Vector2.zero;
74 | Vector2 scrollPathPos = Vector2.zero;
75 |
76 | private static void Open(bool focus)
77 | {
78 | if (_instance != null)
79 | {
80 | return;
81 | }
82 | _viewSub = false;
83 | _instance = (BundleMasterWindow)EditorWindow.GetWindow(typeof(BundleMasterWindow), true, "BundleMasterEditor", focus);
84 | //_instance.position = new Rect(_w / 2, _h / 2, _w, _h);
85 | _instance.maxSize = new Vector2(_w, _h);
86 | _instance.minSize = new Vector2(_w, _h);
87 | //加载配置文件
88 | _bundleMasterRuntimeConfig = AssetDatabase.LoadAssetAtPath(RuntimeConfigPath);
89 | _runtimeConfigLoad = false;
90 | if (_bundleMasterRuntimeConfig != null)
91 | {
92 | _runtimeConfigLoad = true;
93 | }
94 | }
95 | public void OnGUI()
96 | {
97 | Open(false);
98 | if (!_runtimeConfigLoad)
99 | {
100 | GUILayout.BeginArea(new Rect(_w / 4, _h / 8, _w / 2, _h / 4));
101 | if (GUILayout.Button("创建运行时配置文件", GUILayout.Width(_w / 2), GUILayout.Height(_h / 4), GUILayout.ExpandWidth(false), GUILayout.ExpandHeight(false)))
102 | {
103 | _bundleMasterRuntimeConfig = ScriptableObject.CreateInstance();
104 | _bundleMasterRuntimeConfig.AssetLoadMode = AssetLoadMode.Develop;
105 | _bundleMasterRuntimeConfig.MaxDownLoadCount = 8;
106 | _bundleMasterRuntimeConfig.ReDownLoadCount = 3;
107 | if (!Directory.Exists(Path.Combine(Application.dataPath, "Resources")))
108 | {
109 | Directory.CreateDirectory(Path.Combine(Application.dataPath, "Resources"));
110 | }
111 | AssetDatabase.CreateAsset(_bundleMasterRuntimeConfig, RuntimeConfigPath);
112 | AssetDatabase.Refresh();
113 | _runtimeConfigLoad = true;
114 | }
115 | GUILayout.EndArea();
116 | return;
117 | }
118 |
119 | GUILayout.BeginHorizontal();
120 | GUILayout.Label("当前资源加载模式: \t" + _bundleMasterRuntimeConfig.AssetLoadMode, GUILayout.Width(_w / 4), GUILayout.Height(_h / 8), GUILayout.ExpandWidth(false));
121 | if (GUILayout.Button("开发模式", GUILayout.Width(_w / 6), GUILayout.Height(_h / 8), GUILayout.ExpandWidth(true)))
122 | {
123 | _bundleMasterRuntimeConfig.AssetLoadMode = AssetLoadMode.Develop;
124 | DevelopSceneChange.CheckSceneChange(_bundleMasterRuntimeConfig.AssetLoadMode);
125 | needFlush = true;
126 | }
127 | if (GUILayout.Button("本地模式", GUILayout.Width(_w / 6), GUILayout.Height(_h / 8), GUILayout.ExpandWidth(true)))
128 | {
129 | _bundleMasterRuntimeConfig.AssetLoadMode = AssetLoadMode.Local;
130 | DevelopSceneChange.CheckSceneChange(_bundleMasterRuntimeConfig.AssetLoadMode);
131 | needFlush = true;
132 | }
133 | if (GUILayout.Button("构建模式", GUILayout.Width(_w / 6), GUILayout.Height(_h / 8), GUILayout.ExpandWidth(true)))
134 | {
135 | _bundleMasterRuntimeConfig.AssetLoadMode = AssetLoadMode.Build;
136 | DevelopSceneChange.CheckSceneChange(_bundleMasterRuntimeConfig.AssetLoadMode);
137 | needFlush = true;
138 | }
139 | GUILayout.EndHorizontal();
140 | GUILayout.BeginHorizontal(GUILayout.ExpandHeight(false));
141 | GUILayout.Label("最大同时下载资源数: ", GUILayout.Width(_w / 8), GUILayout.ExpandWidth(false));
142 | int maxDownLoadCount = _bundleMasterRuntimeConfig.MaxDownLoadCount;
143 | maxDownLoadCount = EditorGUILayout.IntField(maxDownLoadCount, GUILayout.Width(_w / 16), GUILayout.ExpandWidth(false));
144 | if (_bundleMasterRuntimeConfig.MaxDownLoadCount != maxDownLoadCount)
145 | {
146 | _bundleMasterRuntimeConfig.MaxDownLoadCount = maxDownLoadCount;
147 | needFlush = true;
148 | }
149 | GUILayout.Label("下载失败重试数: ", GUILayout.Width(_w / 10), GUILayout.ExpandWidth(false));
150 | int reDownLoadCount = _bundleMasterRuntimeConfig.ReDownLoadCount;
151 | reDownLoadCount = EditorGUILayout.IntField(reDownLoadCount, GUILayout.Width(_w / 16), GUILayout.ExpandWidth(false));
152 | if (_bundleMasterRuntimeConfig.ReDownLoadCount != reDownLoadCount)
153 | {
154 | _bundleMasterRuntimeConfig.ReDownLoadCount = reDownLoadCount;
155 | needFlush = true;
156 | }
157 | _autoFlush = GUILayout.Toggle(_autoFlush, "是否自动应用更新配置界面数据");
158 | if (!_autoFlush)
159 | {
160 | if (GUILayout.Button("应用更新"))
161 | {
162 | needFlush = false;
163 | Flush();
164 | }
165 | }
166 | GUILayout.EndHorizontal();
167 | GUILayout.BeginHorizontal();
168 | GUILayout.Label("--- <构建AssetBundle配置> ----------------------------------------------------------------------------------------------------------------------------------------------------------------", GUILayout.ExpandWidth(false));
169 | GUILayout.EndHorizontal();
170 | GUILayout.BeginHorizontal();
171 | GUILayout.Label("分包配置总索引文件: ", GUILayout.Width(_w / 8), GUILayout.ExpandWidth(false));
172 | _assetLoadTable = (AssetLoadTable)EditorGUILayout.ObjectField(_assetLoadTable, typeof(AssetLoadTable), true, GUILayout.Width(_w / 3), GUILayout.ExpandWidth(false));
173 | bool noTable = _assetLoadTable == null;
174 | if (GUILayout.Button("查找或创建分包配置索引文件", GUILayout.Width(_w / 5.5f), GUILayout.ExpandWidth(true)))
175 | {
176 | _assetLoadTable = AssetDatabase.LoadAssetAtPath(AssetLoadTablePath);
177 | if (_assetLoadTable == null)
178 | {
179 | _assetLoadTable = ScriptableObject.CreateInstance();
180 | AssetDatabase.CreateAsset(_assetLoadTable, BundleMasterWindow.AssetLoadTablePath);
181 | needFlush = true;
182 | }
183 | }
184 | EditorGUI.BeginDisabledGroup(noTable);
185 | GUI.color = new Color(0.654902F, 0.9921569F, 0.2784314F);
186 | if (GUILayout.Button("添加一个分包配置", GUILayout.Width(_w / 6), GUILayout.ExpandWidth(true)))
187 | {
188 | int index = 0;
189 | while (true)
190 | {
191 | AssetsLoadSetting assetLoadTable = AssetDatabase.LoadAssetAtPath(AssetsLoadSettingPath + "_" + index + ".asset");
192 | if (assetLoadTable == null)
193 | {
194 | AssetDatabase.CreateAsset(ScriptableObject.CreateInstance(), AssetsLoadSettingPath + "_" + index + ".asset");
195 | break;
196 | }
197 | else
198 | {
199 | index++;
200 | }
201 | }
202 | needFlush = true;
203 | }
204 | GUI.color = Color.white;
205 | GUI.color = new Color(0.9921569F, 0.7960784F, 0.509804F);
206 | if (GUILayout.Button("添加一个原生资源包配置", GUILayout.Width(_w / 6), GUILayout.ExpandWidth(true)))
207 | {
208 | int index = 0;
209 | while (true)
210 | {
211 | AssetsOriginSetting assetsOriginSetting = AssetDatabase.LoadAssetAtPath(AssetsOriginSettingPath + "_" + index + ".asset");
212 | if (assetsOriginSetting == null)
213 | {
214 | AssetDatabase.CreateAsset(ScriptableObject.CreateInstance(), AssetsOriginSettingPath + "_" + index + ".asset");
215 | break;
216 | }
217 | else
218 | {
219 | index++;
220 | }
221 | }
222 | needFlush = true;
223 | }
224 | GUI.color = Color.white;
225 | GUILayout.EndHorizontal();
226 |
227 | if (!_viewSub)
228 | {
229 | AssetsLoadMainRender(noTable);
230 | }
231 | else
232 | {
233 | if (_selectAssetsSetting is AssetsLoadSetting)
234 | {
235 | AssetsLoadSettingRender();
236 | }
237 | else
238 | {
239 | OriginLoadSettingRender();
240 | }
241 | }
242 |
243 | if (needFlush && _autoFlush)
244 | {
245 | needFlush = false;
246 | Flush();
247 | }
248 | }
249 |
250 | public void OnDestroy()
251 | {
252 | _instance = null;
253 | }
254 | }
255 | }
--------------------------------------------------------------------------------
/Editor/BundleMasterEditor/BundleUsefulTool.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.IO;
3 | using UnityEditor;
4 | using UnityEngine;
5 |
6 | namespace BM
7 | {
8 | ///
9 | /// 一些打包会用到的实用的功能
10 | ///
11 | public class BundleUsefulTool : EditorWindow
12 | {
13 | [MenuItem("Tools/BuildAsset/Copy资源到StreamingAssets")]
14 | public static void CopyToStreamingAssets()
15 | {
16 | if (!Directory.Exists(Application.streamingAssetsPath))
17 | {
18 | Directory.CreateDirectory(Application.streamingAssetsPath);
19 | }
20 | BuildAssetsTools.DeleteDir(Application.streamingAssetsPath);
21 | AssetLoadTable assetLoadTable = AssetDatabase.LoadAssetAtPath(BundleMasterWindow.AssetLoadTablePath);
22 | foreach (AssetsSetting assetsSetting in assetLoadTable.AssetsSettings)
23 | {
24 | if (!(assetsSetting is AssetsLoadSetting assetsLoadSetting))
25 | {
26 | continue;
27 | }
28 | string assetPathFolder;
29 | #if (Nintendo_Switch || BMWebGL)
30 | assetPathFolder = Path.Combine(assetLoadTable.BuildBundlePath, assetsLoadSetting.BuildName);
31 | #else
32 | if (assetsLoadSetting.EncryptAssets)
33 | {
34 | assetPathFolder = Path.Combine(assetLoadTable.BuildBundlePath + "/../", assetLoadTable.EncryptPathFolder, assetsLoadSetting.BuildName);
35 | }
36 | else
37 | {
38 | assetPathFolder = Path.Combine(assetLoadTable.BuildBundlePath, assetsLoadSetting.BuildName);
39 | }
40 | #endif
41 | string directoryPath = Path.Combine(Application.streamingAssetsPath, assetsLoadSetting.BuildName);
42 | if (!Directory.Exists(directoryPath))
43 | {
44 | Directory.CreateDirectory(directoryPath);
45 | }
46 | DirectoryInfo subBundlePath = new DirectoryInfo(assetPathFolder);
47 | FileInfo[] fileInfos = subBundlePath.GetFiles();
48 | foreach (FileInfo fileInfo in fileInfos)
49 | {
50 | if (fileInfo.DirectoryName == null)
51 | {
52 | AssetLogHelper.LogError("找不到文件的路径: " + fileInfo.Name);
53 | continue;
54 | }
55 | string filePath = Path.Combine(fileInfo.DirectoryName, fileInfo.Name);
56 | string suffix = Path.GetExtension(filePath);
57 | if ((!fileInfo.Name.StartsWith("shader_") && string.IsNullOrWhiteSpace(suffix)) || suffix == ".manifest")
58 | {
59 | continue;
60 | }
61 | File.Copy(filePath, Path.Combine(directoryPath, fileInfo.Name));
62 | }
63 | }
64 | foreach (AssetsSetting assetsSetting in assetLoadTable.AssetsSettings)
65 | {
66 | if (!(assetsSetting is AssetsOriginSetting assetsOriginSetting))
67 | {
68 | continue;
69 | }
70 | string assetPathFolder = Path.Combine(assetLoadTable.BuildBundlePath, assetsOriginSetting.BuildName);
71 | string directoryPath = Path.Combine(Application.streamingAssetsPath, assetsOriginSetting.BuildName);
72 | if (!Directory.Exists(directoryPath))
73 | {
74 | Directory.CreateDirectory(directoryPath);
75 | }
76 | //获取所有资源目录
77 | HashSet files = new HashSet();
78 | HashSet dirs = new HashSet();
79 | BuildAssetsTools.GetOriginsPath(assetPathFolder, files, dirs);
80 | //Copy资源
81 | foreach (string dir in dirs)
82 | {
83 | Directory.CreateDirectory(dir.Replace(assetPathFolder, directoryPath));
84 | }
85 | foreach (string file in files)
86 | {
87 | File.Copy(file, file.Replace(assetPathFolder, directoryPath), true);
88 | }
89 | }
90 | AssetDatabase.Refresh();
91 | AssetLogHelper.Log("已将资源复制到StreamingAssets");
92 | }
93 |
94 | [MenuItem("Tools/BuildAsset/实用工具/清空热更目录下的文件")]
95 | public static void ClearHotfixPathPath()
96 | {
97 | BuildAssetsTools.DeleteDir(AssetComponentConfig.HotfixPath);
98 | AssetDatabase.Refresh();
99 | }
100 |
101 | [MenuItem("Tools/BuildAsset/实用工具/清空本地目录下的文件")]
102 | public static void ClearLocalBundlePath()
103 | {
104 | BuildAssetsTools.DeleteDir(AssetComponentConfig.LocalBundlePath);
105 | AssetDatabase.Refresh();
106 | }
107 |
108 | [MenuItem("Tools/BuildAsset/实用工具/清空打包目录下的文件")]
109 | public static void ClearLocalBuildPath()
110 | {
111 | AssetLoadTable assetLoadTable = AssetDatabase.LoadAssetAtPath(BundleMasterWindow.AssetLoadTablePath);
112 | BuildAssetsTools.DeleteDir(assetLoadTable.BundlePath);
113 | }
114 |
115 | [MenuItem("Tools/BuildAsset/实用工具/清空加密目录下的文件")]
116 | public static void ClearLocalEncryptPath()
117 | {
118 | AssetLoadTable assetLoadTable = AssetDatabase.LoadAssetAtPath(BundleMasterWindow.AssetLoadTablePath);
119 | BuildAssetsTools.DeleteDir(assetLoadTable.EncryptPathFolder);
120 | }
121 | }
122 |
123 |
124 | }
--------------------------------------------------------------------------------
/Editor/BundleMasterEditor/DevelopSceneChange.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 | using UnityEditor;
4 |
5 | namespace BM
6 | {
7 | public static class DevelopSceneChange
8 | {
9 | ///
10 | /// 用于检测在Develop模式下将场景加入BuildSettings
11 | ///
12 | public static void CheckSceneChange(AssetLoadMode assetLoadMode)
13 | {
14 | AssetLoadTable assetLoadTable = AssetDatabase.LoadAssetAtPath(BundleMasterWindow.AssetLoadTablePath);
15 | List assetsLoadSettings = new List();
16 | foreach (AssetsSetting assetsSetting in assetLoadTable.AssetsSettings)
17 | {
18 | if (assetsSetting is AssetsLoadSetting)
19 | {
20 | assetsLoadSettings.Add(assetsSetting as AssetsLoadSetting);
21 | }
22 | }
23 | Dictionary editorBuildSettingsScenes = new Dictionary();
24 | for (int i = 0; i < assetLoadTable.InitScene.Count; i++)
25 | {
26 | string scenePath = AssetDatabase.GetAssetPath(assetLoadTable.InitScene[i]);
27 | if (!editorBuildSettingsScenes.ContainsKey(scenePath))
28 | {
29 | editorBuildSettingsScenes.Add(scenePath, new EditorBuildSettingsScene(scenePath, true));
30 | }
31 | }
32 | if (assetLoadMode == AssetLoadMode.Develop)
33 | {
34 | foreach (AssetsLoadSetting assetsLoadSetting in assetsLoadSettings)
35 | {
36 | if (assetsLoadSetting == null)
37 | {
38 | continue;
39 | }
40 | foreach (SceneAsset sceneAsset in assetsLoadSetting.Scene)
41 | {
42 | if (sceneAsset == null)
43 | {
44 | continue;
45 | }
46 | string scenePath = AssetDatabase.GetAssetPath(sceneAsset);
47 | if (!editorBuildSettingsScenes.ContainsKey(scenePath))
48 | {
49 | editorBuildSettingsScenes.Add(scenePath, new EditorBuildSettingsScene(scenePath, true));
50 | }
51 | }
52 | }
53 | }
54 | EditorBuildSettings.scenes = editorBuildSettingsScenes.Values.ToArray();
55 | }
56 | }
57 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/LiteMultiThreadDownLoad/BundleMaster.LMTD.asmdef:
--------------------------------------------------------------------------------
1 | {
2 | "name": "BundleMaster.LMTD",
3 | "rootNamespace": "",
4 | "references": [],
5 | "includePlatforms": [
6 | "Android",
7 | "Editor",
8 | "iOS",
9 | "LinuxStandalone64",
10 | "macOSStandalone",
11 | "Switch",
12 | "WindowsStandalone32",
13 | "WindowsStandalone64"
14 | ],
15 | "excludePlatforms": [],
16 | "allowUnsafeCode": false,
17 | "overrideReferences": false,
18 | "precompiledReferences": [],
19 | "autoReferenced": true,
20 | "defineConstraints": [],
21 | "versionDefines": [],
22 | "noEngineReferences": false
23 | }
--------------------------------------------------------------------------------
/LiteMultiThreadDownLoad/BundleMaster.LMTD.asmdef.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 3bc86b810ed97834e8d5d8df82632a59
3 | AssemblyDefinitionImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/LiteMultiThreadDownLoad/ILiteThreadAction.cs:
--------------------------------------------------------------------------------
1 | namespace LMTD
2 | {
3 | public interface ILiteThreadAction
4 | {
5 | public void Logic();
6 | }
7 | }
--------------------------------------------------------------------------------
/LiteMultiThreadDownLoad/LMTDProxy.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Net;
3 |
4 | namespace LMTD
5 | {
6 | public class LMTDProxy
7 | {
8 | private static readonly object LockObj = new object();
9 | private static WebProxy _proxy = null;
10 |
11 | public static WebProxy GetProxy()
12 | {
13 | lock (LockObj)
14 | {
15 | _proxy = new WebProxy();
16 | return _proxy;
17 | }
18 | }
19 |
20 | }
21 | }
--------------------------------------------------------------------------------
/LiteMultiThreadDownLoad/LMTDownLoad.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Net;
5 |
6 | namespace LMTD
7 | {
8 | public class LMTDownLoad : ILiteThreadAction, IDisposable
9 | {
10 | private static readonly Queue LmtDownLoadQueue = new Queue();
11 |
12 | private Action _completeCallback;
13 |
14 | ///
15 | /// 下载完成后的回调
16 | ///
17 | public event Action Completed
18 | {
19 | add => _completeCallback += value;
20 | remove => this._completeCallback -= value;
21 | }
22 |
23 | private Action _upDateInfoCallback;
24 |
25 | ///
26 | /// 下载更新循环
27 | ///
28 | public event Action UpDateInfo
29 | {
30 | add => _upDateInfoCallback += value;
31 | remove => this._upDateInfoCallback -= value;
32 | }
33 |
34 | ///
35 | /// 创建一个下载器
36 | ///
37 | public static LMTDownLoad Create(string url, string filePath)
38 | {
39 | LMTDownLoad lmtDownLoad;
40 | lock (LmtDownLoadQueue)
41 | {
42 | if (LmtDownLoadQueue.Count > 0)
43 | {
44 | lmtDownLoad = LmtDownLoadQueue.Dequeue();
45 | lmtDownLoad.url = url;
46 | lmtDownLoad.filePath = filePath;
47 | }
48 | else
49 | {
50 | lmtDownLoad = new LMTDownLoad(url, filePath);
51 | }
52 | }
53 | lmtDownLoad.CancelLock = false;
54 | return lmtDownLoad;
55 | }
56 |
57 | private LMTDownLoad(string url, string filePath)
58 | {
59 | this.url = url;
60 | this.filePath = filePath;
61 | }
62 |
63 | public bool CancelLock = false;
64 |
65 | ///
66 | /// 下载地址
67 | ///
68 | private string url = null;
69 |
70 | ///
71 | /// 文件存储路径
72 | ///
73 | private string filePath = null;
74 |
75 | ///
76 | /// 下载信息
77 | ///
78 | public LmtDownloadInfo LmtDownloadInfo;
79 |
80 | ///
81 | /// 返回下载文件的信息
82 | ///
83 | public LmtDownloadInfo DownLoad()
84 | {
85 | //需要返回的数据
86 | LmtDownloadInfo.DownLoadFileCRC = 0xFFFFFFFF;
87 | LmtDownloadInfo.downLoadSizeValue = 0;
88 | //创建下载请求
89 | HttpWebRequest httpWebRequest = (HttpWebRequest)WebRequest.Create(url);
90 | httpWebRequest.Proxy = LMTDProxy.GetProxy();
91 | HttpWebResponse httpWebResponse;
92 | try
93 | {
94 | httpWebResponse = (HttpWebResponse)httpWebRequest.GetResponse();
95 | }
96 | catch
97 | {
98 | #if UNITY_5_3_OR_NEWER
99 | UnityEngine.Debug.LogError("下载资源失败\n" + url + "\n");
100 | #else
101 | Console.WriteLine("下载失败资源失败\n" + url + "\n");
102 | #endif
103 | LmtDownloadInfo.LmtDownloadResult = LmtDownloadResult.ResponseFail;
104 | return LmtDownloadInfo;
105 | }
106 | //创建一块存储的大小 1mb
107 | byte[] blockBytes = new byte[1048576];
108 | using FileStream fileStream = new FileStream(filePath, FileMode.Create);
109 | try
110 | {
111 | //获取文件流
112 | using Stream receiveStream = httpWebResponse.GetResponseStream();
113 | // ReSharper disable once PossibleNullReferenceException
114 | int blockSize = receiveStream.Read(blockBytes, 0, blockBytes.Length);
115 | while (blockSize > 0)
116 | {
117 | if (CancelLock)
118 | {
119 | LmtDownloadInfo.LmtDownloadResult = LmtDownloadResult.CancelDownLoad;
120 | return LmtDownloadInfo;
121 | }
122 | //计算CRC
123 | for (uint i = 0; i < blockSize; i++)
124 | {
125 | LmtDownloadInfo.DownLoadFileCRC = (LmtDownloadInfo.DownLoadFileCRC << 8) ^ LmtdTable.CRCTable[(LmtDownloadInfo.DownLoadFileCRC >> 24) ^ blockBytes[i]];
126 | }
127 | LmtDownloadInfo.downLoadSizeValue += blockSize;
128 | _upDateInfoCallback?.Invoke();
129 | //循环写入读取数据
130 | fileStream.Write(blockBytes, 0, blockSize);
131 | blockSize = receiveStream.Read(blockBytes, 0, blockBytes.Length);
132 | }
133 | receiveStream.Close();
134 | }
135 | catch
136 | {
137 | #if UNITY_5_3_OR_NEWER
138 | UnityEngine.Debug.LogError("下载资源中断\n" + url + "\n");
139 | #else
140 | Console.WriteLine("下载资源中断\n" + url + "\n");
141 | #endif
142 | LmtDownloadInfo.LmtDownloadResult = LmtDownloadResult.DownLoadFail;
143 | return LmtDownloadInfo;
144 | }
145 | finally
146 | {
147 | fileStream.Close();
148 | httpWebResponse.Close();
149 | httpWebResponse.Dispose();
150 | }
151 | LmtDownloadInfo.LmtDownloadResult = LmtDownloadResult.Success;
152 | return LmtDownloadInfo;
153 | }
154 |
155 | public void Logic()
156 | {
157 | LmtDownloadInfo lmtDownloadInfo = DownLoad();
158 | _completeCallback?.Invoke(lmtDownloadInfo);
159 | }
160 |
161 | public void Dispose()
162 | {
163 | CancelLock = false;
164 | url = null;
165 | filePath = null;
166 | _completeCallback = null;
167 | _upDateInfoCallback = null;
168 | lock (LmtDownLoadQueue)
169 | {
170 | LmtDownLoadQueue.Enqueue(this);
171 | }
172 | }
173 |
174 | }
175 |
176 | ///
177 | /// 下载完成后的回调信息
178 | ///
179 | public struct LmtDownloadInfo
180 | {
181 | public LmtDownloadResult LmtDownloadResult;
182 | public uint DownLoadFileCRC;
183 |
184 | internal long downLoadSizeValue;
185 | ///
186 | /// 已经下载了多少
187 | ///
188 | public long DownLoadSize
189 | {
190 | get { return downLoadSizeValue; }
191 | }
192 |
193 | }
194 |
195 | public enum LmtDownloadResult
196 | {
197 | ///
198 | /// 成功
199 | ///
200 | Success = 0,
201 | ///
202 | /// 请求连接失败
203 | ///
204 | ResponseFail = 1,
205 | ///
206 | /// 下载失败
207 | ///
208 | DownLoadFail = 2,
209 | ///
210 | /// 取消下载
211 | ///
212 | CancelDownLoad = 3
213 | }
214 | }
--------------------------------------------------------------------------------
/LiteMultiThreadDownLoad/LiteThread.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Threading;
4 |
5 | namespace LMTD
6 | {
7 | internal class LiteThread
8 | {
9 | private readonly Thread thread;
10 | private ILiteThreadAction _liteThreadAction = null;
11 |
12 | internal LiteThread()
13 | {
14 | ThreadFactory.ThreadCount++;
15 | thread = new Thread(Run);
16 | thread.Start();
17 | }
18 |
19 | internal void Action(ILiteThreadAction liteThreadAction)
20 | {
21 | this._liteThreadAction = liteThreadAction;
22 | }
23 |
24 | private void Run()
25 | {
26 | while (!ThreadFactory.RecoverKey)
27 | {
28 | Thread.Sleep(1);
29 | if (_liteThreadAction != null)
30 | {
31 | _liteThreadAction.Logic();
32 | _liteThreadAction = null;
33 | lock (ThreadFactory.ThreadPool)
34 | {
35 | //执行完逻辑后自己进池
36 | ThreadFactory.ThreadPool.Enqueue(this);
37 | }
38 | }
39 |
40 | }
41 | Recovery();
42 | }
43 |
44 | ///
45 | /// 回收这个线程
46 | ///
47 | internal void Recovery()
48 | {
49 | ThreadFactory.ThreadCount--;
50 | thread.Abort();
51 | }
52 | }
53 | }
--------------------------------------------------------------------------------
/LiteMultiThreadDownLoad/LmtdTable.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace LMTD
4 | {
5 | internal static class LmtdTable
6 | {
7 | ///
8 | /// 注意此Table和多线程下载的Table以及打AssetBundle加密用的Table需要一样,其中在BM的代码里还有一份拷贝
9 | ///
10 | internal static readonly UInt32[] CRCTable =
11 | {
12 | 0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b, 0x1a864db2, 0x1e475005,
13 | 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61, 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd,
14 | 0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9, 0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75,
15 | 0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3, 0x709f7b7a, 0x745e66cd,
16 | 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039, 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5,
17 | 0xbe2b5b58, 0xbaea46ef, 0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d,
18 | 0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49, 0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95,
19 | 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, 0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d,
20 | 0x34867077, 0x30476dc0, 0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072,
21 | 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, 0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca,
22 | 0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde, 0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02,
23 | 0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1, 0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba,
24 | 0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b, 0xbb60adfc, 0xb6238b25, 0xb2e29692,
25 | 0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6, 0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a,
26 | 0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e, 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2,
27 | 0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34, 0xdc3abded, 0xd8fba05a,
28 | 0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637, 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb,
29 | 0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f, 0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53,
30 | 0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5, 0x3f9b762c, 0x3b5a6b9b,
31 | 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff, 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623,
32 | 0xf12f560e, 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b,
33 | 0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f, 0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3,
34 | 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, 0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b,
35 | 0x9b3660c6, 0x9ff77d71, 0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3,
36 | 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, 0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c,
37 | 0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8, 0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24,
38 | 0x119b4be9, 0x155a565e, 0x18197087, 0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec,
39 | 0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d, 0x2056cd3a, 0x2d15ebe3, 0x29d4f654,
40 | 0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0, 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c,
41 | 0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18, 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4,
42 | 0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662, 0x933eb0bb, 0x97ffad0c,
43 | 0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668, 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4
44 | };
45 | }
46 | }
--------------------------------------------------------------------------------
/LiteMultiThreadDownLoad/ThreadFactory.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Threading;
4 |
5 | namespace LMTD
6 | {
7 | public static class ThreadFactory
8 | {
9 | ///
10 | /// 所有线程的数量
11 | ///
12 | internal static uint ThreadCount = 0;
13 |
14 | ///
15 | /// 所有线程的池
16 | ///
17 | internal static readonly Queue ThreadPool = new Queue();
18 |
19 | private static readonly HashSet AllLiteThread = new HashSet();
20 |
21 | ///
22 | /// 是否开启回收进程,默认不开启
23 | ///
24 | internal static bool RecoverKey
25 | {
26 | set
27 | {
28 | if (value == false)
29 | {
30 | //说明所有进程都已经被回收
31 | }
32 | }
33 | get => recoverKey;
34 | }
35 | private static bool recoverKey = false;
36 |
37 | ///
38 | /// 生命周期
39 | ///
40 | private static Thread _threadFactoryLife = null;
41 |
42 | ///
43 | /// 执行一个逻辑
44 | ///
45 | public static void ThreadAction(ILiteThreadAction liteThreadAction)
46 | {
47 | if (_threadFactoryLife == null)
48 | {
49 | _threadFactoryLife = new Thread(ThreadUpdate);
50 | _threadFactoryLife.Start();
51 | }
52 | LiteThread liteThread;
53 | lock (ThreadPool)
54 | {
55 | if (ThreadPool.Count > 0)
56 | {
57 | liteThread = ThreadPool.Dequeue();
58 | }
59 | else
60 | {
61 | liteThread = new LiteThread();
62 | AllLiteThread.Add(liteThread);
63 | }
64 | }
65 | AllLiteThread.Add(liteThread);
66 | liteThread.Action(liteThreadAction);
67 | }
68 |
69 |
70 | private static long _lastTime = 0;
71 | ///
72 | /// 线程池清空标志位(如果5-10秒内池有多余的线程线程就清空)
73 | ///
74 | private static bool _poolClear = false;
75 | ///
76 | /// 线程自动回收机制
77 | ///
78 | private static void ThreadUpdate()
79 | {
80 | _lastTime = DateTime.Now.Ticks;
81 | while (true)
82 | {
83 | long nowTime = DateTime.Now.Ticks;
84 | if (nowTime - _lastTime > 50000000)
85 | {
86 | _lastTime = nowTime;
87 | //每隔5s一次循环
88 | lock (ThreadPool)
89 | {
90 | if (ThreadPool.Count == 0)
91 | {
92 | continue;
93 | }
94 | if (!_poolClear)
95 | {
96 | _poolClear = true;
97 | continue;
98 | }
99 | LiteThread liteThread = ThreadPool.Dequeue();
100 | AllLiteThread.Remove(liteThread);
101 | liteThread.Recovery();
102 | _poolClear = false;
103 | if (ThreadCount == 0)
104 | {
105 | _lastTime = 0;
106 | _threadFactoryLife?.Abort();
107 | _threadFactoryLife = null;
108 | }
109 | }
110 | }
111 | Thread.Sleep(1);
112 | }
113 | }
114 |
115 | ///
116 | /// 线程池销毁
117 | ///
118 | public static void Destroy()
119 | {
120 | foreach (LiteThread liteThread in AllLiteThread)
121 | {
122 | liteThread.Recovery();
123 | }
124 | AllLiteThread.Clear();
125 | lock (ThreadPool)
126 | {
127 | ThreadPool.Clear();
128 | }
129 | ThreadCount = 0;
130 | _lastTime = 0;
131 | _poolClear = false;
132 | _threadFactoryLife?.Abort();
133 | _threadFactoryLife = null;
134 | }
135 |
136 | }
137 | }
--------------------------------------------------------------------------------
/NintendoAdapter/AssetComponentInit_Nintendo.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text.RegularExpressions;
4 | using UnityEngine;
5 | using ET;
6 | using UnityEngine.Networking;
7 |
8 | namespace BM
9 | {
10 | public static partial class AssetComponent
11 | {
12 | #if Nintendo_Switch
13 | ///
14 | /// 初始化
15 | ///
16 | /// Switch模式下 secretKey 参数失效,仅作占位使用
17 | public static async ETTask Initialize(string bundlePackageName, string secretKey = null)
18 | {
19 | if (AssetComponentConfig.AssetLoadMode == AssetLoadMode.Develop)
20 | {
21 | AssetLogHelper.Log("AssetLoadMode = Develop 不需要初始化Bundle配置文件");
22 | return false;
23 | }
24 | if (BundleNameToRuntimeInfo.ContainsKey(bundlePackageName))
25 | {
26 | AssetLogHelper.LogError(bundlePackageName + " 重复初始化");
27 | return false;
28 | }
29 | BundleRuntimeInfo bundleRuntimeInfo = new BundleRuntimeInfo(bundlePackageName);
30 | BundleNameToRuntimeInfo.Add(bundlePackageName, bundleRuntimeInfo);
31 |
32 | string filePath = BundleFileExistPath_Nintendo(bundlePackageName, "FileLogs.txt");
33 | string fileLogs = await LoadNintendoFileText(filePath);
34 | {
35 | Regex reg = new Regex(@"\<(.+?)>");
36 | MatchCollection matchCollection = reg.Matches(fileLogs);
37 | List dependFileName = new List();
38 | foreach (Match m in matchCollection)
39 | {
40 | string[] fileLog = m.Groups[1].Value.Split('|');
41 | LoadFile loadFile = new LoadFile();
42 | loadFile.FilePath = fileLog[0];
43 | loadFile.AssetBundleName = fileLog[1];
44 |
45 | if (fileLog.Length > 2)
46 | {
47 | for (int i = 2; i < fileLog.Length; i++)
48 | {
49 | dependFileName.Add(fileLog[i]);
50 | }
51 | }
52 | loadFile.DependFileName = dependFileName.ToArray();
53 | dependFileName.Clear();
54 | bundleRuntimeInfo.LoadFileDic.Add(loadFile.FilePath, loadFile);
55 | }
56 | }
57 |
58 | string dependPath = BundleFileExistPath_Nintendo(bundlePackageName, "DependLogs.txt");
59 | string dependLogs = await LoadNintendoFileText(dependPath);
60 | {
61 | Regex reg = new Regex(@"\<(.+?)>");
62 | MatchCollection matchCollection = reg.Matches(dependLogs);
63 | foreach (Match m in matchCollection)
64 | {
65 | string[] dependLog = m.Groups[1].Value.Split('|');
66 | LoadDepend loadDepend = new LoadDepend();
67 | loadDepend.FilePath = dependLog[0];
68 | loadDepend.AssetBundleName = dependLog[1];
69 | bundleRuntimeInfo.LoadDependDic.Add(loadDepend.FilePath, loadDepend);
70 | }
71 | }
72 |
73 | ETTask groupTcs = ETTask.Create();
74 | string groupPath = BundleFileExistPath_Nintendo(bundlePackageName, "GroupLogs.txt");
75 | string groupLogs = await LoadNintendoFileText(groupPath);
76 | {
77 | Regex reg = new Regex(@"\<(.+?)>");
78 | MatchCollection matchCollection = reg.Matches(groupLogs);
79 | foreach (Match m in matchCollection)
80 | {
81 | string[] groupLog = m.Groups[1].Value.Split('|');
82 | LoadGroup loadGroup = new LoadGroup();
83 | loadGroup.FilePath = groupLog[0];
84 | loadGroup.AssetBundleName = groupLog[1];
85 | if (groupLog.Length > 2)
86 | {
87 | for (int i = 2; i < groupLog.Length; i++)
88 | {
89 | loadGroup.DependFileName.Add(groupLog[i]);
90 | }
91 | }
92 | bundleRuntimeInfo.LoadGroupDic.Add(loadGroup.FilePath, loadGroup);
93 | bundleRuntimeInfo.LoadGroupDicKey.Add(loadGroup.FilePath);
94 | }
95 | }
96 |
97 | //加载当前分包的shader
98 | await LoadShader_Nintendo(bundlePackageName);
99 | return true;
100 | }
101 |
102 | private static async ETTask LoadShader_Nintendo(string bundlePackageName)
103 | {
104 | ETTask tcs = ETTask.Create();
105 | string shaderPath = BundleFileExistPath_Nintendo(bundlePackageName, "shader_" + bundlePackageName.ToLower());
106 | //判断文件是否存在
107 | nn.fs.EntryType entryType = nn.fs.EntryType.File;
108 | nn.Result result = nn.fs.FileSystem.GetEntryType(ref entryType, shaderPath);
109 | if (nn.fs.FileSystem.ResultPathNotFound.Includes(result))
110 | {
111 | AssetLogHelper.Log("此分包没有Shader: \n" + shaderPath);
112 | result.abortUnlessSuccess();
113 | return;
114 | }
115 | result.abortUnlessSuccess();
116 | //读取Shader
117 | AssetBundleCreateRequest request = AssetBundle.LoadFromFileAsync(shaderPath);
118 | request.completed += operation =>
119 | {
120 | BundleNameToRuntimeInfo[bundlePackageName].Shader = request.assetBundle;
121 | tcs.SetResult();
122 | };
123 |
124 | await tcs;
125 | }
126 | #endif
127 |
128 | }
129 | }
--------------------------------------------------------------------------------
/NintendoAdapter/AssetComponentTools_Nintendo.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using System.Text;
3 | using ET;
4 |
5 | namespace BM
6 | {
7 | #if Nintendo_Switch
8 | public static partial class AssetComponent
9 | {
10 | ///
11 | /// NintendoSwitch获取Bundle信息文件的路径
12 | ///
13 | internal static string BundleFileExistPath_Nintendo(string bundlePackageName, string fileName)
14 | {
15 | string path = Path.Combine(AssetComponentConfig.LocalBundlePath, bundlePackageName, fileName);
16 | //string path = "rom:/Data/StreamingAssets/" + bundlePackageName + "/" + fileName;
17 | return path;
18 | }
19 |
20 | internal static async ETTask LoadNintendoFileText(string filePath)
21 | {
22 | nn.fs.FileHandle fileHandle = new nn.fs.FileHandle();
23 | nn.fs.EntryType entryType = nn.fs.EntryType.File;
24 |
25 | nn.Result result = nn.fs.FileSystem.GetEntryType(ref entryType, filePath);
26 | if (nn.fs.FileSystem.ResultPathNotFound.Includes(result))
27 | {
28 | AssetLogHelper.LogError("初始化分包未找到要读取的文件: \t" + filePath);
29 | result.abortUnlessSuccess();
30 | return "";
31 | }
32 | result.abortUnlessSuccess();
33 |
34 |
35 | result = nn.fs.File.Open(ref fileHandle, filePath, nn.fs.OpenFileMode.Read);
36 | result.abortUnlessSuccess();
37 |
38 | long fileSize = 0;
39 | result = nn.fs.File.GetSize(ref fileSize, fileHandle);
40 | result.abortUnlessSuccess();
41 |
42 | byte[] data = new byte[fileSize];
43 | result = nn.fs.File.Read(fileHandle, 0, data, fileSize);
44 | result.abortUnlessSuccess();
45 |
46 | nn.fs.File.Close(fileHandle);
47 |
48 | MemoryStream memoryStream = new MemoryStream(data);
49 | StreamReader streamReader = new StreamReader(memoryStream);
50 |
51 | //异步读取内容
52 | string str = await streamReader.ReadToEndAsync();
53 |
54 | //关闭引用
55 | streamReader.Close();
56 | streamReader.Dispose();
57 | memoryStream.Close();
58 | await memoryStream.DisposeAsync();
59 |
60 | return str;
61 | }
62 |
63 |
64 | }
65 | #endif
66 | }
67 |
--------------------------------------------------------------------------------
/NintendoAdapter/AssetComponentUpdate_Nintendo.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using ET;
3 |
4 | namespace BM
5 | {
6 | public static partial class AssetComponent
7 | {
8 | #if Nintendo_Switch
9 | ///
10 | /// 检查分包是否需要更新
11 | ///
12 | /// 所有分包的名称以及是否验证文件CRC
13 | #pragma warning disable CS1998 // 异步方法缺少 "await" 运算符,将以同步方式运行
14 | public static async ETTask CheckAllBundlePackageUpdate(Dictionary bundlePackageNames)
15 | #pragma warning restore CS1998 // 异步方法缺少 "await" 运算符,将以同步方式运行
16 | {
17 | UpdateBundleDataInfo updateBundleDataInfo = new UpdateBundleDataInfo();
18 | updateBundleDataInfo.NeedUpdate = false;
19 |
20 | if (AssetComponentConfig.AssetLoadMode != AssetLoadMode.Build)
21 | {
22 | if (AssetComponentConfig.AssetLoadMode == AssetLoadMode.Local)
23 | {
24 | AssetComponentConfig.HotfixPath = AssetComponentConfig.LocalBundlePath;
25 | }
26 | else
27 | {
28 | #if !UNITY_EDITOR
29 | AssetLogHelper.LogError("AssetLoadMode = AssetLoadMode.Develop 只能在编辑器下运行");
30 | #endif
31 | }
32 | }
33 | else
34 | {
35 | AssetLogHelper.LogError("AssetLoadMode = AssetLoadMode.Build Nintendo_Switch无需更新,请用Local模式");
36 | }
37 | return updateBundleDataInfo;
38 | }
39 |
40 | ///
41 | /// 下载更新
42 | ///
43 | #pragma warning disable CS1998 // 异步方法缺少 "await" 运算符,将以同步方式运行
44 | public static async ETTask DownLoadUpdate(UpdateBundleDataInfo updateBundleDataInfo)
45 | #pragma warning restore CS1998 // 异步方法缺少 "await" 运算符,将以同步方式运行
46 | {
47 | AssetLogHelper.LogError("Nintendo_Switch无需更新");
48 | }
49 |
50 | #endif
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/NintendoAdapter/LoadBaseRuntimeLoad_Nintendo.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using UnityEngine;
3 | using ET;
4 |
5 | namespace BM
6 | {
7 | public partial class LoadBase
8 | {
9 | #if Nintendo_Switch
10 | internal void LoadAssetBundle(string bundlePackageName)
11 | {
12 | AddRefCount();
13 | if (_loadState == LoadState.Finish)
14 | {
15 | return;
16 | }
17 | if (_loadState == LoadState.Loading)
18 | {
19 | AssetLogHelper.LogError("同步加载了正在异步加载的资源, 打断异步加载资源会导致所有异步加载的资源都立刻同步加载出来。资源名: " + FilePath +
20 | "\nAssetBundle包名: " + AssetBundleName);
21 | if (_assetBundleCreateRequest != null)
22 | {
23 | AssetBundle = _assetBundleCreateRequest.assetBundle;
24 | return;
25 | }
26 | }
27 | //资源没有加载过也没有正在加载就同步加载出来
28 | string assetBundlePath = AssetComponent.BundleFileExistPath_Nintendo(bundlePackageName, AssetBundleName);
29 | AssetBundle = AssetBundle.LoadFromFile(assetBundlePath);
30 | _loadState = LoadState.Finish;
31 | }
32 |
33 | ///
34 | /// 异步加载LoadBase的AssetBundle
35 | ///
36 | internal async ETTask LoadAssetBundleAsync(string bundlePackageName)
37 | {
38 | AddRefCount();
39 | if (_loadState == LoadState.Finish)
40 | {
41 | return;
42 | }
43 | BundleRuntimeInfo bundleRuntimeInfo = AssetComponent.BundleNameToRuntimeInfo[bundlePackageName];
44 | string assetBundlePath = AssetComponent.BundleFileExistPath_Nintendo(bundlePackageName, AssetBundleName);
45 | //获取一个协程锁
46 | CoroutineLock coroutineLock = await CoroutineLockComponent.Wait(CoroutineLockType.BundleMaster, LoadPathConvertHelper.LoadPathConvert(assetBundlePath));
47 | if (_loadState == LoadState.NoLoad)
48 | {
49 | _loadState = LoadState.Loading;
50 | await LoadBundleFinish(assetBundlePath);
51 | _loadState = LoadState.Finish;
52 | }
53 | //协程锁解锁
54 | coroutineLock.Dispose();
55 | }
56 |
57 | ///
58 | /// 通过路径直接加载硬盘上的AssetBundle
59 | ///
60 | private async ETTask LoadBundleFinish(string assetBundlePath)
61 | {
62 | if (_loadState == LoadState.Finish)
63 | {
64 | return;
65 | }
66 | ETTask tcs = ETTask.Create(true);
67 | _assetBundleCreateRequest = AssetBundle.LoadFromFileAsync(assetBundlePath);
68 | _assetBundleCreateRequest.completed += operation =>
69 | {
70 | AssetBundle = _assetBundleCreateRequest.assetBundle;
71 | tcs.SetResult();
72 | //判断是否还需要
73 | if (_refCount <= 0)
74 | {
75 | AssetComponent.AddPreUnLoadPool(this);
76 | }
77 | };
78 | await tcs;
79 | }
80 |
81 | ///
82 | /// 强制加载完成
83 | ///
84 | internal void ForceLoadFinish(string bundlePackageName)
85 | {
86 | if (_loadState == LoadState.Finish)
87 | {
88 | return;
89 | }
90 | if (_assetBundleCreateRequest != null)
91 | {
92 | AssetLogHelper.LogError("触发强制加载, 打断异步加载资源会导致所有异步加载的资源都立刻同步加载出来。资源名: " + FilePath +
93 | "\nAssetBundle包名: " + AssetBundleName);
94 | AssetBundle = _assetBundleCreateRequest.assetBundle;
95 | return;
96 | }
97 | string assetBundlePath = AssetComponent.BundleFileExistPath_Nintendo(bundlePackageName, AssetBundleName);
98 | AssetBundle = AssetBundle.LoadFromFile(assetBundlePath);
99 | _loadState = LoadState.Finish;
100 | //判断是否还需要
101 | if (_refCount <= 0)
102 | {
103 | AssetComponent.AddPreUnLoadPool(this);
104 | }
105 | }
106 |
107 | ///
108 | /// 打开进度统计 Switch模式下仅做占位使用
109 | ///
110 | internal void OpenProgress(){}
111 |
112 | ///
113 | /// 获取当前资源加载进度
114 | ///
115 | internal float GetProgress()
116 | {
117 | if (_loadState == LoadState.Finish)
118 | {
119 | return 1;
120 | }
121 | if (_loadState == LoadState.NoLoad)
122 | {
123 | return 0;
124 | }
125 | if (_assetBundleCreateRequest == null)
126 | {
127 | AssetLogHelper.LogError("资源加载中但加载请求为Null");
128 | return 0;
129 | }
130 | return _assetBundleCreateRequest.progress;;
131 | }
132 |
133 | #endif
134 |
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # BundleMaster
2 |
Unity资源加载大师(纯代码ET框架集成用)
3 |
4 |
网站地址: https://www.unitybundlemaster.com
5 |
视频教程
6 |
YouTube : https://www.youtube.com/watch?v=3P7yJu01j0I
7 |
B站 : https://www.bilibili.com/video/BV19341177Ek
8 |
QQ讨论群: 787652036
9 |
非ET框架集成版本: https://github.com/mister91jiao/BundleMaster_IntegrateETTask
10 |
11 |
注意事项:
12 |
WebGL 平台需要加上 BMWebGL 宏,部署使用 Local 模式运行(网页直接刷新就行所以不需要更新),注意避免正在异步加载一个资源的时候有同步加载同一个资源。
13 |
Switch 平台需要加上 Nintendo_Switch 宏,理论上可以进行热更但因为政策原因所以没有对更新功能进行适配,因此部署依然需要在 Local 模式 下运行,除此之外还要加上 NintendoSDKPlugin,不然会报错,政策原因不上传这部分内容,有需要switch开发需要可以找我联系
14 |
15 |
代码示例:
16 |
17 | //初始化流程
18 | public async ETTask Init()
19 | {
20 | //定义要检查的资源包名
21 | Dictionary updatePackageBundle = new Dictionary()
22 | {
23 | {"MainAssets", false},
24 | };
25 | //检查是否需要更新
26 | UpdateBundleDataInfo _updateBundleDataInfo = await AssetComponent.CheckAllBundlePackageUpdate(updatePackageBundle);
27 | if (_updateBundleDataInfo.NeedUpdate)
28 | {
29 | //增量更新更新
30 | await AssetComponent.DownLoadUpdate(_updateBundleDataInfo);
31 | }
32 | //初始化资源包
33 | await AssetComponent.Initialize("MainAssets");
34 | }
35 |
36 | //加载资源代码示例
37 | public async ETTask Example()
38 | {
39 | //同步加载资源
40 | GameObject playerAsset1 = AssetComponent.Load(out LoadHandler handler, "Assets/Bundles/Role/Player1.prefab");
41 | GameObject player1 = UnityEngine.Object.Instantiate(playerAsset1);
42 |
43 | //异步加载资源
44 | GameObject playerAsset2 = await AssetComponent.LoadAsync("Assets/Bundles/Role/Player2.prefab");
45 | GameObject player2 = UnityEngine.Object.Instantiate(playerAsset2);
46 |
47 | //卸载资源
48 | handler.UnLoad();
49 | AssetComponent.UnLoadByPath("Assets/Bundles/Role/Player2.prefab");
50 | }
51 |
52 | //别忘了配置生命周期
53 | void Update()
54 | {
55 | AssetComponent.Update();
56 | }
57 | void OnLowMemory()
58 | {
59 | //移动平台低内存时候调用,可选
60 | //AssetComponent.ForceUnLoadAll();
61 | }
62 | void OnDestroy()
63 | {
64 | //如果当前正在更新资源,取消更新
65 | _updateBundleDataInfo?.CancelUpdate();
66 | //游戏销毁调用
67 | AssetComponent.Destroy();
68 | }
69 |
70 |
友情链接:
71 |
JEngine 一款不错的客户端框架: https://github.com/JasonXuDeveloper/JEngine
72 |
HybridCLR 革命性的热更新解决方案: https://github.com/focus-creative-games/hybridclr
73 |
--------------------------------------------------------------------------------