From 8cf1ac9e1754b80693ce65b11d2bd9ef6c100934 Mon Sep 17 00:00:00 2001 From: Kasumi Hanazuki Date: Sat, 23 May 2026 08:47:58 +0000 Subject: [PATCH] introduce Callback to unify TSFN callbacks --- src/Callback.hpp | 83 +++++++++++++++++++++++++ src/JsonnetImportCallback.cpp | 113 ++++++++++++---------------------- src/JsonnetImportCallback.hpp | 57 ++++------------- src/JsonnetNativeCallback.cpp | 63 +++++-------------- src/JsonnetNativeCallback.hpp | 54 ++++------------ 5 files changed, 162 insertions(+), 208 deletions(-) create mode 100644 src/Callback.hpp diff --git a/src/Callback.hpp b/src/Callback.hpp new file mode 100644 index 00000000..4c4bb7a4 --- /dev/null +++ b/src/Callback.hpp @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: MIT +#pragma once + +#include +#include +#include +#include +#include + +#include "JsonnetVm.hpp" + +namespace nodejsonnet { + + template + struct CallbackPayload { + explicit CallbackPayload(std::shared_ptr vm) : vm{std::move(vm)} {} + + void setResult(Result value) { result.set_value(std::move(value)); } + void setError(std::exception_ptr e) { result.set_exception(e); } + std::future getFuture() { return result.get_future(); } + + protected: + std::shared_ptr getVm() const { return vm; } + + private: + std::shared_ptr vm; + std::promise result; + }; + + template + class Callback { + public: + Callback(Napi::Env env, Napi::Function fun) + : tsfn{ThreadSafeFunction::New(env, fun, PayloadType::resourceName, 0, 1)} {} + + ~Callback() { tsfn.Release(); } + + Callback(Callback const &) = delete; + Callback &operator=(Callback const &) = delete; + + template + auto call(std::shared_ptr vm, Args &&...args) { + // This function runs in a worker thread and cannot access Node VM. + PayloadType payload(std::move(vm), std::forward(args)...); + tsfn.BlockingCall(&payload); + return payload.getFuture().get(); + } + + private: + static void callback(Napi::Env env, Napi::Function fun, std::nullptr_t *, PayloadType *payload) { + // This function runs in the Node main thread. + + auto const result = fun.Call(payload->makeArgs(env)); + + if(!result.IsPromise()) { + payload->resolveResult(result); + return; + } + + auto const on_success = Napi::Function::New( + env, + [](Napi::CallbackInfo const &info) { + static_cast(info.Data())->resolveResult(info[0]); + }, + "onSuccess", payload); + + auto const on_failure = Napi::Function::New( + env, + [](Napi::CallbackInfo const &info) { + auto const p = static_cast(info.Data()); + auto const error = info[0].ToString(); + p->setError(std::make_exception_ptr(std::runtime_error(error))); + }, + "onFailure", payload); + + result.template As().Then(on_success, on_failure); + } + + using ThreadSafeFunction = Napi::TypedThreadSafeFunction; + ThreadSafeFunction tsfn; + }; + +} diff --git a/src/JsonnetImportCallback.cpp b/src/JsonnetImportCallback.cpp index 7731dfe9..9a2cc2fe 100644 --- a/src/JsonnetImportCallback.cpp +++ b/src/JsonnetImportCallback.cpp @@ -1,90 +1,55 @@ // SPDX-License-Identifier: MIT #include "JsonnetImportCallback.hpp" #include -#include namespace nodejsonnet { - JsonnetImportCallback::JsonnetImportCallback(Napi::Env env, Napi::Function fun) - : tsfn{ThreadSafeFunction::New(env, fun, "Jsonnet Import Callback", 0, 1)} { - } - - JsonnetImportCallback::~JsonnetImportCallback() { - this->tsfn.Release(); - } - - JsonnetVm::ImportResult JsonnetImportCallback::call( - std::shared_ptr vm, std::string const &base, std::string const &rel) { - // This function runs in a worker thread and cannot access Node VM. - - Payload payload(std::move(vm), base, rel); - tsfn.BlockingCall(&payload); - return payload.getFuture().get(); - } - - void JsonnetImportCallback::resolveResult(Payload *payload, Napi::Value val) { - auto const obj = val.As(); - auto const vm = payload->getVm(); + namespace detail { - auto const foundHereStr = obj.Get("foundHere").As().Utf8Value(); - auto foundHereBuf = vm->allocBuffer(foundHereStr.size() + 1); - std::memcpy(foundHereBuf.get(), foundHereStr.c_str(), foundHereStr.size() + 1); + ImportCallbackPayload::ImportCallbackPayload( + std::shared_ptr vm, std::string base, std::string rel) + : CallbackPayload{std::move(vm)}, base{std::move(base)}, rel{std::move(rel)} {} - auto const contentVal = obj.Get("content"); - JsonnetVm::Buffer contentBuf; - size_t contentLen; - if(contentVal.IsString()) { - auto const str = contentVal.As().Utf8Value(); - contentBuf = vm->allocBuffer(str.size()); - std::memcpy(contentBuf.get(), str.data(), str.size()); - contentLen = str.size(); - } else { - auto const ta = contentVal.As(); - contentBuf = vm->allocBuffer(ta.ByteLength()); - std::memcpy(contentBuf.get(), static_cast(ta.ArrayBuffer().Data()) + ta.ByteOffset(), - ta.ByteLength()); - contentLen = ta.ByteLength(); + std::vector ImportCallbackPayload::makeArgs(Napi::Env env) const { + return { + Napi::String::New(env, base), + Napi::String::New(env, rel), + }; } - payload->setResult(JsonnetVm::ImportResult{ - std::move(foundHereBuf), - std::move(contentBuf), - contentLen, - }); - } - - void JsonnetImportCallback::callback( - Napi::Env env, Napi::Function fun, std::nullptr_t *, Payload *payload) { - // This function runs in the Node main thread. - - auto const base = Napi::String::New(env, payload->getBase()); - auto const rel = Napi::String::New(env, payload->getRel()); - - auto const result = fun.Call({base, rel}); - - if(!result.IsPromise()) { - resolveResult(payload, result); - return; + void ImportCallbackPayload::resolveResult(Napi::Value val) { + auto const obj = val.As(); + auto const vm = getVm(); + + auto const foundHereStr = obj.Get("foundHere").As().Utf8Value(); + auto foundHereBuf = vm->allocBuffer(foundHereStr.size() + 1); + std::memcpy(foundHereBuf.get(), foundHereStr.c_str(), foundHereStr.size() + 1); + + auto const contentVal = obj.Get("content"); + JsonnetVm::Buffer contentBuf; + size_t contentLen; + + if(contentVal.IsString()) { + auto const str = contentVal.As().Utf8Value(); + contentBuf = vm->allocBuffer(str.size()); + std::memcpy(contentBuf.get(), str.data(), str.size()); + contentLen = str.size(); + } else { + auto const ta = contentVal.As(); + contentBuf = vm->allocBuffer(ta.ByteLength()); + std::memcpy(contentBuf.get(), static_cast(ta.ArrayBuffer().Data()) + ta.ByteOffset(), + ta.ByteLength()); + contentLen = ta.ByteLength(); + } + + setResult(JsonnetVm::ImportResult{ + std::move(foundHereBuf), + std::move(contentBuf), + contentLen, + }); } - auto const on_success = Napi::Function::New( - env, - [](Napi::CallbackInfo const &info) { - resolveResult(static_cast(info.Data()), info[0]); - }, - "onSuccess", payload); - - auto const on_failure = Napi::Function::New( - env, - [](Napi::CallbackInfo const &info) { - auto const payload = static_cast(info.Data()); - auto const error = info[0].ToString(); - payload->setError(std::make_exception_ptr(std::runtime_error(error))); - }, - "onFailure", payload); - - result.As().Then(on_success, on_failure); } } diff --git a/src/JsonnetImportCallback.hpp b/src/JsonnetImportCallback.hpp index 96782a9b..0e8b6fd4 100644 --- a/src/JsonnetImportCallback.hpp +++ b/src/JsonnetImportCallback.hpp @@ -1,66 +1,33 @@ // SPDX-License-Identifier: MIT #pragma once -#include #include #include -#include -#include "JsonnetVm.hpp" -namespace nodejsonnet { - - class JsonnetImportCallback { - public: - JsonnetImportCallback(Napi::Env env, Napi::Function fun); - ~JsonnetImportCallback(); - - JsonnetImportCallback(JsonnetImportCallback const &) = delete; - JsonnetImportCallback &operator=(JsonnetImportCallback const &) = delete; +#include "Callback.hpp" - JsonnetVm::ImportResult call( - std::shared_ptr vm, std::string const &base, std::string const &rel); - - private: - struct Payload { - Payload(std::shared_ptr vm, std::string base, std::string rel) - : vm{std::move(vm)}, base{std::move(base)}, rel{std::move(rel)} { - } +namespace nodejsonnet { - std::shared_ptr getVm() const { - return vm; - } - std::string const &getBase() const { - return base; - } - std::string const &getRel() const { - return rel; - } + namespace detail { - void setResult(JsonnetVm::ImportResult value) { - result.set_value(std::move(value)); - } + struct ImportCallbackPayload : CallbackPayload { + static constexpr char resourceName[] = "Jsonnet Import Callback"; - void setError(std::exception_ptr e) { - result.set_exception(e); - } + ImportCallbackPayload(std::shared_ptr vm, std::string base, std::string rel); - std::future getFuture() { - return result.get_future(); - } + std::vector makeArgs(Napi::Env env) const; + void resolveResult(Napi::Value val); private: - std::shared_ptr vm; std::string base; std::string rel; - std::promise result; }; - static void resolveResult(Payload *payload, Napi::Value val); - static void callback(Napi::Env env, Napi::Function fun, std::nullptr_t *, Payload *payload); + } - using ThreadSafeFunction = Napi::TypedThreadSafeFunction; - - ThreadSafeFunction tsfn; + class JsonnetImportCallback : public Callback { + public: + using Callback::Callback; }; } diff --git a/src/JsonnetNativeCallback.cpp b/src/JsonnetNativeCallback.cpp index 98152859..33df974e 100644 --- a/src/JsonnetNativeCallback.cpp +++ b/src/JsonnetNativeCallback.cpp @@ -1,62 +1,31 @@ // SPDX-License-Identifier: MIT #include "JsonnetNativeCallback.hpp" +#include "JsonValueConverter.hpp" namespace nodejsonnet { - JsonnetNativeCallback::JsonnetNativeCallback(Napi::Env env, Napi::Function fun) - : tsfn{ThreadSafeFunction::New(env, fun, "Jsonnet Native Callback", 0, 1)} { - } - - JsonnetNativeCallback::~JsonnetNativeCallback() { - this->tsfn.Release(); - } - - JsonnetJsonValue *JsonnetNativeCallback::call( - std::shared_ptr vm, std::vector args) { - // This functions runs in a worker thread and cannot access Node VM. - - Payload payload(std::move(vm), std::move(args)); - tsfn.BlockingCall(&payload); - return payload.getFuture().get(); - } + namespace detail { - void JsonnetNativeCallback::callback( - Napi::Env env, Napi::Function fun, std::nullptr_t *, Payload *payload) { - // This functions runs in the Node main thread. + NativeCallbackPayload::NativeCallbackPayload( + std::shared_ptr vm, std::vector args) + : CallbackPayload{std::move(vm)}, args{std::move(args)} {} - JsonValueConverter const conv{payload->getVm()}; - std::vector args; - args.reserve(payload->getArgs().size()); - for(auto const arg: payload->getArgs()) { - args.push_back(conv.toNapiValue(env, arg)); + std::vector NativeCallbackPayload::makeArgs(Napi::Env env) const { + JsonValueConverter const conv{getVm()}; + std::vector ret; + ret.reserve(args.size()); + for(auto const arg : args) { + ret.push_back(conv.toNapiValue(env, arg)); + } + return ret; } - auto const result = fun.Call(args); - if(!result.IsPromise()) { - payload->setResult(conv.toJsonnetJson(result)); - return; + void NativeCallbackPayload::resolveResult(Napi::Value val) { + JsonValueConverter const conv{getVm()}; + setResult(conv.toJsonnetJson(val)); } - auto const on_success = Napi::Function::New( - env, - [](Napi::CallbackInfo const &info) { - auto const payload = static_cast(info.Data()); - JsonValueConverter const conv{payload->getVm()}; - payload->setResult(conv.toJsonnetJson(info[0])); - }, - "onSuccess", payload); - - auto const on_failure = Napi::Function::New( - env, - [](Napi::CallbackInfo const &info) { - auto const payload = static_cast(info.Data()); - auto const error = info[0].ToString(); - payload->setError(std::make_exception_ptr(std::runtime_error(error))); - }, - "onFailure", payload); - - result.As().Then(on_success, on_failure); } } diff --git a/src/JsonnetNativeCallback.hpp b/src/JsonnetNativeCallback.hpp index 6ff7fe9b..f8970196 100644 --- a/src/JsonnetNativeCallback.hpp +++ b/src/JsonnetNativeCallback.hpp @@ -4,62 +4,32 @@ extern "C" { #include } -#include #include -#include +#include -#include "JsonValueConverter.hpp" +#include "Callback.hpp" namespace nodejsonnet { - class JsonnetNativeCallback { - public: - JsonnetNativeCallback(Napi::Env env, Napi::Function fun); - ~JsonnetNativeCallback(); - - JsonnetNativeCallback(JsonnetNativeCallback const &) = delete; - JsonnetNativeCallback &operator=(JsonnetNativeCallback const &) = delete; - - JsonnetJsonValue *call( - std::shared_ptr vm, std::vector args); - - private: - struct Payload { - Payload(std::shared_ptr vm, std::vector args) - : args{std::move(args)}, vm{std::move(vm)} { - } + namespace detail { - std::vector const &getArgs() const { - return args; - } + struct NativeCallbackPayload : CallbackPayload { + static constexpr char resourceName[] = "Jsonnet Native Callback"; - std::shared_ptr getVm() const { - return vm; - } + NativeCallbackPayload(std::shared_ptr vm, std::vector args); - void setResult(JsonnetJsonValue *value) { - result.set_value(value); - } - - void setError(std::exception_ptr e) { - result.set_exception(e); - } - - std::future getFuture() { - return result.get_future(); - } + std::vector makeArgs(Napi::Env env) const; + void resolveResult(Napi::Value val); private: std::vector args; - std::shared_ptr vm; - std::promise result; }; - static void callback(Napi::Env env, Napi::Function fun, std::nullptr_t *, Payload *payload); + } - using ThreadSafeFunction = Napi::TypedThreadSafeFunction; - - ThreadSafeFunction tsfn; + class JsonnetNativeCallback : public Callback { + public: + using Callback::Callback; }; }