Skip to content

Commit

Permalink
fix: Implement createRunOnJS - stub out Promise
Browse files Browse the repository at this point in the history
  • Loading branch information
mrousavy committed May 16, 2024
1 parent ada3a53 commit 30e3d79
Show file tree
Hide file tree
Showing 9 changed files with 163 additions and 105 deletions.
17 changes: 17 additions & 0 deletions package/cpp/JSCallInvoker.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//
// JSCallInvoker.h
// Pods
//
// Created by Marc Rousavy on 16.05.24.
//

#include <jsi/jsi.h>
#include <functional>

namespace RNWorklet {

using namespace facebook;

using JSCallInvoker = std::function<void(std::function<void(jsi::Runtime& targetRuntime)>&&)>;

} // namespace RNWorklet
2 changes: 1 addition & 1 deletion package/cpp/NativeWorkletsModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ NativeWorkletsModule::NativeWorkletsModule(std::shared_ptr<CallInvoker> jsInvoke
NativeWorkletsModule::~NativeWorkletsModule() {}

jsi::Object NativeWorkletsModule::createWorkletsApi(jsi::Runtime &runtime) {
auto worklets = std::make_shared<RNWorklet::JsiWorkletApi>(_jsCallInvoker);
auto worklets = std::make_shared<RNWorklet::JsiWorkletApi>(runtime, _jsCallInvoker);
return jsi::Object::createFromHostObject(runtime, worklets);
}

Expand Down
7 changes: 5 additions & 2 deletions package/cpp/WKTFunctionInvoker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,11 @@ std::shared_ptr<JsiPromiseWrapper> FunctionInvoker::call(jsi::Runtime& fromRunti
// TODO: What do we do here now?
std::string message = exception.what();
callbackToOriginalRuntime([message, promise](jsi::Runtime& originalRuntime) {
jsi::JSError error(originalRuntime, message);
promise->reject(originalRuntime, error.value());
// TODO: We cannot use jsi::JSError here because we wrap it in JsiPromiseWrapper!
// jsi::JSError error(originalRuntime, message);
jsi::Object error(originalRuntime);
error.setProperty(originalRuntime, "message", jsi::String::createFromUtf8(originalRuntime, message));
promise->reject(originalRuntime, jsi::Value(originalRuntime, error));
});
}
});
Expand Down
3 changes: 1 addition & 2 deletions package/cpp/WKTFunctionInvoker.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include "WKTJsiWorklet.h"
#include "WKTJsiPromiseWrapper.h"
#include <memory>
#include "JSCallInvoker.h"

