Unity DOTS中的baking(五)prefabs

Unity DOTS中的baking(五)prefabs

在DOTS的baking过程中,prefabs会被烘焙成entity prefabs。entity prefabs也是一个entity,可以在运行时实例化,就像是prefab一样。我们可以使用EntityPrefabReference这个struct,将prefab注册到baker中。

首先,我们需要定义一个包含EntityPrefabReference的ECS component:

public struct Config : IComponentData
{public EntityPrefabReference PrefabReference;
}

然后,再定义一个authoring component,用来给entity添加Config这个component:

public class ConfigAuthoring : MonoBehaviour
{public GameObject Prefab;class Baker : Baker<ConfigAuthoring>{public override void Bake(ConfigAuthoring authoring){var prefabEntity = new EntityPrefabReference(authoring.Prefab);var entity = GetEntity(TransformUsageFlags.None);AddComponent(entity, new Config{PrefabReference = prefabEntity,});}}
}

此时我们使用传入的prefab新建了一个EntityPrefabReference类型的对象。自此,prefab的baking过程就结束了。接下来,就是要如何加载这个entity prefab了。

要加载 EntityPrefabReference 引用的prefab,还需要将 RequestEntityPrefabLoaded 这个component添加到entity中。当prefab加载结束时,Unity会将PrefabLoadResult这个component添加到包含RequestEntityPrefabLoaded的同一entity中。我们可以在统一的一个system中完成这项工作:

public partial struct LoadPrefabSystem : ISystem
{public void OnCreate(ref SystemState state){state.RequireForUpdate<Config>();}[BurstCompile]public void OnUpdate(ref SystemState state){state.Enabled = false;var config = SystemAPI.GetSingleton<Config>();var configEntity = SystemAPI.GetSingletonEntity<Config>();state.EntityManager.AddComponentData(configEntity, new RequestEntityPrefabLoaded{Prefab = config.PrefabReference});}
}

最后,在使用这个prefab的system中,只有当获取到PrefabLoadResult这个component时,system才会执行:

    public partial struct SpawnSystem : ISystem{public void OnCreate(ref SystemState state){state.RequireForUpdate<Config>();state.RequireForUpdate<PrefabLoadResult>();}[BurstCompile]public void OnUpdate(ref SystemState state){var config = SystemAPI.GetSingleton<Config>();var configEntity = SystemAPI.GetSingletonEntity<Config>();if (!SystemAPI.HasComponent<PrefabLoadResult>(configEntity)){return;}var prefabLoadResult = SystemAPI.GetComponent<PrefabLoadResult>(configEntity);var entity = state.EntityManager.Instantiate(prefabLoadResult.PrefabRoot);var random = Random.CreateFromIndex((uint) state.GlobalSystemVersion);state.EntityManager.SetComponentData(entity,LocalTransform.FromPosition(random.NextFloat(-5, 5), random.NextFloat(-5, 5), 0));}}

同样地,我们来窥探一下Unity内部的实现机制。首先是构造EntityPrefabReference时,调用的构造函数为:

public EntityPrefabReference(GameObject prefab) : this(AssetDatabase.GUIDFromAssetPath(AssetDatabase.GetAssetPath(prefab)))
{
}

可以看到,这里保存了prefab对应的guid,后续是通过guid来进行prefab加载的。我们再来看看请求加载prefab时使用的RequestEntityPrefabLoaded,它只包含了EntityPrefabReference一个成员:

/// <summary>
/// Component to signal the request to convert the referenced prefab.
/// </summary>
public struct RequestEntityPrefabLoaded : IComponentData
{/// <summary>/// The reference of the prefab to be converted./// </summary>public EntityPrefabReference Prefab;
}

容易猜到,Unity内部有一个System,会遍历所有包含RequestEntityPrefabLoaded的Entity。它就是WeakAssetReferenceLoadingSystem,这个System创建时拥有一个WeakAssetReferenceLoadingData类型的Component:

