Unity DOTS中的world

Unity DOTS中的world

            • 注册销毁逻辑
            • 自定义创建逻辑
            • 创建world
            • 创建system group
            • 插入player loop
            • Reference

DOTS中,world是一组entity的集合。entity的ID在其自身的世界中是唯一的。每个world都拥有一个EntityManager,可以用它来创建、销毁和修改world中的entity。一个world还拥有一组system,这些system通常只访问同一个world中的entity。此外,一个world中具有相同component类型的entity集合会被一起存储在一个archetype中,archetype决定了component在内存中的组织方式。

默认情况下,Unity会自动创建两个world,一个是editor world,一个是default world,分别用于编辑器环境与运行时环境。Unity定义了3个宏,用于禁用这两个world的自动创建:

  • #UNITY_DISABLE_AUTOMATIC_SYSTEM_BOOTSTRAP_RUNTIME_WORLD: 禁止defualt world自动创建
  • #UNITY_DISABLE_AUTOMATIC_SYSTEM_BOOTSTRAP_EDITOR_WORLD: 禁止editor world自动创建
  • #UNITY_DISABLE_AUTOMATIC_SYSTEM_BOOTSTRAP: 禁止editor world与default world自动创建

那么,我们先来看看editor world自动创建的时机。通过上述几个宏,可以顺藤摸瓜找到相应的代码:

/// <summary>
/// Can be called when in edit mode in the editor to initialize a the default world.
/// </summary>
public static void DefaultLazyEditModeInitialize()
{
#if UNITY_EDITORif (World.DefaultGameObjectInjectionWorld == null){// * OnDisable (Serialize monobehaviours in temporary backup)// * unload domain// * load new domain// * OnEnable (Deserialize monobehaviours in temporary backup)// * mark entered playmode / load scene// * OnDisable / OnDestroy// * OnEnable (Loading object from scene...)if (UnityEditor.EditorApplication.isPlayingOrWillChangePlaymode){// We are just gonna ignore this enter playmode reload.// Can't see a situation where it would be useful to create something inbetween.// But we really need to solve this at the root. The execution order is kind if crazy.}else{
#if !UNITY_DISABLE_AUTOMATIC_SYSTEM_BOOTSTRAP_EDITOR_WORLDInitialize("Editor World", true);
#endif}}
#endif
}

DefaultLazyEditModeInitialize的有效引用只有两处,一是在SubSceneOnEnable,二是在SubSceneInspectorOnInspectorGUI,换言之只有当场景中存在SubScene时,editor world才会被创建。我们可以实际验证一下,首先创建一个空场景,然后观察Systems Window,发现空空如也:

Unity DOTS中的world1

但如果此时,创建一个SubScene,就不一样了:

Unity DOTS中的world2

再看看default world创建的时机:

#if !UNITY_DISABLE_AUTOMATIC_SYSTEM_BOOTSTRAP_RUNTIME_WORLDstatic class AutomaticWorldBootstrap{[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]static void Initialize(){DefaultWorldInitialization.Initialize("Default World", false);}}
#endif

带有RuntimeInitializeLoadType.BeforeSceneLoad属性的函数会在第一个场景加载时触发,因此,在runtime下,default world一定会被创建。

Unity DOTS中的world3

可以看到,两个world创建调用的其实是同一个函数DefaultWorldInitialization.Initialize,只是参数不同。

