前言
作者: 👨💻sonictk
https://sonictk.github.io/maya_hot_reload_example_public/
详细的说明 & 教程在上面的链接。
Maya 用写 C++ 开发会比较痛苦,一方面是编译问题总是让人烦躁,另一方面加载了 mll 会导致占用,测试起来很不方便。
所以我之前推崇用 Python OpenMaya 做原型设计再转 C++
当然 sonictk 也提到 Fabric Engine 和 Maya Bifrost 使用的时 LLVM IR 的方案来实现 JIT 编译。
具体可以参考另一个项目 giordi91/babycpp
LLVM 热加载
babycpp
基于 LLVM 的解决方案我编译没有通过,代码报类型错误,因此也没有测试成功。
不过也了解了 LLVM 是怎么实现热更新的,运行逻辑和 Python 有点像,但是从本质上不一样。
传统的编译器需要有 前端 优化器 后端组成,一般前端是语言,通过 tokenize 和 AST 等方案将语言解析然后通过优化器生成后端的二进制文件。
LLVM 推出了 LLVM IR 中间语言,这样不管前端用什么语言开发,只要有对应的解析工具生成出 LLVM IR ,j就可以利用 LLVM IR 的优化生成 二进制机器语言高效运行。
babycpp
项目就基于 LLVM IR 的机制开发了一个自己的简化版 C++ 语言,通过 LLVM IR JIT 编译动态改变运行逻辑。
我目前个人理解来看,LLVM IR 模式和 Python 模式还是不一样的,Python 是调用自己编译好的模块来运行的,而 LLVM IR 是直接运行时(JIT)生成机器语言,JIT模式的运行效率有时候比 C++ 的静态编译还要高,因为 JIT 可以根据运行过程推断程序下一步的执行来优化非必要的运行逻辑,所以 LLVM IR 的性能要比 Python 好得多。其实我后面了解了一下 numba 提速 Python 的原理就是利用 LLVM 标准实现的。
不过也正如 sonictk 的文章所提到的,这个方案只能调用暴露的东西,无法对内存的细节进行处理。
基于 dll 加载
https://github.com/FXTD-ODYSSEY/CMakeMaya/tree/master/projects/sonictk/hot_reload
如果使用作者提供的 github 仓库的代码编译会有问题,作者的 thirdparty 仓库编译不通过。
所以我后面是根据作者文章的代码稍微调整组装到一起实现的。
详细讲解之前,我先用最简单的话说明这个 hotreload 方案。
- 编译一个变形器的 mll 插件 和 带逻辑的 dll 文件
- mll 加载之后会调用 dll 的function进行计算
- 修改逻辑之后重新编译 dll
- mll 会重新健在最新的 dll 实现热更新。
实现思路
https://sonictk.github.io/maya_hot_reload_example_public/getting_started/
这篇文章非常好,不仅仅讲解了作者 hot reload 的思路,还附带了 windows lib dll 之间的运行逻辑等知识。
目录结构
代码结构上需要将插件分成两个部分,一个是调用 logic 生成 dll
另一个是 deformer 的代码生成 mll
具体编译配置通过 cmake 配置两个 project 实现。
1 | . |
dll 加载方案
上面三个函数调用了 window API 提供的
LoadLibrary
FreeLibrary
GetProcAddress
加载 dll
然后将分装到
loadDeformerLogicDLL
和unloadDeformerLogicDLL
方法里面。
deformer 在触发计算的时候调用加载 dll。
这样每次触发节点运算的时候会自动按照 dll 的路径进行加载。
问题是怎么在 C++ 动态获取到当前 dll 的路径呢?
在插件加载的时候通过
plugin.loadPath
可以拿到当前 mll 加载的路径。
只要在同一个路径找logic.dll
路径即可。
遇到的坑
编译 dll 占用问题
需要注意的是,mll 被 Maya 加载会产生占用,mll 去加载 dll 也会造成占用。
只有执行unloadDeformerLogicDLL
才会解除 dll 的占用
但是占用会造成编译失败。
于是我用 CMake 的 API 将旧的
logic.dll
改名叫logic_old.dll
windows 下被占用的文件还是可以改名的。
然后执行编译生成新的logic.dll
这时候需要手动触发 Maya 节点的更新,这样就会按照原来的路径加载新的 dll。
CMake 怎么判断 dll 是否占用,我也没有找到合适方法,于是我想到直接删除这个 dll 在判断 dll 是否存在的方法。
extern 问题
1 | static MString kPluginLogicLibraryPath; |
源码这两个变量用的是 static 静态变量。
但是不知道为什么在其他 cpp 文件里面调动得到的是不同的 内存 地址。
https://blog.csdn.net/sksukai/article/details/105612235
1 | extern MString kPluginLogicLibraryPath; |
后续是改成 extern
然后在plugin_main.cpp
里面初始化变量解决问题。
总结
这个方法切实解决了 节点热加载的问题,不需要
unloadPlugin
清空场景之类的操作,测试起来方便了许多。