[译]Godot 引擎 GDNative 架构初探

GDNative的架构从最早叫“DLScript”的时候到目前为止已经发生了很大的变化。随着Godot 3.0版本接近最终发布以及API越来越稳定,是时候对GDNative目前的形态作一个概述了。

GDNATIVELIBRARY

GDNativeLibrary是一种资源类型。它是对每种平台所需的实际二进制文件的一种抽象:包含一些属性、“入口”库加载路径清单及“入口”库所依赖库的清单。

这些清单是一套功能特性标记的简单映射形式 - 一般是一个文件路径;如果有依赖关系的话,就是一组路径。

特性标记

Godot有一套特性标记系统。特性标记表示拥有对应的特定的属性或功能,例如Windows, X11, 32, 64, mobile等等。在导出游戏时,你也可以自行定义标记,从而可能改变游戏的运行方式。

更多关于特性标记的信息,可以去http://docs.godotengine.org/en/latest/learning/workflow/export/feature_tags.html查看。

GDNativeLibrary资源中的列表由键值对形式组成,键中根据需要可以包含多个特性标记,以英文句点“.”分隔。

例如一个支持64位Linux机器的库,它的键名即“X11.64”,如果对应的是Windows的机器,则键名为“Windows.64”。

Godot编辑器提供了GUI来更人性化的进行这种资源的定义和编辑。

[译]Godot 引擎  GDNative 架构初探

它会从上而下的对所有入口进行检测,并跳过那些不存在的特性标记。在所有可用的标记中,第一个会被用作入口,所以排序很重要。

SINGLETON 库

GDNativeLibrary中有一个属性是用于定义其是否支持单例形式使用的。单例库会在Godot启动期间尽可能早地载入,且会调用库中的gdnative_singleton函数。这种库常用于需要提供与Godot紧密结合的功能。

GDNATIVE

GDNative对象代表所载入的库,至于具体要加载哪个库就要从GDNativeLibrary资源文件中匹配了,Godot环境下的C++代码可以去调用该库中的函数。由于这种方式去调用函数太过灵活、底层且不安全,所以是不建议从GDScript这些脚本语言中去调的。

如果真想从脚本语言环境直接调用相应功能,可以用GDNative.call_native方法来满足需求。对于这种函数指针调用的底层细节,抽象出了一种所谓的“调用类型”来进行描述。目前仅有一种预定义调用类型:standard_varcall - 要求被调用的函数签名为 godot_variant function_name(godot_array *)。单例库可以按需注册新的调用类型。

GDNATIVE/GODOT API

如果某个库想调用Godot的一些功能,它就需要去调用Godot的代码。而各种C++编译器之间的移植性非常有问题,所以我们选择用C语言API的形式来封装对C++的调用。这开启了多种语言访问API的可能性,但也带来了一些冗余性。

API 结构

一个库为了访问那些用C封装的函数,它首先要知道那些函数的位置。最直接的想法是留空,然后让操作系统的库加载机制来处理。

不幸的是,这种方式不能在所有平台正常运作(此处Windows可能要尴尬的咳两声),所以为了保证在所有平台设置GDNative库用同样的代码和步骤,我们决定采取另一种途径:在加载函数时,以函数指针结构(struct)的形式传递。

该结构存在于Godot中,并包含版本信息、将来的API改动字段及扩展API列表。

struct godot_gdnative_api_struct {
    unsigned int type;
    godot_gdnative_api_version version;
    const godot_gdnative_api_struct *next;
};

struct godot_gdnative_core_api_struct {
    unsigned int type;
    godot_gdnative_api_version version;
    const godot_gdnative_api_struct *next;
    unsigned int num_extensions;
const godot_gdnative_api_struct **extensions;
    // ...
};

库可以从这种struct中访问所需的函数,也就意味着不再是编写 godot_some_function();这种形式了,而是api->godot_some_function();

有些人喜欢简单的通过函数名而不是struct来访问函数,所以在有需要时,Godot的构建系统会生成一个静态库,来包裹所有的同名函数指针为静态函数。

扩展

GDNative 扩展是一种给库提供GDNative/Godot API 之外功能的方式。它们可以不同方式应用,下面会列出几种当前支持的形式的扩展。

扩展通常带有C语言API,可能还伴随着有自定义数据类型。Godot里通常有用于包裹那些和其它功能密切结合的C函数的C++类/方法。

每个扩展都有它自己的子API结构,其中包含了版本信息及未来API修改信息的字段。

ARVR

采用GDNative来实现一种VR驱动的所有API可以参考文档: file。

这套API的起点是 godot_arvr_register_interface 函数,它需要从一个单例库进行调用。那些要被Godot调用的函数则组织成一个结构以参数的形式传递过去。

目前有 null-driver 的实现、 OpenVR 的实现 和 WIP OpenHMD 的实现。

NATIVESCRIPT

GDNative的早期开发生涯里,它仅被计划用于脚本化编程,后来被发掘出更多灵活和有用的地方,脚本化编程能力现在仅仅是其中一个扩展。

NativeScript 实现了一套“脚本语言” - 在Godot中可以这么叫,但其实是用GDNative库而不是像GDScript那样的文本和文件的形式来保存相关逻辑。

NativeScript会调用库中的一个函数 nativescript_init - 告知Godot哪些类和方法是可用的。在要用到那些类和方法的时候,NativeScript就能很简单的去调用这个库来实现相应功能。

因为 NativeScript 仅对库进行操作,它并不关心这些库是用什么语言构建的,如果开发者要用自己喜欢的编程语言进行库的开发,就使得 NativeScript 成为 Godot 里的一种最佳选择,尽管在这个基础上还要付出很多努力。

那想要更灵活且更像脚本的感觉的话,就应该考虑用一下 PluginScript 了。

PLUGINSCRIPT

PluginScript也是一个扩展,它给Godot加入了封装脚本语言实现的特性。对Godot而言,它是一种运行良好且完全集成的脚本语言,但所有逻辑都是在一个库中实现的。

NativeScript 把库都当作脚本用,而PluginScript是用库来定义脚本。也就是只要在你的Godot项目中添加一些文件,就可以添加一种新的脚本语言支持。

目前为止,这种“野生”的主要应用还只有一个 godot-python项目。

与ARVR扩展类似,PluginScript的API也是非常小巧,仅有一个需要调用的函数 godot_pluginscript_register_language。该函数接受一个struct作为参数,struct里包含函数指针及脚本语言的其它信息。

Godot编辑器重启后,就能生效了。

计划

我们正在计划创建更多的扩展,如可插拔式音视频解码器。

对于GDNative当前的架构,我们已经相当满意了,下一步主要是完善文档和改善语言绑定。