/// <summary>
/// Initializes the default world or runs ICustomBootstrap if one is available.
/// </summary>
/// <param name="defaultWorldName">The name of the world that will be created. Unless there is a custom bootstrap.</param>
/// <param name="editorWorld">Editor worlds by default only include systems with [WorldSystemFilter(WorldSystemFilterFlags.Editor)]. If editorWorld is true, ICustomBootstrap will not be used.</param>
/// <returns>The initialized <see cref="World"/> object.</returns>
public static World Initialize(string defaultWorldName, bool editorWorld = false)
{using var marker = new ProfilerMarker("Create World & Systems").Auto();RegisterUnloadOrPlayModeChangeShutdown();#if ENABLE_PROFILEREntitiesProfiler.Initialize();
#endif#if (UNITY_EDITOR || DEVELOPMENT_BUILD) && !DISABLE_ENTITIES_JOURNALINGEntitiesJournaling.Initialize();
#endifif (!editorWorld){var bootStrap = CreateBootStrap();if (bootStrap != null && bootStrap.Initialize(defaultWorldName)){Assert.IsTrue(World.DefaultGameObjectInjectionWorld != null,$"ICustomBootstrap.Initialize() implementation failed to set " +$"World.DefaultGameObjectInjectionWorld, despite returning true " +$"(indicating the World has been properly initialized)");return World.DefaultGameObjectInjectionWorld;}}var world = new World(defaultWorldName, editorWorld ? WorldFlags.Editor : WorldFlags.Game);World.DefaultGameObjectInjectionWorld = world;AddSystemToRootLevelSystemGroupsInternal(world, GetAllSystemTypeIndices(WorldSystemFilterFlags.Default, editorWorld));ScriptBehaviourUpdateOrder.AppendWorldToCurrentPlayerLoop(world);DefaultWorldInitialized?.Invoke(world);return world;
}

我们来仔细研究一下world的创建流程。它大致分为以下若干步骤:

  1. 注册world的销毁逻辑;
  2. 判断是否有用户自定义的创建逻辑,如果有直接调用并返回;
  3. 如果没有,调用world自带的构造函数创建world;
  4. 创建world的system group,把属于world的尚未创建的system添加到相对应的group中;
  5. 把system group中的sytem,根据不同的执行顺序插入到player loop中;
  6. 初始化完毕,触发DefaultWorldInitialized回调,并返回world。
注册销毁逻辑

注册销毁逻辑这里Unity处理得其实比较粗糙,首先world什么时候应当被销毁?Unity在函数注释中给出了三种情形:

a. 从editor mode切换到play mode,此时editor world需要销毁;

b. 从play mode切换到editor mode,此时default world需要销毁;

c. 卸载当前AppDomain时(例如修改了scripts触发domain reloading),此时editor/default world都需要销毁。

/// <summary>
/// Ensures the current World destruction on shutdown or when entering/exiting Play Mode or Domain Reload.
/// 1) When switching to Play Mode Editor World (if created) has to be destroyed:
///     - after the current scene objects are destroyed and OnDisable/Destroy are called,
///     - before game scene is loaded and Awake/OnEnable are called.
/// 2) When switching to Edit Mode Game World has to be destroyed:
///     - after the current scene objects are destroyed and OnDisable/Destroy are called,
///     - before backup scene is loaded and Awake/OnEnable are called.
/// 3) When Unloading Domain (as well as Editor/Player exit) Editor or Game World has to be destroyed:
///     - after OnDisable/OnBeforeSerialize are called,
///     - before AppDomain.DomainUnload.
/// Point 1) is covered by RuntimeInitializeOnLoadMethod attribute.
/// For points 2) and 3) there are no entry point in the Unity API and they have to be handled by a proxy MonoBehaviour
/// which in OnDisable can drive the World cleanup for both Exit Play Mode and Domain Unload.
/// </summary>
static void RegisterUnloadOrPlayModeChangeShutdown()
{if (s_UnloadOrPlayModeChangeShutdownRegistered)return;var go = new GameObject { hideFlags = HideFlags.HideInHierarchy };if (Application.isPlaying)UnityEngine.Object.DontDestroyOnLoad(go);elsego.hideFlags = HideFlags.HideAndDontSave;go.AddComponent<DefaultWorldInitializationProxy>().IsActive = true;RuntimeApplication.RegisterFrameUpdateToCurrentPlayerLoop();s_UnloadOrPlayModeChangeShutdownRegistered = true;
}

