团结引擎——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定义优势具体说明心电图卷积层搭建 到目前为止,你使用的所有神经网络层都是密集层类型,这…

用快代理换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…

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

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

Spring篇----第十四篇

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

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

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

超详细的Python字典讲解

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

不是詹姆斯了?皮蓬改口称乔丹是他的GOAT

NBA直播吧-nba直播在线观看免费高清直播录像回放NBA直播吧是一个专业NBA直播在线观看免费直播平台&#xff0c;专业提供nba直播免费高清在线观看中文jrs无插件直播&#xff0c;NBA录像回放&#xff0c;CBA直播&#xff0c;WNBA以及篮球直播&#xff0c;足球直播五大联赛等最新直…

蓝桥杯-常用STL(三)

常用STL &#x1f388;1.映射&#x1f388;2.map的基础使用&#x1f52d;2.1引入库&#x1f52d;2.2构造一个映射&#x1f52d;2.3插入一对映射&#x1f52d;2.4判断关键字是否存在&#x1f52d;2.5遍历映射&#x1f52d;2.6清空 &#x1f388;1.映射 &#x1f50e;映射是指两个…

Pycharm环境中,python为变量赋值的时候,如何自动添加空格?

在python中&#xff0c;为变量赋值的时候&#xff0c;如果没有加入空格&#xff0c;代码底部会有灰色波浪线&#xff0c;说明不符合Python规范。 可以在菜单栏选择code&#xff0c;reformat code。重新格式化代码&#xff0c;所有代码会自动格式化。 快捷方式为&#xff1a;ct…

数据库JSON类型到映射JAVA上

Mysql存放JSON数据如何映射JAVA实体类 概述&#xff1a;最近写在写SKU模块中&#xff0c;需要表中字段存放JSON类型数据&#xff0c;mybatis-plus在查询的时候如何跟JSON类型所匹配呢&#xff1f;再次记录一下。 直接上代码&#xff0c;后面有解释到底如何映射上的。 Mysql表…

UV-K5

该方法是安装到python的安装目录了&#xff0c;建立项目的时候勾选 Inherit globel site-packages (使用全局的站点包) &#xff0c;就能调用安装路径的包文件环境。 cmd进入Dos窗口安装 pyserial 模块&#xff1a; pip install pyserial

阿里巴巴中国站获得公司档案信息 API 返回值说明

一、应用场景 阿里巴巴中国站获得公司档案信息 API接口的应用场景可以包括但不限于以下几种情况&#xff1a; 1、企业信息查询&#xff1a;API接口可用于查询企业的基本档案信息&#xff0c;如企业名称、注册地址、法定代表人、经营范围等。这些信息对于了解企业背景、信用状…

手机和windows的便签怎么共享账号使用

在忙碌的生活中&#xff0c;我经常需要在手机和电脑之间同步记事信息。可是&#xff0c;每次当我在手机上记下一些重要事项后&#xff0c;想要在电脑上查看或继续编辑时&#xff0c;总是遇到各种麻烦。因为手机和电脑上的便签软件各不相同&#xff0c;无法实现账号共享和内容同…

从0到1实现五子棋游戏!!

Hello&#xff0c;好久不见宝子们&#xff0c;今天来给大家更一个五子棋的程序~ 我们今天要讲的内容如下&#xff1a; 文章目录 1.五子棋游戏介绍1.1 游戏玩法介绍&#xff1a; 2.准备工作2.1 具体操作流程 3.游戏程序主函数4.初始化棋盘4.1.定义宏变量4.2 初始化棋盘 5.打印…

2024年2月最新微信域名检测拦截接口源码

这段PHP代码用于检测指定域名列表中的域名是否被封。代码首先定义了一个包含待检测域名的数组 $domainList&#xff0c;然后遍历该数组&#xff0c;对每个域名发送HTTP请求并检查响应内容以判断域名是否被封。 具体步骤如下&#xff1a; 1. 定义待检测的域名列表。 2. 遍历域名…

一、深度学习介绍

目录 1、深度学习与机器学习的区别 1.1 特征提取方面 1.2 数据量和计算性能要求 1.3 算法代表 2、深度学习应用场景 1、深度学习与机器学习的区别 1.1 特征提取方面 1.2 数据量和计算性能要求 1.3 算法代表 2、深度学习应用场景