AsyncMixin · 主资产与资源驻留

按主资产 ID 和 Bundle 预加载资源,理解句柄保留、取消与 GC 引用的关系,以及 AssetManager 和打包配置的职责。

本页内容5 节

什么时候使用预加载

打开界面前预热一组图标、控件类或相关配置时,可以使用 AsyncPreloadPrimaryAssetsAndBundles。输入是主资产 ID 数组与 Bundle 名称数组,底层调用 UAssetManager::PreloadPrimaryAssets,并开启递归 Bundle 展开。

与普通软引用加载相比,这条路径会保留加载状态和句柄,使预加载结果在当前作用域仍需要它们时驻留。任务回调完成之后,资源保留仍然继续;调用 CancelAsyncLoading() 或销毁对应 Mixin/Scope 才释放它持有的预加载句柄。

当前项目核对到的 SActorCanvas 使用普通 AsyncLoad。本页整理插件已有的预加载接口与引擎语义,不表示项目已经实现一套基于该接口的预热界面。

先配置 AssetManager

  1. 在项目 AssetManager 设置中登记自己的主资产类型和扫描目录,确保资源返回有效、可找到的 FPrimaryAssetId。
  2. 在需要按包加载的软引用属性上声明 Bundle。例如自己创建的 UPrimaryDataAsset 中,可以用下面的属性把图标归入 UI 包。
  3. 调用时传入该资产的实际 ID 和已声明的包名,并确认主资产与引用资源的 Cook/打包规则。
/** 本数据资产在 UI 预热阶段需要的图标。 */
UPROPERTY(EditDefaultsOnly, Category="Preview", meta=(AssetBundles="UI"))
TSoftObjectPtr<UTexture2D> Icon;

这里的 UI 是示例 Bundle 名称,需要在自己的资产上声明;给参数数组写一个名称不会自动生成资源包配置。主资产扫描、Bundle 数据和打包规则由 AssetManager 管理,AsyncMixin 不补齐这些配置。

用独立作用域持有预热结果

下面的 FManualBundlePreload 是原生 C++ 文档示例,需要自己创建。使用 FPrimaryAssetId 数组重载,输入来自已经登记的资产;不硬编码工程不存在的资产 ID。

ManualBundlePreload.h:

#pragma once

#include "CoreMinimal.h"
#include "AsyncMixin.h"
#include "UObject/PrimaryAssetId.h"

/** 在需要预热资源的期间持有加载作用域,所有操作与销毁均在游戏线程。 */
class FManualBundlePreload
{
public:
	/** 替换上一组预热请求;OnReady 表示步骤结束,资源仍须核对是否有效。 */
	void Begin(
		const TArray<FPrimaryAssetId>& AssetIds,
		const TArray<FName>& BundleNames,
		const FSimpleDelegate& OnReady);

	/** 退出预热阶段,释放本作用域对资源的保留。 */
	void Release();

private:
	/** 必须作为持久成员保存,不能在 Begin 内建立临时局部 Scope。 */
	FAsyncScope Scope;
};

ManualBundlePreload.cpp:

#include "ManualBundlePreload.h"

void FManualBundlePreload::Begin(
	const TArray<FPrimaryAssetId>& AssetIds,
	const TArray<FName>& BundleNames,
	const FSimpleDelegate& OnReady)
{
	Scope.CancelAsyncLoading();
	Scope.AsyncPreloadPrimaryAssetsAndBundles(AssetIds, BundleNames, OnReady);
	Scope.StartAsyncLoading();
}

void FManualBundlePreload::Release()
{
	Scope.CancelAsyncLoading();
}

界面或流程将此对象保存为成员,在需要预热时调用 Begin,不再需要时调用 Release。传入的 OnReady 如果访问其他 UObject,使用弱绑定或有效性检查;作用域只负责自己拥有的步骤,不自动管理委托捕获的所有对象。

不要把 FAsyncScope 建成函数内的临时局部变量:函数返回就会析构并撤销跟踪,也无法在预热完成后继续持有资源。多条预热链应按实际取消范围划分成员。

步骤完成后仍保留预加载资源状态
正在整理图表…
步骤完成后仍保留预加载资源
查看图表源码
stateDiagram-v2
  [*] --> Waiting: 传入主资产 ID 与 Bundle
  Waiting: 加载并等待步骤完成
  Resident: 步骤结束,作用域继续持有句柄
  Released: 释放本作用域的加载保留
  Waiting --> Resident: 执行完成回调
  Waiting --> Released: 取消或销毁
  Resident --> Released: 取消或销毁
  Released --> [*]

图中的“驻留”描述句柄保留阶段,不能由回调执行推导出所有请求都成功;无效 ID 或加载失败仍要检查实际结果。

加载完成与资源释放的区别

行为普通 AsyncLoad主资产 Bundle 预加载
完成回调按步骤顺序执行按步骤顺序执行
全部步骤结束后未混入 Bundle 预加载且没有新任务时,安排释放内部状态保留内部状态和句柄
后续继续使用资源使用方建立强引用或交给已有资源管理者作用域先持有预加载句柄;实际使用者仍管理自身对象寿命
取消或析构撤销未执行步骤并释放自己持有的句柄同时释放本作用域的预加载保留

同一作用域只要走过非空主资产数组的预加载路径,保留策略就作用于这份加载状态;它不是逐资源可选的策略。需要让两类任务有不同寿命时,使用不同 FAsyncScope。头文件中的可配置 retention policy 仍是注释里的设想,当前没有这个开关。

