1 #pragma once 2 3 #include <jsi/jsi.h> 4 #include <memory> 5 #include <string> 6 #include <utility> 7 #include <vector> 8 9 #include "ReanimatedRuntime.h" 10 #include "RuntimeManager.h" 11 #include "Scheduler.h" 12 13 using namespace facebook; 14 15 namespace reanimated { 16 17 class JSRuntimeHelper; 18 19 // Core functions are not allowed to capture outside variables, otherwise they'd 20 // try to access _closure variable which is something we want to avoid for 21 // simplicity reasons. 22 class CoreFunction { 23 private: 24 std::unique_ptr<jsi::Function> rnFunction_; 25 std::unique_ptr<jsi::Function> uiFunction_; 26 std::string functionBody_; 27 std::string location_; 28 JSRuntimeHelper 29 *runtimeHelper_; // runtime helper holds core function references, so we 30 // use normal pointer here to avoid ref cycles. 31 std::unique_ptr<jsi::Function> &getFunction(jsi::Runtime &rt); 32 33 public: 34 CoreFunction(JSRuntimeHelper *runtimeHelper, const jsi::Value &workletObject); 35 template <typename... Args> call(jsi::Runtime & rt,Args &&...args)36 jsi::Value call(jsi::Runtime &rt, Args &&...args) { 37 return getFunction(rt)->call(rt, args...); 38 } 39 }; 40 41 class JSRuntimeHelper { 42 private: 43 jsi::Runtime *rnRuntime_; // React-Native's main JS runtime 44 jsi::Runtime *uiRuntime_; // UI runtime created by Reanimated 45 std::shared_ptr<Scheduler> scheduler_; 46 47 public: JSRuntimeHelper(jsi::Runtime * rnRuntime,jsi::Runtime * uiRuntime,const std::shared_ptr<Scheduler> & scheduler)48 JSRuntimeHelper( 49 jsi::Runtime *rnRuntime, 50 jsi::Runtime *uiRuntime, 51 const std::shared_ptr<Scheduler> &scheduler) 52 : rnRuntime_(rnRuntime), uiRuntime_(uiRuntime), scheduler_(scheduler) {} 53 54 volatile bool uiRuntimeDestroyed = false; 55 std::unique_ptr<CoreFunction> callGuard; 56 std::unique_ptr<CoreFunction> valueUnpacker; 57 uiRuntime()58 inline jsi::Runtime *uiRuntime() const { 59 return uiRuntime_; 60 } 61 rnRuntime()62 inline jsi::Runtime *rnRuntime() const { 63 return rnRuntime_; 64 } 65 isUIRuntime(const jsi::Runtime & rt)66 inline bool isUIRuntime(const jsi::Runtime &rt) const { 67 return &rt == uiRuntime_; 68 } 69 isRNRuntime(const jsi::Runtime & rt)70 inline bool isRNRuntime(const jsi::Runtime &rt) const { 71 return &rt == rnRuntime_; 72 } 73 scheduleOnUI(std::function<void ()> job)74 void scheduleOnUI(std::function<void()> job) { 75 scheduler_->scheduleOnUI(job); 76 } 77 scheduleOnJS(std::function<void ()> job)78 void scheduleOnJS(std::function<void()> job) { 79 scheduler_->scheduleOnJS(job); 80 } 81 82 template <typename... Args> runOnUIGuarded(const jsi::Value & function,Args &&...args)83 inline void runOnUIGuarded(const jsi::Value &function, Args &&...args) { 84 // We only use callGuard in debug mode, otherwise we call the provided 85 // function directly. CallGuard provides a way of capturing exceptions in 86 // JavaScript and propagating them to the main React Native thread such that 87 // they can be presented using RN's LogBox. 88 jsi::Runtime &rt = *uiRuntime_; 89 #ifdef DEBUG 90 callGuard->call(rt, function, args...); 91 #else 92 function.asObject(rt).asFunction(rt).call(rt, args...); 93 #endif 94 } 95 }; 96 97 class Shareable { 98 protected: 99 virtual jsi::Value toJSValue(jsi::Runtime &rt) = 0; 100 101 public: 102 virtual ~Shareable(); 103 104 enum ValueType { 105 UndefinedType, 106 NullType, 107 BooleanType, 108 NumberType, 109 // SymbolType, TODO 110 // BigIntType, TODO 111 StringType, 112 ObjectType, 113 ArrayType, 114 WorkletType, 115 RemoteFunctionType, 116 HandleType, 117 SynchronizedDataHolder, 118 HostObjectType, 119 HostFunctionType, 120 }; 121 Shareable(ValueType valueType)122 explicit Shareable(ValueType valueType) : valueType_(valueType) {} getJSValue(jsi::Runtime & rt)123 virtual jsi::Value getJSValue(jsi::Runtime &rt) { 124 return toJSValue(rt); 125 } 126 valueType()127 inline ValueType valueType() const { 128 return valueType_; 129 } 130 131 static std::shared_ptr<Shareable> undefined(); 132 133 protected: 134 ValueType valueType_; 135 }; 136 137 template <typename BaseClass> 138 class RetainingShareable : virtual public BaseClass { 139 private: 140 std::shared_ptr<JSRuntimeHelper> runtimeHelper_; 141 std::unique_ptr<jsi::Value> remoteValue_; 142 143 public: 144 template <typename... Args> RetainingShareable(const std::shared_ptr<JSRuntimeHelper> & runtimeHelper,Args &&...args)145 RetainingShareable( 146 const std::shared_ptr<JSRuntimeHelper> &runtimeHelper, 147 Args &&...args) 148 : BaseClass(std::forward<Args>(args)...), runtimeHelper_(runtimeHelper) {} getJSValue(jsi::Runtime & rt)149 jsi::Value getJSValue(jsi::Runtime &rt) { 150 if (runtimeHelper_->isRNRuntime(rt)) { 151 // TODO: it is suboptimal to generate new object every time getJS is 152 // called on host runtime – the objects we are generating already exists 153 // and we should possibly just grab a hold of such object and use it here 154 // instead of creating a new JS representation. As far as I understand the 155 // only case where it can be realistically called this way is when a 156 // shared value is created and then accessed on the same runtime 157 return BaseClass::toJSValue(rt); 158 } else if (remoteValue_ == nullptr) { 159 auto value = BaseClass::toJSValue(rt); 160 remoteValue_ = std::make_unique<jsi::Value>(rt, value); 161 return value; 162 } 163 return jsi::Value(rt, *remoteValue_); 164 } ~RetainingShareable()165 ~RetainingShareable() { 166 if (runtimeHelper_->uiRuntimeDestroyed) { 167 // The below use of unique_ptr.release prevents the smart pointer from 168 // calling the destructor of the kept object. This effectively results in 169 // leaking some memory. We do this on purpose, as sometimes we would keep 170 // references to JSI objects past the lifetime of its runtime (e.g., 171 // shared values references from the RN VM holds reference to JSI objects 172 // on the UI runtime). When the UI runtime is terminated, the orphaned JSI 173 // objects would crash the app when their destructors are called, because 174 // they call into a memory that's managed by the terminated runtime. We 175 // accept the tradeoff of leaking memory here, as it has a limited impact. 176 // This scenario can only occur when the React instance is torn down which 177 // happens in development mode during app reloads, or in production when 178 // the app is being shut down gracefully by the system. An alternative 179 // solution would require us to keep track of all JSI values that are in 180 // use which would require additional data structure and compute spent on 181 // bookkeeping that only for the sake of destroying the values in time 182 // before the runtime is terminated. Note that the underlying memory that 183 // jsi::Value refers to is managed by the VM and gets freed along with the 184 // runtime. 185 remoteValue_.release(); 186 } 187 } 188 }; 189 190 class ShareableJSRef : public jsi::HostObject { 191 private: 192 std::shared_ptr<Shareable> value_; 193 194 public: ShareableJSRef(std::shared_ptr<Shareable> value)195 explicit ShareableJSRef(std::shared_ptr<Shareable> value) : value_(value) {} value()196 std::shared_ptr<Shareable> value() const { 197 return value_; 198 } 199 newHostObject(jsi::Runtime & rt,const std::shared_ptr<Shareable> & value)200 static jsi::Object newHostObject( 201 jsi::Runtime &rt, 202 const std::shared_ptr<Shareable> &value) { 203 return jsi::Object::createFromHostObject( 204 rt, std::make_shared<ShareableJSRef>(value)); 205 } 206 }; 207 208 std::shared_ptr<Shareable> extractShareableOrThrow( 209 jsi::Runtime &rt, 210 const jsi::Value &maybeShareableValue, 211 const char *errorMessage = nullptr); 212 213 template <typename T> 214 std::shared_ptr<T> extractShareableOrThrow( 215 jsi::Runtime &rt, 216 const jsi::Value &shareableRef, 217 const char *errorMessage = nullptr) { 218 auto res = std::dynamic_pointer_cast<T>( 219 extractShareableOrThrow(rt, shareableRef, errorMessage)); 220 if (!res) { 221 throw new std::runtime_error( 222 errorMessage != nullptr 223 ? errorMessage 224 : "provided shareable object is of an incompatible type"); 225 } 226 return res; 227 } 228 229 class ShareableArray : public Shareable { 230 public: 231 ShareableArray(jsi::Runtime &rt, const jsi::Array &array); 232 toJSValue(jsi::Runtime & rt)233 jsi::Value toJSValue(jsi::Runtime &rt) override { 234 auto size = data_.size(); 235 auto ary = jsi::Array(rt, size); 236 for (size_t i = 0; i < size; i++) { 237 ary.setValueAtIndex(rt, i, data_[i]->getJSValue(rt)); 238 } 239 return ary; 240 } 241 242 protected: 243 std::vector<std::shared_ptr<Shareable>> data_; 244 }; 245 246 class ShareableObject : public Shareable { 247 public: 248 ShareableObject(jsi::Runtime &rt, const jsi::Object &object); toJSValue(jsi::Runtime & rt)249 jsi::Value toJSValue(jsi::Runtime &rt) override { 250 auto obj = jsi::Object(rt); 251 for (size_t i = 0, size = data_.size(); i < size; i++) { 252 obj.setProperty( 253 rt, data_[i].first.c_str(), data_[i].second->getJSValue(rt)); 254 } 255 return obj; 256 } 257 258 protected: 259 std::vector<std::pair<std::string, std::shared_ptr<Shareable>>> data_; 260 }; 261 262 class ShareableHostObject : public Shareable { 263 public: ShareableHostObject(const std::shared_ptr<JSRuntimeHelper> & runtimeHelper,jsi::Runtime & rt,const std::shared_ptr<jsi::HostObject> & hostObject)264 ShareableHostObject( 265 const std::shared_ptr<JSRuntimeHelper> &runtimeHelper, 266 jsi::Runtime &rt, 267 const std::shared_ptr<jsi::HostObject> &hostObject) 268 : Shareable(HostObjectType), hostObject_(hostObject) {} toJSValue(jsi::Runtime & rt)269 jsi::Value toJSValue(jsi::Runtime &rt) override { 270 return jsi::Object::createFromHostObject(rt, hostObject_); 271 } 272 273 protected: 274 std::shared_ptr<jsi::HostObject> hostObject_; 275 }; 276 277 class ShareableHostFunction : public Shareable { 278 public: ShareableHostFunction(jsi::Runtime & rt,jsi::Function function)279 ShareableHostFunction(jsi::Runtime &rt, jsi::Function function) 280 : Shareable(HostFunctionType), 281 hostFunction_( 282 (assert(function.isHostFunction(rt)), 283 function.getHostFunction(rt))), 284 name_(function.getProperty(rt, "name").asString(rt).utf8(rt)), 285 paramCount_(function.getProperty(rt, "length").asNumber()) {} 286 toJSValue(jsi::Runtime & rt)287 jsi::Value toJSValue(jsi::Runtime &rt) override { 288 return jsi::Function::createFromHostFunction( 289 rt, jsi::PropNameID::forUtf8(rt, name_), paramCount_, hostFunction_); 290 } 291 292 protected: 293 jsi::HostFunctionType hostFunction_; 294 std::string name_; 295 unsigned int paramCount_; 296 }; 297 298 class ShareableWorklet : public ShareableObject { 299 private: 300 std::shared_ptr<JSRuntimeHelper> runtimeHelper_; 301 302 public: ShareableWorklet(const std::shared_ptr<JSRuntimeHelper> & runtimeHelper,jsi::Runtime & rt,const jsi::Object & worklet)303 ShareableWorklet( 304 const std::shared_ptr<JSRuntimeHelper> &runtimeHelper, 305 jsi::Runtime &rt, 306 const jsi::Object &worklet) 307 : ShareableObject(rt, worklet), runtimeHelper_(runtimeHelper) { 308 valueType_ = WorkletType; 309 } toJSValue(jsi::Runtime & rt)310 jsi::Value toJSValue(jsi::Runtime &rt) override { 311 jsi::Value obj = ShareableObject::toJSValue(rt); 312 return runtimeHelper_->valueUnpacker->call(rt, obj); 313 } 314 }; 315 316 class ShareableRemoteFunction 317 : public Shareable, 318 public std::enable_shared_from_this<ShareableRemoteFunction> { 319 private: 320 jsi::Function function_; 321 std::shared_ptr<JSRuntimeHelper> runtimeHelper_; 322 323 public: ShareableRemoteFunction(const std::shared_ptr<JSRuntimeHelper> & runtimeHelper,jsi::Runtime & rt,jsi::Function && function)324 ShareableRemoteFunction( 325 const std::shared_ptr<JSRuntimeHelper> &runtimeHelper, 326 jsi::Runtime &rt, 327 jsi::Function &&function) 328 : Shareable(RemoteFunctionType), 329 function_(std::move(function)), 330 runtimeHelper_(runtimeHelper) {} toJSValue(jsi::Runtime & rt)331 jsi::Value toJSValue(jsi::Runtime &rt) override { 332 if (runtimeHelper_->isUIRuntime(rt)) { 333 #ifdef DEBUG 334 return runtimeHelper_->valueUnpacker->call( 335 rt, 336 ShareableJSRef::newHostObject(rt, shared_from_this()), 337 jsi::String::createFromAscii(rt, "RemoteFunction")); 338 #else 339 return ShareableJSRef::newHostObject(rt, shared_from_this()); 340 #endif 341 } else { 342 return jsi::Value(rt, function_); 343 } 344 } 345 }; 346 347 class ShareableHandle : public Shareable { 348 private: 349 std::shared_ptr<JSRuntimeHelper> runtimeHelper_; 350 std::unique_ptr<ShareableObject> initializer_; 351 std::unique_ptr<jsi::Value> remoteValue_; 352 353 public: ShareableHandle(const std::shared_ptr<JSRuntimeHelper> runtimeHelper,jsi::Runtime & rt,const jsi::Object & initializerObject)354 ShareableHandle( 355 const std::shared_ptr<JSRuntimeHelper> runtimeHelper, 356 jsi::Runtime &rt, 357 const jsi::Object &initializerObject) 358 : Shareable(HandleType), runtimeHelper_(runtimeHelper) { 359 initializer_ = std::make_unique<ShareableObject>(rt, initializerObject); 360 } ~ShareableHandle()361 ~ShareableHandle() { 362 if (runtimeHelper_->uiRuntimeDestroyed) { 363 // The below use of unique_ptr.release prevents the smart pointer from 364 // calling the destructor of the kept object. This effectively results in 365 // leaking some memory. We do this on purpose, as sometimes we would keep 366 // references to JSI objects past the lifetime of its runtime (e.g., 367 // shared values references from the RN VM holds reference to JSI objects 368 // on the UI runtime). When the UI runtime is terminated, the orphaned JSI 369 // objects would crash the app when their destructors are called, because 370 // they call into a memory that's managed by the terminated runtime. We 371 // accept the tradeoff of leaking memory here, as it has a limited impact. 372 // This scenario can only occur when the React instance is torn down which 373 // happens in development mode during app reloads, or in production when 374 // the app is being shut down gracefully by the system. An alternative 375 // solution would require us to keep track of all JSI values that are in 376 // use which would require additional data structure and compute spent on 377 // bookkeeping that only for the sake of destroying the values in time 378 // before the runtime is terminated. Note that the underlying memory that 379 // jsi::Value refers to is managed by the VM and gets freed along with the 380 // runtime. 381 remoteValue_.release(); 382 } 383 } toJSValue(jsi::Runtime & rt)384 jsi::Value toJSValue(jsi::Runtime &rt) override { 385 if (initializer_ != nullptr) { 386 auto initObj = initializer_->getJSValue(rt); 387 remoteValue_ = std::make_unique<jsi::Value>( 388 runtimeHelper_->valueUnpacker->call(rt, initObj)); 389 initializer_ = nullptr; // we can release ref to initializer as this 390 // method should be called at most once 391 } 392 return jsi::Value(rt, *remoteValue_); 393 } 394 }; 395 396 class ShareableSynchronizedDataHolder 397 : public Shareable, 398 public std::enable_shared_from_this<ShareableSynchronizedDataHolder> { 399 private: 400 std::shared_ptr<JSRuntimeHelper> runtimeHelper_; 401 std::shared_ptr<Shareable> data_; 402 std::shared_ptr<jsi::Value> uiValue_; 403 std::shared_ptr<jsi::Value> rnValue_; 404 std::mutex dataAccessMutex_; // Protects `data_`. 405 406 public: ShareableSynchronizedDataHolder(std::shared_ptr<JSRuntimeHelper> runtimeHelper,jsi::Runtime & rt,const jsi::Value & initialValue)407 ShareableSynchronizedDataHolder( 408 std::shared_ptr<JSRuntimeHelper> runtimeHelper, 409 jsi::Runtime &rt, 410 const jsi::Value &initialValue) 411 : Shareable(SynchronizedDataHolder), 412 runtimeHelper_(runtimeHelper), 413 data_(extractShareableOrThrow(rt, initialValue)) {} 414 get(jsi::Runtime & rt)415 jsi::Value get(jsi::Runtime &rt) { 416 std::unique_lock<std::mutex> read_lock(dataAccessMutex_); 417 if (runtimeHelper_->isUIRuntime(rt)) { 418 if (uiValue_ == nullptr) { 419 auto value = data_->getJSValue(rt); 420 uiValue_ = std::make_shared<jsi::Value>(rt, value); 421 return value; 422 } else { 423 return jsi::Value(rt, *uiValue_); 424 } 425 } else { 426 if (rnValue_ == nullptr) { 427 auto value = data_->getJSValue(rt); 428 rnValue_ = std::make_shared<jsi::Value>(rt, value); 429 return value; 430 } else { 431 return jsi::Value(rt, *rnValue_); 432 } 433 } 434 } 435 set(jsi::Runtime & rt,const jsi::Value & data)436 void set(jsi::Runtime &rt, const jsi::Value &data) { 437 std::unique_lock<std::mutex> write_lock(dataAccessMutex_); 438 data_ = extractShareableOrThrow(rt, data); 439 uiValue_.reset(); 440 rnValue_.reset(); 441 } 442 toJSValue(jsi::Runtime & rt)443 jsi::Value toJSValue(jsi::Runtime &rt) override { 444 return ShareableJSRef::newHostObject(rt, shared_from_this()); 445 }; 446 }; 447 448 class ShareableString : public Shareable { 449 public: ShareableString(const std::string & string)450 explicit ShareableString(const std::string &string) 451 : Shareable(StringType), data_(string) {} toJSValue(jsi::Runtime & rt)452 jsi::Value toJSValue(jsi::Runtime &rt) override { 453 return jsi::String::createFromUtf8(rt, data_); 454 } 455 456 protected: 457 std::string data_; 458 }; 459 460 class ShareableScalar : public Shareable { 461 public: ShareableScalar(double number)462 explicit ShareableScalar(double number) : Shareable(NumberType) { 463 data_.number = number; 464 } ShareableScalar(bool boolean)465 explicit ShareableScalar(bool boolean) : Shareable(BooleanType) { 466 data_.boolean = boolean; 467 } ShareableScalar()468 ShareableScalar() : Shareable(UndefinedType) {} ShareableScalar(std::nullptr_t)469 explicit ShareableScalar(std::nullptr_t) : Shareable(NullType) {} 470 toJSValue(jsi::Runtime & rt)471 jsi::Value toJSValue(jsi::Runtime &rt) override { 472 switch (valueType_) { 473 case Shareable::UndefinedType: 474 return jsi::Value(); 475 case Shareable::NullType: 476 return jsi::Value(nullptr); 477 case Shareable::BooleanType: 478 return jsi::Value(data_.boolean); 479 case Shareable::NumberType: 480 return jsi::Value(data_.number); 481 default: 482 throw std::runtime_error( 483 "attempted to convert object that's not of a scalar type"); 484 } 485 } 486 487 protected: 488 union Data { 489 bool boolean; 490 double number; 491 }; 492 493 private: 494 Data data_; 495 }; 496 497 } // namespace reanimated 498