namespace RNWorklet {

Expand Down Expand Up @@ -48,8 +49,6 @@ class FunctionInvoker: public std::enable_shared_from_this<FunctionInvoker> {
*/
static std::shared_ptr<FunctionInvoker> createFunctionInvoker(jsi::Runtime& runtime, const jsi::Value& maybeFunc);

using JSCallInvoker = std::function<void(std::function<void(jsi::Runtime& targetRuntime)>&&)>;

/**
Calls the underlying function or worklet on the given worklet dispatcher.
*/
Expand Down
10 changes: 7 additions & 3 deletions package/cpp/WKTJsiWorkletApi.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ using namespace facebook;

class JsiWorkletApi : public JsiHostObject {
public:
JsiWorkletApi(std::shared_ptr<react::CallInvoker> jsCallInvoker): _jsCallInvoker(jsCallInvoker) { }
JsiWorkletApi(jsi::Runtime& mainRuntime, std::shared_ptr<react::CallInvoker> jsCallInvoker): _mainRuntime(&mainRuntime), _jsCallInvoker(jsCallInvoker) { }

public:
// JSI API
Expand Down Expand Up @@ -65,9 +65,12 @@ class JsiWorkletApi : public JsiHostObject {
runtime, "createRunOnJS expects a function as its first parameter.");
}

auto mainRuntime = _mainRuntime;
auto callInvoker = _jsCallInvoker;
auto caller = JsiWorkletContext::createCallOnJS(runtime, arguments[0], [callInvoker](std::function<void()>&& func) {
callInvoker->invokeAsync(std::move(func));
auto caller = JsiWorkletContext::createCallOnJS(runtime, arguments[0], [callInvoker, mainRuntime](std::function<void(jsi::Runtime& runtime)>&& func) {
callInvoker->invokeAsync([func = std::move(func), mainRuntime]() {
func(*mainRuntime);
});
});

// Now let us create the caller function.
Expand Down Expand Up @@ -176,6 +179,7 @@ class JsiWorkletApi : public JsiHostObject {


private:
jsi::Runtime* _mainRuntime;
std::shared_ptr<react::CallInvoker> _jsCallInvoker;
std::shared_ptr<JsiWorkletContext> _defaultContext;
};
Expand Down
162 changes: 95 additions & 67 deletions package/cpp/WKTJsiWorkletContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -135,12 +135,6 @@ void JsiWorkletContext::addDecorator(jsi::Runtime &runtime, const std::string& p
addDecorator(std::make_shared<JsiJsDecorator>(runtime, propName, value));
}

jsi::HostFunctionType
JsiWorkletContext::createCallInContext(jsi::Runtime &runtime,
const jsi::Value &maybeFunc) {
return createCallInContext(runtime, maybeFunc, shared_from_this());
}

std::string tryGetFunctionName(jsi::Runtime& runtime, const jsi::Value& maybeFunc) {
try {
jsi::Value name = maybeFunc.asObject(runtime).getProperty(runtime, "name");
Expand All @@ -151,86 +145,120 @@ std::string tryGetFunctionName(jsi::Runtime& runtime, const jsi::Value& maybeFun
}


jsi::HostFunctionType
JsiWorkletContext::createCallOnJS(jsi::Runtime &runtime, const jsi::Value &maybeFunc, std::function<void(std::function<void()> &&)> jsCallInvoker) {
throw std::runtime_error("createCallOnJS is not yet implemented!");
JSCallInvoker JsiWorkletContext::getCurrentRuntimeInvoker(jsi::Runtime& currentRuntime) {
JsiWorkletContext* context = JsiWorkletContext::getCurrent(currentRuntime);
if (context == nullptr) {
// We are not on a Worklet runtime - let's use the main React JS CallInvoker.
auto callInvoker = _jsCallInvoker;
auto jsRuntime = _jsRuntime;
return [callInvoker, jsRuntime](std::function<void(jsi::Runtime& targetRuntime)>&& callback) {
callInvoker([jsRuntime, callback = std::move(callback)]() {
callback(*jsRuntime);
});
};
} else {
// We are on a Worklet runtime, let's use the current context's worklet invoker!
std::weak_ptr<JsiWorkletContext> weakContext = context->shared_from_this();
std::string name = context->_name;
return [weakContext, name](std::function<void(jsi::Runtime& targetRuntime)>&& callback) {
std::shared_ptr<JsiWorkletContext> sharedContext = weakContext.lock();
if (sharedContext == nullptr) {
throw std::runtime_error("Cannot dispatch to Worklet Context \"" + name + "\" - it has already been destroyed!");
}
sharedContext->invokeOnWorkletThread([callback = std::move(callback)](JsiWorkletContext*, jsi::Runtime& targetRuntime) {
callback(targetRuntime);
});
};
}
}

jsi::HostFunctionType
JsiWorkletContext::createCallInContext(jsi::Runtime &runtime,
const jsi::Value &maybeFunc,
std::shared_ptr<JsiWorkletContext> targetContext) {
if (targetContext == nullptr) [[unlikely]] {
throw std::runtime_error("WorkletContext::createCallInContext: Target Context cannot be null!");
}
JsiWorkletContext::createCallOnJS(jsi::Runtime &runtime, const jsi::Value &maybeFunc, JSCallInvoker&& jsCallInvoker) {
// Create a FunctionInvoker that can invoke a Worklet or plain JS function.
std::shared_ptr<FunctionInvoker> invoker = FunctionInvoker::createFunctionInvoker(runtime, maybeFunc);

return [invoker, jsCallInvoker = std::move(jsCallInvoker)](jsi::Runtime &runtime,
const jsi::Value &thisValue,
const jsi::Value *arguments,
size_t count) -> jsi::Value {
// Runs the given function on the default main JS runtime
auto callOnTargetRuntime = [jsCallInvoker](std::function<void(jsi::Runtime& runtime)> callback) {
jsCallInvoker(std::move(callback));
};

// Runs the given function on the Runtime that originally invoked this method call (to resolve/reject the Promise)
JSCallInvoker callbackToOriginalRuntime;
JsiWorkletContext* currentContext = getCurrent(runtime);
if (currentContext != nullptr) {
// This function is called from a Worklet context, so we wanna schedule back to that Worklet context to resolve the Promise.
std::weak_ptr<JsiWorkletContext> weakContext = currentContext->shared_from_this();
callbackToOriginalRuntime = [weakContext](std::function<void(jsi::Runtime& toRuntime)> callback) {
auto context = weakContext.lock();
if (context == nullptr) [[unlikely]] {
throw std::runtime_error("Cannot call back to JS - the calling context has already been destroyed!");
}
context->invokeOnWorkletThread([callback = std::move(callback)](JsiWorkletContext*, jsi::Runtime& originalRuntime) {
callback(originalRuntime);
});
};
} else {
// This functio nis called from the default JS Context, so we wanna just schedule back to JS to resolve the Promise.
callbackToOriginalRuntime = jsCallInvoker;
}

// Create and run Promise.
std::shared_ptr<JsiPromiseWrapper> promise = invoker->call(runtime, thisValue, arguments, count, std::move(callOnTargetRuntime), std::move(callbackToOriginalRuntime));
return jsi::Object::createFromHostObject(runtime, promise);
};
}

jsi::HostFunctionType JsiWorkletContext::createCallInContext(jsi::Runtime &runtime, const jsi::Value &maybeFunc) {
// Create a FunctionInvoker that can invoke a Worklet or plain JS function.
std::shared_ptr<FunctionInvoker> invoker = FunctionInvoker::createFunctionInvoker(runtime, maybeFunc);
std::weak_ptr<JsiWorkletContext> weakTarget = targetContext;
std::weak_ptr<JsiWorkletContext> weakSelf = shared_from_this();

return [weakTarget, invoker](jsi::Runtime &runtime, const jsi::Value &thisValue, const jsi::Value *arguments, size_t count) -> jsi::Value {
return [weakSelf, invoker](jsi::Runtime &runtime, const jsi::Value &thisValue, const jsi::Value *arguments, size_t count) -> jsi::Value {
// Runs the given function on the target Worklet Runtime (to run the actual worklet code)
auto runOnTargetRuntime = [weakTarget](std::function<void(jsi::Runtime& toRuntime)> callback) {
auto targetContext = weakTarget.lock();
if (targetContext == nullptr) [[unlikely]] {
JSCallInvoker runOnTargetRuntime = [weakSelf](std::function<void(jsi::Runtime& toRuntime)> callback) {
auto self = weakSelf.lock();
if (self == nullptr) [[unlikely]] {
throw std::runtime_error("Cannot call Worklet - the target context has already been destroyed!");
}
targetContext->invokeOnWorkletThread([callback = std::move(callback)](JsiWorkletContext*, jsi::Runtime& toRuntime) {
self->invokeOnWorkletThread([callback = std::move(callback)](JsiWorkletContext*, jsi::Runtime& toRuntime) {
callback(toRuntime);
});
};

// Runs the given function on the Runtime that originally invoked this method call (to resolve/reject the Promise)
auto callbackToOriginalRuntime = [weakTarget](std::function<void(jsi::Runtime& toRuntime)> callback) {
auto targetContext = weakTarget.lock();
if (targetContext == nullptr) [[unlikely]] {
throw std::runtime_error("Cannot call back to JS - the target context has already been destroyed!");
}
targetContext->invokeOnJsThread(std::move(callback));
};
JSCallInvoker callbackToOriginalRuntime;
JsiWorkletContext* currentContext = getCurrent(runtime);
if (currentContext != nullptr) {
// This function is called from a Worklet context, so we wanna schedule back to that Worklet context to resolve the Promise.
std::weak_ptr<JsiWorkletContext> weakContext = currentContext->shared_from_this();
callbackToOriginalRuntime = [weakContext](std::function<void(jsi::Runtime& toRuntime)> callback) {
auto context = weakContext.lock();
if (context == nullptr) [[unlikely]] {
throw std::runtime_error("Cannot call back to JS - the calling context has already been destroyed!");
}
context->invokeOnWorkletThread([callback = std::move(callback)](JsiWorkletContext*, jsi::Runtime& originalRuntime) {
callback(originalRuntime);
});
};
} else {
// This functio nis called from the default JS Context, so we wanna just schedule back to JS to resolve the Promise.
callbackToOriginalRuntime = [weakSelf](std::function<void(jsi::Runtime& toRuntime)> callback) {
auto self = weakSelf.lock();
if (self == nullptr) [[unlikely]] {
throw std::runtime_error("Cannot call back to JS - the target context has already been destroyed!");
}
self->invokeOnJsThread(std::move(callback));
};
}

// Create and run Promise.
std::shared_ptr<JsiPromiseWrapper> promise = invoker->call(runtime, thisValue, arguments, count, std::move(runOnTargetRuntime), std::move(callbackToOriginalRuntime));
return jsi::Object::createFromHostObject(runtime, promise);
};
}

jsi::HostFunctionType
JsiWorkletContext::createInvoker(jsi::Runtime &runtime,
const jsi::Value *maybeFunc) {
auto rtPtr = &runtime;
auto ctx = JsiWorkletContext::getCurrent(runtime);

// Create host function
return [rtPtr, ctx,
func = std::make_shared<jsi::Function>(
maybeFunc->asObject(runtime).asFunction(runtime))](
jsi::Runtime &runtime, const jsi::Value &thisValue,
const jsi::Value *arguments, size_t count) {
// If we are in the same context let's just call the function directly
if (&runtime == rtPtr) {
return func->call(runtime, arguments, count);
}

// We're about to cross contexts and will need to wrap args
auto thisWrapper = JsiWrapper::wrap(runtime, thisValue);
ArgumentsWrapper argsWrapper(runtime, arguments, count);

// We are on a worklet thread
if (ctx == nullptr) {
throw std::runtime_error("Failed to create Worklet invoker - this Runtime does not have a Worklet Context!");
}
ctx->invokeOnWorkletThread(
[argsWrapper, rtPtr, func = std::move(func)](JsiWorkletContext *,
jsi::Runtime &runtime) mutable {
assert(&runtime == rtPtr && "Expected same runtime ptr!");
auto args = argsWrapper.getArguments(runtime);
func->call(runtime, ArgumentsWrapper::toArgs(args),
argsWrapper.getCount());
func = nullptr;
});

return jsi::Value::undefined();
};
}

} // namespace RNWorklet
38 changes: 14 additions & 24 deletions package/cpp/WKTJsiWorkletContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include "WKTDispatchQueue.h"
#include "WKTJsiBaseDecorator.h"
#include "WKTJsiHostObject.h"
#include "JSCallInvoker.h"

#include <exception>
#include <functional>
Expand Down Expand Up @@ -54,6 +55,11 @@ class JsiWorkletContext
}

size_t getContextId() { return _contextId; }

/**
Gets a function that can schedule calls on the current Runtime.
*/
JSCallInvoker getCurrentRuntimeInvoker(jsi::Runtime& currentRuntime);

/**
Adds a C++ based global decorator.
Expand Down Expand Up @@ -90,8 +96,7 @@ class JsiWorkletContext
throw jsi::JSError(runtime, "createRunAsync expects one parameter.");
}

auto caller =
JsiWorkletContext::createCallInContext(runtime, arguments[0], shared_from_this());
auto caller = this->createCallInContext(runtime, arguments[0]);

// Now let us create the caller function.
return jsi::Function::createFromHostFunction(
Expand Down Expand Up @@ -141,24 +146,21 @@ class JsiWorkletContext
void invokeOnWorkletThread(std::function<void(JsiWorkletContext *context,
jsi::Runtime &runtime)> &&fp);

static jsi::HostFunctionType createInvoker(jsi::Runtime &runtime,
const jsi::Value *maybeFunc);

/**
Calls a worklet function in a given Worklet Context.
Create a function that calls a worklet function in this current Worklet Context.
The returned function can be called from any context.
@param runtime Runtime that is scheduling this call. If this is the same Runtime
as the given target Worklet Context, the function will be called immediately.
@param maybeFunc The function to call, must be a Worklet.
@param targetContext Context to call the function in
@returns A host function type that will return a promise calling the
maybeFunc.
*/
static jsi::HostFunctionType createCallInContext(jsi::Runtime &runtime,
const jsi::Value &maybeFunc,
std::shared_ptr<JsiWorkletContext> targetContext);
jsi::HostFunctionType createCallInContext(jsi::Runtime &runtime,
const jsi::Value &maybeFunc);

/**
Calls a non-worklet function on the React JS context.
Creates a function that calls a non-worklet function on the React JS context.
The returned function can be called from any context.
@param runtime Runtime that is scheduling this call. If this is the React JS
context, the function will be called immediately
@param maybeFunc The function to call, must not be a Worklet.
Expand All @@ -168,19 +170,7 @@ class JsiWorkletContext
*/
static jsi::HostFunctionType createCallOnJS(jsi::Runtime& runtime,
const jsi::Value& maybeFunc,
std::function<void(std::function<void()> &&)> jsCallInvoker);

/**
Calls a worklet function in a given context (or in the JS context if the ctx
parameter is null.
@param runtime Runtime for the calling context
@param maybeFunc Function to call - might be a worklet or might not - depends
on wether we call cross context or not.
@returns A host function type that will return a promise calling the
maybeFunc.
*/
jsi::HostFunctionType createCallInContext(jsi::Runtime &runtime,
const jsi::Value &maybeFunc);
JSCallInvoker&& jsCallInvoker);

private:
jsi::Runtime *_jsRuntime;
Expand Down
Loading

0 comments on commit 30e3d79

Please sign in to comment.