#pragma once #include "holder.h" #include #include #include #include namespace ivm { void AdjustRemotes(int delta); namespace detail { /** * This is basically a tuple that can be constructed in place in a way that v8::Persistent<> will * accept. */ template struct HandleTupleElement { HandleTupleElement(v8::Isolate* isolate, v8::Local local) : persistent{isolate, local} {} v8::Persistent persistent; }; template struct HandleTupleImpl; template struct HandleTupleImpl, Types_...> : HandleTupleElement... { template explicit HandleTupleImpl(v8::Isolate* isolate, v8::Local... locals) : HandleTupleElement{isolate, locals}... { } template auto get() -> auto& { return HandleTupleElement>>::persistent; } static constexpr auto Size = sizeof...(Types_); using Types = std::tuple; }; template using HandleTuple = HandleTupleImpl, Types...>; } // namespace detail /** * This holds a number of persistent handles to some values in a single isolate. It also holds a * handle to the isolate. When the destructor of this class is called it will run `Reset()` on each * handle in the context of the isolate. If the destructor of this class is called after the isolate * has been disposed then Reset() will not be called (but I don't think that causes a memory leak). */ template class RemoteTuple { public: RemoteTuple() = default; template explicit RemoteTuple(v8::Local... handles, Disposer disposer) : isolate{IsolateHolder::GetCurrent()}, handles{ new TupleType{v8::Isolate::GetCurrent(), handles...}, RemoteHandleFree{IsolateHolder::GetCurrent(), std::move(disposer)} } { AdjustRemotes(sizeof...(Types)); static_assert(!v8::NonCopyablePersistentTraits::kResetInDestructor, "Do not reset in destructor"); } explicit RemoteTuple(v8::Local... handles) : RemoteTuple{handles..., DefaultDisposer{}} {} operator bool() const { // NOLINT(hicpp-explicit-conversions) return static_cast(handles); } auto GetIsolateHolder() -> IsolateHolder* { return isolate.get(); } auto GetSharedIsolateHolder() -> std::shared_ptr { return isolate; } template auto Deref() const { using Type = std::tuple_element_t>; return v8::Local::New(v8::Isolate::GetCurrent(), handles->template get()); } private: using TupleType = detail::HandleTuple; struct DefaultDisposer { template void operator()(v8::Persistent& value, Rest&&... rest) const { value.Reset(); (*this)(std::forward(rest)...); } void operator()() const {} }; template class RemoteHandleFree { public: RemoteHandleFree(std::shared_ptr isolate, Disposer disposer) : isolate{std::move(isolate)}, disposer{std::move(disposer)} {} void operator()(TupleType* handles) { isolate->ScheduleTask(std::make_unique>(handles, std::move(disposer)), true, false, true); } private: std::shared_ptr isolate; Disposer disposer; }; template class DisposalTask : public Runnable { public: explicit DisposalTask(TupleType* handles, Disposer disposer) : handles{handles}, disposer{std::move(disposer)} {} private: template void Apply(std::index_sequence /*unused*/) { disposer(handles->template get()...); } void Run() final { Apply(std::make_index_sequence{}); AdjustRemotes(-static_cast(TupleType::Size)); } std::unique_ptr handles; Disposer disposer; }; std::shared_ptr isolate; std::shared_ptr handles; }; /** * Convenient when you only need 1 handle */ template class RemoteHandle { public: RemoteHandle() = default; explicit RemoteHandle(v8::Local handle) : handle{handle} {} template RemoteHandle(v8::Local handle, Disposer disposer) : handle{handle, std::move(disposer)} {} operator bool() const { return bool{handle}; } // NOLINT(hicpp-explicit-conversions) auto Deref() const { return handle.template Deref<0>(); } auto GetIsolateHolder() { return handle.GetIsolateHolder(); } auto GetSharedIsolateHolder() { return handle.GetSharedIsolateHolder(); } private: RemoteTuple handle; }; } // namespace ivm