情形a使用RuntimeInitializeLoadType.SubsystemRegistration属性即可解决,而b和c没有合适的回调时机,只能借助创建一个MonoBehaviour,通过其onDisable方法来曲线救国。

自定义创建逻辑

如果不是editor world,unity允许用户自定义创建world,负责创建的类需要继承自ICustomBootstrap接口,并实现Initialize方法。该方法返回值类型为bool,如果为true则会跳过default world的初始化。

创建world

World的构造函数接受两个参数,一个是name,一个是flag。World类中包含一个非托管struct的WorldUnmanaged对象,构造函数的主要工作就是在初始化这一非托管对象,而WorldUnmanaged类里又包含一个WorldUnmanagedImpl非托管struct的对象,工作重心又转移到了它的初始化身上。它的初始化分为两步,一是构建WorldUnmanagedImpl对象,二是初始化EntityManager

UnsafeUtility.AsRef<WorldUnmanagedImpl>(m_Impl) = new WorldUnmanagedImpl(world,NextSequenceNumber.Data++,flags,worldAllocatorHelper,world.Name);/** if we init the entitymanager inside the WorldUnmanagedImpl ctor, m_Impl will not be set, and so when the* EM asks for the sequence number, it will ask for GetImpl().SequenceNumber and get uninitialized data.* so, init it here instead.*/
m_Impl->m_EntityManager.Initialize(world);
创建system group

一个system group可以包含若干sytem,也可以包含其他的system group。一个sytem group会按照一定顺序在主线程上调用子sytem/sytem group的更新逻辑。Unity默认会创建3个system group:

var initializationSystemGroup = world.GetOrCreateSystemManaged<InitializationSystemGroup>();
var simulationSystemGroup = world.GetOrCreateSystemManaged<SimulationSystemGroup>();
var presentationSystemGroup = world.GetOrCreateSystemManaged<PresentationSystemGroup>();

创建完毕后,Unity接着开始创建所有符合条件的system,再根据system的UpdateInGroup属性,判断system属于上述哪个system group:

// Add systems to their groups, based on the [UpdateInGroup] attribute.
for (int i=0; i<systemTypesOrig.Length; i++)
{SystemHandle system = allSystemHandlesToAdd[i];// Skip the built-in root-level system groupsif (rootGroups.IsRootGroup(systemTypesOrig[i])){continue;}var updateInGroupAttributes = TypeManager.GetSystemAttributes(systemTypesOrig[i],TypeManager.SystemAttributeKind.UpdateInGroup);if (updateInGroupAttributes.Length == 0){defaultGroup.AddSystemToUpdateList(system);}foreach (var attr in updateInGroupAttributes){var group = FindGroup(world, systemTypesOrig[i], attr);if (group != null){group.AddSystemToUpdateList(system);}}
}

如果system没有UpdateInGroup属性,那么就会放到默认的group里,这里就是SimulationSystemGroup;如果有,就根据属性中的参数类型,找到相应的system group。我们可以新增一个system加以验证:

using Unity.Entities;
using UnityEngine;[UpdateInGroup(typeof(InitializationSystemGroup))]
public partial struct FirstSystem : ISystem
{public void OnCreate(ref SystemState state) { Debug.Log("========FirstSystem==========="); }public void OnDestroy(ref SystemState state) { }public void OnUpdate(ref SystemState state) { }
}

Unity DOTS中的world4

可以看到,我们创建的FirstSystem,被归到InitializationSystemGroup里了。将system分完类之后,还需要对system进行排序,因为有的system可能设置了OrderFirst/OrderLast参数,或是拥有UpdateBefore/UpdateAfter的属性。

插入player loop

最后,需要把3个顶层的system group插入到Unity的player loop中,让Unity在自身生命周期的不同阶段驱动system的update。

