团结引擎——DotNet Wasm方案

参考:团结引擎 DotNet WebAssembly(Wasm) 介绍

一、当前编译流程

  1. 通过IL2CPP将C#转成C/C++;
  2. 通过Emscripen将C/C++转成WebAssembly;

二、 当前存在问题

  • IL2CPP在处理类似泛型、反射结构时,由于缺少运行时信息,必须全量生成泛型模板代码,引起Wasm的运存进一步膨胀

三、一些解决方案

1. 基于IL2CPP的的分包处理和代码裁剪

  • 优势
    • 可减少单个Wasm文件的体积
  • 劣势
    • 分包和裁剪都依赖运行时信息,需要引入的其他信息具有不确定性和复杂性

2. 基于.Net 8的Blazor方案

用户的C#代码以Blazor的方式做运行时解释执行代码,引擎的代码保持Wasm的实现;

  • 优势
    • 将用户的C#代码与Wasm分离,极大减少Wasm文件的体积,显著减轻运行内存的压力
  • 劣势
    • 解析执行在运行时处理,会增加额外的CPU使用和延迟
    • 解析执行的代码,无法做代码优化(如删除冗余代码、重排指令顺序、内联函数等)
    • 解析执行需要做类型检查,也会增加CPU开销

四、DotNet Wasm方案

DotNet Wasm 方案以 .NET8 为基础,依赖于 Emscripten 工具链构建 WebAssembly,并且使用裁剪优化后的 mono 作为 .Net 运行时,充分利用引擎原本对 mono 的支持,使得用户几乎可以无感地接入使用。

五、DotNet Wasm整体流程

1. IL2CPP与DotNet Wasm的编译流程图

请添加图片描述

2. 构建流程

①DLL Compile and Strip

Compile C# to IL

使用 Roslyn 将用户的 C# 脚本编译为 IL,以 dll 文件形式参与后续构建流程;

输入:

  • 用户 C# 脚本(包含 Package 和自定义 .asmdef)

输出:

  • dll 文件(IL)
Strip Managed Code

使用 UnityLinker 扫描项目用到的 Dll 并作可选的代码剔除,使得生成的 Dll 更小;

输入:

  • 上一步编译出的 dll 文件
  • Unity Module 中的 dll 文件
  • Plugin 中的 dll 文件
  • .NET BCL

输出:

  • ManagedStripped dlls
  • UnityLinkerToEditorData.json
Generate icall and Register Unity Modules

根据 UnityLinker 的裁剪的结果,生成引擎部分的 Native 注册类,参与后续构建。注意因为项目区别此处生成的类数量也会有差异,对 Wasm 体积产生影响;

输入:

  • UnityLinkerToEditorData.json
  • Unity Modules

输出:

  • UnityClassRegistration.cpp
  • UnityICallRegistration.cpp

②.NET8 MSBuild

Scan for PInvoke and icall

扫描所有的ManagedStripped dll 文件,在 .NET BCL、引擎 Native 模块的函数生成wrapper function table,并生成两个头文件记录;

输入:

  • 所有的 ManagedStripped dll 文件

输出:

  • wrapper function table
  • pinvoke-table.h
  • icall-table.h
Compile Native Files

输入:

  • 上一步的wrapper function table
  • Generate icall and Register Unity Modules时生成的引擎Native注册类
  • Plugins目录中的C/CPP文件

输出:

  • Native Objects
Link and Compile

此步骤为生成 wasm 和 js framework 的核心步骤;

输入:

  • 上一步的Native Objects、Unity的静态链接依赖、.NET运行时依赖(对应产物dotnet.native.wasm)
  • Unity JS Framework、 .NET8 Runtime JS、浏览器基础功能包括 IndexedDB,OpenGL API,Audio,Sensor 等 JS 库(对应产物dotnet.native.js)
  • Dotnet MSBuild(对应产物dotnet.runtime.js)

输出:

  • dotnet.native.wasm(等同于IL2CPP的Webgl.wasm)
  • dotnet.native.js(等同于IL2CPP的Webgl.framework.js)
  • dotnet.runtime.js(等同于IL2CPP的Webgl.framework.js)
