前言

作者: 👨‍💻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 方案。

  1. 编译一个变形器的 mll 插件 和 带逻辑的 dll 文件
  2. mll 加载之后会调用 dll 的function进行计算
  3. 修改逻辑之后重新编译 dll
  4. mll 会重新健在最新的 dll 实现热更新。

实现思路

https://sonictk.github.io/maya_hot_reload_example_public/getting_started/

  这篇文章非常好,不仅仅讲解了作者 hot reload 的思路,还附带了 windows lib dll 之间的运行逻辑等知识。

目录结构

image

  代码结构上需要将插件分成两个部分,一个是调用 logic 生成 dll
  另一个是 deformer 的代码生成 mll
  具体编译配置通过 cmake 配置两个 project 实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
.
├── logic
│ ├── logic.cpp dll 代码逻辑
│ └── logic.h
├── maya_deformer
│ ├── deformer_platform.cpp 调用 <windows.h> API 加载 dll
│ ├── deformer_platform.h
│ ├── deformer.cpp Maya 变形器 deform 调用 deform_platform 提供的方法
│ ├── deformer.h
│ ├── plugin_main.cpp Maya mll 插件初始化函数
│ └── plugin_main.h
├── scripts
│ └── test_deformer.py 测试插件是否修改
├── CMakeLists.txt
└── readme.md

dll 加载方案

image

  上面三个函数调用了 window API 提供的 LoadLibrary FreeLibrary GetProcAddress 加载 dll

image

  然后将分装到 loadDeformerLogicDLLunloadDeformerLogicDLL 方法里面。
  deformer 在触发计算的时候调用加载 dll。

image

  这样每次触发节点运算的时候会自动按照 dll 的路径进行加载。

  问题是怎么在 C++ 动态获取到当前 dll 的路径呢?

image

  在插件加载的时候通过 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
2
static MString kPluginLogicLibraryPath;
static DeformerLogicLibrary kLogicLibrary;

  源码这两个变量用的是 static 静态变量。
  但是不知道为什么在其他 cpp 文件里面调动得到的是不同的 内存 地址。

https://blog.csdn.net/sksukai/article/details/105612235

1
2
extern MString kPluginLogicLibraryPath;
extern DeformerLogicLibrary kLogicLibrary;

  后续是改成 extern
  然后在 plugin_main.cpp 里面初始化变量解决问题。

总结

  这个方法切实解决了 节点热加载的问题,不需要 unloadPlugin 清空场景之类的操作,测试起来方便了许多。