Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 83 additions & 0 deletions src/Callback.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// SPDX-License-Identifier: MIT
#pragma once

#include <exception>
#include <future>
#include <memory>
#include <stdexcept>
#include <napi.h>

#include "JsonnetVm.hpp"

namespace nodejsonnet {

template <typename Result>
struct CallbackPayload {
explicit CallbackPayload(std::shared_ptr<JsonnetVm> 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<Result> getFuture() { return result.get_future(); }

protected:
std::shared_ptr<JsonnetVm> getVm() const { return vm; }

private:
std::shared_ptr<JsonnetVm> vm;
std::promise<Result> result;
};

template <typename PayloadType>
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 <typename... Args>
auto call(std::shared_ptr<JsonnetVm> vm, Args &&...args) {
// This function runs in a worker thread and cannot access Node VM.
PayloadType payload(std::move(vm), std::forward<Args>(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<PayloadType *>(info.Data())->resolveResult(info[0]);
},
"onSuccess", payload);

auto const on_failure = Napi::Function::New(
env,
[](Napi::CallbackInfo const &info) {
auto const p = static_cast<PayloadType *>(info.Data());
auto const error = info[0].ToString();
p->setError(std::make_exception_ptr(std::runtime_error(error)));
},
"onFailure", payload);

result.template As<Napi::Promise>().Then(on_success, on_failure);
}

using ThreadSafeFunction = Napi::TypedThreadSafeFunction<std::nullptr_t, PayloadType, callback>;
ThreadSafeFunction tsfn;
};

}
113 changes: 39 additions & 74 deletions src/JsonnetImportCallback.cpp
Original file line number Diff line number Diff line change
@@ -1,90 +1,55 @@
// SPDX-License-Identifier: MIT
#include "JsonnetImportCallback.hpp"
#include <cstring>
#include <stdexcept>

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<JsonnetVm> 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<Napi::Object>();
auto const vm = payload->getVm();
namespace detail {

auto const foundHereStr = obj.Get("foundHere").As<Napi::String>().Utf8Value();
auto foundHereBuf = vm->allocBuffer(foundHereStr.size() + 1);
std::memcpy(foundHereBuf.get(), foundHereStr.c_str(), foundHereStr.size() + 1);
ImportCallbackPayload::ImportCallbackPayload(
std::shared_ptr<JsonnetVm> 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<Napi::String>().Utf8Value();
contentBuf = vm->allocBuffer(str.size());
std::memcpy(contentBuf.get(), str.data(), str.size());
contentLen = str.size();
} else {
auto const ta = contentVal.As<Napi::TypedArray>();
contentBuf = vm->allocBuffer(ta.ByteLength());
std::memcpy(contentBuf.get(), static_cast<char *>(ta.ArrayBuffer().Data()) + ta.ByteOffset(),
ta.ByteLength());
contentLen = ta.ByteLength();
std::vector<napi_value> 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<Napi::Object>();
auto const vm = getVm();

auto const foundHereStr = obj.Get("foundHere").As<Napi::String>().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<Napi::String>().Utf8Value();
contentBuf = vm->allocBuffer(str.size());
std::memcpy(contentBuf.get(), str.data(), str.size());
contentLen = str.size();
} else {
auto const ta = contentVal.As<Napi::TypedArray>();
contentBuf = vm->allocBuffer(ta.ByteLength());
std::memcpy(contentBuf.get(), static_cast<char *>(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<Payload *>(info.Data()), info[0]);
},
"onSuccess", payload);

auto const on_failure = Napi::Function::New(
env,
[](Napi::CallbackInfo const &info) {
auto const payload = static_cast<Payload *>(info.Data());
auto const error = info[0].ToString();
payload->setError(std::make_exception_ptr(std::runtime_error(error)));
},
"onFailure", payload);

result.As<Napi::Promise>().Then(on_success, on_failure);
}

}
57 changes: 12 additions & 45 deletions src/JsonnetImportCallback.hpp
Original file line number Diff line number Diff line change
@@ -1,66 +1,33 @@
// SPDX-License-Identifier: MIT
#pragma once

#include <future>
#include <memory>
#include <string>
#include <napi.h>
#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<JsonnetVm> vm, std::string const &base, std::string const &rel);

private:
struct Payload {
Payload(std::shared_ptr<JsonnetVm> vm, std::string base, std::string rel)
: vm{std::move(vm)}, base{std::move(base)}, rel{std::move(rel)} {
}
namespace nodejsonnet {

std::shared_ptr<JsonnetVm> 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<JsonnetVm::ImportResult> {
static constexpr char resourceName[] = "Jsonnet Import Callback";

void setError(std::exception_ptr e) {
result.set_exception(e);
}
ImportCallbackPayload(std::shared_ptr<JsonnetVm> vm, std::string base, std::string rel);

std::future<JsonnetVm::ImportResult> getFuture() {
return result.get_future();
}
std::vector<napi_value> makeArgs(Napi::Env env) const;
void resolveResult(Napi::Value val);

private:
std::shared_ptr<JsonnetVm> vm;
std::string base;
std::string rel;
std::promise<JsonnetVm::ImportResult> 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<std::nullptr_t, Payload, callback>;

ThreadSafeFunction tsfn;
class JsonnetImportCallback : public Callback<detail::ImportCallbackPayload> {
public:
using Callback::Callback;
};

}
63 changes: 16 additions & 47 deletions src/JsonnetNativeCallback.cpp
Original file line number Diff line number Diff line change
@@ -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<JsonnetVm> vm, std::vector<JsonnetJsonValue const *> 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<JsonnetVm> vm, std::vector<JsonnetJsonValue const *> args)
: CallbackPayload{std::move(vm)}, args{std::move(args)} {}

JsonValueConverter const conv{payload->getVm()};

std::vector<napi_value> args;
args.reserve(payload->getArgs().size());
for(auto const arg: payload->getArgs()) {
args.push_back(conv.toNapiValue(env, arg));
std::vector<napi_value> NativeCallbackPayload::makeArgs(Napi::Env env) const {
JsonValueConverter const conv{getVm()};
std::vector<napi_value> 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<Payload *>(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<Payload *>(info.Data());
auto const error = info[0].ToString();
payload->setError(std::make_exception_ptr(std::runtime_error(error)));
},
"onFailure", payload);

result.As<Napi::Promise>().Then(on_success, on_failure);
}

}
Loading
Loading