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