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.mzph.cn/pingmian/50089.shtml

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

相关文章

[Spring] MyBatis操作数据库(基础)

&#x1f338;个人主页:https://blog.csdn.net/2301_80050796?spm1000.2115.3001.5343 &#x1f3f5;️热门专栏: &#x1f9ca; Java基本语法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12615970.html?spm1001.2014.3001.5482 &#x1f355; Collection与…

Python酷库之旅-第三方库Pandas(045)

目录 一、用法精讲 156、pandas.Series.count方法 156-1、语法 156-2、参数 156-3、功能 156-4、返回值 156-5、说明 156-6、用法 156-6-1、数据准备 156-6-2、代码示例 156-6-3、结果输出 157、pandas.Series.cov方法 157-1、语法 157-2、参数 157-3、功能 15…

分布式系统常见软件架构模式

常见的分布式软件架构 Peer-to-Peer (P2P) PatternAPI Gateway PatternPub-Sub (Publish-Subscribe)Request-Response PatternEvent Sourcing PatternETL (Extract, Transform, Load) PatternBatching PatternStreaming Processing PatternOrchestration Pattern总结 先上个图&…

.h264 .h265 压缩率的直观感受

1.资源文件 https://download.csdn.net/download/twicave/89579327 上面是.264 .265和原始的YUV420文件&#xff0c;各自的大小。 2.转换工具&#xff1a; 2.1 .h264 .h265互转 可以使用ffmpeg工具&#xff1a;Builds - CODEX FFMPEG gyan.dev 命令行参数&#xff1a; …

liteos定时器回调时间过长造成死机问题解决思路

项目需求 原代码是稳定的&#xff0c;现我实现EMQ平台断开连接的时候&#xff0c;把HSL的模拟点位数据采集到网关&#xff0c;然后存入Flash&#xff0c;当EMQ平台连接的时候&#xff0c;把Flash里面的点位数据放在消息队列里面&#xff0c;不影响实时采集。 核心1&#xff1a…

godot新建项目及设置外部编辑器为vscode

一、新建项目 初次打开界面如下所示&#xff0c;点击取消按钮先关闭掉默认弹出的框 点击①新建弹出中间的弹窗②中填入项目的名称 ③中设置项目的存储路径&#xff0c;点击箭头所指浏览按钮&#xff0c;会弹出如下所示窗口 根据图中所示可以选择或新建自己的游戏存储路径&…

鸿蒙(HarmonyOS)自定义Dialog实现时间选择控件

一、操作环境 操作系统: Windows 11 专业版、IDE:DevEco Studio 3.1.1 Release、SDK:HarmonyOS 3.1.0&#xff08;API 9&#xff09; 二、效果图 三、代码 SelectedDateDialog.ets文件/*** 时间选择*/ CustomDialog export struct SelectedDateDialog {State selectedDate:…

Linux系统上安装Redis

百度网盘&#xff1a; 通过网盘分享的文件&#xff1a;redis_linux 链接: https://pan.baidu.com/s/1ZcECygWA15pQWCuiVdjCtg?pwd8888 提取码: 8888 1.把安装包拖拽到/ruanjian/redis/文件夹中&#xff08;自己选择&#xff09; 2.进入压缩包所在文件夹&#xff0c;解压压缩…

ROM修改进阶教程------修改rom 开机自动安装指定apk 自启脚本完整步骤解析

rom修改的初期认识 在解包修改系统分区过程中。很多客户需求刷完rom后自动安装指定apk。这种与内置apk有区别。而且一些极个别apk无法内置。今天对这种修改rom刷入机型后第一次启动后自动安装指定apk的需求做个步骤解析。 在前期博文中我有做过说明。官方系统固件解…

按图搜索新体验:阿里巴巴拍立淘API返回值详解

阿里巴巴拍立淘API是一项基于图片搜索的商品搜索服务&#xff0c;它允许用户通过上传商品图片&#xff0c;系统自动识别图片中的商品信息&#xff0c;并返回与之相关的搜索结果。以下是对阿里巴巴拍立淘API返回值的详细解析&#xff1a; 一、主要返回值内容 商品信息 商品列表…

Java面试题(每日更新)