Convert dll to WebCIL

将一些DLL转化为WebCIL的wasm格式,便于运行时的JIT进行解释执行;

输入:

  • 用户代码程序集、引擎代码程序集以及 .NET 基础类库 (BCL)

输出:

  • WebCIL类型的wasm

3. 构建产物

请添加图片描述

4. 加载流程

请添加图片描述

①Load First Page

浏览器:

  1. 下载 index.html 和 loader.js
  2. 随后渲染 HTML 页面,执行 loader.js 中的获取 data 文件和 dotnet.js 文件的逻辑,等待下载完成后进行初始化或解析

WX Game:

  1. 下载loader.js
  2. 执行loader.js来下载data并初始化dotnet.js(webgl.wasm.framework.unityweb.js)

②Fetch data & dotnet.js

loader.js 会分别下载 data 文件和 dotnet.js 文件,下载 dotnet.js 之后会马上执行初始化函数,等待初始化结束之后才会执行 callMain 入口函数;

③Fetch blazor.boot.json

dotnet.js 初始化过程中,首先会加载 blazor.boot.json,其中包含了项目中依赖的文件清单与 Hash 值,根据此文件内容来确定加载文件的名称以及是否加载缓存

加载的文件:

  • dotnet.native.wasm文件:wasm 代码运行的核心文件
  • dotnet.native.xxxx.js 和 dotnet.runtime.xxxx.js:负责初始化 JS Module
  • WebCILs:从 dll 转化而来

④Async Download Resources

dotnet.js 初始化获取具体的文件清单后,会异步下载上述所有类型的文件;

其中:

  • dotnet.native.xxxx.js 和 dotnet.runtime.xxxx.js 不会从缓存加载;
  • 其它文件则会根据 hash 值进行判断,如果 hash 值发生变化或者本地缓存不存在才重新下载并且缓存文件,否则直接从缓存中加载;

⑤Initialize mono .NET runtime

dotnet.js 初始化在资源下载完成之后,会调用 dotnet.native.xxxx.js 和 dotnet.runtime.xxxx.js 相关函数初始化 JS Module;

  • dotnet.native.xxxx.js:包含Unity JS Framwrok、User Plugin JS 和 Browser Base Library
  • dotnet.runtime.xxxx.js:包含了 .NET Runtime JS

⑥Initialize Assemblies(WebCILs)

在 WebCIL 下载或者从缓存加载完成之后,dotnet.js 会把 WebCIL (以及其它可能存在的 symbol 和 pdb) 从 ArrayBuffer 转换为 Unit8Array,并且将其复制进 heap,最后 exports 出去留待运行时按需加载。

⑦ Instantiate dotnet.native.wasm

dotnet.native.wasm 作为核心的 wasm 文件,会在 .NET JS Runtime 初始化完成之后调用 WebAssembly.Instantiate 来进行实例化

⑧Export Engine Instance

此时引擎的 Instance 准备完毕,export 以供 loader.js 调用。

⑨Start Game

在上面所有的步骤都完成之后,回到 loader.js 会执行 Wasm 入口函数 callMain,正式进入游戏的启动流程。

⑩Load Assemblies (WebCILs)

在游戏启动后会根据需求加载此前已经在 heap 的 WebCIL,调用相关函数以完成游戏的加载和运行。

六、新旧两种Wasm方案性能情况

1. 性能对比

测试环境:

  • Code Optimization:Runtime Speed
  • 测试环境为 MacBook 32G M1 Pro
  • 使用 Instruments - Activity Monitor 记录运行时内存和 CPU 使用率
  • 使用 Chrome DevTool Performace 和 Memory 工具记录 Frame Time 和 Wasm Heap 大小
  • WebGL 检测 WebContent 基于 WebKit miniBrowser,iOS 检测 WebContent 基于 iOS17.2.1
  • 测试多轮,分别取采样区间中的最小值/中位数/最大值计入表中请添加图片描述

2. 构建时间对比

请添加图片描述

