HOOOS

C++ 刚体暴露给 Lua,如何避免对象销毁导致的崩溃?

0 25 技术蜗牛 Lua游戏开发
Apple

问题:C++ 刚体对象暴露给 Lua 后,如何避免 C++ 对象销毁导致的崩溃?

在使用 C++ 编写物理引擎时,经常需要将 C++ 中的刚体(RigidBody)对象暴露给 Lua 脚本,以便脚本可以控制其速度和位置。 但一个常见的问题是,如果 C++ 中的 RigidBody 对象被销毁了,而 Lua 脚本的回调仍然在尝试访问它,就会导致程序崩溃。 那么,有没有一种好的模式可以解决这个问题呢?

解决方案:使用智能指针和弱引用

一种有效的解决方案是结合使用智能指针(如 std::shared_ptr)和弱引用(std::weak_ptr)。 核心思想是:

  1. C++ 端使用 std::shared_ptr 管理 RigidBody 对象。 std::shared_ptr 能够自动追踪对象的引用计数,并在没有引用指向该对象时自动释放内存。
  2. std::weak_ptr 传递给 Lua。 std::weak_ptr 是一种非侵入式的智能指针,它不会增加对象的引用计数。 Lua 脚本可以通过 std::weak_ptr 尝试获取 RigidBody 对象。
  3. 在 Lua 访问 RigidBody 对象之前,先检查对象是否仍然有效。 Lua 脚本通过 std::weak_ptr::lock() 方法尝试获取 std::shared_ptr。 如果对象仍然存在,lock() 方法会返回一个有效的 std::shared_ptr,否则返回一个空的 std::shared_ptr

C++ 代码示例:

#include <memory>
#include <lua.hpp>

class RigidBody {
public:
    float x, y;
    void set_position(float x, float y) {
        this->x = x;
        this->y = y;
    }
};

// 将 RigidBody 暴露给 Lua 的函数
int lua_RigidBody_set_position(lua_State *L) {
    // 获取 RigidBody 的弱引用
    auto weak_ptr = *static_cast<std::weak_ptr<RigidBody>*>(lua_touserdata(L, 1));

    // 尝试获取 shared_ptr
    std::shared_ptr<RigidBody> rigid_body = weak_ptr.lock();

    // 检查对象是否仍然有效
    if (rigid_body) {
        float x = lua_tonumber(L, 2);
        float y = lua_tonumber(L, 3);
        rigid_body->set_position(x, y);
        return 0;
    } else {
        // 对象已经被销毁
        luaL_error(L, "RigidBody object has been destroyed!");
        return 0; // never reach here
    }
}

// 创建 RigidBody 对象的函数
int lua_RigidBody_create(lua_State *L) {
    // 创建 shared_ptr
    auto rigid_body = std::make_shared<RigidBody>();

    // 创建 weak_ptr
    std::weak_ptr<RigidBody> weak_ptr = rigid_body;

    // 将 weak_ptr 传递给 Lua (需要存储 weak_ptr 的地址)
    *static_cast<std::weak_ptr<RigidBody>*>(lua_newuserdata(L, sizeof(std::weak_ptr<RigidBody>))) = weak_ptr;

    // 设置 metatable,用于垃圾回收 (可选)
    luaL_getmetatable(L, "RigidBody");
    lua_setmetatable(L, -2);

    return 1;
}

// 注册 RigidBody 相关函数的函数
int register_RigidBody(lua_State *L) {
    lua_register(L, "RigidBody_create", lua_RigidBody_create);
    lua_register(L, "RigidBody_set_position", lua_RigidBody_set_position);

    // 创建 metatable,用于垃圾回收 (可选)
    luaL_newmetatable(L, "RigidBody");
    lua_pushstring(L, "__gc");
    lua_pushcfunction(L, [](lua_State *L) {
        // 在垃圾回收时,释放 userdata 中存储的 weak_ptr
        auto weak_ptr = static_cast<std::weak_ptr<RigidBody>*>(lua_touserdata(L, 1));
        weak_ptr->~weak_ptr(); // 显式调用析构函数
        return 0;
    });
    lua_settable(L, -3);

    return 0;
}

Lua 代码示例:

-- 创建 RigidBody 对象
local rigid_body = RigidBody_create()

-- 设置位置
RigidBody_set_position(rigid_body, 10, 20)

-- ... 之后,C++ 端可能销毁了 RigidBody 对象

-- 再次尝试设置位置
RigidBody_set_position(rigid_body, 30, 40) -- 如果 RigidBody 已被销毁,会抛出错误

优点:

  • 安全: 避免了 C++ 对象销毁后 Lua 脚本访问导致的崩溃。
  • 灵活: 允许 C++ 端控制对象的生命周期,而无需 Lua 脚本的干预。
  • 清晰: 代码逻辑清晰易懂,易于维护。

缺点:

  • 性能开销: 引入了智能指针的开销,但通常可以忽略不计。
  • 代码复杂度: 相比直接传递指针,代码稍微复杂一些。

总结:

使用智能指针和弱引用是一种有效的模式,可以安全地将 C++ 对象暴露给 Lua 脚本,并避免 C++ 对象销毁导致的崩溃。 这种模式在游戏开发中被广泛应用,特别是在物理引擎、AI 系统等需要跨语言交互的场景中。

点评评价

captcha
健康