前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >UE引擎里头跑个nodejs服务器是怎样一种体验?

UE引擎里头跑个nodejs服务器是怎样一种体验?

作者头像
车雄生
发布2021-11-10 14:36:09
1.2K0
发布2021-11-10 14:36:09
举报
文章被收录于专栏:咩嗒咩嗒

源起

puerts群上问得最多的一个问题是:为什么npm下载的有些库跑不起来。

不像python、lua、java等语言有个专门的、独立的可执行程序,js虚拟机更多的时候是嵌入到某个宿主里头,比如浏览器、nodejs。js虚拟机实现了某个js标准(比如es5、es6),宿主能力也会通过一些api导出给js使用,比如浏览器的dom操作,nodejs的异步io等。

而puerts则是js虚拟机的另外一个宿主(游戏引擎),向js虚拟机导出的完整的游戏引擎能力。

了解到这些,问题就很好答了:如果仅仅用到某个es规范的js库,它在这些环境可以通用,但如果用到了宿主提供的api则是专用的。

不能用的原因知道了,但禁不住还是想用怎么办?

可行性分析

最容易想到是模拟:你使用的库依赖了哪些原环境的api,新环境实现即可。事实上也有一些尝试在一个环境模拟另一环境的第三方支持。

这方案显而易见工作量大,也很难保证和原api完全一致。

能不能干脆嵌入个nodejs到UE呢?答案是肯定的。可以看笔者之前写的这篇文章《c++游戏服务器嵌入v8 js引擎胎教级教程》 ,里面介绍了怎么在C++程序里头嵌入nodejs,UE也是C++程序,自然也适用。

官方嵌入例子主要做了两个事情:

  1. v8、nodejs的初始化工作;
  2. libuv事件循环驱动;

完成了上述两个工作nodejs就能在宿主程序里跑起来。当然,如果UE和nodejs各玩各的话也没啥意义,所以要实用化,还要加上第三点

  1. 和引擎的互相访问;

对于1,没什么难度,照着官方例子写即可;对于3,puerts已经实现了完善的v8和UE互相访问机制,nodejs也是基于v8,自然可以无缝使用该机制。所以重点是2的实现。

官方的例子是在主线程直接循环等待并处理libuv事件,如果我们也在UE的GameThread这么干会将导致整个界面卡住,行不通。

另开一个线程去调用uv_run?也不行,uv_run在有事件时,需要调用js回调,v8不支持多线程访问,而且多线程也不符合js的语义。

初始方案

通过UE定时器去调用uv_run。实测功能都正常,只是异步io处理很慢。调用http模块下载一个72.6M的文件,耗时197秒,而nodejs程序不到1秒。

无论把定时器间隔改多小也没什么改善,看UE代码才知道原因:UE定时器最小精度是一帧,一帧才执行一次uv_run,难怪那么慢。

即使找到比定时器更频繁的GameThread轮询方式,占用了GameThread大量时间也不合适,似乎进入了死胡同。

从197秒到6秒

另一个用到nodejs嵌入的是Electron,它会有同样的烦恼么?

终于,找到了Electron创始人zcbenz的这篇文章:《Electron Internals: Message Loop Integration》 ,这是它的中文翻译 。结合文章和代码得知它也需要解决类似的问题,它的解决思路也完全使用于UE引擎。

它的解决思路是:既然问题的根源在于uv_run把io事件等待以及js回调调用绑定在一起,那把他们拆开好了:

  • 启动一个poll线程绕过libuv的api,直接系统调用(window下用IOCP,linux下用epoll,mac下用select)等待libuv的事件
  • poll线程等到事件,则通知主线程去调用uv_run,此时已经有事件,主线程会直接调用js回调,无需等待。

可以看下puerts的最终修改

关键函数的说明:

  • PollEvents:Polling线程的逻辑,调用各平台的异步io处理api去阻塞等待,如果有事件,则调用TaskGraph,让GameThread去执行uv_run,并通过信号量等待GameThread完成。
  • UvRunOnce:GameThread任务的主要逻辑,简单的调用uv_run后,通过信号量通知Polling线程继续Polling。

这么一改,下载时间大大改善,但由于Task的执行也有延时,和nodejs还是有差距,最终测试结果在6秒左右。

试一试?

让我们呼应下标题,在UE下启动个典型的nodejs应用试试?

  • clone这个项目:puerts_unreal_demo
  • 后端引擎切换为nodejs
    • 下载nodejs库 ,并解压到puerts_unreal_demo\Plugins\Puerts\ThirdParty目录下
    • 打开puerts_unreal_demo\Plugins\Puerts\Source\JsEnv\JsEnv.Build.cs文件,把UseNodejs改为true
  • 修改QuickStart.ts为如下内容并重新编译ts工程
代码语言:javascript
复制
const PORT = 8081

var http = require('http');

http.createServer(function (request, response) {
    response.writeHead(200, {'Content-Type': 'text/plain'});

    response.end('Hello World\n');
}).listen(PORT);

console.log(`Server running at http://127.0.0.1:${PORT}/`);

应用场景

UE编辑器插件编写,这是我们最推荐的场景,利用nodejs丰富的组件快速的开发插件,而比起官方的python,用typescript开发能改善插件代码的可维护性。

运行时由于我们的nodejs后端尚未支持手机平台,不太建议,如果游戏只发pc平台,可以尝试使用。

小结

  • 介绍了UE下嵌入nodejs怎么处理nodejs的事件循环,其它有自己主循环的应用也可以参考这个思路
  • 通过本文可以得知UE下nodejs编程的一个可选方案
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 源起
  • 可行性分析
  • 初始方案
  • 从197秒到6秒
  • 试一试?
  • 应用场景
  • 小结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档