七、总结

官方总结:

  • 相比 IL2CPP,DotNet Wasm 方案对 Wasm Heap 基本没有影响
  • 相比 IL2CPP,DotNet Wasm 方案的 Frame Time 有轻微的增加,但在浏览器普遍的 60FPS 刷新频率的条件下并不会产生帧率差异
  • 相比 IL2CPP,DotNet Wasm 方案可以获得显著内存收益。并且随着用户脚本复杂度的提高,由于脚本不进入 Wasm 编译链路,相对 IL2CPP 的内存收益会越发明显
  • 相比 IL2CPP,DotNet Wasm 方案并不对 CPU 带来额外负担,并且部分测试用例中 CPU 负载与波动表现优于 IL2CPP
  • 相比 IL2CPP,DotNet Wasm 方案构建时间相对 IL2CPP 大幅降低,这可以让开发者快速进行开发迭代
  • WebCLI的形式将用户代码剥离出来,且不再合并构建,并在Wasm初始化后各自独立加载,天然支持代码热更新

部分新特性:

  • 除了GC Boehm,还支持mono的分代GC Sgen(Simple Generation GC),后者会将GC分散到帧中,在频繁小内存的分配释放场景中帧率更高且波动更小
  • 通过统计解释执行时命中函数的频率,将热点代码动态生成为 Wasm Module,让热点部分进入 Wasm 从而进一步提升执行效率;此外,也可以自己选择部分 DLL 参与 AOT 编译从而直接进入 Wasm,牺牲部分运存换取性能提升(源自 DotNet 8 的新概念,基于JIT)
  • 对于用户的脚本代码,可以在浏览器中直接调试 C#,Native C/C++ 以及 JavaScript,这种开箱即用的调试体验可以极大提升开发效率

个人总结:

  • 新方案摒弃了IL2CPP的方案,改用23年新推出的.NET Blazor方案
  • 新方案使用时间换空间,CPU耗时会增加,而Wasm运存会降低;
  • 新方案将用户代码、部分引擎Manager代码从构建中剥离出来,作为单独Wasm包存在,一定程度上便于热更新;但是这个Wasm包需要通过服务器下载来加载到运存中,暂时不清楚是放在自己的服务器还是官方引擎的服务器中(像微信小游戏的插件Wasm包就必须上传到微信的服务器并通过校验)
  • 新方案中的用户代码在调试模式下,能够在浏览器上直接调试,可能存在安全风险
  • 新方案的编译产物和现有IL2CPP的产物有很大差异,暂时不清楚微信小游戏SDK是否有对应的适配版本

八、看法与疑问

1. 看法

  1. 原有通过Emscripten编译生成的Wasm并在运行时加载执行的流程,类似于AOT;
  2. 而新增的Dotnet Wasm方案更像将部分代码以JIT形式执行,其他部分仍然保留AOT形式;

2. 存疑

Q:产物都是WASM格式文件,浏览器时如何识别并处理哪些可直接执行,哪些需要JIT解释执行?

Q:DotNet Wasm和IL2CPP的产物不同,WX Game SDK是否有对应的适配版本?

Q:该方案需要联网下载部分文件(如dotnet.native.xxxx.js 和 dotnet.runtime.xxxx.js),无网络连接的情况下如何处理?

Q:用户的脚本代码可以在浏览器直接调试,是否存在一定的安全风险?

九、知识延伸

1. Roslyn

微软开源的.NET编译器,支持将C#编译成中间代码IL

2. Unity Linker

特点:

  • 用于剥离托管代码
  • 基于Mono IL Linker的定制版本

执行流程:

  1. 分析项目中的所有程序集,首先标记顶级、根类型、方法、属性、字段等;
  2. 分析已标记为要进行识别的根,并标记这些根所依赖的托管代码;
  3. 完成此静态分析后,所有剩余的未标记代码都无法通过应用程序代码中的任何执行路径来访问,并将从程序集中删除;

3. Blazor WebAssembly