/// <summary>
/// Add this World's three default top-level system groups to a PlayerLoopSystem object.
/// </summary>
/// <remarks>
/// This function performs the following modifications to the provided PlayerLoopSystem:
/// - If an instance of InitializationSystemGroup exists in this World, it is appended to the
///   Initialization player loop phase.
/// - If an instance of SimulationSystemGroup exists in this World, it is appended to the
///   Update player loop phase.
/// - If an instance of PresentationSystemGroup exists in this World, it is appended to the
///   PreLateUpdate player loop phase.
/// If instances of any or all of these system groups don't exist in this World, then no entry is added to the player
/// loop for that system group.
///
/// This function does not change the currently active player loop. If this behavior is desired, it's necessary
/// to call PlayerLoop.SetPlayerLoop(playerLoop) after the systems have been removed.
/// </remarks>
/// <param name="world">The three top-level system groups from this World will be added to the provided player loop.</param>
/// <param name="playerLoop">Existing player loop to modify (e.g.  (e.g. PlayerLoop.GetCurrentPlayerLoop())</param>
public static void AppendWorldToPlayerLoop(World world, ref PlayerLoopSystem playerLoop)
{if (world == null)return;var initGroup = world.GetExistingSystemManaged<InitializationSystemGroup>();if (initGroup != null)AppendSystemToPlayerLoop(initGroup, ref playerLoop, typeof(Initialization));var simGroup = world.GetExistingSystemManaged<SimulationSystemGroup>();if (simGroup != null)AppendSystemToPlayerLoop(simGroup, ref playerLoop, typeof(Update));var presGroup = world.GetExistingSystemManaged<PresentationSystemGroup>();if (presGroup != null)AppendSystemToPlayerLoop(presGroup, ref playerLoop, typeof(PreLateUpdate));
}

这里,Initialization,Update和PreLateUpdate是Unity引擎update过程中的不同阶段。具体add的逻辑很简单,就是递归查找符合type的player loop,然后插入到update list的末尾。在player loop内部的时序里,Initialization在Update之前,而Update又在PreLateUpdate之前。

static bool AppendToPlayerLoopList(Type updateType, PlayerLoopSystem.UpdateFunction updateFunction, ref PlayerLoopSystem playerLoop, Type playerLoopSystemType)
{if (updateType == null || updateFunction == null || playerLoopSystemType == null)return false;if (playerLoop.type == playerLoopSystemType){var oldListLength = playerLoop.subSystemList != null ? playerLoop.subSystemList.Length : 0;var newSubsystemList = new PlayerLoopSystem[oldListLength + 1];for (var i = 0; i < oldListLength; ++i)newSubsystemList[i] = playerLoop.subSystemList[i];newSubsystemList[oldListLength] = new PlayerLoopSystem{type = updateType,updateDelegate = updateFunction};playerLoop.subSystemList = newSubsystemList;return true;}if (playerLoop.subSystemList != null){for (var i = 0; i < playerLoop.subSystemList.Length; ++i){if (AppendToPlayerLoopList(updateType, updateFunction, ref playerLoop.subSystemList[i], playerLoopSystemType))return true;}}return false;
}
Reference

[1] World concepts

[2] RuntimeInitializeOnLoadMethodAttribute

[3] Details of disabling Domain and Scene Reload

[4] Interface ICustomBootstrap

[5] System groups

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

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

相关文章

Python机器学习入门:从理论到实践

文章目录 前言一、机器学习是什么&#xff1f;二、机器学习基本流程三、使用Python进行机器学习1.数据读取2.数据规范化3. 数据降维&#xff08;主成分分析&#xff09;4. 机器学习模型的选择5. 线性回归模型的实现6. 可视化结果 总结 前言 机器学习是人工智能的一个重要分支&…

安装好anaconda,打开jupyter notebook,新建 报500错

解决办法&#xff1a; 打开anaconda prompt 输入 jupyter --version 重新进入jupyter notebook&#xff1a; 可以成功进入进行代码编辑

功能测试与APPSCAN自动化测试结合的提高效率测试策略

