feat : instant asset build pipeline

This commit is contained in:
何冠峰
2026-05-20 19:25:31 +08:00
parent f05f794461
commit 2f8355c3f4
13 changed files with 537 additions and 0 deletions

View File

@@ -0,0 +1,124 @@
#if TUANJIE_1_8_OR_NEWER
using UnityEngine;
namespace YooAsset
{
/// <summary>
/// InstantAsset 表上下文
/// </summary>
internal sealed class InstantAssetTableContext
{
/// <summary>
/// 初始化结果
/// </summary>
public readonly struct InitializeResult
{
/// <summary>
/// 错误信息
/// </summary>
public readonly string Error;
/// <summary>
/// 初始化是否成功
/// </summary>
public bool Succeeded
{
get { return Error == null; }
}
private InitializeResult(string error)
{
Error = error;
}
/// <summary>
/// 创建表示初始化成功的默认结果
/// </summary>
public static InitializeResult Default()
{
return new InitializeResult(null);
}
/// <summary>
/// 创建表示初始化失败的结果
/// </summary>
/// <param name="error">错误信息</param>
public static InitializeResult Failure(string error)
{
return new InitializeResult(error);
}
}
private readonly string _rootPath;
private readonly string _assetTableName;
private readonly string _sceneTableName;
/// <summary>
/// 资源表
/// </summary>
public InstantAssetTable AssetTable { get; private set; }
/// <summary>
/// 场景表
/// </summary>
/// <remarks>
/// 如果不包含场景资源,该值为空值。
/// </remarks>
public InstantAssetTable SceneTable { get; private set; }
public InstantAssetTableContext(string rootPath, string assetTableName)
{
_rootPath = rootPath;
_assetTableName = assetTableName;
_sceneTableName = $"{assetTableName}-scene";
}
/// <summary>
/// 初始化表上下文
/// </summary>
/// <returns>初始化结果</returns>
public InitializeResult Initialize()
{
InstantAsset.SetInstantAssetRootPath(_rootPath);
string assetTablePath = PathUtility.Combine(_rootPath, _assetTableName);
AssetTable = InstantAsset.ReadAssetTable(assetTablePath) as InstantAssetTable;
if (AssetTable == null)
{
string error = $"Failed to load InstantAssetTable: '{assetTablePath}'.";
return InitializeResult.Failure(error);
}
string sceneTablePath = PathUtility.Combine(_rootPath, _sceneTableName);
SceneTable = InstantAsset.ReadAssetTable(sceneTablePath) as InstantAssetTable;
if (SceneTable == null)
{
YooLogger.LogWarning($"InstantAsset scene table not found: '{sceneTablePath}'.");
}
return InitializeResult.Default();
}
/// <summary>
/// 卸载当前上下文持有的所有表
/// </summary>
public void Dispose()
{
if (AssetTable != null)
{
string assetTablePath = PathUtility.Combine(_rootPath, _assetTableName);
InstantAsset.UnloadAssetTable(assetTablePath);
AssetTable = null;
}
if (SceneTable != null)
{
string sceneTablePath = PathUtility.Combine(_rootPath, _sceneTableName);
InstantAsset.UnloadAssetTable(sceneTablePath);
SceneTable = null;
}
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a5b6f5ba1933ecc43aca5558fef29a05
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,57 @@
#if TUANJIE_1_8_OR_NEWER
using UnityEngine;
using UnityEngine.SceneManagement;
namespace YooAsset
{
/// <summary>
/// InstantAsset 资源包句柄
/// </summary>
internal sealed class InstantBundleHandle : IBundleHandle
{
private readonly PackageBundle _packageBundle;
private readonly InstantAssetTable _assetTable;
private readonly InstantAssetTable _sceneTable;
public InstantBundleHandle(PackageBundle packageBundle, InstantAssetTable assetTable, InstantAssetTable sceneTable)
{
_packageBundle = packageBundle;
_assetTable = assetTable;
_sceneTable = sceneTable;
}
/// <inheritdoc/>
public void UnloadBundle()
{
}
/// <inheritdoc/>
public BHLoadAssetOperation LoadAssetAsync(AssetInfo assetInfo)
{
var operation = new IBHLoadAssetOperation(_packageBundle, _assetTable, assetInfo);
return operation;
}
/// <inheritdoc/>
public BHLoadAllAssetsOperation LoadAllAssetsAsync(AssetInfo assetInfo)
{
var operation = new IBHLoadAllAssetsOperation();
return operation;
}
/// <inheritdoc/>
public BHLoadSubAssetsOperation LoadSubAssetsAsync(AssetInfo assetInfo)
{
var operation = new IBHLoadSubAssetsOperation();
return operation;
}
/// <inheritdoc/>
public BHLoadSceneOperation LoadSceneAsync(AssetInfo assetInfo, LoadSceneParameters loadSceneParams, bool allowSceneActivation)
{
var operation = new IBHLoadSceneOperation(_sceneTable, assetInfo, loadSceneParams, allowSceneActivation);
return operation;
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ee9ecce87893f2945abc4f58b56a1752
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,18 @@
#if TUANJIE_1_8_OR_NEWER
namespace YooAsset
{
/// <summary>
/// 全部资源加载操作InstantBundle不支持
/// </summary>
internal sealed class IBHLoadAllAssetsOperation : BHLoadAllAssetsOperation
{
protected override void InternalStart()
{
SetError($"{nameof(IBHLoadAllAssetsOperation)} does not support loading all assets.");
}
protected override void InternalUpdate()
{
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a0ac8e1310da5784cbe904b31d9fc8fe
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,119 @@
#if TUANJIE_1_8_OR_NEWER
using UnityEngine;
namespace YooAsset
{
/// <summary>
/// 单个资源加载操作InstantBundle
/// </summary>
internal sealed class IBHLoadAssetOperation : BHLoadAssetOperation
{
private enum ESteps
{
None,
CheckAssetTable,
LoadAsset,
CheckResult,
Done,
}
private readonly PackageBundle _packageBundle;
private readonly InstantAssetTable _assetTable;
private readonly AssetInfo _assetInfo;
private InstantAssetRequest _request;
private ESteps _steps = ESteps.None;
public IBHLoadAssetOperation(PackageBundle packageBundle, InstantAssetTable assetTable, AssetInfo assetInfo)
{
_packageBundle = packageBundle;
_assetTable = assetTable;
_assetInfo = assetInfo;
}
protected override void InternalStart()
{
_steps = ESteps.CheckAssetTable;
}
protected override void InternalUpdate()
{
if (_steps == ESteps.None || _steps == ESteps.Done)
return;
if (_steps == ESteps.CheckAssetTable)
{
if (_assetTable == null)
{
_steps = ESteps.Done;
SetError($"{nameof(IBHLoadAssetOperation)} asset table is null, cannot load asset: '{_assetInfo.AssetPath}'.");
YooLogger.LogError(Error);
return;
}
_steps = ESteps.LoadAsset;
}
if (_steps == ESteps.LoadAsset)
{
if (IsWaitForCompletion)
{
if (_assetInfo.AssetType == null)
Result = _assetTable.LoadAsset(_assetInfo.AssetPath);
else
Result = _assetTable.LoadAsset(_assetInfo.AssetPath, _assetInfo.AssetType);
}
else
{
if (_assetInfo.AssetType == null)
_request = _assetTable.LoadAssetAsync(_assetInfo.AssetPath);
else
_request = _assetTable.LoadAssetAsync(_assetInfo.AssetPath, _assetInfo.AssetType);
}
_steps = ESteps.CheckResult;
}
if (_steps == ESteps.CheckResult)
{
if (_request != null)
{
// 注意: 异步加载过程中,业务逻辑可能会强制转换为同步加载
if (IsWaitForCompletion)
{
// 强制挂起主线程(注意:该操作会很耗时)
YooLogger.LogWarning("Blocking the main thread while loading an InstantAsset.");
Result = _request.asset;
}
else
{
Progress = _request.progress;
if (_request.isDone == false)
return;
Result = _request.asset;
}
}
if (Result == null)
{
string error;
if (_assetInfo.AssetType == null)
error = $"Failed to load asset: '{_assetInfo.AssetPath}' AssetType: null InstantBundle: '{_packageBundle.BundleName}'.";
else
error = $"Failed to load asset: '{_assetInfo.AssetPath}' AssetType: {_assetInfo.AssetType} InstantBundle: '{_packageBundle.BundleName}'.";
_steps = ESteps.Done;
SetError(error);
YooLogger.LogError(Error);
}
else
{
_steps = ESteps.Done;
SetResult();
}
}
}
protected override void InternalWaitForCompletion()
{
ExecuteBatch();
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 582ff9e5617080c4aa204f8e135f1226
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,130 @@
#if TUANJIE_1_8_OR_NEWER
using UnityEngine;
using UnityEngine.SceneManagement;
namespace YooAsset
{
/// <summary>
/// 场景加载操作InstantBundle
/// </summary>
internal sealed class IBHLoadSceneOperation : BHLoadSceneOperation
{
private enum ESteps
{
None,
CheckSceneTable,
LoadScene,
CheckResult,
Done,
}
private readonly InstantAssetTable _sceneTable;
private readonly AssetInfo _assetInfo;
private readonly LoadSceneParameters _loadSceneParams;
private bool _allowSceneActivation;
private AsyncOperation _asyncOperation;
private ESteps _steps = ESteps.None;
public IBHLoadSceneOperation(InstantAssetTable sceneTable, AssetInfo assetInfo, LoadSceneParameters loadSceneParams, bool allowSceneActivation)
{
_sceneTable = sceneTable;
_assetInfo = assetInfo;
_loadSceneParams = loadSceneParams;
_allowSceneActivation = allowSceneActivation;
}
protected override void InternalStart()
{
_steps = ESteps.CheckSceneTable;
}
protected override void InternalUpdate()
{
if (_steps == ESteps.None || _steps == ESteps.Done)
return;
if (_steps == ESteps.CheckSceneTable)
{
if (_sceneTable == null)
{
_steps = ESteps.Done;
SetError($"{nameof(IBHLoadSceneOperation)} scene table is null, cannot load scene: '{_assetInfo.AssetPath}'.");
YooLogger.LogError(Error);
return;
}
_steps = ESteps.LoadScene;
}
if (_steps == ESteps.LoadScene)
{
if (IsWaitForCompletion)
{
_steps = ESteps.Done;
SetError($"{nameof(IBHLoadSceneOperation)} does not support synchronous scene loading.");
YooLogger.LogError(Error);
return;
}
else
{
_asyncOperation = SceneManager.LoadSceneAsync(_assetInfo.AssetPath, _loadSceneParams);
if (_asyncOperation != null)
{
_asyncOperation.allowSceneActivation = _allowSceneActivation;
_asyncOperation.priority = 100;
Result = SceneManager.GetSceneAt(SceneManager.sceneCount - 1);
_steps = ESteps.CheckResult;
}
else
{
_steps = ESteps.Done;
SetError($"Failed to load scene: '{_assetInfo.AssetPath}'.");
YooLogger.LogError(Error);
}
}
}
if (_steps == ESteps.CheckResult)
{
if (_asyncOperation != null)
{
if (IsWaitForCompletion)
{
YooLogger.LogError("The scene is already loading asynchronously.");
}
else
{
if (_asyncOperation.allowSceneActivation == false)
{
if (_allowSceneActivation)
_asyncOperation.allowSceneActivation = true;
}
Progress = _asyncOperation.progress;
if (_asyncOperation.isDone == false)
return;
}
}
if (Result.IsValid())
{
_steps = ESteps.Done;
SetResult();
}
else
{
_steps = ESteps.Done;
SetError($"Loaded scene is invalid: '{_assetInfo.AssetPath}'.");
YooLogger.LogError(Error);
}
}
}
protected override void InternalWaitForCompletion()
{
ExecuteOnce();
}
protected override void InternalAllowSceneActivation()
{
_allowSceneActivation = true;
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f177d255bd70d3e499548cbfa6a46a26
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,18 @@
#if TUANJIE_1_8_OR_NEWER
namespace YooAsset
{
/// <summary>
/// 子资源加载操作InstantBundle不支持
/// </summary>
internal sealed class IBHLoadSubAssetsOperation : BHLoadSubAssetsOperation
{
protected override void InternalStart()
{
SetError($"{nameof(IBHLoadSubAssetsOperation)} does not support loading sub-assets.");
}
protected override void InternalUpdate()
{
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: bf69bf22c6ce62e4b8d28a5517e0b89d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -35,5 +35,10 @@ namespace YooAsset
/// 自定义运行模式
/// </summary>
CustomPlayMode,
/// <summary>
/// 免构建运行模式
/// </summary>
DatalessPlayMode,
}
}