addon-tools-el/include/addon-tools.hpp

434 lines
16 KiB
C++

#ifndef _ADDON_TOOLS_HPP_
#define _ADDON_TOOLS_HPP_
#include <napi.h>
#define NAPI_ENV Napi::Env env = info.Env();
#define NAPI_HS Napi::HandleScope scope(env);
#define JS_STR(VAL) Napi::String::New(env, VAL)
#define JS_NUM(VAL) Napi::Number::New(env, static_cast<double>(VAL))
#define JS_EXT(VAL) Napi::External::New(env, reinterpret_cast<void*>(VAL))
#define JS_BOOL(VAL) Napi::Boolean::New(env, static_cast<bool>(VAL))
#define JS_FUN(VAL) Napi::Function::New(env, VAL)
#define JS_OBJ(VAL) Napi::Object::New(env, VAL)
#define RET_VALUE(VAL) return VAL;
#define RET_UNDEFINED RET_VALUE(env.Undefined())
#define RET_NULL RET_VALUE(env.Null())
#define RET_STR(VAL) RET_VALUE(JS_STR(VAL))
#define RET_NUM(VAL) RET_VALUE(JS_NUM(VAL))
#define RET_EXT(VAL) RET_VALUE(JS_EXT(VAL))
#define RET_BOOL(VAL) RET_VALUE(JS_BOOL(VAL))
#define RET_FUN(VAL) RET_VALUE(JS_FUN(VAL))
#define RET_OBJ(VAL) RET_VALUE(JS_OBJ(VAL))
#define JS_THROW(VAL) \
Napi::Error::New(env, VAL).ThrowAsJavaScriptException();
#define REQ_ARGS(N) \
if (info.Length() < (N)) { \
JS_THROW("Expected at least " #N " arguments"); \
}
#define IS_EMPTY(VAL) (VAL.IsNull() || VAL.IsUndefined())
#define IS_ARG_EMPTY(I) IS_EMPTY(info[I])
#define CHECK_REQ_ARG(I, C, T) \
if (info.Length() <= (I) || ! info[I].C) { \
JS_THROW("Argument " #I " must be of type `" T "`"); \
}
#define CHECK_LET_ARG(I, C, T) \
if ( ! (IS_ARG_EMPTY(I) || info[I].C) ) { \
JS_THROW( \
"Argument " #I \
" must be of type `" T \
"` or be `null`/`undefined`" \
); \
}
#define REQ_STR_ARG(I, VAR) \
CHECK_REQ_ARG(I, IsString(), "String"); \
std::string VAR = info[I].ToString().Utf8Value();
#define USE_STR_ARG(I, VAR, DEF) \
CHECK_LET_ARG(I, IsString(), "String"); \
std::string VAR = IS_ARG_EMPTY(I) ? (DEF) : info[I].ToString().Utf8Value();
#define LET_STR_ARG(I, VAR) USE_STR_ARG(I, VAR, "")
#define REQ_INT32_ARG(I, VAR) \
CHECK_REQ_ARG(I, IsNumber(), "Int32"); \
int VAR = info[I].ToNumber().Int32Value();
#define USE_INT32_ARG(I, VAR, DEF) \
CHECK_LET_ARG(I, IsNumber(), "Int32"); \
int VAR = IS_ARG_EMPTY(I) ? (DEF) : info[I].ToNumber().Int32Value();
#define LET_INT32_ARG(I, VAR) USE_INT32_ARG(I, VAR, 0)
#define REQ_INT_ARG(I, VAR) REQ_INT32_ARG(I, VAR)
#define USE_INT_ARG(I, VAR, DEF) USE_INT32_ARG(I, VAR, DEF)
#define LET_INT_ARG(I, VAR) LET_INT32_ARG(I, VAR)
#define REQ_UINT32_ARG(I, VAR) \
CHECK_REQ_ARG(I, IsNumber(), "Uint32"); \
unsigned int VAR = info[I].ToNumber().Uint32Value();
#define USE_UINT32_ARG(I, VAR, DEF) \
CHECK_LET_ARG(I, IsNumber(), "Uint32"); \
unsigned int VAR = IS_ARG_EMPTY(I) \
? (DEF) \
: info[I].ToNumber().Uint32Value();
#define LET_UINT32_ARG(I, VAR) USE_UINT32_ARG(I, VAR, 0)
#define REQ_UINT_ARG(I, VAR) REQ_UINT_ARG(I, VAR)
#define USE_UINT_ARG(I, VAR, DEF) USE_UINT32_ARG(I, VAR, DEF)
#define LET_UINT_ARG(I, VAR) LET_UINT32_ARG(I, VAR)
#define REQ_BOOL_ARG(I, VAR) \
CHECK_REQ_ARG(I, IsBoolean(), "Bool"); \
bool VAR = info[I].ToBoolean().Value();
#define USE_BOOL_ARG(I, VAR, DEF) \
CHECK_LET_ARG(I, IsBoolean(), "Bool"); \
bool VAR = IS_ARG_EMPTY(I) ? (DEF) : info[I].ToBoolean().Value();
#define LET_BOOL_ARG(I, VAR) USE_BOOL_ARG(I, VAR, false)
#define REQ_OFFS_ARG(I, VAR) \
CHECK_REQ_ARG(I, IsNumber(), "Number"); \
size_t VAR = static_cast<size_t>(info[I].ToNumber().DoubleValue());
#define USE_OFFS_ARG(I, VAR, DEF) \
CHECK_LET_ARG(I, IsNumber(), "Number"); \
size_t VAR = IS_ARG_EMPTY(I) \
? (DEF) \
: static_cast<size_t>(info[I].ToNumber().DoubleValue());
#define LET_OFFS_ARG(I, VAR) USE_OFFS_ARG(I, VAR, 0)
#define REQ_DOUBLE_ARG(I, VAR) \
CHECK_REQ_ARG(I, IsNumber(), "Number"); \
double VAR = info[I].ToNumber().DoubleValue();
#define USE_DOUBLE_ARG(I, VAR, DEF) \
CHECK_LET_ARG(I, IsNumber(), "Number"); \
double VAR = IS_ARG_EMPTY(I) ? (DEF) : info[I].ToNumber().DoubleValue();
#define LET_DOUBLE_ARG(I, VAR) USE_DOUBLE_ARG(I, VAR, 0.0)
#define REQ_FLOAT_ARG(I, VAR) \
CHECK_REQ_ARG(I, IsNumber(), "Number"); \
float VAR = info[I].ToNumber().FloatValue();
#define USE_FLOAT_ARG(I, VAR, DEF) \
CHECK_LET_ARG(I, IsNumber(), "Number"); \
float VAR = IS_ARG_EMPTY(I) ? (DEF) : info[I].ToNumber().FloatValue();
#define LET_FLOAT_ARG(I, VAR) USE_FLOAT_ARG(I, VAR, 0.f)
#define REQ_EXT_ARG(I, VAR) \
CHECK_REQ_ARG(I, IsExternal(), "Pointer"); \
Napi::External VAR = info[I].As<Napi::External>();
#define USE_EXT_ARG(I, VAR, DEF) \
CHECK_LET_ARG(I, IsExternal(), "Pointer"); \
Napi::External VAR = IS_ARG_EMPTY(I) ? (DEF) : info[I].As<Napi::External>();
#define LET_EXT_ARG(I, VAR) USE_EXT_ARG(I, VAR, JS_EXT(nullptr))
#define REQ_FUN_ARG(I, VAR) \
CHECK_REQ_ARG(I, IsFunction(), "Function"); \
Napi::Function VAR = info[I].As<Napi::Function>();
#define REQ_OBJ_ARG(I, VAR) \
CHECK_REQ_ARG(I, IsObject(), "Object"); \
Napi::Object VAR = info[I].As<Napi::Object>();
#define USE_OBJ_ARG(I, VAR, DEF) \
CHECK_LET_ARG(I, IsObject(), "Object"); \
Napi::Object VAR = IS_ARG_EMPTY(I) ? (DEF) : info[I].As<Napi::Object>();
#define LET_OBJ_ARG(I, VAR) USE_OBJ_ARG(I, VAR, info[I].As<Napi::Object>())
#define REQ_ARRV_ARG(I, VAR) \
CHECK_REQ_ARG(I, IsArrayBuffer(), "Object"); \
Napi::ArrayBuffer VAR = info[I].As<Napi::ArrayBuffer>();
#define REQ_BUF_ARG(I, VAR) \
CHECK_REQ_ARG(I, IsBuffer(), "Buffer"); \
Napi::Buffer<uint8_t> VAR = info[I].As< Napi::Buffer<uint8_t> >();
#define CTOR_CHECK(T) \
if ( ! info.IsConstructCall() ) \
JS_THROW(T " must be called with the 'new' keyword.");
#define DES_CHECK \
if (_isDestroyed) return;
#define THIS_CHECK \
NAPI_ENV; \
if (_isDestroyed) RET_UNDEFINED;
#define SETTER_CHECK(C, T) \
if ( ! value.C ) \
JS_THROW("Value must be " T);
#define JS_METHOD(NAME) Napi::Value NAME(const Napi::CallbackInfo &info)
#define JS_GETTER(NAME) Napi::Value NAME(const Napi::CallbackInfo &info)
#define JS_SETTER(NAME) \
void NAME(const Napi::CallbackInfo &info, const Napi::Value &value)
#define ACCESSOR_RW(CLASS, NAME) \
InstanceAccessor(#NAME, &CLASS::NAME ## Getter, &CLASS::NAME ## Setter)
#define ACCESSOR_R(CLASS, NAME) \
InstanceAccessor(#NAME, &CLASS::NAME ## Getter, nullptr)
#define ACCESSOR_M(CLASS, NAME) \
InstanceMethod(#NAME, &CLASS::NAME)
#define THIS_OBJ(VAR) \
Napi::Object VAR = info.This().As<Napi::Object>();
#define SETTER_STR_ARG \
SETTER_CHECK(IsNumber(), "String"); \
std::string v = value.ToString().Utf8Value();
#define SETTER_INT32_ARG \
SETTER_CHECK(IsNumber(), "Int32"); \
int v = value.ToNumber().Int32Value();
#define SETTER_INT_ARG SETTER_INT32_ARG
#define SETTER_BOOL_ARG \
SETTER_CHECK(IsBoolean(), "Bool"); \
bool v = value.ToBoolean().Value();
#define SETTER_UINT32_ARG \
SETTER_CHECK(IsNumber(), "Uint32"); \
unsigned int v = value.ToNumber().Uint32Value();
#define SETTER_UINT_ARG SETTER_UINT32_ARG
#define SETTER_OFFS_ARG \
SETTER_CHECK(IsNumber(), "Number"); \
size_t v = static_cast<size_t>(value.ToNumber().DoubleValue());
#define SETTER_DOUBLE_ARG \
SETTER_CHECK(IsNumber(), "Number"); \
double v = value.ToNumber().DoubleValue();
#define SETTER_FLOAT_ARG \
SETTER_CHECK(IsNumber(), "Number"); \
float v = value.ToNumber().FloatValue();
#define SETTER_EXT_ARG \
SETTER_CHECK(IsExternal(), "Pointer"); \
Napi::External v = value.As<Napi::External>();
#define SETTER_FUN_ARG \
SETTER_CHECK(IsFunction(), "Function"); \
Napi::Function v = value.As<Napi::Function>()
#define SETTER_OBJ_ARG \
SETTER_CHECK(IsObject(), "Object"); \
Napi::Object v = value.As<Napi::Object>()
#define SETTER_ARRV_ARG \
SETTER_CHECK(IsArrayBuffer(), "TypedArray"); \
Napi::ArrayBuffer v = value.As<Napi::ArrayBuffer>();
#define GET_AND_THROW_LAST_ERROR() \
do { \
const napi_extended_error_info *error_info; \
napi_get_last_error_info((env), &error_info); \
bool is_pending; \
napi_is_exception_pending((env), &is_pending); \
/* If an exception is already pending, don't rethrow it */ \
if (!is_pending) { \
const char* error_message = error_info->error_message != NULL \
? error_info->error_message \
: "empty error message"; \
JS_THROW(error_message); \
} \
} while (0)
#define NAPI_CALL(the_call, ATE) \
do { \
if ((the_call) != napi_ok) { \
GET_AND_THROW_LAST_ERROR(); \
ATE; \
} \
} while (0)
#define JS_RUN_3(code, VAR, ATE) \
napi_value __RESULT_ ## VAR; \
NAPI_CALL( \
napi_run_script(env, napi_value(JS_STR(code)), &__RESULT_ ## VAR), \
ATE \
); \
Napi::Value VAR(env, __RESULT_ ## VAR);
#define JS_RUN_2(code, VAR) JS_RUN_3(code, VAR, return)
#define JS_RUN JS_RUN_3
template<typename Type>
inline Type* getArrayData(Napi::Env env, Napi::Object obj, int *num = nullptr) {
Type *data = nullptr;
if (data.IsTypedArray()) {
Napi::ArrayBuffer arr = obj.As<Napi::TypedArray>().ArrayBuffer();
if (num) {
*num = arr.ByteLength() / sizeof(Type);
}
data = static_cast<Type *>(arr.Data());
} else if (data.IsArrayBuffer()) {
Napi::ArrayBuffer arr = obj.As<Napi::ArrayBuffer>();
if (num) {
*num = arr.ByteLength() / sizeof(Type);
}
data = static_cast<Type *>(arr.Data());
} else {
if (num) {
*num = 0;
}
JS_THROW("Argument must be of type `TypedArray`.");
}
return data;
}
template<typename Type>
inline Type* getBufferData(Napi::Env env, Napi::Object obj, int *num = nullptr) {
Type *data = nullptr;
if (num) {
*num = 0;
}
if ( ! obj.IsBuffer() ) {
JS_THROW("Argument must be of type `Buffer`.");
return data;
}
Napi::Buffer<uint8_t> arr = obj.As< Napi::Buffer<uint8_t> >();
if (num) {
*num = arr.Length() / sizeof(Type);
}
data = arr.Data();
return data;
}
inline void *getData(Napi::Env env, Napi::Object obj) {
void *pixels = nullptr;
if (obj.IsArrayBuffer()) {
pixels = getArrayData<unsigned char>(env, obj);
} else if (obj.IsTypedArray()) {
pixels = getArrayData<unsigned char>(
env,
obj.As<Napi::TypedArray>().ArrayBuffer()
);
} else if (obj.Has("data")) {
Napi::Object data = obj.Get("data").As<Napi::Object>();
if (data.IsArrayBuffer()) {
pixels = getArrayData<unsigned char>(env, data);
} else if (data.IsBuffer()) {
pixels = getBufferData<unsigned char>(env, data);
} else if (data.IsTypedArray()) {
pixels = getArrayData<unsigned char>(
env,
data.As<Napi::TypedArray>().ArrayBuffer()
);
}
}
return pixels;
}
inline void consoleLog(Napi::Env env, int argc, Napi::Value *argv) {
JS_RUN_2("((...args) => console.log(...args))", log);
std::vector<napi_value> args;
for (int i = 0; i < argc; i++) {
args.push_back(napi_value(argv[i]));
}
log.As<Napi::Function>().Call(napi_value(env.Null()), args);
}
inline void consoleLog(Napi::Env env, const std::string &message) {
Napi::Value arg = JS_STR(message);
consoleLog(env, 1, &arg);
}
inline void eventEmit(
Napi::Env env,
Napi::Object that,
const std::string &name,
int argc = 0,
Napi::Value *argv = nullptr
) {
if ( ! that.Has("emit") ) {
return;
}
Napi::String eventName = JS_STR(name);
Napi::Function thatEmit = that.Get("emit").As<Napi::Function>();
std::vector<napi_value> args;
args.push_back(napi_value(eventName));
for (int i = 0; i < argc; i++) {
args.push_back(napi_value(argv[i]));
}
thatEmit.Call(napi_value(that), args);
}
#endif // _ADDON_TOOLS_HPP_