struct WeakAssetReferenceLoadingData : IComponentData
{public struct LoadedPrefab{public int RefCount;public Entity SceneEntity;public Entity PrefabRoot;}public NativeParallelMultiHashMap<EntityPrefabReference, Entity> InProgressLoads;public NativeParallelHashMap<EntityPrefabReference, LoadedPrefab> LoadedPrefabs;
}

InProgressLoads是一个一对多的HashMap,它记录了当前有哪些Entity发起了加载同一个prefab的请求。LoadedPrefabs则是一对一的HashMap,它保存了当前已经加载完成的prefab信息,包含引用计数,prefab对应的Entity。这个Component在System的OnCreate方法执行时就被添加到System上,直到System销毁时在OnDestroy方法中移除。

System的OnUpdate方法才是重头戏。它负责prefab引用计数的管理,加载和卸载prefab的操作。

[BurstCompile]
public void OnUpdate(ref SystemState state)
{var ecb = new EntityCommandBuffer(Allocator.Temp);foreach(var (req, e) in SystemAPI.Query<RequestEntityPrefabLoaded>().WithEntityAccess().WithNone<PrefabAssetReference>()){StartLoadRequest(ref state, e, req.Prefab, ref ecb);}ecb.Playback(state.EntityManager);var sceneEntitiesToUnload = new NativeList<Entity>(Allocator.Temp);var prefabIdsToRemove = new NativeList<EntityPrefabReference>(Allocator.Temp);var inProgressLoadsToRemove = new NativeParallelHashMap<EntityPrefabReference, Entity>(16, Allocator.Temp);var PARSToRemove = new NativeList<Entity>(Allocator.Temp);ref var loadingData = ref state.EntityManager.GetComponentDataRW<WeakAssetReferenceLoadingData>(state.SystemHandle).ValueRW;foreach (var (prefabReference, e) in SystemAPI.Query<PrefabAssetReference>().WithEntityAccess().WithNone<RequestEntityPrefabLoaded>()){if (loadingData.LoadedPrefabs.TryGetValue(prefabReference._ReferenceId, out var loadedPrefab)){if (loadedPrefab.RefCount > 1){loadedPrefab.RefCount--;loadingData.LoadedPrefabs[prefabReference._ReferenceId] = loadedPrefab;}else{prefabIdsToRemove.Add(prefabReference._ReferenceId);sceneEntitiesToUnload.Add(loadedPrefab.SceneEntity);}}else{inProgressLoadsToRemove.Add(prefabReference._ReferenceId, e);}PARSToRemove.Add(e);}for (int i = 0; i < sceneEntitiesToUnload.Length; i++){SceneSystem.UnloadScene(state.WorldUnmanaged, sceneEntitiesToUnload[i], SceneSystem.UnloadParameters.DestroyMetaEntities);}for (int i = 0; i < prefabIdsToRemove.Length; i++){loadingData.LoadedPrefabs.Remove(prefabIdsToRemove[i]);}foreach (var k in inProgressLoadsToRemove.GetKeyArray(Allocator.Temp)){loadingData.InProgressLoads.Remove(k, inProgressLoadsToRemove[k]);}for (int i = 0; i < PARSToRemove.Length; i++){state.EntityManager.RemoveComponent<PrefabAssetReference>(PARSToRemove[i]);}
}

方法稍微有点长,但可以划分为几个部分。第一块是查询带有RequestEntityPrefabLoaded但是又没有PrefabAssetReference的Entity,前者表示请求加载prefab,后者表示该请求System是否已经在处理中。这个Component可以避免prefab的重复加载。

对于满足条件的query,则会调用StartLoadRequest方法:

