#include "ShareableValue.h" #include "SharedParent.h" #include "RuntimeManager.h" #include "MutableValue.h" #include "MutableValueSetterProxy.h" #include "RemoteObject.h" #include "FrozenObject.h" #include "RuntimeDecorator.h" namespace reanimated { const char *HIDDEN_HOST_OBJECT_PROP = "__reanimatedHostObjectRef"; const char *ALREADY_CONVERTED= "__alreadyConverted"; const char *CALL_ASYNC = "__callAsync"; const char *PRIMAL_FUNCTION = "__primalFunction"; std::string CALLBACK_ERROR_SUFFIX = R"( Possible solutions are: a) If you want to synchronously execute this method, mark it as a Worklet b) If you want to execute this method on the JS thread, wrap it using runOnJS )"; void addHiddenProperty(jsi::Runtime &rt, jsi::Value &&value, jsi::Object &obj, const char *name) { jsi::Object globalObject = rt.global().getPropertyAsObject(rt, "Object"); jsi::Function defineProperty = globalObject.getPropertyAsFunction(rt, "defineProperty"); jsi::String internalPropName = jsi::String::createFromUtf8(rt, name); jsi::Object paramForDefineProperty(rt); paramForDefineProperty.setProperty(rt, "enumerable", false); paramForDefineProperty.setProperty(rt, "value", value); defineProperty.call(rt, obj, internalPropName, paramForDefineProperty); } void freeze(jsi::Runtime &rt, jsi::Object &obj) { jsi::Object globalObject = rt.global().getPropertyAsObject(rt, "Object"); jsi::Function freeze = globalObject.getPropertyAsFunction(rt, "freeze"); freeze.call(rt, obj); } void ShareableValue::adaptCache(jsi::Runtime &rt, const jsi::Value &value) { // when adapting from host object we can assign cached value immediately such that we avoid // running `toJSValue` in the future when given object is accessed if (RuntimeDecorator::isWorkletRuntime(rt)) { if (remoteValue.expired()) { remoteValue = getWeakRef(rt); } (*remoteValue.lock()) = jsi::Value(rt, value); } else { hostValue = std::make_unique(rt, value); } } void ShareableValue::adapt(jsi::Runtime &rt, const jsi::Value &value, ValueType objectType) { bool isRNRuntime = RuntimeDecorator::isReactRuntime(rt); if (value.isObject()) { jsi::Object object = value.asObject(rt); jsi::Value hiddenValue = object.getProperty(rt, HIDDEN_HOST_OBJECT_PROP); if (!(hiddenValue.isUndefined())) { jsi::Object hiddenProperty = hiddenValue.asObject(rt); if (hiddenProperty.isHostObject(rt)) { type = ValueType::FrozenObjectType; if (object.hasProperty(rt, "__worklet") && object.isFunction(rt)) { type = ValueType::WorkletFunctionType; } valueContainer = std::make_unique( hiddenProperty.getHostObject(rt) ); if (object.hasProperty(rt, ALREADY_CONVERTED)) { adaptCache(rt, value); } return; } } } if (objectType == ValueType::MutableValueType) { type = ValueType::MutableValueType; valueContainer = std::make_unique( std::make_shared(rt, value, runtimeManager, runtimeManager->scheduler) ); } else if (value.isUndefined()) { type = ValueType::UndefinedType; } else if (value.isNull()) { type = ValueType::NullType; } else if (value.isBool()) { type = ValueType::BoolType; valueContainer = std::make_unique(value.getBool()); } else if (value.isNumber()) { type = ValueType::NumberType; valueContainer = std::make_unique(value.asNumber()); } else if (value.isString()) { type = ValueType::StringType; valueContainer = std::make_unique(value.asString(rt).utf8(rt)); } else if (value.isObject()) { auto object = value.asObject(rt); if (object.isFunction(rt)) { if (object.getProperty(rt, "__worklet").isUndefined()) { // not a worklet, we treat this as a host function type = ValueType::HostFunctionType; containsHostFunction = true; //Check if it's a hostFunction wrapper jsi::Value primalFunction = object.getProperty(rt, PRIMAL_FUNCTION); if (!primalFunction.isUndefined()) { jsi::Object handlerAsObject = primalFunction.asObject(rt); std::shared_ptr handler = handlerAsObject.getHostObject(rt); valueContainer = std::make_unique(handler); } else { valueContainer = std::make_unique( std::make_shared(std::make_shared(object.asFunction(rt)), rt)); } } else { // a worklet type = ValueType::WorkletFunctionType; valueContainer = std::make_unique(std::make_shared(rt, object, runtimeManager)); auto& frozenObject = ValueWrapper::asFrozenObject(valueContainer); containsHostFunction |= frozenObject->containsHostFunction; if (isRNRuntime && !containsHostFunction) { addHiddenProperty(rt, createHost(rt, frozenObject), object, HIDDEN_HOST_OBJECT_PROP); } } } else if (object.isArray(rt)) { type = ValueType::FrozenArrayType; auto array = object.asArray(rt); valueContainer = std::make_unique(); auto& frozenArray = ValueWrapper::asFrozenArray(valueContainer); for (size_t i = 0, size = array.size(rt); i < size; i++) { auto sv = adapt(rt, array.getValueAtIndex(rt, i), runtimeManager); containsHostFunction |= sv->containsHostFunction; frozenArray.push_back(sv); } } else if (object.isHostObject(rt)) { type = ValueType::MutableValueType; valueContainer = std::make_unique(object.getHostObject(rt)); adaptCache(rt, value); } else if (object.isHostObject(rt)) { type = ValueType::RemoteObjectType; valueContainer = std::make_unique( object.getHostObject(rt) ); adaptCache(rt, value); } else if (objectType == ValueType::RemoteObjectType) { type = ValueType::RemoteObjectType; valueContainer = std::make_unique( std::make_shared(rt, object, runtimeManager, runtimeManager->scheduler) ); } else { // create frozen object based on a copy of a given object type = ValueType::FrozenObjectType; valueContainer = std::make_unique( std::make_shared(rt, object, runtimeManager) ); auto& frozenObject = ValueWrapper::asFrozenObject(valueContainer); containsHostFunction |= frozenObject->containsHostFunction; if (isRNRuntime) { if (!containsHostFunction) { addHiddenProperty(rt, createHost(rt, frozenObject), object, HIDDEN_HOST_OBJECT_PROP); } freeze(rt, object); } } } else if (value.isSymbol()) { type = ValueType::StringType; valueContainer = std::make_unique(value.asSymbol(rt).toString(rt)); } else { throw "Invalid value type"; } } std::shared_ptr ShareableValue::adapt(jsi::Runtime &rt, const jsi::Value &value, RuntimeManager *runtimeManager, ValueType valueType) { auto sv = std::shared_ptr(new ShareableValue(runtimeManager, runtimeManager->scheduler)); sv->adapt(rt, value, valueType); return sv; } jsi::Value ShareableValue::getValue(jsi::Runtime &rt) { // TODO: maybe we can cache toJSValue results on a per-runtime basis, need to avoid ref loops if (RuntimeDecorator::isWorkletRuntime(rt)) { if (remoteValue.expired()) { auto ref = getWeakRef(rt); remoteValue = ref; } if (remoteValue.lock()->isUndefined()) { (*remoteValue.lock()) = jsi::Value(rt, toJSValue(rt)); } return jsi::Value(rt, *remoteValue.lock()); } else { if (hostValue.get() == nullptr) { hostValue = std::make_unique(rt, toJSValue(rt)); } return jsi::Value(rt, *hostValue); } } jsi::Object ShareableValue::createHost(jsi::Runtime &rt, std::shared_ptr host) { return jsi::Object::createFromHostObject(rt, host); } jsi::Value createFrozenWrapper(jsi::Runtime &rt, std::shared_ptr frozenObject) { jsi::Object __reanimatedHiddenHost = jsi::Object::createFromHostObject(rt, frozenObject); jsi::Object obj = frozenObject->shallowClone(rt); jsi::Object globalObject = rt.global().getPropertyAsObject(rt, "Object"); jsi::Function freeze = globalObject.getPropertyAsFunction(rt, "freeze"); if (!frozenObject->containsHostFunction) { addHiddenProperty(rt, std::move(__reanimatedHiddenHost), obj, HIDDEN_HOST_OBJECT_PROP); addHiddenProperty(rt, true, obj, ALREADY_CONVERTED); } return freeze.call(rt, obj); } jsi::Value ShareableValue::toJSValue(jsi::Runtime &rt) { switch (type) { case ValueType::UndefinedType: return jsi::Value::undefined(); case ValueType::NullType: return jsi::Value::null(); case ValueType::BoolType: return jsi::Value(ValueWrapper::asBoolean(valueContainer)); case ValueType::NumberType: return jsi::Value(ValueWrapper::asNumber(valueContainer)); case ValueType::StringType: { auto& stringValue = ValueWrapper::asString(valueContainer); return jsi::Value(rt, jsi::String::createFromUtf8(rt, stringValue)); } case ValueType::FrozenObjectType: { auto& frozenObject = ValueWrapper::asFrozenObject(valueContainer); return createFrozenWrapper(rt, frozenObject); } case ValueType::FrozenArrayType: { auto& frozenArray = ValueWrapper::asFrozenArray(valueContainer); jsi::Array array(rt, frozenArray.size()); for (size_t i = 0; i < frozenArray.size(); i++) { array.setValueAtIndex(rt, i, frozenArray[i]->toJSValue(rt)); } return array; } case ValueType::RemoteObjectType: { auto& remoteObject = ValueWrapper::asRemoteObject(valueContainer); if (RuntimeDecorator::isWorkletRuntime(rt)) { remoteObject->maybeInitializeOnWorkletRuntime(rt); } return createHost(rt, remoteObject); } case ValueType::MutableValueType: { auto& mutableObject = ValueWrapper::asMutableValue(valueContainer); return createHost(rt, mutableObject); } case ValueType::HostFunctionType: { auto hostFunctionWrapper = ValueWrapper::asHostFunctionWrapper(valueContainer); auto& hostRuntime = hostFunctionWrapper->value->hostRuntime; if (hostRuntime == &rt) { // function is accessed from the same runtime it was crated, we just return same function obj return jsi::Value(rt, *hostFunctionWrapper->value->getPureFunction().get()); } else { // function is accessed from a different runtime, we wrap function in host func that'd enqueue // call on an appropriate thread auto runtimeManager = this->runtimeManager; auto hostFunction = hostFunctionWrapper->value; auto warnFunction = [runtimeManager, hostFunction]( jsi::Runtime &rt, const jsi::Value &thisValue, const jsi::Value *args, size_t count ) -> jsi::Value { jsi::Value jsThis = rt.global().getProperty(rt, "jsThis"); std::string workletLocation = jsThis.asObject(rt).getProperty(rt, "__location").toString(rt).utf8(rt); std::string exceptionMessage = "Tried to synchronously call "; if(hostFunction->functionName.empty()) { exceptionMessage += "anonymous function"; } else { exceptionMessage += "function {" + hostFunction->functionName + "}"; } exceptionMessage += " from a different thread.\n\nOccurred in worklet location: "; exceptionMessage += workletLocation; exceptionMessage += CALLBACK_ERROR_SUFFIX; runtimeManager->errorHandler->setError(exceptionMessage); runtimeManager->errorHandler->raise(); return jsi::Value::undefined(); }; auto clb = [runtimeManager, hostFunction, hostRuntime]( jsi::Runtime &rt, const jsi::Value &thisValue, const jsi::Value *args, size_t count ) -> jsi::Value { // TODO: we should find thread based on runtime such that we could also call UI methods // from RN and not only RN methods from UI std::vector> params; for (int i = 0; i < count; ++i) { params.push_back(ShareableValue::adapt(rt, args[i], runtimeManager)); } std::function job = [hostFunction, hostRuntime, params] { jsi::Value * args = new jsi::Value[params.size()]; for (int i = 0; i < params.size(); ++i) { args[i] = params[i]->getValue(*hostRuntime); } jsi::Value returnedValue = hostFunction->getPureFunction().get()->call(*hostRuntime, static_cast(args), (size_t)params.size()); delete [] args; // ToDo use returned value to return promise }; runtimeManager->scheduler->scheduleOnJS(job); return jsi::Value::undefined(); }; jsi::Function wrapperFunction = jsi::Function::createFromHostFunction(rt, jsi::PropNameID::forAscii(rt, "hostFunction"), 0, warnFunction); jsi::Function res = jsi::Function::createFromHostFunction(rt, jsi::PropNameID::forAscii(rt, "hostFunction"), 0, clb); addHiddenProperty(rt, std::move(res), wrapperFunction, CALL_ASYNC); jsi::Object functionHandler = createHost(rt, hostFunctionWrapper->value); addHiddenProperty(rt, std::move(functionHandler), wrapperFunction, PRIMAL_FUNCTION); return wrapperFunction; } } case ValueType::WorkletFunctionType: { auto runtimeManager = this->runtimeManager; auto& frozenObject = ValueWrapper::asFrozenObject(this->valueContainer); if (RuntimeDecorator::isWorkletRuntime(rt)) { // when running on worklet thread we prep a function auto jsThis = std::make_shared(frozenObject->shallowClone(*runtimeManager->runtime)); std::shared_ptr funPtr(runtimeManager->workletsCache->getFunction(rt, frozenObject)); auto name = funPtr->getProperty(rt, "name").asString(rt).utf8(rt); auto clb = [=]( jsi::Runtime &rt, const jsi::Value &thisValue, const jsi::Value *args, size_t count ) mutable -> jsi::Value { jsi::Value oldJSThis = rt.global().getProperty(rt, "jsThis"); rt.global().setProperty(rt, "jsThis", *jsThis); //set jsThis jsi::Value res = jsi::Value::undefined(); try { if (thisValue.isObject()) { res = funPtr->callWithThis(rt, thisValue.asObject(rt), args, count); } else { res = funPtr->call(rt, args, count); } } catch(jsi::JSError &e) { throw e; } catch(...) { // TODO find out a way to get the error's message on hermes jsi::Value location = jsThis->getProperty(rt, "__location"); std::string str = "Javascript worklet error"; if (location.isString()) { str += "\nIn file: " + location.asString(rt).utf8(rt); } runtimeManager->errorHandler->setError(str); runtimeManager->errorHandler->raise(); } rt.global().setProperty(rt, "jsThis", oldJSThis); //clean jsThis return res; }; return jsi::Function::createFromHostFunction(rt, jsi::PropNameID::forAscii(rt, name.c_str()), 0, clb); } else { // when run outside of UI thread we enqueue a call on the UI thread auto clb = [=]( jsi::Runtime &rt, const jsi::Value &thisValue, const jsi::Value *args, size_t count ) -> jsi::Value { // TODO: we should find thread based on runtime such that we could also call UI methods // from RN and not only RN methods from UI std::vector> params; for (int i = 0; i < count; ++i) { params.push_back(ShareableValue::adapt(rt, args[i], runtimeManager)); } runtimeManager->scheduler->scheduleOnUI([=] { jsi::Runtime &rt = *runtimeManager->runtime.get(); auto jsThis = createFrozenWrapper(rt, frozenObject).getObject(rt); auto code = jsThis.getProperty(rt, "asString").asString(rt).utf8(rt); std::shared_ptr funPtr(runtimeManager->workletsCache->getFunction(rt, frozenObject)); jsi::Value * args = new jsi::Value[params.size()]; for (int i = 0; i < params.size(); ++i) { args[i] = params[i]->getValue(rt); } jsi::Value returnedValue; jsi::Value oldJSThis = rt.global().getProperty(rt, "jsThis"); rt.global().setProperty(rt, "jsThis", jsThis); //set jsThis try { returnedValue = funPtr->call(rt, static_cast(args), (size_t)params.size()); } catch(std::exception &e) { std::string str = e.what(); runtimeManager->errorHandler->setError(str); runtimeManager->errorHandler->raise(); } catch(...) { // TODO find out a way to get the error's message on hermes jsi::Value location = jsThis.getProperty(rt, "__location"); std::string str = "Javascript worklet error"; if (location.isString()) { str += "\nIn file: " + location.asString(rt).utf8(rt); } runtimeManager->errorHandler->setError(str); runtimeManager->errorHandler->raise(); } rt.global().setProperty(rt, "jsThis", oldJSThis); //clean jsThis delete [] args; // ToDo use returned value to return promise }); return jsi::Value::undefined(); }; return jsi::Function::createFromHostFunction(rt, jsi::PropNameID::forAscii(rt, "_workletFunction"), 0, clb); } } default: { throw "Unable to find conversion method for this type"; } } throw "convert error"; } }