ScriptX – 全能的腳本引擎抽象層開源

ScriptX是一個腳本引擎抽象層。對下封裝多種腳本引擎,對上暴露統一的API,使得上層呼叫者可以完全隔離底層的引擎實現(後端)。

ScriptX不僅隔離了幾種JavaScript引擎,甚至可以隔離不同腳本程式語言,使得上層在無需改變程式碼的前提下無縫切換腳本引擎腳本程式語言

ScriptX的術語中,"前端"指對外的C++ API,"後端"則指不同的底層引擎,目前已經實現的後端有:V8, node.js,JavaScriptCore, WebAssembly, Lua.

簡介

ScriptX 的介面使用現代C++屬性。並且做到100%符合C++標準,完全跨平台。

所有API以ScriptX.h聚合標頭檔暴露出來。

設計目標: 多語言 | 多引擎實現 | 高效能 | API易用 | 跨平台

屬性介紹

1. 支援多種引擎,多腳本程式語言

ScriptX設計之初就目標為支援多種腳本程式語言,並在JavaScript上實現了V8和JavaScriptCore的引擎封裝。後續為了驗證ScriptX的多語言設計,實現了完整的Lua繫結。目前針對WebAssembly的支援也已經完成。

2. 現代的 C++ API

API設計上符合現代 C++ 風格,如:

  1. 三種參考型別Local/Global/Weak,使用copy, move語意實現自動的記憶體管理(自動引用計數)

  2. 使用variadic template 支援非常方便的 Function::call 語法

  3. 使用Template Meta-Programing 實現直接繫結C++函式

現代語言屬性,引用空指標安全(nullibility safety 請參考kotlin的概念)。

註:ScriptX要求C++17(或1z)以上的編譯器支援,並需要開啟異常屬性,(可以關閉RTTI屬性)。

3. 高效能

高效能是ScriptX設計上的重要指標。在實現過程中也充分體現了 Zero-Overhead 的C++思想。並在增加功能屬性的時候透過相關的效能測試。

測試指標:單次JS到C++函式呼叫耗時,微秒

測試環境:iMac i9-9900k 32G RAM@macOS 10.15

效能測試表示,在Release模式下,ScriptX可以達到幾乎和原生繫結相同的效能。(由於ScriptX使用大量模板,請勿在Debug版進行效能測試)

4. 支援異常處理

ScriptX透過一系列的技術手段實現了腳本的異常和C++異常相互打通的能力。在呼叫引擎API時無需判斷回傳值,可以使用異常統一處理,避免crash。

5. 易用的API

易用的API => 開心的工程師=> 高效 => 高品質

ScriptX 設計的時候充分考慮到API的易用性,包括操作友好簡單,不易出錯,錯誤訊息明顯,便於定位問題等。

6. 簡單高效的繫結API

當app作為宿主使用腳本引擎時,通常都是需要注入大量native 繫結的函式/類來為腳本邏輯提供能力。ScriptX 設計的ClassDeifine相關繫結API簡單易用,並且可以支援直接繫結C++函式,極大的提升工作效率。

7. 可以與原生引擎API互操作

ScriptX在提供引擎封裝的同時,也提供了一套工具方法實現原生型別和ScriptX型別的相互轉換。

程式碼印象

我們透過一段比較完整的程式碼來對ScriptX留下一個整體印象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
EngineScope enter(engine);try {
  engine->eval("function fibo(x) { if (x<=2 ) return 1; else return fibo(x-1) + fibo(x-2) }");
  Local<Function> fibo = engine->get("fibo").asFunction();
  Local<Value> ret = fibo.call({}, 10);
  ret.asNumber().toInt32() == 55;


  auto log = Function::newFunction(
      [](const std::string& msg) {
        std::cerr << "[log]: " << msg << std::endl;
      });
  // or use: Function::newFunction(std::puts);
  engine->set("log", log);
  engine->eval("log('hello world');");


  auto json = engine->eval(R"( JSON.parse('{"length":1,"info":{"version": "1.18","time":132}}'); )")
                  .asObject();
  json.get("length").asNumber().toInt32() == 1;


  auto info = json.get("info").asObject();
  info.get("version").asString().toString() ==  "1.18";
  info.get("time").asNumber().toInt32() == 132;


  Local<Object> bind = engine->eval("...").asObject();
  MyBind* ptr = engine->getNativeInstance<MyBind>(bind);
  ptr->callCppFunction();


} catch (Exception& e) {
  FAIL() << e.message() << e.stacktrace();
  // or FAIL() << e;
}
  1. 使用 EngineScope 進入引擎環境

  2. 絕大多是API可以接受C++原生型別作為引數,內部自動轉換型別

  3. 可以從C/C++函式直接建立腳本函式(native 繫結)

  4. 支援腳本的異常處理

  5. API強型別

程式碼品質

程式碼品質高標準要求

  1. 上百個測試用例,單測覆蓋率達87%

  2. 圈複雜度僅1.18。

  3. 藉助clang-format保證程式碼格式統一。

  4. 使用clang-tidy發現潛在問題。

  5. 在clang和MSVC編譯器上都開啟了"warning as error"級別的錯誤訊息。

https://github.com/Tencent/ScriptX

(點選文末閱讀原文直接訪問)

請給專案 一個 Star !

歡迎提出你的 issue 和 PR!

國內對映地址:

https://git.code.tencent.com/Tencent_Open_Source/ScriptX

(登入後才能訪問公開專案)

騰訊工蜂原始碼系統為開源開發者提供完整、最新的騰訊開源專案國內對映