1 #pragma once 2 3 #include <jsi/jsi.h> 4 5 #include <memory> 6 #include <unordered_map> 7 #include <utility> 8 9 #include "RuntimeLifecycleMonitor.h" 10 11 namespace RNJsi { 12 13 namespace jsi = facebook::jsi; 14 15 class BaseRuntimeAwareCache { 16 public: setMainJsRuntime(jsi::Runtime * rt)17 static void setMainJsRuntime(jsi::Runtime *rt) { _mainRuntime = rt; } 18 19 protected: getMainJsRuntime()20 static jsi::Runtime *getMainJsRuntime() { 21 assert(_mainRuntime != nullptr && 22 "Expected main Javascript runtime to be set in the " 23 "BaseRuntimeAwareCache class."); 24 25 return _mainRuntime; 26 } 27 28 private: 29 static jsi::Runtime *_mainRuntime; 30 }; 31 32 /** 33 * Provides a way to keep data specific to a jsi::Runtime instance that gets 34 * cleaned up when that runtime is destroyed. This is necessary because JSI does 35 * not allow for its associated objects to be retained past the runtime 36 * lifetime. If an object (e.g. jsi::Values or jsi::Function instances) is kept 37 * after the runtime is torn down, its destructor (once it is destroyed 38 * eventually) will result in a crash (JSI objects keep a pointer to memory 39 * managed by the runtime, accessing that portion of the memory after runtime is 40 * deleted is the root cause of that crash). 41 * 42 * In order to provide an efficient implementation that does not add an overhead 43 * for the cases when only a single runtiome is used, which is the primary 44 * usecase, the following assumption has been made: Only for secondary runtimes 45 * we track destruction and clean up the store associated with that runtime. For 46 * the first runtime we assume that the object holding the store is destroyed 47 * prior to the destruction of that runtime. 48 * 49 * The above assumption makes it work without any overhead when only single 50 * runtime is in use. Specifically, we don't perform any additional operations 51 * related to tracking runtime lifecycle when only a single runtime is used. 52 */ 53 template <typename T> 54 class RuntimeAwareCache : public BaseRuntimeAwareCache, 55 public RuntimeLifecycleListener { 56 57 public: onRuntimeDestroyed(jsi::Runtime * rt)58 void onRuntimeDestroyed(jsi::Runtime *rt) override { 59 if (getMainJsRuntime() != rt) { 60 // We are removing a secondary runtime 61 _secondaryRuntimeCaches.erase(rt); 62 } 63 } 64 ~RuntimeAwareCache()65 ~RuntimeAwareCache() { 66 for (auto &cache : _secondaryRuntimeCaches) { 67 RuntimeLifecycleMonitor::removeListener( 68 *static_cast<jsi::Runtime *>(cache.first), this); 69 } 70 } 71 get(jsi::Runtime & rt)72 T &get(jsi::Runtime &rt) { 73 // We check if we're accessing the main runtime - this is the happy path 74 // to avoid us having to lookup by runtime for caches that only has a single 75 // runtime 76 if (getMainJsRuntime() == &rt) { 77 return _primaryCache; 78 } else { 79 if (_secondaryRuntimeCaches.count(&rt) == 0) { 80 // we only add listener when the secondary runtime is used, this assumes 81 // that the secondary runtime is terminated first. This lets us avoid 82 // additional complexity for the majority of cases when objects are not 83 // shared between runtimes. Otherwise we'd have to register all objecrts 84 // with the RuntimeMonitor as opposed to only registering ones that are 85 // used in secondary runtime. Note that we can't register listener here 86 // with the primary runtime as it may run on a separate thread. 87 RuntimeLifecycleMonitor::addListener(rt, this); 88 89 T cache; 90 _secondaryRuntimeCaches.emplace(&rt, std::move(cache)); 91 } 92 } 93 return _secondaryRuntimeCaches.at(&rt); 94 } 95 96 private: 97 std::unordered_map<void *, T> _secondaryRuntimeCaches; 98 T _primaryCache; 99 }; 100 101 } // namespace RNJsi 102