A versatile script engine abstraction layer.

Overview

ScriptX

A versatile script engine abstraction layer


README中文 License UnitTests GitHub code size in bytes Lines of code GitHub top language Coverage Status

ScriptX Architecture

ScriptX is a script engine abstraction layer. A variety of script engines are encapsulated on the bottom and a unified API is exposed on the top, so that the upper-layer caller can completely isolate the underlying engine implementation (back-end).

ScriptX not only isolates several JavaScript engines, but can even isolate different scripting languages, so that the upper layer can seamlessly switch between scripting engine and scripting language without changing the code.

In ScriptX terminology, "front-end" refers to the external C++ API, and "back-end" refers to different underlying engines. The currently implemented back-ends include: V8, node.js, JavaScriptCore, WebAssembly, Lua.

Introduction

The interface of ScriptX uses modern C++ features. And to be 100% in line with the C++ standard, completely cross-platform.

All APIs are exposed in the ScriptX.h aggregate header file.

Design goals: Multi-language | Multi-engine implementation | High performance | API easy to use | Cross-platform

First impression

We use a relatively complete code to leave an overall impression of ScriptX.

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. Use EngineScope to enter the engine environment
  2. Most APIs can accept C++ native types as parameters and automatically convert types internally
  3. Script functions can be created directly from C/C++ functions (native binding)
  4. Support script exception handling
  5. API strong typing

Feature introduction

1. Support multiple engines, multiple scripting languages

At the beginning of the design of ScriptX, the goal was to support multiple scripting languages, and the engine package of V8 and JavaScriptCore was implemented on JavaScript. In order to verify the multi-language design of ScriptX, a complete Lua binding was implemented. Currently support for WebAssembly has also been completed.

2. Modern C++ API

API design conforms to modern C++ style, such as:

  1. Three reference types Local/Global/Weak, using copy and move semantics to achieve automatic memory management (automatic reference counting)
  2. Use variadic template to support the very convenient Function::call syntax
  3. Use Template Meta-Programing to directly bind C++ functions

Modern language features, refer to null pointer safety (nullability safety please refer to the concept of kotlin).

Note: ScriptX requires C++17 (or 1z) or higher compiler support, and needs to turn on the exception feature (you can turn off the RTTI feature).

3. High performance

High performance is an important indicator in the design of ScriptX. The C++ idea of Zero-Overhead is also fully embodied in the implementation process. And pass relevant performance tests when adding functional features.

Performance test comparison data

Test indicator: single time consuming from JS to C++ function call, microsecond

Test environment: iMac i9-9900k 32G RAM @macOS 10.15

Test Index: single function call from js to C++ in micro seconds. Test Environment: iMac i9-9900k 32G RAM @macOS 10.15

The performance test shows that in Release mode, ScriptX can achieve almost the same performance as the native binding. (Because ScriptX uses a large number of templates, please do not perform performance testing in the Debug version)

4. Convenient exception handling

ScriptX has realized the ability of script exceptions and C++ exceptions to communicate with each other through a series of technical means. There is no need to judge the return value when calling the engine API, and unified exception handling can be used to avoid crashes.

For example, users can catch exceptions thrown by js code in the C++ layer, and get the message and stack; they can also throw a C++ exception (script::Exception) in the native function and pass it through to the js code.

For details, please refer to ExceptionTest and Related Documents

5. Easy-to-use API

Easy-to-use API => Happy Engineer => Efficient => High Quality

ScriptX was designed with full consideration of the ease of use of the API, including friendly and simple operation, not easy to make mistakes, obvious error messages, and easy to locate problems. Under this guiding ideology, ScriptX has done a lot of things that native engines can't do.

For example: V8 does not perform GC when destroying, resulting in many bound native classes that cannot be released. ScriptX does additional logic to handle this situation.

