Attention: Here be dragons

This is the latest (unstable) version of this documentation, which may document features not available in or compatible with released stable versions of Godot.

处理兼容问题

听说你为方法添加了新参数、更改了返回类型、更改了参数的类型或默认值,而现在自动测试却在抱怨兼容性问题?

我们应该避免破坏兼容性,但如果必须破坏,也有相应的系统来处理这种情况,使过渡尽可能顺畅。

实际案例

这些修改取自拉取请求 #88047,为 AStarGrid2D 等 AStar 类添加了新的路径选项。其中包含了对 core/math/a_star_grid_2d.h 中的这些方法的修改:

Vector<Vector2> get_point_path(const Vector2i &p_from, const Vector2i &p_to);
TypedArray<Vector2i> get_id_path(const Vector2i &p_from, const Vector2i &p_to);

修改后:

Vector<Vector2> get_point_path(const Vector2i &p_from, const Vector2i &p_to, bool p_allow_partial_path = false);
TypedArray<Vector2i> get_id_path(const Vector2i &p_from, const Vector2i &p_to, bool p_allow_partial_path = false);

这段代码的作用是在文件中添加兼容方法的绑定,应该放在代码的 protected 部分,通常放在 _bind_methods() 旁边即可:

#ifndef DISABLE_DEPRECATED
    TypedArray<Vector2i> _get_id_path_bind_compat_88047(const Vector2i &p_from, const Vector2i &p_to);
    Vector<Vector2> _get_point_path_bind_compat_88047(const Vector2i &p_from, const Vector2i &p_to);
    static void _bind_compatibility_methods();
#endif

这些方法的命名应该以 _ 开头,以表明它们是内部方法;结尾则应该是 _bind_compat_ ,后面紧跟引入该变更的 PR 编号(在这个例子里就是 88047)。这些兼容性方法需要在专门的文件中实现,比如在这个案例中,就是放在 core/math/a_star_grid_2d.compat.inc 这样的文件里:

core/math/a_star_grid_2d.compat.inc
/**************************************************************************/
/*  a_star_grid_2d.compat.inc                                             */
/**************************************************************************/
/*                         This file is part of:                          */
/*                             GODOT ENGINE                               */
/*                        https://godotengine.org                         */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur.                  */
/*                                                                        */
/* Permission is hereby granted, free of charge, to any person obtaining  */
/* a copy of this software and associated documentation files (the        */
/* "Software"), to deal in the Software without restriction, including    */
/* without limitation the rights to use, copy, modify, merge, publish,    */
/* distribute, sublicense, and/or sell copies of the Software, and to     */
/* permit persons to whom the Software is furnished to do so, subject to  */
/* the following conditions:                                              */
/*                                                                        */
/* The above copyright notice and this permission notice shall be         */
/* included in all copies or substantial portions of the Software.        */
/*                                                                        */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,        */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF     */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY   */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,   */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE      */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                 */
/**************************************************************************/

#ifndef DISABLE_DEPRECATED

#include "core/variant/typed_array.h"

TypedArray<Vector2i> AStarGrid2D::_get_id_path_bind_compat_88047(const Vector2i &p_from_id, const Vector2i &p_to_id) {
    return get_id_path(p_from_id, p_to_id, false);
}

Vector<Vector2> AStarGrid2D::_get_point_path_bind_compat_88047(const Vector2i &p_from_id, const Vector2i &p_to_id) {
    return get_point_path(p_from_id, p_to_id, false);
}

void AStarGrid2D::_bind_compatibility_methods() {
    ClassDB::bind_compatibility_method(D_METHOD("get_id_path", "from_id", "to_id"), &AStarGrid2D::_get_id_path_bind_compat_88047);
    ClassDB::bind_compatibility_method(D_METHOD("get_point_path", "from_id", "to_id"), &AStarGrid2D::_get_point_path_bind_compat_88047);
}

#endif // DISABLE_DEPRECATED

除非兼容性的变更非常复杂,否则兼容性方法应该直接调用那个被修改过的方法,而不是把那个方法的代码重新复制一遍。另外,请务必确保该方法的默认参数保持一致(在上面的例子中,默认参数应该是 false )。

该文件应当放置在原始文件旁,以 .compat.inc 结尾而不是 .cpp.h。接下来,应当将该文件包含进需要添加兼容性方法的 .cpp 文件中,这样 core/math/a_star_grid_2d.cpp 就变成了这样:

core/math/a_star_grid_2d.cpp
#include "a_star_grid_2d.h"
#include "a_star_grid_2d.compat.inc"

#include "core/variant/typed_array.h"

最后,需要把 GDExtension API 的变更给记录下来。具体做法是:首先在主分支(master)上编译 Godot,然后运行它,并带上 --dump-extension-api 这个标志(命令行参数):

git switch master
scons
godot --dump-extension-api

这会在你当前的目录下生成一个名为 extension_api.json 的文件。接着,切换回你的功能分支(feature branch),重新编译 Godot,然后运行它。运行时需要带上 --validate-extension-api 标志,并在后面跟上你刚刚生成的那个 extension_api.json 文件的路径。

git switch my-feature-branch
scons
godot --validate-extension-api /path/to/extension_api.json

这会生成一些以 Validate extension JSON 开头的行,就像下面这样:

Validate extension JSON: Error: Field 'classes/AStar2D/methods/get_id_path/arguments': size changed value in new API, from 2 to 3.
Validate extension JSON: Error: Field 'classes/AStar2D/methods/get_point_path/arguments': size changed value in new API, from 2 to 3.
Validate extension JSON: Error: Field 'classes/AStar3D/methods/get_id_path/arguments': size changed value in new API, from 2 to 3.
Validate extension JSON: Error: Field 'classes/AStar3D/methods/get_point_path/arguments': size changed value in new API, from 2 to 3.
Validate extension JSON: Error: Field 'classes/AStarGrid2D/methods/get_id_path/arguments': size changed value in new API, from 2 to 3.
Validate extension JSON: Error: Field 'classes/AStarGrid2D/methods/get_point_path/arguments': size changed value in new API, from 2 to 3.

注意

如果你遇到了某个方法的 Hash changed (哈希值已变更)报错,这意味着对应的兼容性绑定(compatibility binding)缺失了或者不正确。这类行不应该被直接添加到验证文件中,而是应该通过正确绑定兼容性方法来解决。请务必仔细检查以下几点:

  • 对于兼容性方法(也就是那个以 PR 编号结尾的方法),它的参数类型、参数名称以及默认值,必须与你修改之前的旧版方法保持完全一致。

  • _bind_compatibility_methods() 函数中,ClassDB::bind_compatibility_method() 里的 D_METHOD() 宏所传入的参数名称,必须与原始方法在 ClassDB::bind_method() 调用中使用的参数名称完全一致。

请添加这几行内容,并在后面附上一段注释,解释具体做了哪些 API 变更,以及为了防止程序崩溃(或兼容性破坏)采取了哪些措施。然后将这个验证文件以 GitHub 的 Pull Request(拉取请求)ID 来命名,并放入它原本会破坏兼容性的那个 Godot 版本对应的文件夹中。

因为这个例子是针对 PR #88047 的,所以它的文件名应该是 GH-88047.txt。又因为这是在 4.3 版本开发期间完成的(也就是相对于 4.2 版本有了变动),所以这个文件应该放在 misc/extension_api_validation/4.2-stable/ 文件夹中。

下面是对于 PR #88047的文档的完整示例:

misc/extension_api_validation/4.2-stable/GH-88047.txt
GH-88047
--------
Validate extension JSON: Error: Field 'classes/AStar2D/methods/get_id_path/arguments': size changed value in new API, from 2 to 3.
Validate extension JSON: Error: Field 'classes/AStar2D/methods/get_point_path/arguments': size changed value in new API, from 2 to 3.
Validate extension JSON: Error: Field 'classes/AStar3D/methods/get_id_path/arguments': size changed value in new API, from 2 to 3.
Validate extension JSON: Error: Field 'classes/AStar3D/methods/get_point_path/arguments': size changed value in new API, from 2 to 3.
Validate extension JSON: Error: Field 'classes/AStarGrid2D/methods/get_id_path/arguments': size changed value in new API, from 2 to 3.
Validate extension JSON: Error: Field 'classes/AStarGrid2D/methods/get_point_path/arguments': size changed value in new API, from 2 to 3.

Added optional "allow_partial_path" argument to get_id_path and get_point_path methods in AStar classes.
Compatibility methods registered.

好啦,就是这样!在实际操作中,你可能会遇到一些更复杂的情况,比如需要重新排列参数、修改返回类型等等,但上面这些已经涵盖了使用这套系统的基础核心了。

详情见拉取请求 #76446