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.xdnf.cn/news/1486026.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

[AWS]MSK调用,报错Access denied

背景&#xff1a;首先MSK就是配置一个AWS的托管 kafka&#xff0c;创建完成之后就交给开发进行使用&#xff0c;开发通常是从代码中&#xff0c;编写AWS的access_key 和secret_key进行调用。 但是开发在进行调用的时候&#xff0c;一直报错连接失败&#xff0c;其实问题很简单&…

【机器学习】机器学习之计算学习理论--评估机器学习能够学到什么程度

引言 计算学习理论&#xff08;Computational Learning Theory&#xff0c;CLT&#xff09;是机器学习的一个分支&#xff0c;它使用数学工具来分析和理解机器学习算法的效率和可能性 计算学习理论主要关注三个核心问题&#xff1a;学习模型的表示、学习算法的效率和学习的泛化…

Matlab画不同指标的对比图

目录 一、指标名字可修改 二、模型名字可修改 三、输入数据可修改 软件用的是Matlab R2024a。 clear,clc,close all figure1figure(1); % set(figure1,Position,[300,100,800,600],Color,[1 1 1]) axes1 axes(Parent,figure1);%% Initialize data points 一、指标名字可修…

Astro 4.12 发布,新增支持服务器岛屿

近日&#xff0c;Astro 发布了最新的 4.12 版本&#xff0c;此版本包含 Server Islands&#xff08;服务器岛屿&#xff09;&#xff0c;这是 Astro 将高性能静态 HTML 和动态服务器生成的组件集成在一起的新解决方案&#xff0c;此版本还包括对分页和语法突出显示的改进。 要…

如何检查我的网站是否支持HTTPS

HTTPS是一种用于安全通信的协议&#xff0c;是HTTP的安全版本。HTTPS的主要作用在于为互联网上的数据传输提供安全性和隐私保护。通常是需要在网站安装部署SSL证书来实现网络数据加密传输&#xff0c;安全加密功能。 那么如果要检查你的网站是否支持HTTPS&#xff0c;可以看下…

C#基于SkiaSharp实现印章管理(4)

前几篇文章实现了绘制不同外形印章的功能&#xff0c;印章内部一般包含圆形、线条等形状&#xff0c;有些印章内部还有五角星&#xff0c;然后就是各种样式的文字。本文实现在印章内部绘制圆形、线条、矩形、椭圆等四种形状。   定义FigureType枚举记录印章内部形状&#xff…

数据结构——堆(C语言版)

树 树的概念&#xff1a; 树&#xff08;Tree&#xff09;是一种抽象数据结构&#xff0c;它由节点&#xff08;node&#xff09;的集合组成&#xff0c;这些节点通过边相连&#xff0c;把 节点集合按照逻辑顺序抽象成图像&#xff0c;看起来就像一个倒挂着的树&#xff0c;也…

react入门到实战-day1

这react门课我是学习b站黑马的课程&#xff0c;不是打公告哈&#xff0c;我只是过一遍&#xff0c;让自己对学过的知识有印象&#xff0c;所以笔记是有很大部分直接复制总结过来的&#xff0c;方便后面的我进行复习。如有冒犯&#xff0c;联系必删 React介绍以及创建方式 React…

基于FPGA的以太网设计(2)----以太网的硬件架构(MAC+PHY)

1、概述 以太网的电路架构一般由MAC、PHY、变压器、RJ45和传输介质组成,示意图如下所示: 需要注意的是,上图是一个简化了的模型,它描述的是两台主机之间的直接连接,但在实际应用中基本都是多台主机构成的局域网,它们之间并不直接相连,而是通过交换机Switch来进行…

JAVA开发工具IDEA如何连接操作数据库

一、下载驱动 下载地址&#xff1a;【免费】mysql-connector-j-8.2.0.jar资源-CSDN文库 二、导入驱动 鼠标右击下载到IDEA中的jar包&#xff0c;选择Add as Library选项 如图就导入成功 三、加载驱动 Class.forName("com.mysql.cj.jdbc.Driver"); 四、驱动管理…