void StartLoadRequest(ref SystemState state, Entity entity, EntityPrefabReference loadRequestWeakReferenceId, ref EntityCommandBuffer ecb)
{ref var loadingData = ref state.EntityManager.GetComponentDataRW<WeakAssetReferenceLoadingData>(state.SystemHandle).ValueRW;if (loadingData.LoadedPrefabs.TryGetValue(loadRequestWeakReferenceId, out var loadedPrefab)){ecb.AddComponent(entity, new PrefabAssetReference { _ReferenceId = loadRequestWeakReferenceId});ecb.AddComponent(entity, new PrefabLoadResult { PrefabRoot = loadedPrefab.PrefabRoot});loadedPrefab.RefCount++;loadingData.LoadedPrefabs.Remove(loadRequestWeakReferenceId);loadingData.LoadedPrefabs.Add(loadRequestWeakReferenceId, loadedPrefab);return;}ecb.AddComponent(entity, new PrefabAssetReference { _ReferenceId = loadRequestWeakReferenceId });if (!loadingData.InProgressLoads.ContainsKey(loadRequestWeakReferenceId)){var loadParameters = new SceneSystem.LoadParameters { Flags = SceneLoadFlags.NewInstance };var sceneEntity = SceneSystem.LoadPrefabAsync(state.WorldUnmanaged, loadRequestWeakReferenceId, loadParameters);ecb.AddComponent(sceneEntity, new WeakAssetPrefabLoadRequest { WeakReferenceId = loadRequestWeakReferenceId });}loadingData.InProgressLoads.Add(loadRequestWeakReferenceId, entity);
}

方法首先检查prefab是否已经加载完成,如果完成了则直接往发起请求的Entity上添加PrefabAssetReferencePrefabLoadResult这两个Component,前者表示System已经处理了,后者表示prefab加载完成的结果。然后还要更新下prefab的引用计数。

如果还未加载完成,则要判断是已经在加载中,还是还没发起加载请求。如果还不在加载中,则需要调用SceneSystem.LoadPrefabAsync发起加载请求,该方法返回一个表示prefab当前加载状态的Entity,如果prefab加载完成,Entity上会添加一个PrefabRoot类型的Component,它包含了加载完成的prefab。

/// <summary>
/// Load a prefab by its weak reference id.
/// A PrefabRoot component is added to the returned entity when the load completes.
/// </summary>
/// <param name="world">The <see cref="World"/> in which the prefab is loaded.</param>
/// <param name="prefabReferenceId">The weak asset reference to the prefab.</param>
/// <param name="parameters">The load parameters for the prefab.</param>
/// <returns>An entity representing the loading state of the prefab.</returns>
public static Entity LoadPrefabAsync(WorldUnmanaged world, EntityPrefabReference prefabReferenceId, LoadParameters parameters = default)
{return LoadSceneAsync(world, prefabReferenceId.Id.GlobalId.AssetGUID, parameters);
}

发起加载请求后,还要为Scene Entity添加WeakAssetPrefabLoadRequest类型的Component。这个component的作用是为了让prefab加载完成时通知到当前System,类似注册了一个回调函数。只有带有该Component的Scene Entity完成prefab加载时,才会调用System的CompleteLoad方法:

if (prefabRoot != Entity.Null)
{var sceneEntity = EntityManager.GetComponentData<SceneEntityReference>(sectionEntity).SceneEntity;EntityManager.AddComponentData(sceneEntity, new PrefabRoot {Root = prefabRoot});if (EntityManager.HasComponent<WeakAssetPrefabLoadRequest>(sceneEntity)){WeakAssetReferenceLoadingSystem.CompleteLoad(ref systemState,sceneEntity,prefabRoot,EntityManager.GetComponentData<WeakAssetPrefabLoadRequest>(sceneEntity).WeakReferenceId);}
}

CompleteLoad方法更新System的WeakAssetReferenceLoadingData信息,所有请求加载该prefab的Entity都会添加PrefabAssetReferencePrefabLoadResult这两个Component,并且Entity的数量就是该prefab的引用计数。如果没有这样的Entity,则直接卸载这个prefab。