V8 and JSCore require that other APIs of ScriptX cannot be called in the finalize callback, otherwise it will crash, which also makes the code logic difficult to implement. ScriptX uses MessageQueue to avoid this problem perfectly.

The global references of V8 and JSCore must be released before the engine is destroyed, otherwise it will cause problems such as crash and failure to destroy. ScriptX guarantees to actively reset all Global / Weak references during Engine destruction.

6. Simple yet efficient binding API

When an app is used as a host to use a scripting engine, it is usually necessary to inject a large number of native-bound functions/classes to provide capabilities for the scripting logic. The ClassDeifine related binding API designed by ScriptX is simple and easy to use, and can support direct binding of C++ functions, which greatly improves work efficiency.

7. Interoperable with native engine API

While ScriptX provides engine encapsulation, it also provides a set of tools and methods to achieve mutual conversion between native types and ScriptX types.

For details, please refer to InteroperateTest and Related Documents

Code quality

High code quality requirements

  1. Hundreds of test cases, UnitTests coverage rate reaches 90+%
  2. The cyclomatic complexity is only 1.18.
  3. Use clang-format to ensure uniform code format.
  4. Use clang-tidy to find potential problems.
  5. Both clang and MSVC compilers have opened "warning as error" level error messages.

Code directory structure

root
├── README.md
├── src
│ ├── Engine.h
│ └── ...
├── backend
│ ├── JavaScriptCore
│ ├── Lua
│ ├── Python
│ ├── QuickJs
│ ├── Ruby
│ ├── SpiderMonkey
│ ├── Template
│ ├── V8
│ ├── WKWebView
│ └── WebAssembly
├── docs
│ ├── Basics.md
│ └── ...
└── test
    ├── CMakeLists.txt
    └── src
        ├── Demo.cc
        └── ...
  1. src: External API, mainly header files
  2. backend: Implementation of various engine backends
  3. docs: Rich documentation
  4. test: Various unit tests

Getting started

Some important classes in ScriptX:

  1. ScriptEngine
  2. EngineScope
  3. Exception
  4. Value, Null, Object, String, Number, Boolean, Function, Array, ByteBuffer, Unsupported
  5. Local<Value>, Local<Null>, Local<Object>, Local<String>, Local<Number>, Local<Boolean>, Local<Function> , Local<Array>, Local<ByteBuffer>, Local<Unsupported>
  6. Global<T>, Weak<T>

Before officially using ScriptX, please spend half an hour read carefully the following documents, and be familiar with several concepts in ScriptX.

  1. CMake project introduction guide
  2. Basic Concepts This part is more important
    1. Agreement
    2. Predefined Macro
    3. Engine and MessageQueue
    4. Scope
    5. Value
    6. Local
    7. Global / Weak
  3. Exception Handling
  4. C++ Binding
    1. Create a Native Function
    2. defineClass
    3. ScriptClass
    4. Various operations
    5. Binding C++ functions directly
    6. converter
    7. Binding of existing C++ classes
    8. Tips: Choose overloaded functions
    9. Tips: Differences in different languages
  5. Interop with native engine API
    1. script::v8_interop
    2. script::jsc_interop
    3. script::lua_interop
  6. JavaScript language description
    1. Type comparison table
  7. Lua language description
  8. WebAssemble Description
  9. node.js description
  10. FAQ
  11. Quick Start Guide
  12. Performance related
  13. ScriptX at the 2020 Pure C++ Conference