每日五道&#xff01;学会就去面试&#xff01; 本文的宗旨是为读者朋友们整理一份详实而又权威的面试清单&#xff0c;下面一起进入主题吧。 目录 1.概述 2.Java 基础 2.1 JDK 和 JRE 有什么区别&#xff1f; 2.2 和 equals 的区别是什么&#xff1f; 2.3 两个对象的…

ECharts实现按月统计和MTBF统计

一、数据准备 下表是小明最近一年的旅游记录 create_datecity_namecost_money2023-10-10 10:10:10北京14992023-11-11 11:11:11上海29992023-12-12 12:12:12上海19992024-01-24 12:12:12北京1232024-01-24 12:12:12上海2232024-02-24 12:12:12广州5642024-02-24 12:12:12北京…

leetcode-148. 排序链表

题目描述 给你链表的头结点 head &#xff0c;请将其按 升序 排列并返回 排序后的链表 。 示例 1&#xff1a; 输入&#xff1a;head [4,2,1,3] 输出&#xff1a;[1,2,3,4]示例 2&#xff1a; 输入&#xff1a;head [-1,5,3,4,0] 输出&#xff1a;[-1,0,3,4,5]示例 3&#x…

react18+

主要是围绕函数式组件讲&#xff0c;18主要用就是函数式组件&#xff0c;学习前先熟悉下原生js的基本使用&#xff0c;主要是事件 1、UI操作 1.1、书写jsx标签语言 基本写法和原生如同一则&#xff0c;只是放在一个方法里面返回而已&#xff0c;我们称这样的写法为函数式组件…

OrangePi Zero2 全志H616 开发初探

目录&#xff1a; 一、刷机和系统启动1、TF 卡格式化&#xff1a;2、镜像烧录&#xff1a;3、登录系统&#xff1a; 二、基于官方外设开发1、wiringPi 外设 SDK 安装&#xff1a;2、C 文件编译&#xff1a;3、基于官方外设的应用开发&#xff1a;① GPIO 输入输出&#xff1a;②…

【个人亲试最新】WSL2中的Ubuntu 22.04安装Docker

文章目录 Wsl2中的Ubuntu22.04安装Docker其他问题wsl中执行Ubuntu 报错&#xff1a;System has not been booted with systemd as init system (PID 1). Can‘t operate. 参考博客 &#x1f60a;点此到文末惊喜↩︎ Wsl2中的Ubuntu22.04安装Docker 确定为wsl2ubuntu22.04&#…

vue接入google map自定义marker教程

需求背景 由于客户需求&#xff0c;原来系统接入的高德地图&#xff0c;他们不接受&#xff0c;需要换成google地图。然后就各种百度&#xff0c;各种Google&#xff0c;却不能实现。----无语&#xff0c;就连google地图官方的api也是一坨S-H-I。所以才出现这篇文章。 google地…

【Python实战因果推断】56_因果推理概论6

目录 Causal Quantities: An Example Bias Causal Quantities: An Example 让我们看看在我们的商业问题中&#xff0c;你如何定义这些量。首先&#xff0c;你要注意到&#xff0c;你永远无法知道价格削减&#xff08;即促销活动&#xff09;对某个特定商家的确切影响&#xf…

AMEsim液压阀伯德图绘制方法

之前也在液压圈论坛里面发过类似的贴子&#xff0c;具体可以看这个网址&#x1f6aa;&#x1f449;&#xff1a;如何得出说明书里面的伯德图曲线&#xff1f;&#xff0c;回复的人还是比较少&#xff0c;这个方法重要信息是参考百度文库这篇文章&#x1f6aa;&#x1f449;&…

【系统架构设计师】计算机组成与体系结构 ⑯ ( 奇偶校验码 | CRC 循环冗余码 | 海明码 | 模 2 除法 )

文章目录 一、校验码1、校验码由来2、奇偶校验码3、CRC 循环冗余码 ( 重点考点 )4、海明码校验 ( 软考不经常考到 ) 二、CRC 循环冗余码 ( 重点考点 )1、模 2 除法概念2、模 2 除法步骤3、模 2 除法示例4、CRC 循环冗余码示例 15、CRC 循环冗余码示例 2 参考之前的博客 : 【计…