Skip to content

Commit

Permalink
fix: Try to use simple Promise approach... fails
Browse files Browse the repository at this point in the history
  • Loading branch information
mrousavy committed May 16, 2024
1 parent 6eebe9c commit e179d2f
Show file tree
Hide file tree
Showing 9 changed files with 176 additions and 63 deletions.
2 changes: 1 addition & 1 deletion package/cpp/JSCallInvoker.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@ namespace RNWorklet {

using namespace facebook;

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

} // namespace RNWorklet
30 changes: 24 additions & 6 deletions package/cpp/WKTFunctionInvoker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,9 @@ void FunctionInvoker::callAndForget(jsi::Runtime& fromRuntime,
const jsi::Value& thisValue,
const jsi::Value* arguments,
size_t count,
JSCallInvoker&& runOnTargetRuntime) {
JSCallInvoker&& runOnTargetRuntime,
std::function<void(std::shared_ptr<JsiWrapper> result)>&& resolve,
std::function<void(std::exception exception)>&& reject) {
// Start by wrapping the arguments
ArgumentsWrapper argsWrapper(fromRuntime, arguments, count);

Expand All @@ -90,6 +92,8 @@ void FunctionInvoker::callAndForget(jsi::Runtime& fromRuntime,
std::shared_ptr<FunctionInvoker> self = shared_from_this();
runOnTargetRuntime([self,
thisWrapper,
resolve = std::move(resolve),
reject = std::move(reject),
argsWrapper = std::move(argsWrapper)
](jsi::Runtime& targetRuntime) {
// Now we are on the target Runtime, let's unwrap all arguments and extract them into this target Runtime.
Expand All @@ -98,23 +102,37 @@ void FunctionInvoker::callAndForget(jsi::Runtime& fromRuntime,

try {
// Call the actual function or worklet
jsi::Value result;
if (self->isWorkletFunction()) {
// It's a Worklet, so we need to inject the captured values and call it as a Worklet
auto workletInvoker = self->_workletFunctionOrNull;
workletInvoker->call(targetRuntime, unwrappedThis, ArgumentsWrapper::toArgs(args), argsWrapper.getCount());
result = workletInvoker->call(targetRuntime, unwrappedThis, ArgumentsWrapper::toArgs(args), argsWrapper.getCount());
} else {
// It's a normal JS func, so we just call it.
auto plainFunction = self->_plainFunctionOrNull;
if (unwrappedThis.isObject()) {
// ...with `this`
plainFunction->callWithThis(targetRuntime, unwrappedThis.asObject(targetRuntime), ArgumentsWrapper::toArgs(args), argsWrapper.getCount());
result = plainFunction->callWithThis(targetRuntime, unwrappedThis.asObject(targetRuntime), ArgumentsWrapper::toArgs(args), argsWrapper.getCount());
} else {
// ...without `this`
plainFunction->call(targetRuntime, ArgumentsWrapper::toArgs(args), argsWrapper.getCount());
result = plainFunction->call(targetRuntime, ArgumentsWrapper::toArgs(args), argsWrapper.getCount());
}
}
} catch (...) {
// ignore errors.

// Resolve the promise (potentially on the calling Thread)
std::shared_ptr<JsiWrapper> wrapper = JsiWrapper::wrap(targetRuntime, result);
resolve(wrapper);
} catch (std::exception& exception) {
// Reject the promise (potentially on the calling Thread)
reject(exception);

#if DEBUG
// TODO: Remove this?
std::string message = exception.what();
jsi::Object console = targetRuntime.global().getPropertyAsObject(targetRuntime, "console");
jsi::Function errorFn = console.getPropertyAsFunction(targetRuntime, "error");
errorFn.call(targetRuntime, jsi::String::createFromUtf8(targetRuntime, "Worklet threw an error:"), jsi::String::createFromUtf8(targetRuntime, message));
#endif
}
});
}
Expand Down
4 changes: 3 additions & 1 deletion package/cpp/WKTFunctionInvoker.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@ class FunctionInvoker: public std::enable_shared_from_this<FunctionInvoker> {
const jsi::Value& thisValue,
const jsi::Value* arguments,
size_t count,
JSCallInvoker&& runOnTargetRuntime);
JSCallInvoker&& runOnTargetRuntime,
std::function<void(std::shared_ptr<JsiWrapper> result)>&& resolve,
std::function<void(std::exception exception)>&& reject);

/**
Calls the underlying function or worklet on the given worklet dispatcher.
Expand Down
80 changes: 80 additions & 0 deletions package/cpp/WKTJsiPromise.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
//
// WKTJsiPromise.cpp
// react-native-worklets-core
//
// Created by Marc Rousavy on 16.05.24.
//

#include "WKTJsiPromise.h"
#include "WKTJsiWorkletContext.h"

namespace RNWorklet {

jsi::Value JsiPromise::createPromise(jsi::Runtime& runtime, JSCallInvoker runOnMainJSRuntime, RunPromiseCallback&& run) {
// C++ equivalent of the following JS Code:
// return new Promise((resolve, reject) => {
// run(resolve, reject)
// })

jsi::Function promiseCtor = runtime.global().getPropertyAsFunction(runtime, "Promise");
jsi::HostFunctionType runCtor = [run = std::move(run), runOnMainJSRuntime](jsi::Runtime& runtime,
const jsi::Value& thisValue,
const jsi::Value* arguments,
size_t count) -> jsi::Value {
std::shared_ptr<jsi::Function> resolve = std::make_shared<jsi::Function>(arguments[0].asObject(runtime).asFunction(runtime));
std::shared_ptr<jsi::Function> reject = std::make_shared<jsi::Function>(arguments[1].asObject(runtime).asFunction(runtime));

Resolver resolveWrapper;
Rejecter rejectWrapper;

JsiWorkletContext* callingContext = JsiWorkletContext::getCurrent(runtime);
if (callingContext != nullptr) {
// This is being called from a Worklet Context.
// We need to resolve/reject on this context.
std::weak_ptr<JsiWorkletContext> weakContext = callingContext->shared_from_this();
resolveWrapper = [weakContext, resolve](std::shared_ptr<JsiWrapper> wrappedResult) {
auto context = weakContext.lock();
if (context == nullptr) {
throw std::runtime_error("Cannot resolve Promise - calling Worklet Context has already been destroyed!");
}
context->invokeOnWorkletThread([wrappedResult, resolve](JsiWorkletContext*, jsi::Runtime& runtime) {
jsi::Value result = wrappedResult->unwrap(runtime);
resolve->call(runtime, result);
});
};
rejectWrapper = [weakContext, reject](std::exception error) {
auto context = weakContext.lock();
if (context == nullptr) {
throw std::runtime_error("Cannot resolve Promise - calling Worklet Context has already been destroyed!");
}
context->invokeOnWorkletThread([error](JsiWorkletContext*, jsi::Runtime& runtime) {
// TODO: Stack!!
jsi::JSError(runtime, error.what());
});
};
} else {
// This is being called from the main JS Context.
// We need to resolve/reject on this context.
resolveWrapper = [resolve, runOnMainJSRuntime](std::shared_ptr<JsiWrapper> wrappedResult) {
runOnMainJSRuntime([wrappedResult, resolve](jsi::Runtime& runtime) {
jsi::Value result = wrappedResult->unwrap(runtime);
resolve->call(runtime, result);
});
};
rejectWrapper = [reject, runOnMainJSRuntime](std::exception error) {
runOnMainJSRuntime([error](jsi::Runtime& runtime) {
// TODO: Stack!!
jsi::JSError(runtime, error.what());
});
};
}

run(runtime, resolveWrapper, rejectWrapper);
return jsi::Value::undefined();
};

jsi::Function argument = jsi::Function::createFromHostFunction(runtime, jsi::PropNameID::forUtf8(runtime, "run"), 2, runCtor);
return promiseCtor.callAsConstructor(runtime, argument);
}

} // namespace RNWorklets
27 changes: 27 additions & 0 deletions package/cpp/WKTJsiPromise.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//
// WKTJsiPromise.h
// react-native-worklets-core
//
// Created by Marc Rousavy on 16.05.24.
//

#pragma once

#include <jsi/jsi.h>
#include "WKTJsiWrapper.h"
#include "JSCallInvoker.h"

namespace RNWorklet {

using namespace facebook;

class JsiPromise {
public:
using Resolver = std::function<void(std::shared_ptr<JsiWrapper>)>;
using Rejecter = std::function<void(std::exception)>;
using RunPromiseCallback = std::function<void(jsi::Runtime& runtime, Resolver resolve, Rejecter reject)>;

static jsi::Value createPromise(jsi::Runtime& runtime, JSCallInvoker runOnMainJSRuntime, RunPromiseCallback&& run);
};

} // namespace RNWorklets
1 change: 0 additions & 1 deletion package/cpp/WKTJsiWorkletApi.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@

#include "WKTJsiHostObject.h"
#include "WKTJsiJsDecorator.h"
#include "WKTJsiPromiseWrapper.h"
#include "WKTJsiSharedValue.h"
#include "WKTJsiWorklet.h"
#include "WKTJsiWorkletContext.h"
Expand Down
92 changes: 40 additions & 52 deletions package/cpp/WKTJsiWorkletContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include "WKTJsiSetImmediateDecorator.h"
#include "WKTJsiJsDecorator.h"
#include "WKTFunctionInvoker.h"
#include "WKTJsiPromise.h"

#include <exception>
#include <functional>
Expand Down Expand Up @@ -182,33 +183,24 @@ JsiWorkletContext::createCallOnJS(jsi::Runtime &runtime, const jsi::Value &maybe
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 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);
// Run function.
auto promise = JsiPromise::createPromise(runtime,
jsCallInvoker,
[invoker,
&thisValue,
&arguments,
&count,
callOnTargetRuntime = std::move(callOnTargetRuntime)
](jsi::Runtime& runtime,
JsiPromise::Resolver resolve,
JsiPromise::Rejecter reject) {
invoker->callAndForget(runtime, thisValue, arguments, count, std::move(callOnTargetRuntime), std::move(resolve), std::move(reject));
});
return promise;
};
}

Expand All @@ -229,35 +221,31 @@ jsi::HostFunctionType JsiWorkletContext::createCallInContext(jsi::Runtime &runti
});
};

// 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 = [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);
// Run function.
JSCallInvoker jsCallInvoker = [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!");
}
self->_jsCallInvoker([self, callback = std::move(callback)]() {
callback(*self->_jsRuntime);
});
};
auto promise = JsiPromise::createPromise(runtime,
jsCallInvoker,
[invoker,
&thisValue,
&arguments,
&count,
runOnTargetRuntime = std::move(runOnTargetRuntime)
](jsi::Runtime& runtime,
JsiPromise::Resolver resolve,
JsiPromise::Rejecter reject) {
invoker->callAndForget(runtime, thisValue, arguments, count, std::move(runOnTargetRuntime), std::move(resolve), std::move(reject));
});

return promise;
};
}

Expand Down
1 change: 0 additions & 1 deletion package/cpp/wrappers/WKTJsiObjectWrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
#include <string>
#include <vector>

#include "WKTJsiPromiseWrapper.h"
#include "WKTJsiWorklet.h"
#include "WKTJsiWrapper.h"

Expand Down
2 changes: 1 addition & 1 deletion package/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ export interface IWorkletNativeApi {
/**
* Get the current Worklet context, or `undefined` if called in main React JS context.
*/
currentContext: IWorkletContext;
currentContext: IWorkletContext | undefined;
/**
* Returns true if jsi/cpp believes that the passed value is an array.
*/
Expand Down

0 comments on commit e179d2f

Please sign in to comment.