/// <summary>
/// Marks a prefab as loaded and cleans up the in progress state.
/// </summary>
/// <param name="state">The entity system state.</param>
/// <param name="sceneEntity">The entity representing the loading state of the scene.</param>
/// <param name="prefabRoot">The root entity of a converted prefab.</param>
/// <param name="weakReferenceId">The prefab reference used to initiate the load.</param>
public static void CompleteLoad(ref SystemState state, Entity sceneEntity, Entity prefabRoot, EntityPrefabReference weakReferenceId)
{ref var loadingData = ref state.EntityManager.GetComponentDataRW<WeakAssetReferenceLoadingData>(state.WorldUnmanaged.GetExistingUnmanagedSystem<WeakAssetReferenceLoadingSystem>()).ValueRW;if (!loadingData.InProgressLoads.TryGetFirstValue(weakReferenceId, out var entity, out var it)){
#if UNITY_EDITORif (loadingData.LoadedPrefabs.TryGetValue(weakReferenceId, out var loadedPrefab)){// Prefab was reloaded, patch all references to point to the new prefab rootloadedPrefab.PrefabRoot = prefabRoot;loadedPrefab.SceneEntity = sceneEntity;loadingData.LoadedPrefabs[weakReferenceId] = loadedPrefab;var query = state.GetEntityQuery(ComponentType.ReadOnly<RequestEntityPrefabLoaded>());foreach (var req in query.ToComponentDataArray<RequestEntityPrefabLoaded>(Allocator.Temp)){if (req.Prefab == weakReferenceId)loadedPrefab.PrefabRoot = prefabRoot;}return;}
#endif//No one was waiting for this load, unload it immediatelySceneSystem.UnloadScene(state.WorldUnmanaged, sceneEntity, SceneSystem.UnloadParameters.DestroyMetaEntities);return;}state.EntityManager.AddComponentData(prefabRoot, new RequestEntityPrefabLoaded() {Prefab =  weakReferenceId});int count = 0;do{state.EntityManager.AddComponentData(entity, new PrefabAssetReference { _ReferenceId = weakReferenceId});state.EntityManager.AddComponentData(entity, new PrefabLoadResult { PrefabRoot =  prefabRoot});count++;} while (loadingData.InProgressLoads.TryGetNextValue(out entity, ref it));loadingData.InProgressLoads.Remove(weakReferenceId);loadingData.LoadedPrefabs.Add(weakReferenceId, new WeakAssetReferenceLoadingData.LoadedPrefab { SceneEntity = sceneEntity, PrefabRoot = prefabRoot, RefCount = count});
}

看完加载的部分,再来看看卸载相关的逻辑。我们再次回到OnUpdate方法。接下来查询所有包含PrefabAssetReference并且不包含RequestEntityPrefabLoaded的Entity,这与前面恰好相反,它表示所有需要取消加载prefab的Entity。如果prefab已经加载完成了,此时需要将其引用计数减一;如果引用计数为0了,则需要将其卸载。如果prefab还在加载中,则需要从请求加载的hashmap中把Entity移除。最后,为了避免重复处理这类取消加载的Entity,处理完毕后需要移除它们身上的PrefabAssetReference

当然,实际上另一种情形更常见,就是Entity在其生命周期内请求加载了某个prefab之后一直在使用,直到它被destroy。这种情况外部并不需要做额外的操作,Entity所持有的prefab引用计数也会自动正确地更新。这是怎么做到的呢?

答案就在RequestEntityPrefabLoadedPrefabAssetReference这两个Component上。默认Entity是同时拥有这两个Component的,但是PrefabAssetReference是ICleanupComponentData,意味着Entity销毁时它也不会被销毁,这样被销毁的Entity身上,只有一个PrefabAssetReference,就会在System的OnUpdate时触发prefab的清理工作。

Reference

[1] Prefabs in baking

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/4716.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

瓦片编辑器成功移植到小熊猫C++ 2.25.1版本,解决_findnext移植问题

移植之后出现绿色屏幕闪退 查了版本回滚直到不闪退&#xff0c;发现是在读取自定义文件上出问题 然后在找读取自定义文件函数&#xff0c;发现是读取图片部分出问题 然后就卡住了 调试半天&#xff0c;不是数据溢出&#xff0c;于是就看 函数_findnext,网上搜 ———_findn…

Java项目启动检测 Redis 是否启动,未启动则启动(macOS 版本)

文章目录 一、概述二、代码 一、概述 启动项目时&#xff0c;检测macos 上 Redis 是否启动&#xff0c;未启动&#xff0c;则启动。 二、代码 /*** 用途: 项目启动时检查 redis&#xff0c;未启动则启动&#xff08;开发环境&#xff09;** author: ADAM* create: 2024/04/2…

4.Docker本地镜像发布至阿里云仓库、私有仓库、DockerHub

文章目录 0、镜像的生成方法1、本地镜像发布到阿里云仓库2、本地镜像发布到私有仓库3、本地镜像发布到Docker Hub仓库 Docker仓库是集中存放镜像的地方&#xff0c;分为公共仓库和私有仓库。 注册服务器是存放仓库的具体服务器&#xff0c;一个注册服务器上可以有多个仓库&…

项目开发规范

Restful REST&#xff0c;表述性状态转换&#xff0c;他是一种软件架构风格 使用URL定位资源&#xff0c;HTTP动词描述操作 根据发出请求类型来区分操作 GET&#xff1a; 查询id为1的用户POST&#xff1a;新增用户PUT&#xff1a;修改用户DELETE&#xff1a;删除id为1的用户 …

springboot权限验证学习-上

创建maven项目 创建父工程 这类项目和原来项目的区别在于&#xff0c;打包方式是pom 由于pom项目一般都是用来做父项目的&#xff0c;所以该项目的src文件夹可以删除掉。 创建子工程 子工程pom.xml 父工程pom.xml 添加依赖 父工程导入依赖包 <!--导入springboot 父工程…

18.Nacos配置管理-微服务读取Nacos中的配置

需要解决的问题 1.实现配置更改热更新&#xff0c;而不是改动了配置文件还要去重启服务才能生效。 2.对多个微服务的配置文件统一集中管理。而不是需要对每个微服务逐一去修改配置文件&#xff0c;特别是公共通用的配置。 配置管理服务中的配置发生改变后&#xff0c;回去立…

病理组学+配对 mIHC 验证+转录组多组学

目录 病理DeepRisk网络模型构建 DPS和新辅助化疗 mIHC 验证 STAD转录组层面 病理DeepRisk网络模型构建 自有数据训练&#xff0c;TCGA数据进行验证&#xff0c;然后配对mIF验证&#xff0c;最后还在转录组层面分析。 该模型基于中山数据集&#xff08;n 1120&#xff09…

【AIGC调研系列】Sora级别的国产视频大模型-Vidu

Vidu能够达到Sora级别的标准。Vidu被多个来源认为是国内首个Sora级别的视频大模型[2][3][4]。它采用了团队原创的Diffusion与Transformer融合的架构U-ViT&#xff0c;能够生成长达16秒、分辨率高达1080P的高清视频内容[1][6]。此外&#xff0c;Vidu的一致性、运动幅度都达到了S…

美易官方:巴菲特股东大会召开在即,新浪财经中美投资人酒会5月4日举行

在五月的阳光下&#xff0c;全球投资者的目光都聚焦在了美国中部城市奥马哈。这里是“投资界春晚”——巴菲特股东大会的举办地&#xff0c;也是全球投资者共享投资智慧的圣地。今年的巴菲特股东大会更是备受瞩目&#xff0c;不仅有“股神”巴菲特的精彩演讲&#xff0c;还有新…

【Spring】IOC/DI中常用的注解@Lazy、@Scope与@Conditional

目录 1、Lazy 懒加载bean 1.1、与component配合使用 1.2、与Bean注解配合使用 2、Scope bean的作用域 2.1、不指定Scope 2.2、指定Scope为 prototype 3、Conditional 条件注解 1、Lazy 懒加载bean Lazy用于指定单例bean实例化的时机&#xff0c;在没有指定此注解时&…

基于SpringBoot+Vue校园竞赛管理系统的设计与实现

项目介绍&#xff1a; 传统信息的管理大部分依赖于管理人员的手工登记与管理&#xff0c;然而&#xff0c;随着近些年信息技术的迅猛发展&#xff0c;让许多比较老套的信息管理模式进行了更新迭代&#xff0c;竞赛信息因为其管理内容繁杂&#xff0c;管理数量繁多导致手工进行…

Linux学习阶段划分及学习方法

目录 Linux学习阶段划分 Linux学习方法 整理自 一周学会Linux韩顺平 Linux学习阶段划分 这节可以让你知道你目前是什么水平 1、Linux环境下基本操作命令 例如文件管理&#xff0c;用户管理&#xff0c;vi vim编辑器 2、Linux的各种配置 例如网络的配置&#xff0c;服务的…

安卓常用组件(启停活动页面、活动之间传递信息、收发应用广播、操作后台服务)

启停活动页面 Activity的启动和结束 页面跳转可以使用startActivity接口&#xff0c;具体格式为startActivity(new Intent(this, 目标页面.class));。 关闭一个页面可以直接调用finish();方法即可退出页面。 Activity的生命周期 页面在安卓有个新的名字叫活动&#xff0c;因…

单例模式及其应用

单例模式介绍&#xff1a; 单例模式是一种常见的设计模式&#xff0c;其目的是确保某个类只有一个实例存在&#xff0c;并提供一个全局访问点。 在实现单例模式时&#xff0c;一般需要注意以下几点&#xff1a; 私有化构造函数&#xff1a;防止外部直接实例化对象。私有静态…

微软ML Copilot框架释放机器学习能力

摘要&#xff1a;大模型席卷而来&#xff0c;通过大量算法模型训练推理&#xff0c;能根据人类输入指令产生图文&#xff0c;其背后是大量深度神经网络模型在做运算&#xff0c;这一过程称之为机器学习&#xff0c;本文从微软语言大模型出发&#xff0c;详解利用大型语言模型&a…

取得Claude模型的Key

目录 1.登录Claude \ Anthropic。需要美丽国IP。 2.取得Key 3.可用模型 4.帮助文档地址 5.使用这个key 6.取得Credit 1.登录Claude \ Anthropic。需要美丽国IP。 2.取得Key 3.可用模型 4.帮助文档地址 https://docs.anthropic.com/claude/docs/intro-to-claude --------…

前后端分离,使用sa-token作为安全框架快速搭建一个微服务项目

之前写过一个单体项目&#xff0c;现在想把它升级为微服务项目。在拆分升级的过程中发现了很多问题&#xff0c;本次就来记录一下遇到的问题和解决方法。&#xff08;这篇文章只是记录一下拆分项目的基础架构&#xff0c;并使用sa-token做微服务项目的安全框架&#xff0c;快速…

upload-labs后续(14-)

图片马 在图片中加入木马&#xff0c;然后上传让浏览器解析&#xff0c;达到上传木马的目的 制作图片马&#xff1a;可以在notepad或者winhex中打开文件&#xff0c;在文件头中加入图片的文件头&#xff0c;再在文件末尾加入木马即可。 图片文件头如下&#xff1a; 1.Png图片…

学生党学习亚马逊云科技AWS、求职上岸就申AWS Cloud Club队长!

毕业了怎么找工作&#xff1f;该怎么学AWS&#xff1f;这是同学们最关心的问题。最近AWS推出的Cloud Club校园社区计划就可以完美解决这些问题&#xff01; &#x1f3eb;AWS校园社区计划是在学校构建校园社团(全球学校)&#xff0c;带着大家学最热的开发、AI/ML技术&#xff0…

IDEA主题美化【保姆级】

前言 一款好的 IDEA 主题虽然不能提高我们的开发效率&#xff0c;但一个舒适简单的主题可以使开发人员更舒适的开发&#xff0c;时常换一换主题可以带来不一样的体验&#xff0c;程序员的快乐就这么简单。话不多说&#xff0c;先上我自己认为好看的主题设置。 最终效果图: 原…