566 lines
18 KiB
C#
566 lines
18 KiB
C#
//
|
||
// Assets.cs
|
||
//
|
||
// Author:
|
||
// fjy <jiyuan.feng@live.com>
|
||
//
|
||
// Copyright (c) 2020 fjy
|
||
//
|
||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||
// of this software and associated documentation files (the "Software"), to deal
|
||
// in the Software without restriction, including without limitation the rights
|
||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||
// copies of the Software, and to permit persons to whom the Software is
|
||
// furnished to do so, subject to the following conditions:
|
||
//
|
||
// The above copyright notice and this permission notice shall be included in
|
||
// all copies or substantial portions of the Software.
|
||
//
|
||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||
// THE SOFTWARE.
|
||
|
||
#define LOG_ENABLE
|
||
using ET;
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using System.Diagnostics;
|
||
using System.IO;
|
||
using UnityEngine;
|
||
using Debug = UnityEngine.Debug;
|
||
using Object = UnityEngine.Object;
|
||
|
||
namespace libx
|
||
{
|
||
public class AssetsUpdateSystem : UpdateSystem<Assets>
|
||
{
|
||
public override void Update(Assets self)
|
||
{
|
||
self.Update();
|
||
}
|
||
}
|
||
|
||
public sealed class Assets : Entity
|
||
{
|
||
public static readonly string ManifestAsset = "Assets/Res/Common/Manifest.asset";
|
||
public static readonly string Extension = ".unity3d";
|
||
|
||
public static bool runtimeMode =true;
|
||
public static Func<string, Type, Object> loadDelegate = null;
|
||
private const string TAG = "[Assets]";
|
||
|
||
[Conditional("XASSETLOG")]
|
||
private static void Log(string s)
|
||
{
|
||
Debug.Log(string.Format("{0}{1}", TAG, s));
|
||
}
|
||
|
||
#region API
|
||
|
||
/// <summary>
|
||
/// 读取所有资源路径
|
||
/// </summary>
|
||
/// <returns></returns>
|
||
public static string[] GetAllAssetPaths()
|
||
{
|
||
var assets = new List<string>();
|
||
assets.AddRange(_assetToBundles.Keys);
|
||
return assets.ToArray();
|
||
}
|
||
|
||
public static string basePath { get; set; }
|
||
|
||
public static string updatePath { get; set; }
|
||
|
||
public static void AddSearchPath(string path)
|
||
{
|
||
_searchPaths.Add(path);
|
||
}
|
||
|
||
public static ManifestRequest Initialize()
|
||
{
|
||
|
||
if (string.IsNullOrEmpty(basePath))
|
||
{
|
||
basePath = Application.streamingAssetsPath + Path.DirectorySeparatorChar;
|
||
}
|
||
|
||
if (string.IsNullOrEmpty(updatePath))
|
||
{
|
||
updatePath = Application.persistentDataPath + Path.DirectorySeparatorChar;
|
||
}
|
||
|
||
Clear();
|
||
|
||
Log(string.Format(
|
||
"Initialize with: runtimeMode={0}\nbasePath:{1}\nupdatePath={2}",
|
||
runtimeMode, basePath, updatePath));
|
||
|
||
ManifestRequest request = new ManifestRequest {url = ManifestAsset};
|
||
AddAssetRequest(request);
|
||
return request;
|
||
}
|
||
|
||
public static void Clear()
|
||
{
|
||
_searchPaths.Clear();
|
||
_activeVariants.Clear();
|
||
_assetToBundles.Clear();
|
||
_bundleToDependencies.Clear();
|
||
}
|
||
|
||
private static SceneAssetRequest _runningScene;
|
||
|
||
public static SceneAssetRequest LoadSceneAsync(string path, bool additive)
|
||
{
|
||
if (string.IsNullOrEmpty(path))
|
||
{
|
||
Debug.LogError("invalid path");
|
||
return null;
|
||
}
|
||
|
||
path = GetExistPath(path);
|
||
SceneAssetAsyncRequest asset = new SceneAssetAsyncRequest(path, additive);
|
||
if (! additive)
|
||
{
|
||
if (_runningScene != null)
|
||
{
|
||
_runningScene.Release();
|
||
_runningScene = null;
|
||
}
|
||
_runningScene = asset;
|
||
}
|
||
asset.Load();
|
||
asset.Retain();
|
||
_scenes.Add(asset);
|
||
Log(string.Format("LoadScene:{0}", path));
|
||
return asset;
|
||
}
|
||
|
||
public static void UnloadScene(SceneAssetRequest scene)
|
||
{
|
||
scene.Release();
|
||
}
|
||
|
||
public static AssetRequest LoadAssetAsync(string path, Type type)
|
||
{
|
||
return LoadAsset(path, type, true);
|
||
}
|
||
|
||
public static AssetRequest LoadAsset(string path, Type type)
|
||
{
|
||
return LoadAsset(path, type, false);
|
||
}
|
||
|
||
public static void UnloadAsset(AssetRequest asset)
|
||
{
|
||
asset.Release();
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Private
|
||
|
||
internal static void OnLoadManifest(Manifest manifest)
|
||
{
|
||
_activeVariants.AddRange(manifest.activeVariants);
|
||
|
||
var assets = manifest.assets;
|
||
string[] dirs = manifest.dirs;
|
||
var bundles = manifest.bundles;
|
||
|
||
foreach (BundleRef item in bundles)
|
||
_bundleToDependencies[item.name] = Array.ConvertAll(item.deps, id => bundles[id].name);
|
||
|
||
foreach (AssetRef item in assets)
|
||
{
|
||
string path = string.Format("{0}/{1}", dirs[item.dir], item.name);
|
||
if (item.bundle >= 0 && item.bundle < bundles.Length)
|
||
{
|
||
_assetToBundles[path] = bundles[item.bundle].name;
|
||
}
|
||
else
|
||
{
|
||
Debug.LogError(string.Format("{0} bundle {1} not exist.", path, item.bundle));
|
||
}
|
||
}
|
||
}
|
||
|
||
private static Dictionary<string, AssetRequest> _assets = new Dictionary<string, AssetRequest>();
|
||
|
||
private static List<AssetRequest> _loadingAssets = new List<AssetRequest>();
|
||
|
||
private static List<SceneAssetRequest> _scenes = new List<SceneAssetRequest>();
|
||
|
||
private static List<AssetRequest> _unusedAssets = new List<AssetRequest>();
|
||
|
||
public void Update()
|
||
{
|
||
UpdateAssets();
|
||
UpdateBundles();
|
||
}
|
||
|
||
private static void UpdateAssets()
|
||
{
|
||
for (int i = 0; i < _loadingAssets.Count; ++i)
|
||
{
|
||
AssetRequest request = _loadingAssets[i];
|
||
if (request.Update())
|
||
continue;
|
||
_loadingAssets.RemoveAt(i);
|
||
--i;
|
||
}
|
||
|
||
foreach (var item in _assets)
|
||
{
|
||
if (item.Value.isDone && item.Value.IsUnused())
|
||
{
|
||
_unusedAssets.Add(item.Value);
|
||
}
|
||
}
|
||
|
||
if (_unusedAssets.Count > 0)
|
||
{
|
||
for (int i = 0; i < _unusedAssets.Count; ++i)
|
||
{
|
||
AssetRequest request = _unusedAssets[i];
|
||
Log(string.Format("UnloadAsset:{0}", request.url));
|
||
_assets.Remove(request.url);
|
||
request.Unload();
|
||
}
|
||
_unusedAssets.Clear();
|
||
}
|
||
|
||
for (int i = 0; i < _scenes.Count; ++i)
|
||
{
|
||
SceneAssetRequest request = _scenes[i];
|
||
if (request.Update() || !request.IsUnused())
|
||
continue;
|
||
_scenes.RemoveAt(i);
|
||
Log(string.Format("UnloadScene:{0}", request.url));
|
||
request.Unload();
|
||
--i;
|
||
}
|
||
}
|
||
|
||
private static void AddAssetRequest(AssetRequest request)
|
||
{
|
||
_assets.Add(request.url, request);
|
||
_loadingAssets.Add(request);
|
||
request.Load();
|
||
}
|
||
|
||
private static AssetRequest LoadAsset(string path, Type type, bool async)
|
||
{
|
||
if (string.IsNullOrEmpty(path))
|
||
{
|
||
Debug.LogError("invalid path");
|
||
return null;
|
||
}
|
||
|
||
path = GetExistPath(path);
|
||
|
||
AssetRequest request;
|
||
if (_assets.TryGetValue(path, out request))
|
||
{
|
||
request.Retain();
|
||
_loadingAssets.Add(request);
|
||
return request;
|
||
}
|
||
|
||
string assetBundleName;
|
||
if (GetAssetBundleName(path, out assetBundleName))
|
||
{
|
||
request = async
|
||
? new BundleAssetAsyncRequest(assetBundleName)
|
||
: new BundleAssetRequest(assetBundleName);
|
||
}
|
||
else
|
||
{
|
||
if (path.StartsWith("http://", StringComparison.Ordinal) ||
|
||
path.StartsWith("https://", StringComparison.Ordinal) ||
|
||
path.StartsWith("file://", StringComparison.Ordinal) ||
|
||
path.StartsWith("ftp://", StringComparison.Ordinal) ||
|
||
path.StartsWith("jar:file://", StringComparison.Ordinal))
|
||
request = new WebAssetRequest();
|
||
else
|
||
request = new AssetRequest();
|
||
}
|
||
|
||
request.url = path;
|
||
request.assetType = type;
|
||
AddAssetRequest(request);
|
||
request.Retain();
|
||
Log(string.Format("LoadAsset:{0}", path));
|
||
return request;
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Paths
|
||
|
||
private static List<string> _searchPaths = new List<string>();
|
||
|
||
private static string GetExistPath(string path)
|
||
{
|
||
#if UNITY_EDITOR
|
||
if (!runtimeMode)
|
||
{
|
||
if (File.Exists(path))
|
||
return path;
|
||
|
||
foreach (string item in _searchPaths)
|
||
{
|
||
string existPath = string.Format("{0}/{1}", item, path);
|
||
if (File.Exists(existPath))
|
||
return existPath;
|
||
}
|
||
|
||
Debug.LogError("找不到资源路径" + path);
|
||
return path;
|
||
}
|
||
#endif
|
||
if (_assetToBundles.ContainsKey(path))
|
||
return path;
|
||
|
||
foreach (string item in _searchPaths)
|
||
{
|
||
string existPath = string.Format("{0}/{1}", item, path);
|
||
if (_assetToBundles.ContainsKey(existPath))
|
||
return existPath;
|
||
}
|
||
|
||
Debug.LogError("资源没有收集打包" + path);
|
||
return path;
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Bundles
|
||
|
||
private static readonly int MAX_BUNDLES_PERFRAME = 0;
|
||
|
||
private static Dictionary<string, BundleRequest> _bundles = new Dictionary<string, BundleRequest>();
|
||
|
||
private static List<BundleRequest> _loadingBundles = new List<BundleRequest>();
|
||
|
||
private static List<BundleRequest> _unusedBundles = new List<BundleRequest>();
|
||
|
||
private static List<BundleRequest> _toloadBundles = new List<BundleRequest>();
|
||
|
||
private static List<string> _activeVariants = new List<string>();
|
||
|
||
private static Dictionary<string, string> _assetToBundles = new Dictionary<string, string>();
|
||
|
||
private static Dictionary<string, string[]> _bundleToDependencies = new Dictionary<string, string[]>();
|
||
|
||
internal static bool GetAssetBundleName(string path, out string assetBundleName)
|
||
{
|
||
if (runtimeMode)
|
||
{
|
||
return _assetToBundles.TryGetValue(path, out assetBundleName);
|
||
}
|
||
assetBundleName = null;
|
||
return false;
|
||
}
|
||
private static string[] GetAllDependencies(string bundle)
|
||
{
|
||
string[] deps;
|
||
if (_bundleToDependencies.TryGetValue(bundle, out deps))
|
||
return deps;
|
||
|
||
return new string[0];
|
||
}
|
||
|
||
internal static BundleRequest LoadBundle(string assetBundleName)
|
||
{
|
||
return LoadBundle(assetBundleName, false);
|
||
}
|
||
|
||
internal static BundleRequest LoadBundleAsync(string assetBundleName)
|
||
{
|
||
return LoadBundle(assetBundleName, true);
|
||
}
|
||
|
||
internal static void UnloadBundle(BundleRequest bundle)
|
||
{
|
||
bundle.Release();
|
||
}
|
||
|
||
private static void UnloadDependencies(BundleRequest bundle)
|
||
{
|
||
for (int i = 0; i < bundle.dependencies.Count; i++)
|
||
{
|
||
BundleRequest item = bundle.dependencies[i];
|
||
item.Release();
|
||
}
|
||
|
||
bundle.dependencies.Clear();
|
||
}
|
||
|
||
private static void LoadDependencies(BundleRequest bundle, string assetBundleName, bool asyncRequest)
|
||
{
|
||
string[] dependencies = GetAllDependencies(assetBundleName);
|
||
if (dependencies.Length <= 0)
|
||
return;
|
||
for (int i = 0; i < dependencies.Length; i++)
|
||
{
|
||
string item = dependencies[i];
|
||
bundle.dependencies.Add(LoadBundle(item, asyncRequest));
|
||
}
|
||
}
|
||
|
||
internal static BundleRequest LoadBundle(string assetBundleName, bool asyncMode)
|
||
{
|
||
if (string.IsNullOrEmpty(assetBundleName))
|
||
{
|
||
Debug.LogError("assetBundleName == null");
|
||
return null;
|
||
}
|
||
|
||
assetBundleName = RemapVariantName(assetBundleName);
|
||
string url = GetDataPath(assetBundleName) + assetBundleName;
|
||
|
||
BundleRequest bundle;
|
||
|
||
if (_bundles.TryGetValue(url, out bundle))
|
||
{
|
||
bundle.Retain();
|
||
_loadingBundles.Add(bundle);
|
||
return bundle;
|
||
}
|
||
|
||
if (url.StartsWith("http://", StringComparison.Ordinal) ||
|
||
url.StartsWith("https://", StringComparison.Ordinal) ||
|
||
url.StartsWith("file://", StringComparison.Ordinal) ||
|
||
url.StartsWith("ftp://", StringComparison.Ordinal))
|
||
bundle = new WebBundleRequest();
|
||
else
|
||
bundle = asyncMode ? new BundleAsyncRequest() : new BundleRequest();
|
||
|
||
bundle.url = url;
|
||
_bundles.Add(url, bundle);
|
||
|
||
if (MAX_BUNDLES_PERFRAME > 0 && (bundle is BundleAsyncRequest || bundle is WebBundleRequest))
|
||
{
|
||
_toloadBundles.Add(bundle);
|
||
}
|
||
else
|
||
{
|
||
bundle.Load();
|
||
_loadingBundles.Add(bundle);
|
||
Log("LoadBundle: " + url);
|
||
}
|
||
|
||
LoadDependencies(bundle, assetBundleName, asyncMode);
|
||
|
||
bundle.Retain();
|
||
return bundle;
|
||
}
|
||
|
||
private static string GetDataPath(string bundleName)
|
||
{
|
||
if (string.IsNullOrEmpty(updatePath))
|
||
return basePath;
|
||
|
||
if (File.Exists(updatePath + bundleName))
|
||
return updatePath;
|
||
|
||
return basePath;
|
||
}
|
||
|
||
private static void UpdateBundles()
|
||
{
|
||
int max = MAX_BUNDLES_PERFRAME;
|
||
if (_toloadBundles.Count > 0 && max > 0 && _loadingBundles.Count < max)
|
||
for (int i = 0; i < Math.Min(max - _loadingBundles.Count, _toloadBundles.Count); ++i)
|
||
{
|
||
BundleRequest item = _toloadBundles[i];
|
||
if (item.loadState == LoadState.Init)
|
||
{
|
||
item.Load();
|
||
_loadingBundles.Add(item);
|
||
_toloadBundles.RemoveAt(i);
|
||
--i;
|
||
}
|
||
}
|
||
|
||
for (int i = 0; i < _loadingBundles.Count; i++)
|
||
{
|
||
BundleRequest item = _loadingBundles[i];
|
||
if (item.Update())
|
||
continue;
|
||
_loadingBundles.RemoveAt(i);
|
||
--i;
|
||
}
|
||
|
||
foreach (var item in _bundles)
|
||
{
|
||
if (item.Value.isDone && item.Value.IsUnused())
|
||
{
|
||
_unusedBundles.Add(item.Value);
|
||
}
|
||
}
|
||
|
||
if (_unusedBundles.Count <= 0) return;
|
||
{
|
||
for (int i = 0; i < _unusedBundles.Count; i++)
|
||
{
|
||
BundleRequest item = _unusedBundles[i];
|
||
if (item.isDone)
|
||
{
|
||
UnloadDependencies(item);
|
||
item.Unload();
|
||
_bundles.Remove(item.url);
|
||
Log("UnloadBundle: " + item.url);
|
||
}
|
||
}
|
||
_unusedBundles.Clear();
|
||
}
|
||
}
|
||
|
||
private static string RemapVariantName(string assetBundleName)
|
||
{
|
||
var bundlesWithVariant = _activeVariants;
|
||
// Get base bundle path
|
||
string baseName = assetBundleName.Split('.')[0];
|
||
|
||
int bestFit = int.MaxValue;
|
||
int bestFitIndex = -1;
|
||
// Loop all the assetBundles with variant to find the best fit variant assetBundle.
|
||
for (int i = 0; i < bundlesWithVariant.Count; i++)
|
||
{
|
||
string[] curSplit = bundlesWithVariant[i].Split('.');
|
||
string curBaseName = curSplit[0];
|
||
string curVariant = curSplit[1];
|
||
|
||
if (curBaseName != baseName)
|
||
continue;
|
||
|
||
int found = bundlesWithVariant.IndexOf(curVariant);
|
||
|
||
// If there is no active variant found. We still want to use the first
|
||
if (found == -1)
|
||
found = int.MaxValue - 1;
|
||
|
||
if (found >= bestFit)
|
||
continue;
|
||
bestFit = found;
|
||
bestFitIndex = i;
|
||
}
|
||
|
||
if (bestFit == int.MaxValue - 1)
|
||
Debug.LogWarning(
|
||
"Ambiguous asset bundle variant chosen because there was no matching active variant: " +
|
||
bundlesWithVariant[bestFitIndex]);
|
||
|
||
return bestFitIndex != -1 ? bundlesWithVariant[bestFitIndex] : assetBundleName;
|
||
}
|
||
|
||
#endregion
|
||
}
|
||
} |