Lua 与 ArkTS 交互 —— 优雅的callStaticMethod

Lua 与 ArkTS 交互 —— 优雅的 callStaticMethod

背景与问题

在游戏开发中,Lua 与 ArkTS 的交互随着接入 SDK 的 API 增加而变得频繁。传统方式下,每次新增接口都需要重新执行 Lua 绑定操作,这一过程繁琐且效率低下。基于以下现有技术:

  1. Lua 与 C++ 交互可通过 tolua 绑定实现

  2. C++ 与 ArkTS 交互可通过 NAPI 实现

为解决频繁绑定的问题,本文提供了一种改进方案,实现了类似 luaj.callStaticMethod 和 luaoc.callStaticMethod 的接口,减少重复的绑定操作。

实现方案

核心目标

实现两个关键接口,以支持 Lua 与 ArkTS 的灵活交互:

  • 同步接口 luats.callStaticMethodSync:通过模板函数传递不同类型参数,并获取 ArkTS 不同类型的返回值给 Lua

  • 异步接口 luats.callStaticMethodAsyn:利用定时器获取 ArkTS 异步结果,并自动回调给 Lua

核心代码(CCLuaArkTsBridge.cpp)

统一接口实现

// 统一接口:支持同步/异步调用,自动处理回调
// key: 例'OAuth.sendReq', params:例'{"key":"value"}', funcId:例200
void CCLuaArkTsBridge::callStaticMethodAsyn(const std::string key, const char *params /*= ""*/, int funcId /*= 0*/)
{
    ...
    // 注册异步回调
    if (funcId > 0)
    {
        _asyncInfoMap[key] = funcId;
        if (_asyncInfoMap.size() == 1) { // 刚从0变为1
            startSchedule();
        }
        // 异步调用:通过Js_Cocos2dxHelper传递key
        JSFunction::getFunction(key).invokeAsync(key, params);
    } else {
        // 同步调用:无返回值,如果需要返回值使用callStaticMethodSync
        JSFunction::getFunction(key).invoke<void>(params);
    }
}

调度相关方法

// 启动调度(仅当未在调度时)
void CCLuaArkTsBridge::startSchedule() {
    if (!_isScheduling && _scheduler) {
        _scheduler->schedule(CC_SCHEDULE_SELECTOR(CCLuaArkTsBridge::checkResult), this, 0.0f, CC_REPEAT_FOREVER, 0.0f, false);
        _isScheduling = true;
    }
}

// 停止调度(仅当正在调度时)
void CCLuaArkTsBridge::stopSchedule() {
    if (_isScheduling && _scheduler) {
        _scheduler->unschedule(CC_SCHEDULE_SELECTOR(CCLuaArkTsBridge::checkResult), this);
        _isScheduling = false;
    }
}

void CCLuaArkTsBridge::checkResult(float /*dt*/) {
    ...
    // 遍历所有注册的key
    for (const auto& pair : _asyncInfoMap) {
        const std::string& key = pair.first;
        int funcId = pair.second;
        // 从Js_Cocos2dxHelper获取结果
        std::string result = Js_Cocos2dxHelper::getAsyncInfo(key);
        if (!result.empty()) {
            Js_Cocos2dxHelper::clearAsyncInfo(key); // 立即清除避免重复处理
            // 回调到Lua
            Director::getInstance()->getScheduler()->performFunctionInCocosThread([key, result, funcId]() {
                LuaStack* luaStack = LuaEngine::getInstance()->getLuaStack();
                luaStack->pushString(result.c_str());
                int ret = luaStack->executeFunctionByHandler(funcId, 1);
            });
            _asyncInfoMap.erase(key);
            
            if (_asyncInfoMap.empty()) {
                stopSchedule(); // 若删除后_map为空,停止调度
            }
        }
    }
}

实现原理

  1. 同步调用( luats.callStaticMethodSync
  • 利用模板函数处理不同类型的参数传递

  • 自动获取 ArkTS 不同类型的返回值并传递给 Lua

// 模板同步调用:指定返回类型T,自动处理不同类型的返回值
    template <typename T>
    T callStaticMethodSync(const std::string key, const std::string params = "") {
        try {
            return JSFunction::getFunction(key).invoke<T>(params.c_str());
        } catch (...) {
            CCLOG("Sync call failed for key: %s, type mismatch", key.c_str());
            return T();
        }
    }
  1. 异步调用( luats.callStaticMethodAsyn
  • 通过注册异步回调,将接口调用信息存储在 _asyncInfoMap 中

  • 启动定时器定期检查异步调用结果

  • 当获取到结果后,自动回调 Lua 函数处理结果

通过这种方式,无需频繁进行 Lua 绑定操作,即可实现 Lua 与 ArkTS 的灵活交互,提升了开发效率。