Comments
  • Exception when using promises with QuickJs backend

    Exception when using promises with QuickJs backend

    Hi, When running the following code with QuickJs backend an exception is thrown

    const promise = new Promise((resolve, reject) => {
        setTimeout(() => { resolve('Ok') }, 300);
    });
    
    promise.then(x => { console.log(x); });  // Exception gets thrown here
    

    The exception is about the missing engine scope, thrown from QjsEngine::newPrototype newRawFunction lambda. This is because when executing the promise_reaction_job (that is the .then() part) and eventually the lambda function created in QjsEngine::newPrototype, the engine scope is not set (the promise resolution is called on the message loop) so the args.thiz() in newRawFunction throws an exception.

    It seems this can be solved by creating a scope in there:

    // QjsEngine.hpp
    // QjsEngine::newPrototype
        auto fun = newRawFunction(
            this, const_cast<FuncDef*>(&f), definePtr,
            [](const Arguments& args, void* data1, void* data2, bool) {
              EngineScope scope(args.engine());  // <---------------------------- *HERE*
              auto ptr = static_cast<InstanceClassOpaque*>(
                  JS_GetOpaque(qjs_interop::peekLocal(args.thiz()), kInstanceClassId));
              if (ptr == nullptr || ptr->classDefine != data2) {
                throw Exception(u8"call function on wrong receiver");
              }
              auto f = static_cast<FuncDef*>(data1);
              Tracer tracer(args.engine(), f->traceName);
              return (f->callback)(static_cast<T*>(ptr->scriptClassPolymorphicPointer), args);
            });
    
    opened by Archie3d 9
  • 在同一次构建中更换编译开关重复构建ScriptX时报错

    在同一次构建中更换编译开关重复构建ScriptX时报错

    本来打算在同一次构建中对每种语言分别生成一个Target的输出 每次include( ScriptX/CMakeLists.txt )前先修改SCRIPTX_BACKEND 结果cmake报错 CMake Error at src/ScriptX/CMakeLists.txt:50 (add_library): [cmake] add_library cannot create target "ScriptX" because another target with the [cmake] same name already exists. The existing target is a static library created [cmake] in source directory 也就是说在同一次构建中不能有两个同名对象 那该如何实现同时构建多种语言的多个target呢 或者有无办法实现构建完一个target后执行clean

    opened by yqs112358 7
  • Local<Array>'s behavior in lua may be wrong

    Local's behavior in lua may be wrong

    My test code is : image

    And the result is: image

    It is clear that in the test code, arr is an array in lua. It seems a bit confusing since the .isArray() returns True while .kind() == ValueKind::kArray returns False

    opened by yqs112358 4
  • 希望可以增加对 Js ESM 的支持

    希望可以增加对 Js ESM 的支持

    lua原生require可以正常使用。但在V8 Js测试 ESM 时提示

    Uncaught SyntaxError: Cannot use import statement outside a module

    原生V8支持ESM需要通过.mjs后缀,但是由于ScriptX是基于eval的加载,没法让引擎知道当前文件需要支持ESM 貌似QuickJs就原生支持了ESM,ScriptX可以在V8这边做一些处理吗

    opened by yqs112358 3
  • SCRIPTX_BACKEND gets erased by ScriptX/CMakeLists.txt

    SCRIPTX_BACKEND gets erased by ScriptX/CMakeLists.txt

    Environment

    • cmake: version 3.22.1
    • ScriptX: latest master branch

    Problem

    When I try to import ScriptX into my project, I follow the ScriptX cmake guide:

    set(SCRIPTX_BACKEND QuickJs)
    include(ScriptX/CMakeLists.txt)
    

    However, I get an error the first time I run cmake:

    CMake Error at ScriptX/CMakeLists.txt:81 (message):
      Platform not set.  Please set SCRIPTX_BACKEND.  Candidates:
      V8;JavaScriptCore;SpiderMonkey;QuickJs;WebAssembly;WKWebView;Lua;Python;Ruby;Empty
    

    If I run cmake again, it works now. 🧐

    Reason

    This seems to be because line 42 of CMakeLists.txt resets the variable to a blank string the first time it is run.

    You can see this by adding some logging:

    message("backend: [${SCRIPTX_BACKEND}]")  # backend: [QuickJs]
    set(SCRIPTX_BACKEND "" CACHE STRING "choose which ScriptX Backend(Implementation) to use, V8 or JavaScriptCore or etc...")
    message("backend: [${SCRIPTX_BACKEND}]")  # backend: []
    

    According to the documentation, this is expected if policy CMP0126 is set to OLD.

    That policy was introduced in version 3.21. However, since ScriptX does cmake_minimum_required(VERSION 3.0), this policy is set to the OLD behavior (see here).

    Workaround

    If you are using cmake 3.21 or newer, you can set the policy explicitly:

    # add this line first:
    set(CMAKE_POLICY_DEFAULT_CMP0126 NEW)
    # ...now we can do these lines:
    set(SCRIPTX_BACKEND QuickJs)
    include(ScriptX/CMakeLists.txt)
    

    If you are running an older version, you can copy line 42 into your file and change it, like this:

    set(SCRIPTX_BACKEND QuickJs CACHE STRING "choose which ScriptX Backend(Implementation) to use, V8 or JavaScriptCore or etc...")
    include(ScriptX/CMakeLists.txt)
    

    Solution

    It seems there are a few options.

    1. Change line 42 to make it capture the user variable:
      set(SCRIPTX_BACKEND "${SCRIPTX_BACKEND}" CACHE STRING "choose which ScriptX Backend(Implementation) to use, V8 or JavaScriptCore or etc...")
      
    2. Change the cmake guide to recommend the workarounds above.
    3. Change the minimum required version to 3.21. I'm not sure if this is desirable for the project, since some users may be using older versions.

    Option 1 works in my tests and seems like the cleanest fix, but I'm not sure what is best.

    Thanks

    ScriptX is a really impressive library! It's well-designed and well-engineered, and promises to a be a huge benefit to my project. Thank you for making it 🎉

    opened by jrpat 2
  • Memory leak in QuickJs backend

    Memory leak in QuickJs backend

    Hi, When compiled with QuickJs backend I experience memory leaks reported by MSVC compiler. I use std::shared_ptr<script::ScriptEngine> with provided script::ScriptEngine::Deleter().

    One problem is inside QjsEngine::destroy() which is missing the delete this; statement.

    And another one is in MessageQueue.cc because of the global static runningQueue_. Could be a false-positive, but it can be fixed by moving the runningQueue_ inside the getRunningQueue() function:

    using RunnineQueueMap = std::unordered_map<MessageQueue*, int>;
    
    static inline RunnineQueueMap& getRunningQueue() {
      SCRIPTX_THREAD_LOCAL(RunnineQueueMap, runningQueue_);
      return internal::getThreadLocal(runningQueue_);
    }
    

    Thanks!

    Great project!

    opened by Archie3d 2
  • V8引擎作为DLL嵌入工作时出错

    V8引擎作为DLL嵌入工作时出错

    编译正确无警告,使用的是你预编译的V8,在单独的exe项目工作正常,,到作为DLL加载时就出问题了 不知有无遇到过这样的问题 经测试lua版dll运行正常。

    由于目前没找到编译玩的x64的jscore或者quickjs所以只能搞v8。。。或者不知可否发一下您precompile完测试用的jsc或qjs(泪目)

    opened by yqs112358 2
  • 用V8作为后端启动时遇到问题

    用V8作为后端启动时遇到问题

    问题描述:把小游戏demo单独提取出来,选择V8为backend,把test自动从github拉取的那个作者编译好的v8提取出来,加入项目并链接。接下来启动demo的时候报错 # # Fatal error in , line 0 # Failed to deserialize the V8 snapshot blob. This can mean that the snapshot blob file is corrupted or missing. # # # #FailureMessage Object: 0000005F167AF088 看起来像是V8快照加载失败 不知该如何解决 另外问一下v8该如何编译才能符合作为scriptx后端的要求

    opened by yqs112358 2
  • 循环引用内存泄漏

    循环引用内存泄漏

    const image = new Image(60, 45); // Using optional size for image image.onload = function() { canvas.draw(image) }; image.src = 'https://mdn.mozillademos.org/files/5397/rhino.jpg';

    如果Image是c++实现的,就会出现内存泄漏吧

    opened by breakerrorchen 1
  • Fix typo in QjsLocalReference.cc

    Fix typo in QjsLocalReference.cc

    I got a compiler error when trying to build:

    QjsLocalReference.cc:212:6: error: expected value in expression
    #elif
         ^
    

    changing the #elif to an #else fixes it.

    opened by jrpat 0
  • Add new APIs to

    Add new APIs to "eval a file" in Engine.h

    如题,至今引擎加载源码都只能依靠读取文件然后eval,在适配其他引擎过程中此种加载方式会带来一些困扰 比如

    • V8 engine 和 embedded NodeJs 需要使用.mjs后缀判定此文件为ES Module文件,以启动ES Module支持
    • Python解释器用import可以更方便地实现实例隔离,而非使用其标准中尚不太完善的subinterpreter
    • 等等

    因此有必要为Engine.h提供支持加载指定路径文件的eval,并在不同引擎做特殊处理

    opened by yqs112358 0
  • Python单元测试

    Python单元测试

    请问如何配置python单元测试?

    ######## ScriptX config ##########
    
    # 1. import ScriptX
    #   set which backend engine to use
    set(SCRIPTX_BACKEND Python CACHE STRING "" FORCE)
    

    我将test文件夹的cmake改成这样 结果编译时仍有v8的引用

    配置cmake时我发现有仓库的克隆 但是文件里面我并没有发现git链接 就感觉很奇怪

    opened by twoone-3 2
  • scriptDynamicCast返回NULL导致空指针崩溃

    scriptDynamicCast返回NULL导致空指针崩溃

    调用栈: image

    • Engine.hpp:39 是把引擎类型从ScriptEngine向下转型为V8Engine, scriptDynamicCast返回了NULL;
    • 工程开启了rtti, scriptDynamicCast的实现使用了dynamic_cast;

    scriptDynamicCast参数类型: R: V8Engine T: ScriptEngine

    scriptDynamicCast里测试代码来看:

    • 使用static_cast 转换时t_static_cast非空;
    • 使用dynamic_cast 转换时t_forward_dynamic_cast 为空;

    请问是否scriptDynamicCast的实现在向下类型转换时有问题?

    opened by chadguo 1