FPGA开发在verilog中关于阻塞和非阻塞赋值的区别

一、概念 阻塞赋值&#xff1a;阻塞赋值的赋值号用“”表示&#xff0c;对应的是串行执行。 对应的电路结构往往与触发沿没有关系&#xff0c;只与输入电平的变化有关系。阻塞赋值的操作可以认为是只有一个步骤的操作&#xff0c;即计算赋值号右边的语句并更新赋值号左边的语句…

软件缺陷(Bug)、禅道

目录 软件缺陷的判定标准 软件缺陷的核心内容 构成缺陷的基本要素 缺陷报告 缺陷管理 缺陷的跟踪流程 项目管理工具--禅道 软件在使用过程中存在的任何问题&#xff08;如&#xff1a;错误、异常等&#xff09;&#xff0c;都叫软件的缺陷&#xff0c;简称bug。 软件缺…

学习记录--Bert、Albert、RoBerta

目录 Bert 1&#xff1a;输入 2&#xff1a;Bert结构 3&#xff1a;模型预训练 Albert 1&#xff1a;SOP任务 2&#xff1a;embedding因式分解 3&#xff1a;参数共享 RoBerta 参考&#xff1a; BERT原理和结构详解_bert结构-CSDN博客 [LLM] 自然语言处理 --- ALBER…

鸿蒙华为登录(以及导航页面跳转)

//登录华为登录界面以及跳转 //切记一定要写路径&#xff0c;不写路径&#xff0c;容易报错&#xff0c;还有一定要记得导一下包&#xff08;Arouter&#xff09; //接下来是鸿蒙界面导航跳转 //进行跳转 TabContent组件不支持设置通用宽度属性&#xff0c;其宽度默认撑满Tab…

在spyder中使用arcgis pro的包

历时2天终于搞定了 目标&#xff1a;在anconda中新建一个arcpyPro环境&#xff0c;配置arcgispro3.0中的arcpy 一、安装arcgispro3.0 如果安装完之后打开arcgispro3.0闪退&#xff0c;就去修改注册表&#xff08;在另一台电脑安装arcgispro遇到过&#xff09; 安装成功后可…

MySQL聚合函数(DQL)

先看一下我的表内容和数据&#xff0c;再做接下来的例子和讲解 1.聚合函数的基本语法 SELECT 聚合函数&#xff08;表中的某个字段&#xff09;FROM 表名; 2. 常见的聚合函数 举例 1.统计该企业的数量 select count(idcard) from emp; 2.统计该企业员工的平均年龄 select…

Mindspore框架循环神经网络RNN模型实现情感分类|(二)RNN模型构建

Mindspore框架循环神经网络RNN模型实现情感分类 Mindspore框架循环神经网络RNN模型实现情感分类|&#xff08;一&#xff09;IMDB影评数据集准备 Mindspore框架循环神经网络RNN模型实现情感分类|&#xff08;二&#xff09;RNN模型构建 Mindspore框架循环神经网络RNN模型实现情…

【C++】详解 set | multiset

目录 1.集合类 set 0.引入 1.set 介绍 1. 构造 2.Insert 插入 3.find 查找 4.count 判断是否在 5.erase 删除 6.lower_bound 和 upper_bound 区间查找 拓展&#xff1a;lower_bound 函数底层实现 equal_range 值区间 2.multiset 类 0.引入&#xff1a;不去重的 se…

Xlua原理 二

一已经介绍了初步的lua与C#通信的原理&#xff0c;和xlua的LuaEnv的初始化内容。 这边介绍下Wrap文件。 一.Wrap介绍 导入xlua后可以看到会多出上图菜单。 点击后生成一堆wrap文件&#xff0c;这些文件是lua调用C#时进行映射查找用的中间代码。这样就不需要去反射调用节约性…

Anaconda下安装配置Jupyter

Anaconda下安装配置Jupyter 1、安装 conda activate my_env #激活虚拟环境 pip install jupyter #安装 jupyter notebook --generate-config #生成配置文件提示配置文件的位置&#xff1a; Writing default config to: /root/.jupyter/jupyter_notebook_config.py检查版本&am…