背景 手工探索性测试&#xff08;Manual Exploratory Testing&#xff0c;简称MET&#xff09;是一种软件测试方法&#xff0c;它依赖于测试人员的直觉、经验和即兴发挥来探索应用程序或系统。与传统的脚本化测试相比&#xff0c;手工探索性测试不遵循固定的测试脚本&#xff0…

基于 PyTorch 的模型瘦身三部曲:量化、剪枝和蒸馏,让模型更短小精悍!

基于 PyTorch 的模型量化、剪枝和蒸馏 1. 模型量化1.1 原理介绍1.2 PyTorch 实现 2. 模型剪枝2.1 原理介绍2.2 PyTorch 实现 3. 模型蒸馏3.1 原理介绍3.2 PyTorch 实现 参考文献 1. 模型量化 1.1 原理介绍 模型量化是将模型参数从高精度&#xff08;通常是 float32&#xff0…

【北京迅为】《i.MX8MM嵌入式Linux开发指南》-第三篇 嵌入式Linux驱动开发篇-第四十四章 注册字符设备号

i.MX8MM处理器采用了先进的14LPCFinFET工艺&#xff0c;提供更快的速度和更高的电源效率;四核Cortex-A53&#xff0c;单核Cortex-M4&#xff0c;多达五个内核 &#xff0c;主频高达1.8GHz&#xff0c;2G DDR4内存、8G EMMC存储。千兆工业级以太网、MIPI-DSI、USB HOST、WIFI/BT…

【Linux】汇总TCP网络连接状态命令

输入命令&#xff1a; netstat -na | awk /^tcp/ {S[$NF]} END {for(a in S) print a, S[a]} 显示&#xff1a; 让我们逐步解析这个命令&#xff1a; netstat -na: netstat 是一个用于显示网络连接、路由表、接口统计等信息的命令。 -n 选项表示输出地址和端口以数字格式显示…

Armv8/Armv9架构的学习大纲-学习方法-自学路线-付费学习路线

本文给大家列出了Arm架构的学习大纲、学习方法、自学路线、付费学习路线。有兴趣的可以关注&#xff0c;希望对您有帮助。 如果大家有需要的&#xff0c;欢迎关注我的CSDN课程&#xff1a;https://edu.csdn.net/lecturer/6964 ARM 64位架构介绍 ARM 64位架构介绍 ARM架构概况…

Wi-SUN无线通信技术 — 大规模分散式物联网应用首选

引言 在数字化浪潮的推动下&#xff0c;物联网&#xff08;IoT&#xff09;正逐渐渗透到我们生活的方方面面。Wi-SUN技术以其卓越的性能和广泛的应用前景&#xff0c;成为了大规模分散式物联网应用的首选。本文将深入探讨Wi-SUN技术的市场现状、核心优势、实际应用中的案例以及…

JavaEE (1)

web开发概述 所谓web开发,指的是从网页中向后端程序发送请求,与后端程序进行 交互. 流程图如下 Web服务器是指驻留于因特网上某种类型计算机的程序. 可以向浏览器等Web客户端提供文档&#xff0c;也可以放置网站文件&#xff0c;让全世界浏览&#xff1b; 它是一个容器&…

C++ —— 关于模板初阶

1.什么是模板 在C中&#xff0c;模板&#xff08;template&#xff09;是一种通用的编程工具&#xff0c;允许程序员编写通用代码以处理多种数据型或数据结构&#xff0c;而不需要为每种特定类型编写重复的代码&#xff0c;通过模板&#xff0c;可以实现代码的复用和泛化提高代…

QT5.9.9+Android开发环境搭建

文章目录 1.安装准备1.1 下载地址1.2 安装前准备2.安装过程2.1 JDK安装2.1.1 安装2.1.2 环境变量配置2.2 SDK配置2.2.1 安装2.2.2 环境变量配置2.2.3 adb 错误解决2.2.4 其他SDK安装2.2.5 AVD虚拟机配置2.3 NDK配置2.4 QT 5.9.9安装配置2.4.1 QT安装2.4.2 配置安卓环境3.QT工程…

