| // Copyright 2021 The Dawn & Tint Authors | |
| // | |
| // Redistribution and use in source and binary forms, with or without | |
| // modification, are permitted provided that the following conditions are met: | |
| // | |
| // 1. Redistributions of source code must retain the above copyright notice, this | |
| // list of conditions and the following disclaimer. | |
| // | |
| // 2. Redistributions in binary form must reproduce the above copyright notice, | |
| // this list of conditions and the following disclaimer in the documentation | |
| // and/or other materials provided with the distribution. | |
| // | |
| // 3. Neither the name of the copyright holder nor the names of its | |
| // contributors may be used to endorse or promote products derived from | |
| // this software without specific prior written permission. | |
| // | |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
| // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
| // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
| // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | |
| // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
| // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |
| // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
| // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |
| // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| // This file provides core interop helpers used by the code generated by the | |
| // templates. | |
| #ifndef SRC_DAWN_NODE_INTEROP_CORE_H_ | |
| #define SRC_DAWN_NODE_INTEROP_CORE_H_ | |
| #include <algorithm> | |
| #include <concepts> | |
| #include <cstdint> | |
| #include <limits> | |
| #include <optional> | |
| #include <string> | |
| #include <type_traits> | |
| #include <unordered_map> | |
| #include <utility> | |
| #include <variant> | |
| #include <vector> | |
| #include "src/dawn/node/interop/NodeAPI.h" | |
| #include "src/dawn/node/utils/Debug.h" | |
| #include "src/utils/assert.h" | |
| #define ENABLE_INTEROP_LOGGING 0 // Enable for verbose interop logging | |
| #if ENABLE_INTEROP_LOGGING | |
| #define INTEROP_LOG(...) LOG(__VA_ARGS__) | |
| #else | |
| #define INTEROP_LOG(...) | |
| #endif | |
| // A helper macro for constructing a PromiseInfo with the current file, function and line. | |
| // See PromiseInfo | |
| #define PROMISE_INFO \ | |
| ::wgpu::interop::PromiseInfo { \ | |
| __FILE__, __FUNCTION__, __LINE__ \ | |
| } | |
| namespace wgpu::interop { | |
| //////////////////////////////////////////////////////////////////////////////// | |
| // Primitive JavaScript types | |
| //////////////////////////////////////////////////////////////////////////////// | |
| using Object = Napi::Object; | |
| using Event = Napi::Object; | |
| using ArrayBuffer = Napi::ArrayBuffer; | |
| using Int8Array = Napi::TypedArrayOf<int8_t>; | |
| using Int16Array = Napi::TypedArrayOf<int16_t>; | |
| using Int32Array = Napi::TypedArrayOf<int32_t>; | |
| using Uint8Array = Napi::TypedArrayOf<uint8_t>; | |
| using Uint16Array = Napi::TypedArrayOf<uint16_t>; | |
| using Uint32Array = Napi::TypedArrayOf<uint32_t>; | |
| using Float32Array = Napi::TypedArrayOf<float>; | |
| using Float64Array = Napi::TypedArrayOf<double>; | |
| using DataView = Napi::TypedArray; | |
| // Datatype used for undefined values. | |
| struct UndefinedType {}; | |
| inline constexpr UndefinedType Undefined; | |
| template <typename T> | |
| using FrozenArray = std::vector<T>; | |
| // A wrapper class for integers that's as transparent as possible and is used to distinguish | |
| // that the type is tagged with the [Clamp] WebIDL attribute. | |
| template <typename T> | |
| requires std::integral<T> | |
| struct ClampedInteger { | |
| using IntegerType = T; | |
| ClampedInteger() : value(0) {} | |
| explicit(false) ClampedInteger(T v) : value(v) {} | |
| // NOLINTNEXTLINE(google-explicit-constructor) | |
| operator T() const { return value; } | |
| T value; | |
| }; | |
| // A wrapper class for integers that's as transparent as possible and is used to distinguish | |
| // that the type is tagged with the [EnforceRange] WebIDL attribute. | |
| template <typename T> | |
| requires std::integral<T> | |
| struct EnforceRangeInteger { | |
| using IntegerType = T; | |
| EnforceRangeInteger() : value(0) {} | |
| explicit(false) EnforceRangeInteger(T v) : value(v) {} | |
| // NOLINTNEXTLINE(google-explicit-constructor) | |
| operator T() const { return value; } | |
| T value; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////////// | |
| // Result | |
| //////////////////////////////////////////////////////////////////////////////// | |
| // Result is used to hold an success / error state by functions that perform JS <-> C++ | |
| // conversion | |
| struct [[nodiscard]] Result { | |
| // Returns true if the operation succeeded, false if there was an error | |
| inline explicit operator bool() const { return error.empty(); } | |
| // If Result is an error, then a new Error is returned with the | |
| // stringified values append to the error message. | |
| // If Result is a success, then a success Result is returned. | |
| template <typename... VALUES> | |
| Result Append(VALUES&&... values) { | |
| if (*this) { | |
| return *this; | |
| } | |
| std::stringstream ss; | |
| ss << error << "\n"; | |
| utils::Write(ss, std::forward<VALUES>(values)...); | |
| return {ss.str()}; | |
| } | |
| // The error message, if the operation failed. | |
| std::string error; | |
| }; | |
| // A successful result | |
| extern Result Success; | |
| // Returns a Result with the given error message | |
| Result Error(std::string msg); | |
| //////////////////////////////////////////////////////////////////////////////// | |
| // Interface<T> | |
| //////////////////////////////////////////////////////////////////////////////// | |
| // Interface<T> is a templated wrapper around a JavaScript object, which | |
| // implements the template-generated interface type T. Interfaces are returned | |
| // by either calling T::Bind() or T::Create(). | |
| template <typename T> | |
| class Interface { | |
| public: | |
| // Constructs an Interface with no JS object. | |
| inline Interface() {} | |
| // Constructs an Interface wrapping the given JS object. | |
| // The JS object must have been created with a call to T::Bind(). | |
| explicit inline Interface(Napi::Object o) : object(o) {} | |
| // Implicit conversion operators to Napi objects. | |
| // NOLINTNEXTLINE(google-explicit-constructor) | |
| inline operator napi_value() const { return object; } | |
| // NOLINTNEXTLINE(google-explicit-constructor) | |
| inline operator const Napi::Value&() const { return object; } | |
| // NOLINTNEXTLINE(google-explicit-constructor) | |
| inline operator const Napi::Object&() const { return object; } | |
| // Member and dereference operators | |
| inline T* operator->() const { return T::Unwrap(object); } | |
| inline T* operator*() const { return T::Unwrap(object); } | |
| // As<IMPL>() returns the unwrapped object cast to the implementation type. | |
| // The interface implementation *must* be of the template type IMPL. | |
| template <typename IMPL> | |
| inline IMPL* As() const { | |
| return static_cast<IMPL*>(T::Unwrap(object)); | |
| } | |
| private: | |
| Napi::Object object; | |
| }; | |
| //////////////////////////////////////////////////////////////////////////////// | |
| // Promise<T> | |
| //////////////////////////////////////////////////////////////////////////////// | |
| // Info holds details about where the promise was constructed. | |
| // Used for printing debug messages when a promise is finalized without being resolved | |
| // or rejected. | |
| // Use the PROMISE_INFO macro to populate this structure. | |
| struct PromiseInfo { | |
| const char* file = nullptr; | |
| const char* function = nullptr; | |
| int line = 0; | |
| }; | |
| enum class PromiseState { | |
| Pending, | |
| Resolved, | |
| Rejected, | |
| Discarded, | |
| }; | |
| // A tag used in a Promise constructor to say it won't be used and doesn't need to be initialized. | |
| struct UnusedPromiseTag {}; | |
| inline constexpr UnusedPromiseTag kUnusedPromise; | |
| namespace detail { | |
| // Base class for Promise<T> specializations. | |
| class PromiseBase { | |
| public: | |
| // Implicit conversion operators to Napi promises. | |
| // NOLINTNEXTLINE(google-explicit-constructor) | |
| inline operator napi_value() const { | |
| return state_ ? static_cast<napi_value>(state_->deferred.Promise()) : nullptr; | |
| } | |
| // NOLINTNEXTLINE(google-explicit-constructor) | |
| inline operator Napi::Value() const { | |
| return state_ ? state_->deferred.Promise() : Napi::Value(); | |
| } | |
| // NOLINTNEXTLINE(google-explicit-constructor) | |
| inline operator Napi::Promise() const { | |
| return state_ ? state_->deferred.Promise() : Napi::Promise(nullptr, nullptr); | |
| } | |
| // Comparison operator between promises | |
| friend bool operator==(const PromiseBase& lhs, const PromiseBase& rhs) { | |
| return lhs.state_ == rhs.state_; | |
| } | |
| friend bool operator!=(const PromiseBase& lhs, const PromiseBase& rhs) { return !(lhs == rhs); } | |
| // Reject() rejects the promise with the given failure value. | |
| void Reject(Napi::Value value) const { | |
| state_->deferred.Reject(value); | |
| state_->state = PromiseState::Rejected; | |
| } | |
| void Reject(Napi::Error err) const { Reject(err.Value()); } | |
| void Reject(std::string err) const { Reject(Napi::Error::New(state_->deferred.Env(), err)); } | |
| PromiseState GetState() const { return state_->state; } | |
| // Place the promise into the Discarded state. | |
| // This state will not throw a fatal error if this promise has not been rejected or resolved by | |
| // the time it is finalized. This is useful for Node tear-down where the attempting to create | |
| // new objects will result in a fatal error. | |
| void Discard() { state_->state = PromiseState::Discarded; } | |
| protected: | |
| void Resolve(Napi::Value value) const { | |
| state_->deferred.Resolve(value); | |
| state_->state = PromiseState::Resolved; | |
| } | |
| struct State { | |
| Napi::Promise::Deferred deferred; | |
| PromiseInfo info; | |
| PromiseState state = PromiseState::Pending; | |
| }; | |
| explicit PromiseBase(UnusedPromiseTag) : state_(nullptr) {} | |
| PromiseBase(Napi::Env env, const PromiseInfo& info) | |
| : state_(new State{Napi::Promise::Deferred::New(env), info}) { | |
| state_->deferred.Promise().AddFinalizer( | |
| [](Napi::Env, State* state) { | |
| if (state->state == PromiseState::Pending) { | |
| ::wgpu::utils::Fatal("Promise not resolved or rejected", state->info.file, | |
| state->info.line, state->info.function); | |
| } | |
| delete state; | |
| }, | |
| state_); | |
| } | |
| State* const state_; | |
| }; | |
| } // namespace detail | |
| // Promise<T> is a templated wrapper around a JavaScript promise, which can | |
| // resolve to the template type T. | |
| template <typename T> | |
| class Promise : public detail::PromiseBase { | |
| public: | |
| // Constructor | |
| Promise(Napi::Env env, const PromiseInfo& info) : PromiseBase(env, info) {} | |
| Promise(Napi::Env, UnusedPromiseTag) : PromiseBase(kUnusedPromise) {} | |
| // Resolve() fulfills the promise with the given value. | |
| void Resolve(T&& value) const { | |
| PromiseBase::Resolve(ToJS(state_->deferred.Env(), std::forward<T>(value))); | |
| } | |
| }; | |
| // Specialization for Promises that resolve with no value | |
| template <> | |
| class Promise<void> : public detail::PromiseBase { | |
| public: | |
| // Constructor | |
| Promise(Napi::Env env, const PromiseInfo& info) : PromiseBase(env, info) {} | |
| Promise(Napi::Env, UnusedPromiseTag) : PromiseBase(kUnusedPromise) {} | |
| // Resolve() fulfills the promise. | |
| void Resolve() const { PromiseBase::Resolve(state_->deferred.Env().Undefined()); } | |
| }; | |
| //////////////////////////////////////////////////////////////////////////////// | |
| // Converter<T> | |
| //////////////////////////////////////////////////////////////////////////////// | |
| // Converter<T> is specialized for each type T which can be converted from C++ | |
| // to JavaScript, or JavaScript to C++. | |
| // Each specialization of Converter<T> is expected to have two static methods | |
| // with the signatures: | |
| // | |
| // // FromJS() converts the JavaScript value 'in' to the C++ value 'out'. | |
| // static Result FromJS(Napi::Env, Napi::Value in, T& out); | |
| // | |
| // // ToJS() converts the C++ value 'in' to a JavaScript value, and returns | |
| // // this value. | |
| // static Napi::Value ToJS(Napi::Env, T in); | |
| template <typename T> | |
| class Converter {}; | |
| template <> | |
| class Converter<Napi::Object> { | |
| public: | |
| static inline Result FromJS(Napi::Env, Napi::Value value, Napi::Object& out) { | |
| if (value.IsObject()) { | |
| out = value.ToObject(); | |
| return Success; | |
| } | |
| return Error("value is not an object"); | |
| } | |
| static inline Napi::Value ToJS(Napi::Env, Napi::Object value) { return value; } | |
| }; | |
| template <> | |
| class Converter<ArrayBuffer> { | |
| public: | |
| static inline Result FromJS(Napi::Env, Napi::Value value, ArrayBuffer& out) { | |
| if (value.IsArrayBuffer()) { | |
| out = value.As<ArrayBuffer>(); | |
| return Success; | |
| } | |
| return Error("value is not a ArrayBuffer"); | |
| } | |
| static inline Napi::Value ToJS(Napi::Env, ArrayBuffer value) { return value; } | |
| }; | |
| template <> | |
| class Converter<Napi::TypedArray> { | |
| public: | |
| static inline Result FromJS(Napi::Env, Napi::Value value, Napi::TypedArray& out) { | |
| if (value.IsTypedArray()) { | |
| out = value.As<Napi::TypedArray>(); | |
| return Success; | |
| } | |
| return Error("value is not a TypedArray"); | |
| } | |
| static inline Napi::Value ToJS(Napi::Env, ArrayBuffer value) { return value; } | |
| }; | |
| template <typename T> | |
| class Converter<Napi::TypedArrayOf<T>> { | |
| public: | |
| // clang-format off | |
| // The Napi element type of T | |
| static constexpr napi_typedarray_type element_type = | |
| std::is_same<T, int8_t>::value ? napi_int8_array | |
| : std::is_same<T, uint8_t>::value ? napi_uint8_array | |
| : std::is_same<T, int16_t>::value ? napi_int16_array | |
| : std::is_same<T, uint16_t>::value ? napi_uint16_array | |
| : std::is_same<T, int32_t>::value ? napi_int32_array | |
| : std::is_same<T, uint32_t>::value ? napi_uint32_array | |
| : std::is_same<T, float>::value ? napi_float32_array | |
| : std::is_same<T, double>::value ? napi_float64_array | |
| : std::is_same<T, int64_t>::value ? napi_bigint64_array | |
| : std::is_same<T, uint64_t>::value ? napi_biguint64_array | |
| : static_cast<napi_typedarray_type>(-1); | |
| // clang-format on | |
| static_assert(static_cast<int>(element_type) >= 0, | |
| "unsupported T type for Napi::TypedArrayOf<T>"); | |
| static inline Result FromJS(Napi::Env, Napi::Value value, Napi::TypedArrayOf<T>& out) { | |
| if (value.IsTypedArray()) { | |
| auto arr = value.As<Napi::TypedArrayOf<T>>(); | |
| if (arr.TypedArrayType() == element_type) { | |
| out = arr; | |
| return Success; | |
| } | |
| return Error("value is not a TypedArray of the correct element type"); | |
| } | |
| return Error("value is not a TypedArray"); | |
| } | |
| static inline Napi::Value ToJS(Napi::Env, ArrayBuffer value) { return value; } | |
| }; | |
| template <> | |
| class Converter<Napi::Function> { | |
| public: | |
| static inline Result FromJS(Napi::Env, Napi::Value value, Napi::Function& out) { | |
| if (value.IsFunction()) { | |
| out = value.As<Napi::Function>(); | |
| return Success; | |
| } | |
| return Error("value is not a Function"); | |
| } | |
| static inline Napi::Value ToJS(Napi::Env, Napi::Function value) { return value; } | |
| }; | |
| template <> | |
| class Converter<std::string> { | |
| public: | |
| static Result FromJS(Napi::Env, Napi::Value, std::string&); | |
| static Napi::Value ToJS(Napi::Env, std::string); | |
| }; | |
| template <> | |
| class Converter<bool> { | |
| public: | |
| static Result FromJS(Napi::Env, Napi::Value, bool&); | |
| static Napi::Value ToJS(Napi::Env, bool); | |
| }; | |
| template <> | |
| class Converter<int8_t> { | |
| public: | |
| static Result FromJS(Napi::Env, Napi::Value, int8_t&); | |
| static Napi::Value ToJS(Napi::Env, int8_t); | |
| }; | |
| template <> | |
| class Converter<uint8_t> { | |
| public: | |
| static Result FromJS(Napi::Env, Napi::Value, uint8_t&); | |
| static Napi::Value ToJS(Napi::Env, uint8_t); | |
| }; | |
| template <> | |
| class Converter<int16_t> { | |
| public: | |
| static Result FromJS(Napi::Env, Napi::Value, int16_t&); | |
| static Napi::Value ToJS(Napi::Env, int16_t); | |
| }; | |
| template <> | |
| class Converter<uint16_t> { | |
| public: | |
| static Result FromJS(Napi::Env, Napi::Value, uint16_t&); | |
| static Napi::Value ToJS(Napi::Env, uint16_t); | |
| }; | |
| template <> | |
| class Converter<int32_t> { | |
| public: | |
| static Result FromJS(Napi::Env, Napi::Value, int32_t&); | |
| static Napi::Value ToJS(Napi::Env, int32_t); | |
| }; | |
| template <> | |
| class Converter<uint32_t> { | |
| public: | |
| static Result FromJS(Napi::Env, Napi::Value, uint32_t&); | |
| static Napi::Value ToJS(Napi::Env, uint32_t); | |
| }; | |
| template <> | |
| class Converter<int64_t> { | |
| public: | |
| static Result FromJS(Napi::Env, Napi::Value, int64_t&); | |
| static Napi::Value ToJS(Napi::Env, int64_t); | |
| }; | |
| template <> | |
| class Converter<uint64_t> { | |
| public: | |
| static Result FromJS(Napi::Env, Napi::Value, uint64_t&); | |
| static Napi::Value ToJS(Napi::Env, uint64_t); | |
| }; | |
| // Sometimes size_t is uint32_t, or uint64_t. Only define the conversion to size_t if it is a | |
| // unique type, otherwise we have C++ compilation error for the redefinition of a template. | |
| namespace detail { | |
| struct InvalidType; | |
| inline constexpr bool kSizetIsUniqueType = | |
| !std::is_same_v<size_t, uint32_t> && !std::is_same_v<size_t, uint64_t>; | |
| } // namespace detail | |
| template <> | |
| class Converter<std::conditional_t<detail::kSizetIsUniqueType, size_t, detail::InvalidType>> { | |
| public: | |
| static Result FromJS(Napi::Env, Napi::Value, size_t&); | |
| static Napi::Value ToJS(Napi::Env, size_t); | |
| }; | |
| template <> | |
| class Converter<float> { | |
| public: | |
| static Result FromJS(Napi::Env, Napi::Value, float&); | |
| static Napi::Value ToJS(Napi::Env, float); | |
| }; | |
| template <> | |
| class Converter<double> { | |
| public: | |
| static Result FromJS(Napi::Env, Napi::Value, double&); | |
| static Napi::Value ToJS(Napi::Env, double); | |
| }; | |
| // [Clamp]ed integers must convert values outside of the integer range by clamping them. | |
| template <typename T> | |
| class Converter<ClampedInteger<T>> { | |
| public: | |
| static Result FromJS(Napi::Env env, Napi::Value value, ClampedInteger<T>& out) { | |
| double doubleValue; | |
| Result res = Converter<double>::FromJS(env, value, doubleValue); | |
| if (!res) { | |
| return res; | |
| } | |
| // Check for clamping first. | |
| constexpr T kMin = std::numeric_limits<T>::min(); | |
| constexpr T kMax = std::numeric_limits<T>::max(); | |
| if (doubleValue < kMin) { | |
| out = kMin; | |
| return Success; | |
| } | |
| if (doubleValue > kMax) { | |
| out = kMax; | |
| return Success; | |
| } | |
| // Yay, no clamping! We can convert the integer type as usual. | |
| T correctValue; | |
| res = Converter<T>::FromJS(env, value, correctValue); | |
| if (!res) { | |
| return res; | |
| } | |
| out = correctValue; | |
| return Success; | |
| } | |
| static Napi::Value ToJS(Napi::Env env, const ClampedInteger<T>& value) { | |
| return Converter<T>::ToJS(env, value.value); | |
| } | |
| }; | |
| // [EnforceRange] integers cause a TypeError when converted from out of range values | |
| template <typename T> | |
| class Converter<EnforceRangeInteger<T>> { | |
| public: | |
| static Result FromJS(Napi::Env env, Napi::Value value, EnforceRangeInteger<T>& out) { | |
| double doubleValue; | |
| Result res = Converter<double>::FromJS(env, value, doubleValue); | |
| if (!res) { | |
| return res; | |
| } | |
| // Check for out of range and throw a type error. | |
| // Note that the number must both be representable in the integer type, but also below | |
| // MAX_SAFE_INTEGER after which consecutive double values might skip over some integer | |
| // values. | |
| constexpr double kMaxSafeInteger = (uint64_t{1} << 53) - uint64_t{1}; | |
| constexpr double kMinSafeInteger = -kMaxSafeInteger; | |
| constexpr double kMin = | |
| std::max(kMinSafeInteger, static_cast<double>(std::numeric_limits<T>::min())); | |
| constexpr double kMax = | |
| std::min(kMaxSafeInteger, static_cast<double>(std::numeric_limits<T>::max())); | |
| if (!(kMin <= doubleValue && doubleValue <= kMax)) { | |
| return Error("Values are out of the range of that integer."); | |
| } | |
| // Yay, no error! We can convert the integer type as usual. | |
| T correctValue; | |
| res = Converter<T>::FromJS(env, value, correctValue); | |
| if (!res) { | |
| return res; | |
| } | |
| out = correctValue; | |
| return Success; | |
| } | |
| static Napi::Value ToJS(Napi::Env env, const EnforceRangeInteger<T>& value) { | |
| return Converter<T>::ToJS(env, value.value); | |
| } | |
| }; | |
| template <> | |
| class Converter<UndefinedType> { | |
| public: | |
| static Result FromJS(Napi::Env, Napi::Value, UndefinedType&); | |
| static Napi::Value ToJS(Napi::Env, UndefinedType); | |
| }; | |
| std::ostream& operator<<(std::ostream& o, const UndefinedType&); | |
| template <typename T> | |
| class Converter<Interface<T>> { | |
| public: | |
| static Result FromJS(Napi::Env env, Napi::Value value, Interface<T>& out) { | |
| if (!value.IsObject()) { | |
| return Error("value is not object"); | |
| } | |
| auto obj = value.As<Napi::Object>(); | |
| if (!T::Unwrap(obj)) { | |
| return Error("object is not of the correct interface type"); | |
| } | |
| out = Interface<T>(obj); | |
| return Success; | |
| } | |
| static Napi::Value ToJS(Napi::Env env, const Interface<T>& value) { return {env, value}; } | |
| }; | |
| template <typename T> | |
| class Converter<std::optional<T>> { | |
| public: | |
| static Result FromJS(Napi::Env env, Napi::Value value, std::optional<T>& out) { | |
| if (value.IsNull() || value.IsUndefined()) { | |
| out.reset(); | |
| return Success; | |
| } | |
| T v{}; | |
| auto res = Converter<T>::FromJS(env, value, v); | |
| if (!res) { | |
| return res; | |
| } | |
| out = std::move(v); | |
| return Success; | |
| } | |
| static Napi::Value ToJS(Napi::Env env, std::optional<T> value) { | |
| if (value.has_value()) { | |
| return Converter<T>::ToJS(env, value.value()); | |
| } | |
| return env.Null(); | |
| } | |
| }; | |
| template <typename T> | |
| class Converter<std::vector<T>> { | |
| public: | |
| static inline Result FromJS(Napi::Env env, Napi::Value value, std::vector<T>& out) { | |
| if (!value.IsArray()) { | |
| return Error("value is not an array"); | |
| } | |
| auto arr = value.As<Napi::Array>(); | |
| std::vector<T> vec(arr.Length()); | |
| for (size_t i = 0; i < vec.size(); i++) { | |
| auto res = Converter<T>::FromJS(env, arr[static_cast<uint32_t>(i)], vec[i]); | |
| if (!res) { | |
| return res.Append("for array element ", i); | |
| } | |
| } | |
| out = std::move(vec); | |
| return Success; | |
| } | |
| static inline Napi::Value ToJS(Napi::Env env, const std::vector<T>& vec) { | |
| auto arr = Napi::Array::New(env, vec.size()); | |
| for (size_t i = 0; i < vec.size(); i++) { | |
| arr.Set(static_cast<uint32_t>(i), Converter<T>::ToJS(env, vec[i])); | |
| } | |
| return arr; | |
| } | |
| }; | |
| template <typename K, typename V> | |
| class Converter<std::unordered_map<K, V>> { | |
| public: | |
| static inline Result FromJS(Napi::Env env, Napi::Value value, std::unordered_map<K, V>& out) { | |
| if (!value.IsObject()) { | |
| return Error("value is not an object"); | |
| } | |
| auto obj = value.ToObject(); | |
| auto keys = obj.GetPropertyNames(); | |
| std::unordered_map<K, V> map(keys.Length()); | |
| for (uint32_t i = 0; i < static_cast<uint32_t>(keys.Length()); i++) { | |
| K key{}; | |
| V val{}; | |
| auto key_res = Converter<K>::FromJS(env, keys[i], key); | |
| if (!key_res) { | |
| return key_res.Append("for object key"); | |
| } | |
| auto value_res = Converter<V>::FromJS(env, obj.Get(keys[i]), val); | |
| if (!value_res) { | |
| return value_res.Append("for object value of key: ", key); | |
| } | |
| map[key] = val; | |
| } | |
| out = std::move(map); | |
| return Success; | |
| } | |
| static inline Napi::Value ToJS(Napi::Env env, std::unordered_map<K, V> value) { | |
| auto obj = Napi::Object::New(env); | |
| for (auto it : value) { | |
| obj.Set(Converter<K>::ToJS(env, it.first), Converter<V>::ToJS(env, it.second)); | |
| } | |
| return obj; | |
| } | |
| }; | |
| template <typename... TYPES> | |
| class Converter<std::variant<TYPES...>> { | |
| template <typename TY> | |
| static inline Result TryFromJS(Napi::Env env, Napi::Value value, std::variant<TYPES...>& out) { | |
| TY v{}; | |
| auto res = Converter<TY>::FromJS(env, value, v); | |
| if (!res) { | |
| return Error("no possible types matched"); | |
| } | |
| out = std::move(v); | |
| return Success; | |
| } | |
| template <typename T0, typename T1, typename... TN> | |
| static inline Result TryFromJS(Napi::Env env, Napi::Value value, std::variant<TYPES...>& out) { | |
| if (TryFromJS<T0>(env, value, out)) { | |
| return Success; | |
| } | |
| return TryFromJS<T1, TN...>(env, value, out); | |
| } | |
| public: | |
| static inline Result FromJS(Napi::Env env, Napi::Value value, std::variant<TYPES...>& out) { | |
| return TryFromJS<TYPES...>(env, value, out); | |
| } | |
| static inline Napi::Value ToJS(Napi::Env env, std::variant<TYPES...> value) { | |
| return std::visit( | |
| [&](auto&& v) { | |
| using T = std::remove_cv_t<std::remove_reference_t<decltype(v)>>; | |
| return Converter<T>::ToJS(env, v); | |
| }, | |
| value); | |
| } | |
| }; | |
| template <typename T> | |
| class Converter<Promise<T>> { | |
| public: | |
| static inline Result FromJS(Napi::Env env, Napi::Value, Promise<T>&) { UNIMPLEMENTED(env, {}); } | |
| static inline Napi::Value ToJS(Napi::Env, Promise<T> promise) { return promise; } | |
| }; | |
| //////////////////////////////////////////////////////////////////////////////// | |
| // Helpers | |
| //////////////////////////////////////////////////////////////////////////////// | |
| // FromJS() is a helper function which delegates to | |
| // Converter<T>::FromJS() | |
| template <typename T> | |
| inline Result FromJS(Napi::Env env, Napi::Value value, T& out) { | |
| return Converter<T>::FromJS(env, value, out); | |
| } | |
| // FromJSOptional() is similar to FromJS(), but if 'value' is undefined | |
| // then 'out' is left unassigned. | |
| template <typename T> | |
| inline Result FromJSOptional(Napi::Env env, Napi::Value value, T& out) { | |
| if (value.IsUndefined()) { | |
| return Success; | |
| } | |
| return Converter<T>::FromJS(env, value, out); | |
| } | |
| // ToJS() is a helper function which delegates to Converter<T>::ToJS() | |
| template <typename T> | |
| inline Napi::Value ToJS(Napi::Env env, T&& value) { | |
| return Converter<std::remove_cv_t<std::remove_reference_t<T>>>::ToJS(env, | |
| std::forward<T>(value)); | |
| } | |
| // DefaultedParameter can be used in the tuple parameter types passed to | |
| // FromJS(const Napi::CallbackInfo& info, PARAM_TYPES& args), for parameters | |
| // that have a default value. If the argument is omitted in the call, then | |
| // DefaultedParameter::default_value will be assigned to | |
| // DefaultedParameter::value. | |
| template <typename T> | |
| struct DefaultedParameter { | |
| T value; // The argument value assigned by FromJS() | |
| T default_value; // The default value if no argument supplied | |
| // Implicit conversion operator. Returns value. | |
| // NOLINTNEXTLINE(google-explicit-constructor) | |
| inline operator const T&() const { return value; } | |
| }; | |
| // IsDefaultedParameter<T>::value is true iff T is of type DefaultedParameter. | |
| template <typename T> | |
| struct IsDefaultedParameter { | |
| static constexpr bool value = false; | |
| }; | |
| template <typename T> | |
| struct IsDefaultedParameter<DefaultedParameter<T>> { | |
| static constexpr bool value = true; | |
| }; | |
| // FromJS() is a helper function for bulk converting the arguments of 'info'. | |
| // PARAM_TYPES is a std::tuple<> describing the C++ function parameter types. | |
| // Parameters may be of the templated DefaultedParameter type, in which case | |
| // the parameter will default to the default-value if omitted. | |
| template <typename PARAM_TYPES, int BASE_INDEX = 0> | |
| inline Result FromJS(const Napi::CallbackInfo& info, PARAM_TYPES& args) { | |
| if constexpr (BASE_INDEX < std::tuple_size_v<PARAM_TYPES>) { | |
| using T = std::tuple_element_t<BASE_INDEX, PARAM_TYPES>; | |
| auto& value = info[BASE_INDEX]; | |
| auto& out = std::get<BASE_INDEX>(args); | |
| if constexpr (IsDefaultedParameter<T>::value) { | |
| // Parameter has a default value. | |
| // Check whether the argument was provided. | |
| if (value.IsNull()) { | |
| return Success; | |
| } else if (value.IsUndefined()) { | |
| // Use default value for this parameter | |
| out.value = out.default_value; | |
| } else { | |
| // Argument was provided | |
| auto res = FromJS(info.Env(), value, out.value); | |
| if (!res) { | |
| return res; | |
| } | |
| } | |
| } else { | |
| // Parameter does not have a default value. | |
| auto res = FromJS(info.Env(), value, out); | |
| if (!res) { | |
| return res; | |
| } | |
| } | |
| // Convert the rest of the arguments | |
| return FromJS<PARAM_TYPES, BASE_INDEX + 1>(info, args); | |
| } else { | |
| return Success; | |
| } | |
| } | |
| // Calls a promise-returning function f, catching exceptions and returning them into a rejected | |
| // promise instead. This is used to implement the WebIDL semantics for exceptions in | |
| // promise-returning functions. | |
| template <typename F> | |
| Napi::Value CatchExceptionIntoPromise(Napi::Env env, F&& f) { | |
| Napi::Value result = f(); | |
| DAWN_ASSERT(result.IsEmpty() || result.IsPromise()); | |
| if (!env.IsExceptionPending()) { | |
| return result; | |
| } | |
| // For some reason these two calls MUST be in this order or Node crashes in Reject(). | |
| Napi::Error error = env.GetAndClearPendingException(); | |
| Napi::Promise::Deferred deferred = Napi::Promise::Deferred::New(env); | |
| deferred.Reject(error.Value()); | |
| return deferred.Promise(); | |
| } | |
| void ChainPrototype(Napi::Value baseClassValue, Napi::Function derivedClassConstructor); | |
| } // namespace wgpu::interop | |
| #endif // SRC_DAWN_NODE_INTEROP_CORE_H_ |