Owner
Tencent
Tencent
Autotools-style configure script wrapper around CMake

configure-cmake configure-cmake is an autotools-style configure script for CMake-based projects. People building software on Linux or BSD generally ex

Evan Nemerson 82 Dec 14, 2022
custom lua godot engine module

Godot Lua Module Table of contents: About Features TODO Compiling Examples Contributing And Feature Requests About This is a Godot engine module that

Trey Moller 65 Jan 7, 2023
Low Level Graphics Library (LLGL) is a thin abstraction layer for the modern graphics APIs OpenGL, Direct3D, Vulkan, and Metal

Low Level Graphics Library (LLGL) Documentation NOTE: This repository receives bug fixes only, but no major updates. Pull requests may still be accept

Lukas Hermanns 1.5k Jan 8, 2023
eCAL - enhanced Communication Abstraction Layer

eCAL - enhanced Communication Abstraction Layer Copyright (c) 2020, Continental Corporation. ?? http://ecal.io License Build States Preface The enhanc

Continental 4 Nov 20, 2022
eCAL - enhanced Communication Abstraction Layer

eCAL - enhanced Communication Abstraction Layer The enhanced Communication Abstraction Layer (eCAL) is a middleware that enables scalable, high perfor

Continental 4 Nov 20, 2022
Yet another abstraction layer - a general purpose C++ library.