【Linux】进程信号 --- 信号处理

&#x1f466;个人主页&#xff1a;Weraphael ✍&#x1f3fb;作者简介&#xff1a;目前正在学习c和算法 ✈️专栏&#xff1a;Linux &#x1f40b; 希望大家多多支持&#xff0c;咱一起进步&#xff01;&#x1f601; 如果文章有啥瑕疵&#xff0c;希望大佬指点一二 如果文章对…

Java---异常

乐观学习&#xff0c;乐观生活&#xff0c;才能不断前进啊&#xff01;&#xff01;&#xff01; 我的主页&#xff1a;optimistic_chen 我的专栏&#xff1a;c语言 &#xff0c;Java 欢迎大家访问~ 创作不易&#xff0c;大佬们点赞鼓励下吧~ 文章目录 什么是异常异常的分类编译…

安装 VMware vSphere vCenter 8.0

安装 VMware vSphere vCenter 8.0 1、运行安装程序 2、语言选择中文 3、点下一步 4、接受许可协议&#xff0c;点下一步 5、填写部署vCenter服务的ESXI主机IP地址以及对应ESXI主机的账号密码&#xff0c;这里将vCenter服务部署在192.168.1.14这台ESXi主机上 6、接受证书警告 7…

新手小白的pytorch学习第十弹----多类别分类问题模型以及九、十弹的练习

目录 1 多类别分类模型1.1 创建数据1.2 创建模型1.3 模型传出的数据1.4 损失函数和优化器1.5 训练和测试1.6 衡量模型性能的指标 2 练习Exercise 之前我们已经学习了 二分类问题&#xff0c;二分类就像抛硬币正面和反面&#xff0c;只有两种情况。 这里我们要探讨一个 多类别…

基于关键字驱动设计Web UI自动化测试框架!

引言 在自动化测试领域&#xff0c;关键字驱动测试&#xff08;Keyword-Driven Testing, KDT&#xff09;是一种高效且灵活的方法&#xff0c;它通过抽象测试用例中的操作为关键字&#xff0c;实现了测试用例与测试代码的分离&#xff0c;从而提高了测试脚本的可维护性和可扩展…

记录解决springboot项目上传图片到本地,在html里不能回显的问题

项目场景&#xff1a; 项目场景&#xff1a;在我的博客系统里&#xff1a;有个相册模块&#xff1a;需要把图片上传到项目里&#xff0c;在html页面上显示 解决方案 1.建一个文件夹 例如在windows系统下。可以在项目根目录下建个photos文件夹&#xff0c;把上传的图片文件…

[PM]产品运营

生命周期 运营阶段 主要工作 拉新 新用户的定义 冷启动 拉新方式 促活 用户活跃的原因 量化活跃度 运营社区化/内容化 留存 用户流失 培养用户习惯 用户挽回 变现 变现方式 付费模式 广告模式 数据变现 变现指标 传播 营销 认识营销 电商营销中心 拼团活动 1.需求整理 2.…

JMeter请求导出Excel

前言 今天记录一个使用JMeter模拟浏览器请求后端导出&#xff0c;并下载Excel到指定位置的过程 创建请求 同样先创建一个线程组&#xff0c;再创建一个请求&#xff0c;设置好请求路径&#xff0c;端口号等 查看结果树 右键--添加--监听器--查看结果树 这里可以查看&#…

VUE之---slot插槽

什么是插槽 slot 【插槽】&#xff0c; 是 Vue 的内容分发机制&#xff0c; 组件内部的模板引擎使用slot 元素作为承载分发内容的出口。slot 是子组件的一个模板标签元素&#xff0c; 而这一个标签元素是否显示&#xff0c; 以及怎么显示是由父组件决定的。 VUE中slot【插槽】…