/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #pragma once #include #include #include #include namespace facebook::react { template class SyncCallback; template class AsyncCallback { public: AsyncCallback(jsi::Runtime &runtime, jsi::Function function, std::shared_ptr jsInvoker) : callback_(std::make_shared>(runtime, std::move(function), std::move(jsInvoker))) { } void operator()(Args... args) const noexcept { call(std::forward(args)...); } void call(Args... args) const noexcept { callWithArgs(std::nullopt, std::forward(args)...); } void callWithPriority(SchedulerPriority priority, Args... args) const noexcept { callWithArgs(priority, std::forward(args)...); } void call(std::function &&callImpl) const noexcept { callWithFunction(std::nullopt, std::move(callImpl)); } void callWithPriority(SchedulerPriority priority, std::function &&callImpl) const noexcept { callWithFunction(priority, std::move(callImpl)); } private: friend Bridging; std::shared_ptr> callback_; void callWithArgs(std::optional priority, Args... args) const noexcept { if (auto wrapper = callback_->wrapper_.lock()) { auto fn = [callback = callback_, argsPtr = std::make_shared>(std::make_tuple(std::forward(args)...))]( jsi::Runtime &) { callback->apply(std::move(*argsPtr)); }; auto &jsInvoker = wrapper->jsInvoker(); if (priority) { jsInvoker.invokeAsync(*priority, std::move(fn)); } else { jsInvoker.invokeAsync(std::move(fn)); } } } void callWithFunction( std::optional priority, std::function &&callImpl) const noexcept { if (auto wrapper = callback_->wrapper_.lock()) { // Capture callback_ and not wrapper_. If callback_ is deallocated or the // JSVM is shutdown before the async task is scheduled, the underlying // function will have been deallocated. auto fn = [callback = callback_, callImpl = std::move(callImpl)](jsi::Runtime &rt) { if (auto wrapper2 = callback->wrapper_.lock()) { callImpl(rt, wrapper2->callback()); } }; auto &jsInvoker = wrapper->jsInvoker(); if (priority) { jsInvoker.invokeAsync(*priority, std::move(fn)); } else { jsInvoker.invokeAsync(std::move(fn)); } } } }; // You must ensure that when invoking this you're located on the JS thread, or // have exclusive control of the JS VM context. If you cannot ensure this, use // AsyncCallback instead. template class SyncCallback { public: SyncCallback(jsi::Runtime &rt, jsi::Function function, std::shared_ptr jsInvoker) : wrapper_(CallbackWrapper::createWeak(std::move(function), rt, std::move(jsInvoker))) { } // Disallow copying, as we can no longer safely destroy the callback // from the destructor if there's multiple copies SyncCallback(const SyncCallback &) = delete; SyncCallback &operator=(const SyncCallback &) = delete; // Allow move SyncCallback(SyncCallback &&other) noexcept : wrapper_(std::move(other.wrapper_)) {} SyncCallback &operator=(SyncCallback &&other) noexcept { wrapper_ = std::move(other.wrapper_); return *this; } ~SyncCallback() { if (auto wrapper = wrapper_.lock()) { wrapper->destroy(); } } R operator()(Args... args) const { return call(std::forward(args)...); } R call(Args... args) const { auto wrapper = wrapper_.lock(); // If the wrapper has been deallocated, we can no longer provide a return // value consistently, so our only option is to throw if (!wrapper) { if constexpr (std::is_void_v) { return; } else { throw std::runtime_error("Failed to call invalidated sync callback"); } } auto &callback = wrapper->callback(); auto &rt = wrapper->runtime(); auto jsInvoker = wrapper->jsInvokerPtr(); if constexpr (std::is_void_v) { callback.call(rt, bridging::toJs(rt, std::forward(args), jsInvoker)...); } else { return bridging::fromJs( rt, callback.call(rt, bridging::toJs(rt, std::forward(args), jsInvoker)...), jsInvoker); } } private: friend AsyncCallback; friend Bridging; R apply(std::tuple &&args) const { return apply(std::move(args), std::index_sequence_for{}); } template R apply(std::tuple &&args, std::index_sequence /*unused*/) const { return call(std::move(std::get(args))...); } // Held weakly so lifetime is managed by LongLivedObjectCollection. std::weak_ptr wrapper_; }; template struct Bridging> { static AsyncCallback fromJs(jsi::Runtime &rt, jsi::Function &&value, const std::shared_ptr &jsInvoker) { return AsyncCallback(rt, std::move(value), jsInvoker); } static jsi::Function toJs(jsi::Runtime &rt, const AsyncCallback &value) { return value.callback_->function_.getFunction(rt); } }; template struct Bridging> { static SyncCallback fromJs(jsi::Runtime &rt, jsi::Function &&value, const std::shared_ptr &jsInvoker) { return SyncCallback(rt, std::move(value), jsInvoker); } static jsi::Function toJs(jsi::Runtime &rt, const SyncCallback &value) { return value.function_.getFunction(rt); } }; template struct Bridging> { using Func = std::function; using IndexSequence = std::index_sequence_for; static constexpr size_t kArgumentCount = sizeof...(Args); static jsi::Function toJs(jsi::Runtime &rt, Func fn, const std::shared_ptr &jsInvoker) { return jsi::Function::createFromHostFunction( rt, jsi::PropNameID::forAscii(rt, "BridgedFunction"), kArgumentCount, [fn = std::make_shared(std::move(fn)), jsInvoker]( jsi::Runtime &rt, const jsi::Value &, const jsi::Value *args, size_t count) -> jsi::Value { if (count < kArgumentCount) { throw jsi::JSError(rt, "Incorrect number of arguments"); } if constexpr (std::is_void_v) { callFromJs(*fn, rt, args, jsInvoker, IndexSequence{}); return jsi::Value(); } else { return bridging::toJs(rt, callFromJs(*fn, rt, args, jsInvoker, IndexSequence{}), jsInvoker); } }); } private: template static R callFromJs( Func &fn, jsi::Runtime &rt, const jsi::Value *args, const std::shared_ptr &jsInvoker, std::index_sequence /*unused*/) { return fn(bridging::fromJs(rt, args[Index], jsInvoker)...); } }; template struct Bridging< std::function, std::enable_if_t, std::function>>> : Bridging> {}; template struct Bridging : Bridging> {}; template struct Bridging : Bridging> {}; } // namespace facebook::react