特点:

  • Blazor应用、其依赖项及.Net运行时并行下载到浏览器;
  • 应用将在浏览器线程中直接执行
  • .NET运行时包含.NET中间语言IL解释器,并支持JIT运行时

4. WebCIL

特点:

  • 是一种适用于 .NET 程序集的 Web 友好打包格式,旨在支持在限制性网络环境中使用 Blazor WebAssembly;
  • 文件格式为标准的.wasm的WebAssembly文件;
  • 可以将DLL 信息封装为符合 Wasm Binary Format 的容器格式;
  • 运行时会将 Payload 复制到 Wasm 内存中;

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

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

相关文章

程序员面试技巧分享

目录 前言1 自我介绍的艺术1.1 简明扼要1.2 强调独特之处1.3 项目亮点突显1.4 结合公司文化 2 技术问题回答的技巧2.1 明确问题理解2.2 结构清晰的回答2.3 强调解决问题的方法 3 团队协作经验的展示3.1 共享成功经验:3.2 强调沟通和解决冲突的能力: 结语…

基础!!!吴恩达deeplearning.ai:卷积层

以下内容有任何不理解可以翻看我之前的博客哦:吴恩达deeplearning.ai专栏 文章目录 回顾——密集层 Dense Layer卷积层 Convolutional Neural Network定义优势具体说明心电图卷积层搭建 到目前为止,你使用的所有神经网络层都是密集层类型,这…

HarmonyOS | 状态管理(六) | LocalStorage(页面级UI状态存储)

系列文章目录 1.HarmonyOS | 状态管理(一) | State装饰器 2.HarmonyOS | 状态管理(二) | Prop装饰器 3.HarmonyOS | 状态管理(三) | Link装饰器 4.HarmonyOS | 状态管理(四) | Provide和Consume装饰器 5.HarmonyOS | 状态管理(五) | Observed装饰器和ObjectLink装饰器 文章目录…

该类型的 CollectionView 不支持从调度程序线程以外的线程对其 SourceCollection 进行的更改。

