Unity DOTS中的world

news2024/11/25 12:52:09

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_EDITOR
    if (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_WORLD
            Initialize("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_WORLD
    static 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_PROFILER
    EntitiesProfiler.Initialize();
#endif

#if (UNITY_EDITOR || DEVELOPMENT_BUILD) && !DISABLE_ENTITIES_JOURNALING
    EntitiesJournaling.Initialize();
#endif

    if (!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);
    else
        go.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 groups
    if (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.coloradmin.cn/o/1941698.html

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

相关文章

ssh到huawei交换机

import re from netmiko import ConnectHandlerips [ 10.32.5.130, 10.32.5.131, 10.32.5.132, 10.32.5.133, 10.32.5.134, 10.32.5.135, ]for ip in ips:hw_fw {device_type: huawei,host: ip, username: 493031,password: 密码,port: 22 }net_connect ConnectHandler(*…

力扣第二十五题——K个一组反转链表

内容介绍 给你链表的头节点 head &#xff0c;每 k 个节点一组进行翻转&#xff0c;请你返回修改后的链表。 k 是一个正整数&#xff0c;它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍&#xff0c;那么请将最后剩余的节点保持原有顺序。 你不能只是单纯的改变节点内…

centos/Ubuntu安装Nginx服务器

安装方式 使用系统自带的软件包管理器快速安装&#xff08;如centos的yum&#xff09;到官网下载压缩包安装&#xff08;https://nginx.org/en/download.html&#xff09;docker容器实例 下面是昨天以第二种方式安装的命令小记&#xff01; centos # 下载&#xff08;https…

Web开发:ASP.NET CORE使用Ajax定时获取后端数据

一、低难度&#xff08;刷新a标签&#xff09; 1、需求 给a标签每15s刷新一次&#xff0c;显示最新的时间&#xff08;时间必须由后端获取&#xff09; 应该如何操作呢 2、代码 后端 using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using Mi…

【专题】百度萝卜快跑体验:Robotaxi发展现状与展望报告合集PDF分享(附原数据表)

原文链接&#xff1a; https://tecdat.cn/?p37054 百度“萝卜快跑”近期因事故与抵制引发关注&#xff0c;武汉部署超300辆全无人驾驶车。体验显示其安全但策略保守&#xff0c;行驶效率低于人类司机&#xff0c;价格亲民。阅读原文&#xff0c;获取专题报告合集全文&#xf…

第2章-数学建模

目录 一、数据类型 【函数】&#xff1a; &#xff08;1&#xff09;find()、rfind()、index()、rindex()、count() &#xff08;2&#xff09;split()、rsplit() &#xff08;3&#xff09;join() &#xff08;4&#xff09;strip()、rstrip()、lstrip() &#xff08;5&…

物联网mqtt网关搭建背后的技术原理

前言 物联网是现在比较热门的软件领域&#xff0c;众多物联网厂商都有自己的物联网平台&#xff0c;而物联网平台其中一个核心的模块就是Mqtt网关。这篇文章的目的是手把手教大家写书写一个mqtt网关&#xff0c;后端存储支持Kafka/Pulsar&#xff0c;支持mqtt 连接、断链、发送…

GPT盘新增容量后如何扩容?

场景&#xff1a;一块5T的GPT盘&#xff0c;现有需求再加10T&#xff0c; 在虚拟化平台加10T盘后&#xff0c;机器不重启&#xff0c;执行命令 echo 1 > /sys/block/sdb/device/rescan刷新磁盘容量&#xff0c;可看到容量已刷出。 但执行fdisk /dev/sdb时&#xff0c;发现创…

css class 多种规则判断样式

需求 商品分类选择变量 上下的分类需要添加圆角 这个就设计到多个分类的判断 解决方法 在class里面多写些判断 判断上下的分类 做成圆角即可 代码 <!-- html --> <view v-for"(item,index) in tabbar" :key"index" class"u-tab-ite…

如何帮助自闭症儿童更好地控制面部表情

作为星贝育园自闭症康复学校的老师&#xff0c;帮助自闭症儿童更好地控制面部表情是我们工作中的一项重要任务。 在孩子进入星贝育园之前&#xff0c;我们会先对其进行个性化的观察报告。通过一段时间的仔细观察和评估&#xff0c;全面了解孩子的行为特点、能力优势以及…

[css3] 如何设置边框颜色渐变

div {border: 4px solid;border-image: linear-gradient(to right, #8f41e9, #578aef) 1; }参考&#xff1a; 5种CSS实现渐变色边框&#xff08;Gradient borders方法的汇总

linux/windows wps node.js插件对PPT状态监听并且通知其他应用

需求背景 公司要求对Window系统&#xff0c;和国产操作系统&#xff08;UOS&#xff09;的wps 软件在 PPT开始播放 结束播放&#xff0c;和播放中翻页 上一页 下一页 等状态进行监听&#xff0c;并通知到我们桌面应用。 技术方案 开发WPS插件&#xff0c;使用node.JS 插件开…

MongoDB教程(十七):MongoDB主键类型ObjectId

&#x1f49d;&#x1f49d;&#x1f49d;首先&#xff0c;欢迎各位来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里不仅可以有所收获&#xff0c;同时也能感受到一份轻松欢乐的氛围&#xff0c;祝你生活愉快&#xff01; 文章目录 引言一、Object…

共享充电桩语音ic方案,展现它的“说话”的能力

随着电动汽车的普及&#xff0c;充电设施的便捷性、智能化需求日益凸显&#xff0c;共享充电桩语音IC应运而生&#xff0c;成为连接人与机器、实现智能交互的桥梁。本文将为大家介绍共享充电桩语音ic的概述、应用词条以及优势&#xff0c;希望能够帮助您。 一、NV170D语音ic概述…

(laya)地图缩放保持视口点不变算法

文章目录 结论视口中心保持不变【未化简过程】&#xff08;代码选中部分为x方向的计算&#xff09;部分解析 结论 视口中心点保持不变 scl&#xff1a;新的缩放比 x&#xff1a;缩放为1时的偏移坐标 stHalfW&#xff1a;舞台半宽 视口左上角点保持不变 去掉 stHalfW即可 视…

探索NVM:让Node.js开发如虎添翼的利器

文章目录 前言一、NVM简介&#xff1a;版本管理的瑞士军刀二、NVM能解决什么问题&#xff1f;三、如何使用NVM​&#xff1f;总结 前言 在这个日新月异的编程世界里&#xff0c;Node.js凭借其高效的非阻塞I/O操作和轻量级的事件驱动模型&#xff0c;成为了全栈开发、微服务架构…

Linux之基础IO(上)

目录 库函数文件操作 写文件 读文件 系统调用文件操作 写文件 读文件 文件描述符fd 深刻理解linux下一切皆文件 重定向原理 在c语言中我们学习了fopen&#xff0c;fread&#xff0c;fwrite接口&#xff0c;用于进行文件相关的操作&#xff0c;在之前我们学习了计算…

站在资本投资领域如何看待分布式光纤传感行业?

近年来&#xff0c;资本投资领域对于分布式光纤传感行业并不十分敏感。这主要是由于分布式光纤传感技术是一个专业且小众的领域&#xff0c;其生命周期相对较长&#xff0c;缺乏爆发性&#xff0c;与消费品或商业模式创新产业有所不同。此外&#xff0c;国内的投资环境也是影响…

webStorm 实时模板笔记

文章目录 1、单斜杠效果 2、双斜杠效果 3、控制台打印效果 1、单斜杠 /** $END$ */效果 2、双斜杠 /*** $END$* author Ikun* since $DATE$ $TIME$ */DATE date() ✔ TIME time() ✔效果 3、控制台打印 console.log("███████$EXPR_COPY$>>>>&a…

040403数据选择器比较器运算电路

数据选择器&比较器&运算电路 4.4.3数据选择器0.数据选择器的定义与功能1.4选1数据选择器2.集成电路数据选择器利用8选1数据选择器组成函数产生器的一般步骤 4.4.4 数值比较器1.1位数值比较器2.2位数值比较器3.集成数值比较器4.集成数值比较器的位数扩展 4.4.5算术运算电…