释放句柄表示本作用域不再保留资源,并不等于资源立刻卸载。已创建控件、其他 UPROPERTY、别的活动句柄或 AssetManager 的正式加载状态都可能继续引用它们;真正回收还取决于其他引用与 GC。

引擎的预加载也不会自动成为 AssetManager 的正式加载状态。不能仅调用 UnloadPrimaryAsset 就推断本 Scope 的预加载保留已经释放;结束本次预热应释放对应作用域,正式资产加载有自己的配对规则。

常见问题

  • 编辑器正常,打包后为空:先核对主资产扫描、实际 GetPrimaryAssetId()、Bundle 数据和 Cook 规则,再检查路径与资源类型。
  • 完成回调发生,但资源不齐:完成是步骤终止,读取所需资源分别判断;空 ID 数组同样可以形成立即完成的步骤。
  • 关闭预热后内存没马上下降:检查使用中的控件、强引用与其他加载句柄;释放 Scope 不会强制卸载共享资源或立即触发 GC。
  • 资源一直被本批次保留:确认在业务退出时调用 Release,而不是只检查 IsAsyncLoadingInProgress();加载已结束时该查询可以为 false,句柄仍然存在。

实现入口:插件 AsyncMixin.cpp 中的 AsyncPreloadPrimaryAssetsAndBundles 与 CompleteAsyncLoading,引擎 Engine/Source/Runtime/Engine/Classes/Engine/AssetManager.h 中的 PreloadPrimaryAssets。本例按接口与句柄语义核对,未创建资产、编译或进行打包运行验收。

Starry手册导览从模块进入子模块与具体功能,查阅功能用途、配置步骤、技术架构和扩展方法。工程分层与系统协作了解 Framework、Gameplay 与通用插件的依赖方向,以及配置装配和运行状态的边界。模块总览与接入从行为系统、执行系统与机会调度了解 AstraAI,再为角色配置第一套组合决策。组合与覆盖正确区分优先级、行为分数与 Stack 顺序,按完整路径组织可撤销的配置。评分与抢占沿着条件、优先级、分数、持续有效性与冷却,解释一个行为为何开始或退出。Logic 与生命周期把动作接入 Start、Cancel、Cleanup,正确处理共享模板、蓝图实例与过期异步回调。BT、StateTree 与 HTN复用控制器上的树组件,正确传入数据、报告整树结果并等待安全退出。Objective 与机会生成把领域事实整理成当前机会,让评分只读数据,让生成器独立管理订阅。命令发布与领取发布一条命令后,经机会评估、Take、执行与 EndUse 完成一次受控使用。行为区域用可挂载的 Zone 组件分配行为层,并把回区机会与实际导航分开。调试与排查从初始化到候选、机会、应用层与后端,沿真实运行路径定位 AI 没有行动的原因。模块总览与使用了解通用调试工具的操作、远程连接与窗口扩展,再由各模块的排查说明定位具体问题。netImgui 远程面板在独立桌面程序查看调试窗口,区分远程 UI、游戏视口绘制与玩法网络权限。窗口扩展与属性绘制复用窗口生命周期、反射 Inspector 和场景绘制,避免为每个调试页重做基础设施。模块总览与接入从配置绑定、动作处理、通道缓存与玩家改键了解 Input,再接入角色的输入配置。绑定与配置生命周期理解跨 IMC 的共享绑定、配置所有权、延迟初始化,以及切换角色时的清理。Handler 与能力分派选择正确的输入阶段,把输入交给移动、GAS、GameplayEvent 或 UI,保持共享 Handler 无玩家状态。修饰器与双击选择输入值变换所在层,理解玩家设置修饰器与 Double Tap 的时序规则。通道输入缓存按 GameplayTag 通道开窗,保留首批或末批输入,正常关闭执行,中断取消丢弃。改键与玩家设置通过 GameSettings 统一编辑与应用,区分 SharedSettings 偏好和 Enhanced Input 键映射数据。调试与排查使用 Gamepad 与 Actions 观察和模拟输入,沿设备、映射、绑定、缓存与能力定位问题。模块总览与接入使用 UE 5.8 原生 Async Message System,把事件通知与接收方解耦,查阅消息收发、路由和 Starry 的实际接入。蓝图收发与解绑通过消息 Id、监听、载荷封装与解包的核心连线,掌握蓝图收发和任务解绑。C++ 收发与解绑按取得系统、登记监听、读取载荷、发送与解绑的顺序,查阅原生 C++ 接口用法。标签、时机与生命周期理解消息的标签层级、World 和 Endpoint 路由、游戏线程派发、载荷快照与 C++ 监听清理。项目消息与排查查阅 Starry 已接入的消息标签、载荷和触发点,区分本地通知、状态复制与 RPC 转发,并沿真实链路排查。模块总览与项目接入管理软引用加载、有序回调与任务作用域,了解指示器画布的实际接入,以及加载完成后的资源和宿主生命周期。软引用加载与 C++ 用法用组件加载软对象和控件类,保存加载结果,理解路径数组、同步回调、失败结果,以及重载与退出时的取消清理。主资产与资源驻留按主资产 ID 和 Bundle 预加载资源,理解句柄保留、取消与 GC 引用的关系,以及 AssetManager 和打包配置的职责。回调顺序与独立作用域理解并行加载与有序回调的区别,使用 FAsyncScope 分开取消任务,了解 AsyncEvent、开始完成通知和等待条件的当前边界。在文档中说明蓝图用法将真实蓝图节点嵌入正文,结合步骤、引脚和连线说明功能,节点文本随文章保存。维护这本手册本地文件与网页编辑共用一份 MDX,日常只改内容与关系,沿用共享版式和图表。