Yet Another Abstraction Layer What yaal is a cross platform, general purpose C++ library. This library provides unified, high level, C++ interfaces an

Marcin Konarski 14 Jul 27, 2022
NVRHI (NVIDIA Rendering Hardware Interface) is a library that implements a common abstraction layer over multiple graphics APIs

NVRHI Introduction NVRHI (NVIDIA Rendering Hardware Interface) is a library that implements a common abstraction layer over multiple graphics APIs (GA

NVIDIA GameWorks 445 Jan 3, 2023
Direct3D to OpenGL abstraction layer

TOGL Direct3D -> OpenGL translation layer. Taken directly from the DOTA2 source tree; supports: Limited subset of Direct3D 9.0c Bytecode-level HLSL ->

Valve Software 2k Dec 27, 2022
A interpreter that runs the script which is programmed in the language of FF0 script (or you can call it as Warfarin)

ff0-script A interpreter that runs the script which is programmed in the language of FF0 script (or you can call it as Warfarin) You can do it, unders

null 24 Apr 27, 2022
GnuTLS implements the TLS/SSL (Transport Layer Security aka Secure Sockets Layer) protocol

GnuTLS implements the TLS/SSL (Transport Layer Security aka Secure Sockets Layer) protocol

Jonathan Bastien-Filiatrault 3 Jun 3, 2021
Vireo is a lightweight and versatile video processing library written in C++11

Overview Vireo is a lightweight and versatile video processing library that powers our video transcoding service, deep learning recognition systems an

Twitter 875 Jan 2, 2023
OpenVSLAM: A Versatile Visual SLAM Framework

OpenVSLAM: A Versatile Visual SLAM Framework NOTE: This is a community fork of xdspacelab/openvslam. It was created to continue active development of

null 551 Jan 9, 2023
Vireo is a lightweight and versatile video processing library written in C++11

Overview Vireo is a lightweight and versatile video processing library that powers our video transcoding service, deep learning recognition systems an

Twitter 874 Dec 27, 2022
LVGL - Light and Versatile Graphics Library

Powerful and easy-to-use embedded GUI library with many widgets, advanced visual effects (opacity, antialiasing, animations) and low memory requirements (16K RAM, 64K Flash).

LVGL 11k Jan 5, 2023
An efficient and versatile system call hook mechanism

Zpoline: hooking system calls without pain Zpoline is a novel system call hook mechanism that offers the following advantages. 100 times faster than p

null 109 Dec 28, 2022
Arbitrary Precision provides C++ long integer types that behave as basic integer types. This library aims to be intuitive and versatile in usage, rather than fast.

Arbitrary Precision (AP) Cross-platform and cross-standard header-only arbitrary precision arithmetic library. Currently it offers integer types that

null 17 Sep 28, 2022
Code accompanying our SIGGRAPH 2021 Technical Communications paper "Transition Motion Tensor: A Data-Driven Approach for Versatile and Controllable Agents in Physically Simulated Environments"

SIGGRAPH ASIA 2021 Technical Communications Transition Motion Tensor: A Data-Driven Framework for Versatile and Controllable Agents in Physically Simu

null 10 Apr 21, 2022
A powerful and versatile dynamic instrumentation toolkit.

MIGI Migi(My Ideas Got Incepted) is a powerful and versatile dynamic instrumentation toolkit. How it works By injecting Python scripts into target hos

nomads 5 Oct 22, 2022
Golang bindings of Sciter: the Embeddable HTML/CSS/script engine for modern UI development

Go bindings for Sciter Check this page for other language bindings (Delphi / D / Go / .NET / Python / Rust). Attention The ownership of project is tra

Terra Informatica Software, Inc 2.5k Dec 23, 2022
A GIF art engine that will allow you to generate multi-layer GIFs from single GIFs as layers.

A GIF art engine that will allow you to generate multi-layer GIFs from single GIFs as layers. All the code in this repository has been written by me in c++, inspired by the generative art engine of HashLips that does not support GIFs as layers. The problem arose from my and my teamleader's need to generate animated images and then GIFs, in the same way as HashLips generated static images.

Andre 63 Jan 2, 2023