报错原因 在异步的时候,调用了其他异步,导致UI工程线程该变了数据源,所以只需要将线程变为原始的UI线程。 解决方案 await QueuedTask.Run(() > { Application.Current.Dispatcher.Invoke(() >{报错的代码,…

红外测温仪芯片方案开发设计

红外测温仪由光学系统、光电探测器、信号放大器及信号处理、显示输出等部分组成。光学系统汇集其视场内的目标红外辐射能量,视场的大小由测温仪的光学零件以及位置决定。被测物体辐射的红外首先进入测温仪的光学系统,再由光学系统汇聚射入的红外线&#…

【探索AI】十 深度学习如何入门

深度学习概念 深度学习是一种机器学习的方法,通过构建和训练多层神经网络来解决复杂的模式识别和决策任务。以下是与深度学习相关的一些基本概念: 神经网络:神经网络是由大量的人工神经元(节点)组成的计算模型。它们通…

前端程序员如何使用GPT

前端程序员如何使用GPT GPT自述 GPT(Generative Pre-trained Transformer)是一种基于深度学习的自然语言处理模型,它在近年来取得了非常显著的成果。许多前端程序员也开始尝试使用GPT来提升工作效率和创造力。那么,前端程序员如何…

python学习笔记-内置类型

Python内置类型是Python编程语言中自带的基本数据类型,它们用于存储和处理数据。其中包括数字、序列、映射、类、实例和异常等主要类型。 在这些内置类型中,有一些是可变的,它们具有修改自身内容的能力,比如添加、移除或重排成员…

用快代理换Ip爬取boss直聘招聘信息

import requests import random from prettytable import PrettyTable tb PrettyTable() tb.field_names [区域,详情页链接,领导,经营领域,公司名,招聘人数,学历要求,工作经验要求,职位名称,期望薪资,技能要求,福利]headers {"User-Agent":"Mozilla/5.0 (Win…

跳跃游戏Ⅱ

问题 给定一个长度为 n 的 0 索引整数数组 nums。初始位置为 nums[0]。 每个元素 nums[i] 表示从索引 i 向前跳转的最大长度。换句话说&#xff0c;如果你在 nums[i] 处&#xff0c;你可以跳转到任意 nums[i j] 处: 0 < j < nums[i] i j < n 返回到达 nums[n - …

独立分体式比例阀控制器

比例阀放大器的主要作用是对比例阀进行控制&#xff0c;它产生所需的电信号&#xff0c;并对这些信号进行综合、比较、校正和放大。这样的设备通常包括稳压电源、颤振信号发生器等&#xff0c;以确保比例阀能够准确地响应控制指令。外置模块式的设计使得这种放大器可以方便地与…

windows系统下安装RabbitMQ

一、RabbitMQ安装软件资源准备 因为RabbitMQ是Erlang语言开发的&#xff0c;因此安装Erlang环境在进行安装RbbitMQ的操作&#xff0c;选择两者版本时一定要参考版本的兼容性 1.RabbitMQ国内下载地址&#xff0c;因官网下载比较缓慢&#xff0c;还是国内的稍微快些 https://r…

Kubernetes IoTDB系列 | IoTDB数据库同步|IoTDB数据库高可用

目录 一、介绍二、应用场景三、IoTDB 数据库搭建四、修改同步配置文件1、配置接收端的参数2、配置发送端的参数五、启动同步功能发送端六、测试一、介绍 IoTDB 数据库同步是指将一个节点的数据复制到其他节点,以确保数据的冗余和可用性。在分布式环境中,数据同步是保证系统高…

【日常聊聊】程序员的金三银四

&#x1f34e;个人博客&#xff1a;个人主页 &#x1f3c6;个人专栏&#xff1a;日常聊聊 ⛳️ 功不唐捐&#xff0c;玉汝于成 目录 前言 正文 方向一&#xff1a;面试技巧分享 方向二&#xff1a;面试题解析 方向三&#xff1a;公司文化解读 方向四&#xff1a;职业规…

【Vision Pro 应用】Spatialty Coffee:探索咖啡制作的全新维度

Spatialty Coffee,一个引领咖啡文化潮流的应用程序,为用户提供了一个全新的平台,用以浏览和发现最佳的滤过咖啡配方。这款应用不仅满足了咖啡爱好者对于高品质咖啡的追求,同时也为那些希望提升咖啡制作技巧的人提供了有力的支持。 1.广泛支持的滤过咖啡制作器 Spatialty …

Spring篇----第十四篇

系列文章目录 文章目录 系列文章目录前言一、介绍一下 WebApplicationContext二、什么是 spring?三、使用 Spring 框架的好处是什么?四、Spring 由哪些模块组成?前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站,…

数据结构:链队

一、定义两个结构体 定义两个结构体,一个结构体是结点的结构体&#xff0c;一个结构体是保留指向对头结点和队尾结点指针的结构体 #ifndef __LINK_QUEUE_H__ #define __LINK_QUEUE_H__ #include <stdio.h> #include <stdlib.h>typedef struct link_node{int data…

android studio Unable to download file ‘xxx‘ in offline mode.

如果网络连接正常&#xff0c;但是报这个错&#xff0c;说明开启了离线模式&#xff0c;关掉就行了。 "toggle offline mode" 意思是切换到离线模式 点击右上角的Gradle&#xff0c;然后关闭倒数第二个图标。

利用NSKeyedUnarchiver把数据存储到本地

在开发中&#xff0c;如果使用plist存贮到本地的话&#xff0c;存贮的类型有限&#xff0c;这时候&#xff0c;我们可以就可以考虑利用NSKeyedUnarchiver把数据存储到本地&#xff0c;通过这个方法我们可以将model存贮到本地&#xff0c;一般用来保存用户的账号信息之类的 第一…

超详细的Python字典讲解

一、概念 1.定义 字典是另一种可变容器模型&#xff0c;且可存储任意类型对象。是可变数据类型 字典类型的创建 1.字典的每个键值key>value对用冒号:分隔&#xff0c;每个键值对之间用逗号,分割&#xff0c;整个字典包括在花括号{}中 d {key1:value1,key:value} 2.使…