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> 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: 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 58 inline jsi::Runtime *uiRuntime() const { 59 return uiRuntime_; 60 } 61 62 inline jsi::Runtime *rnRuntime() const { 63 return rnRuntime_; 64 } 65 66 inline bool isUIRuntime(const jsi::Runtime &rt) const { 67 return &rt == uiRuntime_; 68 } 69 70 inline bool isRNRuntime(const jsi::Runtime &rt) const { 71 return &rt == rnRuntime_; 72 } 73 74 void scheduleOnUI(std::function<void()> job) { 75 scheduler_->scheduleOnUI(job); 76 } 77 78 void scheduleOnJS(std::function<void()> job) { 79 scheduler_->scheduleOnJS(job); 80 } 81 82 template <typename... 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 }; 120 121 explicit Shareable(ValueType valueType) : valueType_(valueType) {} 122 virtual jsi::Value getJSValue(jsi::Runtime &rt) { 123 return toJSValue(rt); 124 } 125 126 inline ValueType valueType() const { 127 return valueType_; 128 } 129 130 static std::shared_ptr<Shareable> undefined(); 131 132 protected: 133 ValueType valueType_; 134 }; 135 136 template <typename BaseClass> 137 class RetainingShareable : virtual public BaseClass { 138 private: 139 std::shared_ptr<JSRuntimeHelper> runtimeHelper_; 140 std::unique_ptr<jsi::Value> remoteValue_; 141 142 public: 143 template <typename... Args> 144 RetainingShareable( 145 const std::shared_ptr<JSRuntimeHelper> &runtimeHelper, 146 Args &&...args) 147 : BaseClass(std::forward<Args>(args)...), runtimeHelper_(runtimeHelper) {} 148 jsi::Value getJSValue(jsi::Runtime &rt) { 149 if (runtimeHelper_->isRNRuntime(rt)) { 150 // TODO: it is suboptimal to generate new object every time getJS is 151 // called on host runtime – the objects we are generating already exists 152 // and we should possibly just grab a hold of such object and use it here 153 // instead of creating a new JS representation. As far as I understand the 154 // only case where it can be realistically called this way is when a 155 // shared value is created and then accessed on the same runtime 156 return BaseClass::toJSValue(rt); 157 } else if (remoteValue_ == nullptr) { 158 auto value = BaseClass::toJSValue(rt); 159 remoteValue_ = std::make_unique<jsi::Value>(rt, value); 160 return value; 161 } 162 return jsi::Value(rt, *remoteValue_); 163 } 164 ~RetainingShareable() { 165 if (runtimeHelper_->uiRuntimeDestroyed) { 166 // The below use of unique_ptr.release prevents the smart pointer from 167 // calling the destructor of the kept object. This effectively results in 168 // leaking some memory. We do this on purpose, as sometimes we would keep 169 // references to JSI objects past the lifetime of its runtime (e.g., 170 // shared values references from the RN VM holds reference to JSI objects 171 // on the UI runtime). When the UI runtime is terminated, the orphaned JSI 172 // objects would crash the app when their destructors are called, because 173 // they call into a memory that's managed by the terminated runtime. We 174 // accept the tradeoff of leaking memory here, as it has a limited impact. 175 // This scenario can only occur when the React instance is torn down which 176 // happens in development mode during app reloads, or in production when 177 // the app is being shut down gracefully by the system. An alternative 178 // solution would require us to keep track of all JSI values that are in 179 // use which would require additional data structure and compute spent on 180 // bookkeeping that only for the sake of destroying the values in time 181 // before the runtime is terminated. Note that the underlying memory that 182 // jsi::Value refers to is managed by the VM and gets freed along with the 183 // runtime. 184 remoteValue_.release(); 185 } 186 } 187 }; 188 189 class ShareableJSRef : public jsi::HostObject { 190 private: 191 std::shared_ptr<Shareable> value_; 192 193 public: 194 explicit ShareableJSRef(std::shared_ptr<Shareable> value) : value_(value) {} 195 std::shared_ptr<Shareable> value() const { 196 return value_; 197 } 198 199 static jsi::Object newHostObject( 200 jsi::Runtime &rt, 201 const std::shared_ptr<Shareable> &value) { 202 return jsi::Object::createFromHostObject( 203 rt, std::make_shared<ShareableJSRef>(value)); 204 } 205 }; 206 207 std::shared_ptr<Shareable> extractShareableOrThrow( 208 jsi::Runtime &rt, 209 const jsi::Value &maybeShareableValue, 210 const char *errorMessage = nullptr); 211 212 template <typename T> 213 std::shared_ptr<T> extractShareableOrThrow( 214 jsi::Runtime &rt, 215 const jsi::Value &shareableRef, 216 const char *errorMessage = nullptr) { 217 auto res = std::dynamic_pointer_cast<T>( 218 extractShareableOrThrow(rt, shareableRef, errorMessage)); 219 if (!res) { 220 throw new std::runtime_error( 221 errorMessage != nullptr 222 ? errorMessage 223 : "provided shareable object is of an incompatible type"); 224 } 225 return res; 226 } 227 228 class ShareableArray : public Shareable { 229 public: 230 ShareableArray(jsi::Runtime &rt, const jsi::Array &array); 231 232 jsi::Value toJSValue(jsi::Runtime &rt) override { 233 auto size = data_.size(); 234 auto ary = jsi::Array(rt, size); 235 for (size_t i = 0; i < size; i++) { 236 ary.setValueAtIndex(rt, i, data_[i]->getJSValue(rt)); 237 } 238 return ary; 239 } 240 241 protected: 242 std::vector<std::shared_ptr<Shareable>> data_; 243 }; 244 245 class ShareableObject : public Shareable { 246 public: 247 ShareableObject(jsi::Runtime &rt, const jsi::Object &object); 248 jsi::Value toJSValue(jsi::Runtime &rt) override { 249 auto obj = jsi::Object(rt); 250 for (size_t i = 0, size = data_.size(); i < size; i++) { 251 obj.setProperty( 252 rt, data_[i].first.c_str(), data_[i].second->getJSValue(rt)); 253 } 254 return obj; 255 } 256 257 protected: 258 std::vector<std::pair<std::string, std::shared_ptr<Shareable>>> data_; 259 }; 260 261 class ShareableHostObject : public Shareable { 262 public: 263 ShareableHostObject( 264 const std::shared_ptr<JSRuntimeHelper> &runtimeHelper, 265 jsi::Runtime &rt, 266 const std::shared_ptr<jsi::HostObject> &hostObject) 267 : Shareable(HostObjectType), hostObject_(hostObject) {} 268 jsi::Value toJSValue(jsi::Runtime &rt) override { 269 return jsi::Object::createFromHostObject(rt, hostObject_); 270 } 271 272 protected: 273 std::shared_ptr<jsi::HostObject> hostObject_; 274 }; 275 276 class ShareableWorklet : public ShareableObject { 277 private: 278 std::shared_ptr<JSRuntimeHelper> runtimeHelper_; 279 280 public: 281 ShareableWorklet( 282 const std::shared_ptr<JSRuntimeHelper> &runtimeHelper, 283 jsi::Runtime &rt, 284 const jsi::Object &worklet) 285 : ShareableObject(rt, worklet), runtimeHelper_(runtimeHelper) { 286 valueType_ = WorkletType; 287 } 288 jsi::Value toJSValue(jsi::Runtime &rt) override { 289 jsi::Value obj = ShareableObject::toJSValue(rt); 290 return runtimeHelper_->valueUnpacker->call(rt, obj); 291 } 292 }; 293 294 class ShareableRemoteFunction 295 : public Shareable, 296 public std::enable_shared_from_this<ShareableRemoteFunction> { 297 private: 298 jsi::Function function_; 299 std::shared_ptr<JSRuntimeHelper> runtimeHelper_; 300 301 public: 302 ShareableRemoteFunction( 303 const std::shared_ptr<JSRuntimeHelper> &runtimeHelper, 304 jsi::Runtime &rt, 305 jsi::Function &&function) 306 : Shareable(RemoteFunctionType), 307 function_(std::move(function)), 308 runtimeHelper_(runtimeHelper) {} 309 jsi::Value toJSValue(jsi::Runtime &rt) override { 310 if (runtimeHelper_->isUIRuntime(rt)) { 311 #ifdef DEBUG 312 return runtimeHelper_->valueUnpacker->call( 313 rt, 314 ShareableJSRef::newHostObject(rt, shared_from_this()), 315 jsi::String::createFromAscii(rt, "RemoteFunction")); 316 #else 317 return ShareableJSRef::newHostObject(rt, shared_from_this()); 318 #endif 319 } else { 320 return jsi::Value(rt, function_); 321 } 322 } 323 }; 324 325 class ShareableHandle : public Shareable { 326 private: 327 std::shared_ptr<JSRuntimeHelper> runtimeHelper_; 328 std::unique_ptr<ShareableObject> initializer_; 329 std::unique_ptr<jsi::Value> remoteValue_; 330 331 public: 332 ShareableHandle( 333 const std::shared_ptr<JSRuntimeHelper> runtimeHelper, 334 jsi::Runtime &rt, 335 const jsi::Object &initializerObject) 336 : Shareable(HandleType), runtimeHelper_(runtimeHelper) { 337 initializer_ = std::make_unique<ShareableObject>(rt, initializerObject); 338 } 339 ~ShareableHandle() { 340 if (runtimeHelper_->uiRuntimeDestroyed) { 341 // The below use of unique_ptr.release prevents the smart pointer from 342 // calling the destructor of the kept object. This effectively results in 343 // leaking some memory. We do this on purpose, as sometimes we would keep 344 // references to JSI objects past the lifetime of its runtime (e.g., 345 // shared values references from the RN VM holds reference to JSI objects 346 // on the UI runtime). When the UI runtime is terminated, the orphaned JSI 347 // objects would crash the app when their destructors are called, because 348 // they call into a memory that's managed by the terminated runtime. We 349 // accept the tradeoff of leaking memory here, as it has a limited impact. 350 // This scenario can only occur when the React instance is torn down which 351 // happens in development mode during app reloads, or in production when 352 // the app is being shut down gracefully by the system. An alternative 353 // solution would require us to keep track of all JSI values that are in 354 // use which would require additional data structure and compute spent on 355 // bookkeeping that only for the sake of destroying the values in time 356 // before the runtime is terminated. Note that the underlying memory that 357 // jsi::Value refers to is managed by the VM and gets freed along with the 358 // runtime. 359 remoteValue_.release(); 360 } 361 } 362 jsi::Value toJSValue(jsi::Runtime &rt) override { 363 if (initializer_ != nullptr) { 364 auto initObj = initializer_->getJSValue(rt); 365 remoteValue_ = std::make_unique<jsi::Value>( 366 runtimeHelper_->valueUnpacker->call(rt, initObj)); 367 initializer_ = nullptr; // we can release ref to initializer as this 368 // method should be called at most once 369 } 370 return jsi::Value(rt, *remoteValue_); 371 } 372 }; 373 374 class ShareableSynchronizedDataHolder 375 : public Shareable, 376 public std::enable_shared_from_this<ShareableSynchronizedDataHolder> { 377 private: 378 std::shared_ptr<JSRuntimeHelper> runtimeHelper_; 379 std::shared_ptr<Shareable> data_; 380 std::shared_ptr<jsi::Value> uiValue_; 381 std::shared_ptr<jsi::Value> rnValue_; 382 std::mutex dataAccessMutex_; // Protects `data_`. 383 384 public: 385 ShareableSynchronizedDataHolder( 386 std::shared_ptr<JSRuntimeHelper> runtimeHelper, 387 jsi::Runtime &rt, 388 const jsi::Value &initialValue) 389 : Shareable(SynchronizedDataHolder), 390 runtimeHelper_(runtimeHelper), 391 data_(extractShareableOrThrow(rt, initialValue)) {} 392 393 jsi::Value get(jsi::Runtime &rt) { 394 std::unique_lock<std::mutex> read_lock(dataAccessMutex_); 395 if (runtimeHelper_->isUIRuntime(rt)) { 396 if (uiValue_ == nullptr) { 397 auto value = data_->getJSValue(rt); 398 uiValue_ = std::make_shared<jsi::Value>(rt, value); 399 return value; 400 } else { 401 return jsi::Value(rt, *uiValue_); 402 } 403 } else { 404 if (rnValue_ == nullptr) { 405 auto value = data_->getJSValue(rt); 406 rnValue_ = std::make_shared<jsi::Value>(rt, value); 407 return value; 408 } else { 409 return jsi::Value(rt, *rnValue_); 410 } 411 } 412 } 413 414 void set(jsi::Runtime &rt, const jsi::Value &data) { 415 std::unique_lock<std::mutex> write_lock(dataAccessMutex_); 416 data_ = extractShareableOrThrow(rt, data); 417 uiValue_.reset(); 418 rnValue_.reset(); 419 } 420 421 jsi::Value toJSValue(jsi::Runtime &rt) override { 422 return ShareableJSRef::newHostObject(rt, shared_from_this()); 423 }; 424 }; 425 426 class ShareableString : public Shareable { 427 public: 428 explicit ShareableString(const std::string &string) 429 : Shareable(StringType), data_(string) {} 430 jsi::Value toJSValue(jsi::Runtime &rt) override { 431 return jsi::String::createFromUtf8(rt, data_); 432 } 433 434 protected: 435 std::string data_; 436 }; 437 438 class ShareableScalar : public Shareable { 439 public: 440 explicit ShareableScalar(double number) : Shareable(NumberType) { 441 data_.number = number; 442 } 443 explicit ShareableScalar(bool boolean) : Shareable(BooleanType) { 444 data_.boolean = boolean; 445 } 446 ShareableScalar() : Shareable(UndefinedType) {} 447 explicit ShareableScalar(std::nullptr_t) : Shareable(NullType) {} 448 449 jsi::Value toJSValue(jsi::Runtime &rt) override { 450 switch (valueType_) { 451 case Shareable::UndefinedType: 452 return jsi::Value(); 453 case Shareable::NullType: 454 return jsi::Value(nullptr); 455 case Shareable::BooleanType: 456 return jsi::Value(data_.boolean); 457 case Shareable::NumberType: 458 return jsi::Value(data_.number); 459 default: 460 throw std::runtime_error( 461 "attempted to convert object that's not of a scalar type"); 462 } 463 } 464 465 protected: 466 union Data { 467 bool boolean; 468 double number; 469 }; 470 471 private: 472 Data data_; 473 }; 474 475 } // namespace reanimated 476