Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multithreading with worklets #1561

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 46 additions & 3 deletions Common/cpp/NativeModules/NativeReanimatedModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include "FrozenObject.h"
#include <functional>
#include <thread>
#include <future>
#include <memory>
#include "JSIStoreValueUser.h"

Expand Down Expand Up @@ -61,22 +62,24 @@ NativeReanimatedModule::NativeReanimatedModule(std::shared_ptr<CallInvoker> jsIn
std::unique_ptr<jsi::Runtime> rt,
std::shared_ptr<ErrorHandler> errorHandler,
std::function<jsi::Value(jsi::Runtime &, const int, const jsi::String &)> propObtainer,
PlatformDepMethodsHolder platformDepMethodsHolder) : NativeReanimatedModuleSpec(jsInvoker),
PlatformDepMethodsHolder platformDepMethodsHolder,
std::function<std::unique_ptr<jsi::Runtime>()> runtimeObtainer) : NativeReanimatedModuleSpec(jsInvoker),
runtime(std::move(rt)),
mapperRegistry(new MapperRegistry()),
eventHandlerRegistry(new EventHandlerRegistry()),
requestRender(platformDepMethodsHolder.requestRender),
propObtainer(propObtainer),
errorHandler(errorHandler),
workletsCache(new WorkletsCache()),
scheduler(scheduler)
scheduler(scheduler),
runtimeObtainer(runtimeObtainer)
{
auto requestAnimationFrame = [=](FrameCallback callback) {
frameCallbacks.push_back(callback);
maybeRequestRender();
};

RuntimeDecorator::addNativeObjects(*runtime,
RuntimeDecorator::decorateUI(*runtime,
platformDepMethodsHolder.updaterFunction,
requestAnimationFrame,
platformDepMethodsHolder.scrollToFunction,
Expand Down Expand Up @@ -190,6 +193,46 @@ jsi::Value NativeReanimatedModule::getViewProp(jsi::Runtime &rt, const jsi::Valu
return jsi::Value::undefined();
}

jsi::Value NativeReanimatedModule::spawnThread(jsi::Runtime &rt, const jsi::Value &operations) {
jsi::Object object = operations.asObject(rt);

if (!object.isFunction(rt) || object.getProperty(rt, "__worklet").isUndefined()) {
errorHandler->setError("Function passed to spawnThread doesn't seem to be a worklet");
errorHandler->raise();
return jsi::Value::undefined();
}

const int threadId = ++this->currentThreadId;

std::shared_ptr<ShareableValue> workletShareable = ShareableValue::adapt(rt, operations, this, ValueType::UndefinedType, threadId);

std::shared_ptr<Th> th = std::make_shared<Th>();
this->threads.insert(std::make_pair(threadId, th));

auto job = [=]() {
std::unique_ptr<jsi::Runtime> customRuntime = runtimeObtainer();
std::shared_ptr<Th> th = this->threads.at(threadId);
th->rt = std::move(customRuntime);
RuntimeDecorator::decorateCustomThread(*th->rt);
jsi::Value result = jsi::Value::undefined();

jsi::Function func = workletShareable->getValue(*th->rt, threadId).asObject(*th->rt).asFunction(*th->rt);
std::shared_ptr<jsi::Function> funcPtr = std::make_shared<jsi::Function>(std::move(func));
try {
result = funcPtr->callWithThis(*th->rt, *funcPtr);
}
catch (std::exception &e) {
std::string str = e.what();
errorHandler->setError(str);
errorHandler->raise();
}
return result;
};

threads.at(threadId)->thread = std::make_shared<std::thread>(job);
return jsi::Value::undefined();
}

void NativeReanimatedModule::onEvent(std::string eventName, std::string eventAsString)
{
try
Expand Down
13 changes: 13 additions & 0 deletions Common/cpp/NativeModules/NativeReanimatedModuleSpec.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,16 @@ static jsi::Value __hostFunction_NativeReanimatedModuleSpec_getViewProp(
return jsi::Value::undefined();
}

static jsi::Value __hostFunction_NativeReanimatedModuleSpec_spawnThread(
jsi::Runtime &rt,
TurboModule &turboModule,
const jsi::Value *args,
size_t count) {
static_cast<NativeReanimatedModuleSpec *>(&turboModule)
->spawnThread(rt, std::move(args[0]));
return jsi::Value::undefined();
}

NativeReanimatedModuleSpec::NativeReanimatedModuleSpec(std::shared_ptr<CallInvoker> jsInvoker)
: TurboModule("NativeReanimated", jsInvoker) {
methodMap_["installCoreFunctions"] = MethodMetadata{
Expand All @@ -115,6 +125,9 @@ NativeReanimatedModuleSpec::NativeReanimatedModuleSpec(std::shared_ptr<CallInvok

methodMap_["getViewProp"] = MethodMetadata{
3, __hostFunction_NativeReanimatedModuleSpec_getViewProp};

methodMap_["spawnThread"] = MethodMetadata{
3, __hostFunction_NativeReanimatedModuleSpec_spawnThread};
}

}
Expand Down
20 changes: 12 additions & 8 deletions Common/cpp/Registries/WorkletsCache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,24 @@ using namespace facebook;
namespace reanimated
{

jsi::Value eval(jsi::Runtime &rt, const char *code) {
return rt.global().getPropertyAsFunction(rt, "eval").call(rt, code);
jsi::Function WorkletsCache::functionFromString(jsi::Runtime &rt, const std::string &code) {
return rt.global().getPropertyAsFunction(rt, "eval").call(rt, ("(" + code + ")").c_str()).getObject(rt).getFunction(rt);
}

jsi::Function function(jsi::Runtime &rt, const std::string& code) {
return eval(rt, ("(" + code + ")").c_str()).getObject(rt).getFunction(rt);
std::shared_ptr<jsi::Function> WorkletsCache::obtainFunction(jsi::Runtime &rt, const std::string &code) {
jsi::Function fun = functionFromString(rt, code);
std::shared_ptr<jsi::Function> funPtr(new jsi::Function(std::move(fun)));
return std::move(funPtr);
}

std::shared_ptr<jsi::Function> WorkletsCache::getFunction(jsi::Runtime &rt, std::shared_ptr<FrozenObject> frozenObj) {
std::shared_ptr<jsi::Function> WorkletsCache::getFunction(jsi::Runtime &rt, std::shared_ptr<FrozenObject> frozenObj, const int customThreadId) {
if (customThreadId != -1) {
// worklets cache wouldn't work for custom threads as every time we have a different RT
return obtainFunction(rt, frozenObj->map["asString"]->stringValue);
}
long long workletHash = frozenObj->map["__workletHash"]->numberValue;
if (worklets.count(workletHash) == 0) {
jsi::Function fun = function(rt, frozenObj->map["asString"]->stringValue);
std::shared_ptr<jsi::Function> funPtr(new jsi::Function(std::move(fun)));
worklets[workletHash] = funPtr;
worklets[workletHash] = obtainFunction(rt, frozenObj->map["asString"]->stringValue);
}
return worklets[workletHash];
}
Expand Down
9 changes: 5 additions & 4 deletions Common/cpp/SharedItems/FrozenObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,19 @@

namespace reanimated {

FrozenObject::FrozenObject(jsi::Runtime &rt, const jsi::Object &object, NativeReanimatedModule *module) {
FrozenObject::FrozenObject(jsi::Runtime &rt, const jsi::Object &object, NativeReanimatedModule *module, const int customThreadId) : customThreadId(customThreadId) {
auto propertyNames = object.getPropertyNames(rt);
for (size_t i = 0, count = propertyNames.size(rt); i < count; i++) {
auto propertyName = propertyNames.getValueAtIndex(rt, i).asString(rt);
map[propertyName.utf8(rt)] = ShareableValue::adapt(rt, object.getProperty(rt, propertyName), module);
map[propertyName.utf8(rt)] = ShareableValue::adapt(rt, object.getProperty(rt, propertyName), module, ValueType::UndefinedType, customThreadId);
}
}

jsi::Object FrozenObject::shallowClone(jsi::Runtime &rt) {
jsi::Object FrozenObject::shallowClone(jsi::Runtime &rt, const int customThreadId) {
this->customThreadId = (this->customThreadId == -1 && this->customThreadId != customThreadId) ? customThreadId : this->customThreadId;
jsi::Object object(rt);
for (auto prop : map) {
object.setProperty(rt, jsi::String::createFromUtf8(rt, prop.first), prop.second->getValue(rt));
object.setProperty(rt, jsi::String::createFromUtf8(rt, prop.first), prop.second->getValue(rt, customThreadId));
}
return object;
}
Expand Down
40 changes: 23 additions & 17 deletions Common/cpp/SharedItems/ShareableValue.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ void ShareableValue::adaptCache(jsi::Runtime &rt, const jsi::Value &value) {
}

void ShareableValue::adapt(jsi::Runtime &rt, const jsi::Value &value, ValueType objectType) {
bool isRNRuntime = !(module->isUIRuntime(rt));
bool isNotUIRuntime = !(module->isUIRuntime(rt));
if (value.isObject()) {
jsi::Object object = value.asObject(rt);
jsi::Value hiddenValue = object.getProperty(rt, HIDDEN_HOST_OBJECT_PROP);
Expand Down Expand Up @@ -99,8 +99,8 @@ void ShareableValue::adapt(jsi::Runtime &rt, const jsi::Value &value, ValueType
} else {
// a worklet
type = ValueType::WorkletFunctionType;
frozenObject = std::make_shared<FrozenObject>(rt, object, module);
if (isRNRuntime) {
frozenObject = std::make_shared<FrozenObject>(rt, object, module, customThreadId);
if (isNotUIRuntime) {
addHiddenProperty(rt, createHost(rt, frozenObject), object, HIDDEN_HOST_OBJECT_PROP);
}
}
Expand All @@ -124,8 +124,8 @@ void ShareableValue::adapt(jsi::Runtime &rt, const jsi::Value &value, ValueType
} else {
// create frozen object based on a copy of a given object
type = ValueType::ObjectType;
frozenObject = std::make_shared<FrozenObject>(rt, object, module);
if (isRNRuntime) {
frozenObject = std::make_shared<FrozenObject>(rt, object, module, customThreadId);
if (isNotUIRuntime) {
addHiddenProperty(rt, createHost(rt, frozenObject), object, HIDDEN_HOST_OBJECT_PROP);
freeze(rt, object);
}
Expand All @@ -138,13 +138,13 @@ void ShareableValue::adapt(jsi::Runtime &rt, const jsi::Value &value, ValueType
}
}

std::shared_ptr<ShareableValue> ShareableValue::adapt(jsi::Runtime &rt, const jsi::Value &value, NativeReanimatedModule *module, ValueType valueType) {
auto sv = std::shared_ptr<ShareableValue>(new ShareableValue(module, module->scheduler));
std::shared_ptr<ShareableValue> ShareableValue::adapt(jsi::Runtime &rt, const jsi::Value &value, NativeReanimatedModule *module, ValueType valueType, const int customThreadId) {
auto sv = std::shared_ptr<ShareableValue>(new ShareableValue(module, module->scheduler, customThreadId));
sv->adapt(rt, value, valueType);
return sv;
}

jsi::Value ShareableValue::getValue(jsi::Runtime &rt) {
jsi::Value ShareableValue::getValue(jsi::Runtime &rt, const int customThreadId) {
// TODO: maybe we can cache toJSValue results on a per-runtime basis, need to avoid ref loops
if (module->isUIRuntime(rt)) {
if (remoteValue.expired()) {
Expand All @@ -157,7 +157,11 @@ jsi::Value ShareableValue::getValue(jsi::Runtime &rt) {
}
return jsi::Value(rt, *remoteValue.lock());
} else {
if (hostValue.get() == nullptr) {
std::string s = hostValue.get() == nullptr ? "true" : "false";
if (hostValue.get() == nullptr || customThreadId != -1) {
// recreate objects for custom threads, because for every one of them
// there is a separate RT - we have to avoid errors caused by
// accessing the value created with a different RT
hostValue = std::make_unique<jsi::Value>(rt, toJSValue(rt));
}
return jsi::Value(rt, *hostValue);
Expand All @@ -168,9 +172,9 @@ jsi::Object ShareableValue::createHost(jsi::Runtime &rt, std::shared_ptr<jsi::Ho
return jsi::Object::createFromHostObject(rt, host);
}

jsi::Value createFrozenWrapper(jsi::Runtime &rt, std::shared_ptr<FrozenObject> frozenObject) {
jsi::Value createFrozenWrapper(jsi::Runtime &rt, std::shared_ptr<FrozenObject> frozenObject, const int customThreadId) {
jsi::Object __reanimatedHiddenHost = jsi::Object::createFromHostObject(rt, frozenObject);
jsi::Object obj = frozenObject->shallowClone(rt);
jsi::Object obj = frozenObject->shallowClone(rt, customThreadId);
jsi::Object globalObject = rt.global().getPropertyAsObject(rt, "Object");
jsi::Function freeze = globalObject.getPropertyAsFunction(rt, "freeze");
addHiddenProperty(rt, std::move(__reanimatedHiddenHost), obj, HIDDEN_HOST_OBJECT_PROP);
Expand All @@ -191,7 +195,7 @@ jsi::Value ShareableValue::toJSValue(jsi::Runtime &rt) {
case ValueType::StringType:
return jsi::Value(rt, jsi::String::createFromAscii(rt, stringValue));
case ValueType::ObjectType:
return createFrozenWrapper(rt, frozenObject);
return createFrozenWrapper(rt, frozenObject, customThreadId);
case ValueType::ArrayType: {
jsi::Array array(rt, frozenArray.size());
for (size_t i = 0; i < frozenArray.size(); i++) {
Expand All @@ -200,7 +204,7 @@ jsi::Value ShareableValue::toJSValue(jsi::Runtime &rt) {
return array;
}
case ValueType::RemoteObjectType:
if (module->isUIRuntime(rt)) {
if (module->isUIRuntime(rt) || customThreadId != -1) { // todo check this(custom threads)
remoteObject->maybeInitializeOnUIRuntime(rt);
}
return createHost(rt, remoteObject);
Expand Down Expand Up @@ -270,11 +274,13 @@ jsi::Value ShareableValue::toJSValue(jsi::Runtime &rt) {
case ValueType::WorkletFunctionType:
auto module = this->module;
auto frozenObject = this->frozenObject;
if (module->isUIRuntime(rt)) {
if (module->isUIRuntime(rt) || customThreadId != -1) {
// when running on UI thread we prep a function
// for the custom threads we also just want to prepare

auto jsThis = std::make_shared<jsi::Object>(frozenObject->shallowClone(*module->runtime));
std::shared_ptr<jsi::Function> funPtr(module->workletsCache->getFunction(rt, frozenObject));
auto jsThis = std::make_shared<jsi::Object>(frozenObject->shallowClone(rt, customThreadId));
// cache will not work for custom threads because for every thread we have a different RT
std::shared_ptr<jsi::Function> funPtr(module->workletsCache->getFunction(rt, frozenObject, customThreadId));
auto name = funPtr->getProperty(rt, "name").asString(rt).utf8(rt);

auto clb = [=](
Expand Down Expand Up @@ -332,7 +338,7 @@ jsi::Value ShareableValue::toJSValue(jsi::Runtime &rt) {
retain_this->module->scheduler->scheduleOnUI([retain_this, params] {
NativeReanimatedModule *module = retain_this->module;
jsi::Runtime &rt = *module->runtime.get();
auto jsThis = createFrozenWrapper(rt, retain_this->frozenObject).getObject(rt);
auto jsThis = createFrozenWrapper(rt, retain_this->frozenObject, retain_this->customThreadId).getObject(rt);
auto code = jsThis.getProperty(rt, "asString").asString(rt).utf8(rt);
std::shared_ptr<jsi::Function> funPtr(retain_this->module->workletsCache->getFunction(rt, retain_this->frozenObject));

Expand Down
58 changes: 56 additions & 2 deletions Common/cpp/Tools/RuntimeDecorator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,61 @@

namespace reanimated {

void RuntimeDecorator::addNativeObjects(jsi::Runtime &rt,
void RuntimeDecorator::decorateCustomThread(jsi::Runtime &rt) {
rt.global().setProperty(rt, "_WORKLET", jsi::Value(true));

jsi::Object dummyGlobal(rt);
auto dummyFunction = [](
jsi::Runtime &rt,
const jsi::Value &thisValue,
const jsi::Value *args,
size_t count
) -> jsi::Value {
return jsi::Value::undefined();
};
jsi::Function __reanimatedWorkletInit = jsi::Function::createFromHostFunction(rt, jsi::PropNameID::forAscii(rt, "__reanimatedWorkletInit"), 1, dummyFunction);

dummyGlobal.setProperty(rt, "__reanimatedWorkletInit", __reanimatedWorkletInit);
rt.global().setProperty(rt, "global", dummyGlobal);

rt.global().setProperty(rt, "jsThis", jsi::Value::undefined());

auto callback = [](
jsi::Runtime &rt,
const jsi::Value &thisValue,
const jsi::Value *args,
size_t count
) -> jsi::Value {
const jsi::Value *value = &args[0];
if (value->isString()) {
Logger::log(value->getString(rt).utf8(rt).c_str());
} else if (value->isNumber()) {
Logger::log(value->getNumber());
} else if (value->isUndefined()) {
Logger::log("undefined");
} else {
Logger::log("unsupported value type");
}
return jsi::Value::undefined();
};
jsi::Value log = jsi::Function::createFromHostFunction(rt, jsi::PropNameID::forAscii(rt, "_log"), 1, callback);
rt.global().setProperty(rt, "_log", log);

auto clb5 = [](
jsi::Runtime &rt,
const jsi::Value &thisValue,
const jsi::Value *args,
size_t count
) -> jsi::Value {
rt.global().setProperty(rt, "console", args[0]);
return jsi::Value::undefined();
};
jsi::Value setGlobalConsole = jsi::Function::createFromHostFunction(rt, jsi::PropNameID::forAscii(rt, "_setGlobalConsole"), 1, clb5);
rt.global().setProperty(rt, "_setGlobalConsole", setGlobalConsole);
}


void RuntimeDecorator::decorateUI(jsi::Runtime &rt,
UpdaterFunction updater,
RequestFrameFunction requestFrame,
ScrollToFunction scrollTo,
Expand All @@ -14,7 +68,7 @@ void RuntimeDecorator::addNativeObjects(jsi::Runtime &rt,
rt.global().setProperty(rt, "_WORKLET", jsi::Value(true));

jsi::Object dummyGlobal(rt);
auto dummyFunction = [requestFrame](
auto dummyFunction = [](
jsi::Runtime &rt,
const jsi::Value &thisValue,
const jsi::Value *args,
Expand Down
Loading