@shopify/react-native-skia 2.11.1 → 2.11.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/cpp/api/JsiSkFont.h +3 -2
  2. package/cpp/api/recorder/Paint.h +6 -2
  3. package/cpp/jsi/NativeObject.h +50 -32
  4. package/cpp/jsi/RuntimeAwareCache.cpp +1 -0
  5. package/cpp/jsi/RuntimeAwareCache.h +29 -0
  6. package/cpp/rnskia/RNSkManager.cpp +5 -0
  7. package/lib/commonjs/dom/nodes/datatypes/Rect.js +5 -1
  8. package/lib/commonjs/dom/nodes/datatypes/Rect.js.map +1 -1
  9. package/lib/commonjs/external/reanimated/useVideo.js +8 -2
  10. package/lib/commonjs/external/reanimated/useVideo.js.map +1 -1
  11. package/lib/commonjs/skia/types/JsiInstance.d.ts +1 -0
  12. package/lib/commonjs/skia/types/JsiInstance.js.map +1 -1
  13. package/lib/commonjs/sksg/Recorder/DrawingContext.js +10 -5
  14. package/lib/commonjs/sksg/Recorder/DrawingContext.js.map +1 -1
  15. package/lib/commonjs/views/SkiaPictureView.web.js +19 -14
  16. package/lib/commonjs/views/SkiaPictureView.web.js.map +1 -1
  17. package/lib/module/dom/nodes/datatypes/Rect.js +5 -1
  18. package/lib/module/dom/nodes/datatypes/Rect.js.map +1 -1
  19. package/lib/module/external/reanimated/useVideo.js +8 -2
  20. package/lib/module/external/reanimated/useVideo.js.map +1 -1
  21. package/lib/module/skia/types/JsiInstance.d.ts +1 -0
  22. package/lib/module/skia/types/JsiInstance.js.map +1 -1
  23. package/lib/module/sksg/Recorder/DrawingContext.js +10 -5
  24. package/lib/module/sksg/Recorder/DrawingContext.js.map +1 -1
  25. package/lib/module/views/SkiaPictureView.web.js +19 -14
  26. package/lib/module/views/SkiaPictureView.web.js.map +1 -1
  27. package/lib/typescript/src/skia/types/JsiInstance.d.ts +1 -0
  28. package/package.json +1 -1
  29. package/src/dom/nodes/datatypes/Rect.ts +6 -1
  30. package/src/external/reanimated/useVideo.ts +7 -2
  31. package/src/skia/types/JsiInstance.ts +2 -0
  32. package/src/sksg/Recorder/DrawingContext.ts +10 -5
  33. package/src/views/SkiaPictureView.web.tsx +17 -14
@@ -135,8 +135,9 @@ public:
135
135
  bool isEmbolden() { return getObject()->isEmbolden(); }
136
136
 
137
137
  std::shared_ptr<JsiSkTypeface> getTypeface() {
138
- return std::make_shared<JsiSkTypeface>(
139
- getContext(), sk_sp<SkTypeface>(getObject()->getTypeface()));
138
+ // refTypeface(), not getTypeface(): the wrapper owns what it is handed.
139
+ return std::make_shared<JsiSkTypeface>(getContext(),
140
+ getObject()->refTypeface());
140
141
  }
141
142
 
142
143
  void setEdging(double edging) {
@@ -157,8 +157,12 @@ public:
157
157
  // Reset the paint that savePaint() just pushed instead of pushing a
158
158
  // second one: the matching RestorePaintDeclaration pops a single frame,
159
159
  // so an extra push would leave the enclosing group's opacity on the
160
- // stack and leak it onto every sibling drawn afterwards.
161
- ctx->getPaint() = SkPaint();
160
+ // stack and leak it onto every sibling drawn afterwards. The fresh
161
+ // paint is anti-aliased to match the TS player, which resets with
162
+ // Skia.Paint().
163
+ SkPaint freshPaint;
164
+ freshPaint.setAntiAlias(true);
165
+ ctx->getPaint() = freshPaint;
162
166
  }
163
167
  auto &paint = ctx->getPaint();
164
168
  if (props.opacity.has_value()) {
@@ -51,26 +51,32 @@ struct PrototypeCacheEntry {
51
51
  *
52
52
  * When used with static storage (like prototype caches), the cache persists
53
53
  * across hot reloads. But the JSI objects inside become invalid when the
54
- * runtime is destroyed. This wrapper tracks which runtime the cache was
55
- * created for and allocates a new cache when the runtime changes.
54
+ * runtime is destroyed. This wrapper tracks which main-runtime generation the
55
+ * cache was created for and allocates a new cache when the native module is
56
+ * reinstalled (RNSkManager construction bumps the generation).
57
+ *
58
+ * The generation is used instead of the runtime pointer on purpose: on an
59
+ * in-process runtime recreate (expo-updates reloadAsync, DevSettings.reload)
60
+ * Hermes frequently allocates the new runtime at the address of the freed
61
+ * one. A pointer comparison then sees "same runtime", keeps the stale cache,
62
+ * and create()/installConstructor() end up passing a jsi::Object owned by the
63
+ * dead runtime to the new one (use-after-free).
56
64
  *
57
65
  * The old cache is intentionally leaked - we cannot safely destroy JSI
58
66
  * objects after their runtime is gone.
59
67
  */
60
68
  template <typename T> struct StaticRuntimeAwareCache {
61
69
  RNJsi::RuntimeAwareCache<T> *cache = nullptr;
62
- jsi::Runtime *cacheRuntime = nullptr;
63
-
64
- RNJsi::RuntimeAwareCache<T> &get(jsi::Runtime &rt) {
65
- auto mainRuntime = RNJsi::BaseRuntimeAwareCache::getMainJsRuntime();
66
- if (&rt == mainRuntime && cacheRuntime != mainRuntime) {
67
- // Main runtime changed (hot reload) - allocate new cache, leak old one
68
- cache = new RNJsi::RuntimeAwareCache<T>();
69
- cacheRuntime = mainRuntime;
70
- }
71
- if (cache == nullptr) {
70
+ uint64_t cacheGeneration = 0;
71
+
72
+ RNJsi::RuntimeAwareCache<T> &get(jsi::Runtime & /*rt*/) {
73
+ auto generation =
74
+ RNJsi::BaseRuntimeAwareCache::getMainJsRuntimeGeneration();
75
+ if (cache == nullptr || cacheGeneration != generation) {
76
+ // First use, or the main runtime was reinstalled (hot reload / OTA):
77
+ // allocate a fresh cache and leak the old one.
72
78
  cache = new RNJsi::RuntimeAwareCache<T>();
73
- cacheRuntime = mainRuntime;
79
+ cacheGeneration = generation;
74
80
  }
75
81
  return *cache;
76
82
  }
@@ -143,9 +149,29 @@ public:
143
149
  */
144
150
  static void installPrototype(jsi::Runtime &runtime) {
145
151
  std::lock_guard<std::mutex> lock(getPrototypeCacheMutex());
152
+ ensurePrototypeLocked(runtime);
153
+ }
154
+
155
+ /**
156
+ * Look up (and install on first use) the prototype entry for this runtime.
157
+ *
158
+ * Callers must hold getPrototypeCacheMutex() and must consume the returned
159
+ * entry while still holding it: the lookup and the use of the prototype
160
+ * have to share one lock scope, because a reinstall of the native module
161
+ * (generation bump, see StaticRuntimeAwareCache) can swap the cache from
162
+ * any thread. A caller that released the lock in between would re-resolve
163
+ * a fresh cache with an empty entry and hand out an object without a
164
+ * prototype.
165
+ *
166
+ * The returned reference stays valid for the runtime's lifetime: the
167
+ * RuntimeAwareCache is heap-allocated and never freed, and
168
+ * std::unordered_map keeps references to its elements stable across
169
+ * rehashes.
170
+ */
171
+ static PrototypeCacheEntry &ensurePrototypeLocked(jsi::Runtime &runtime) {
146
172
  auto &entry = getPrototypeCache(runtime).get(runtime);
147
173
  if (entry.prototype.has_value()) {
148
- return; // Already installed
174
+ return entry; // Already installed
149
175
  }
150
176
 
151
177
  // Create prototype object
@@ -264,6 +290,7 @@ public:
264
290
 
265
291
  // Cache the prototype
266
292
  entry.prototype = std::move(prototype);
293
+ return entry;
267
294
  }
268
295
 
269
296
  /**
@@ -274,13 +301,8 @@ public:
274
301
  * created internally by the native code).
275
302
  */
276
303
  static void installConstructor(jsi::Runtime &runtime) {
277
- installPrototype(runtime);
278
-
279
304
  std::lock_guard<std::mutex> lock(getPrototypeCacheMutex());
280
- auto &entry = getPrototypeCache(runtime).get(runtime);
281
- if (!entry.prototype.has_value()) {
282
- return;
283
- }
305
+ auto &entry = ensurePrototypeLocked(runtime);
284
306
 
285
307
  // Create a constructor function that throws when called directly
286
308
  auto ctor = jsi::Function::createFromHostFunction(
@@ -308,8 +330,6 @@ public:
308
330
  */
309
331
  static jsi::Value create(jsi::Runtime &runtime,
310
332
  std::shared_ptr<Derived> instance) {
311
- installPrototype(runtime);
312
-
313
333
  // Store creation runtime for logging etc.
314
334
  instance->setCreationRuntime(&runtime);
315
335
 
@@ -319,18 +339,16 @@ public:
319
339
  // Attach native state
320
340
  obj.setNativeState(runtime, instance);
321
341
 
322
- // Set prototype
342
+ // Install (on first use) and apply the prototype in a single lock scope
343
+ // so a concurrent cache swap cannot slip in between the two steps.
323
344
  {
324
345
  std::lock_guard<std::mutex> lock(getPrototypeCacheMutex());
325
- auto &entry = getPrototypeCache(runtime).get(runtime);
326
- if (entry.prototype.has_value()) {
327
- // Use Object.setPrototypeOf to set the prototype
328
- auto objectCtor =
329
- runtime.global().getPropertyAsObject(runtime, "Object");
330
- auto setPrototypeOf =
331
- objectCtor.getPropertyAsFunction(runtime, "setPrototypeOf");
332
- setPrototypeOf.call(runtime, obj, *entry.prototype);
333
- }
346
+ auto &entry = ensurePrototypeLocked(runtime);
347
+ // Use Object.setPrototypeOf to set the prototype
348
+ auto objectCtor = runtime.global().getPropertyAsObject(runtime, "Object");
349
+ auto setPrototypeOf =
350
+ objectCtor.getPropertyAsFunction(runtime, "setPrototypeOf");
351
+ setPrototypeOf.call(runtime, obj, *entry.prototype);
334
352
  }
335
353
 
336
354
  // Set memory pressure hint for GC
@@ -3,5 +3,6 @@
3
3
  namespace RNJsi {
4
4
 
5
5
  std::atomic<jsi::Runtime *> BaseRuntimeAwareCache::_mainRuntime{nullptr};
6
+ std::atomic<uint64_t> BaseRuntimeAwareCache::_mainRuntimeGeneration{0};
6
7
 
7
8
  } // namespace RNJsi
@@ -3,6 +3,7 @@
3
3
  #include <jsi/jsi.h>
4
4
 
5
5
  #include <atomic>
6
+ #include <cstdint>
6
7
  #include <memory>
7
8
  #include <mutex>
8
9
  #include <unordered_map>
@@ -20,6 +21,33 @@ public:
20
21
  // Atomic: hot reload rewrites this from the JS thread while worklet
21
22
  // runtime threads read it concurrently.
22
23
  _mainRuntime.store(rt, std::memory_order_release);
24
+ // Every (re)installation of the native module starts a new generation.
25
+ // Callers that hold process-wide state derived from the main runtime
26
+ // (see StaticRuntimeAwareCache) must key their invalidation on this
27
+ // counter and NOT on the runtime pointer: when the JS runtime is
28
+ // destroyed and recreated in-process (expo-updates OTA apply,
29
+ // DevSettings.reload) the new jsi::Runtime is frequently allocated at
30
+ // the exact address of the freed one, so a pointer comparison cannot
31
+ // tell a reload from the steady state.
32
+ _mainRuntimeGeneration.fetch_add(1, std::memory_order_acq_rel);
33
+ }
34
+ static uint64_t getMainJsRuntimeGeneration() {
35
+ return _mainRuntimeGeneration.load(std::memory_order_acquire);
36
+ }
37
+ /**
38
+ * Called when the main runtime is about to go away (RNSkManager destruction).
39
+ * Bumps the generation so process-wide caches keyed on it
40
+ * (StaticRuntimeAwareCache) stop trusting their contents before the next
41
+ * install(). This closes the window where a runtime allocated at the dead
42
+ * runtime's address would otherwise be taken for the main runtime and be
43
+ * handed the stale jsi::Objects of the previous one.
44
+ *
45
+ * The pointer itself is intentionally left in place: worklet runtime
46
+ * threads may still call getMainJsRuntime() while teardown overlaps, and
47
+ * that accessor asserts on nullptr.
48
+ */
49
+ static void invalidateMainJsRuntime() {
50
+ _mainRuntimeGeneration.fetch_add(1, std::memory_order_acq_rel);
23
51
  }
24
52
  static jsi::Runtime *getMainJsRuntime() {
25
53
  auto *rt = _mainRuntime.load(std::memory_order_acquire);
@@ -31,6 +59,7 @@ public:
31
59
 
32
60
  private:
33
61
  static std::atomic<jsi::Runtime *> _mainRuntime;
62
+ static std::atomic<uint64_t> _mainRuntimeGeneration;
34
63
  };
35
64
 
36
65
  /**
@@ -30,6 +30,11 @@ RNSkManager::RNSkManager(
30
30
  }
31
31
 
32
32
  RNSkManager::~RNSkManager() {
33
+ // The main runtime is going away: key out the process-wide prototype
34
+ // caches now rather than at the next install(), so nothing allocated at
35
+ // this runtime's address in between can be mistaken for it.
36
+ RNJsi::BaseRuntimeAwareCache::invalidateMainJsRuntime();
37
+
33
38
  // Free up any references
34
39
  _viewApi = nullptr;
35
40
  _jsRuntime = nullptr;
@@ -10,7 +10,11 @@ var _Radius = require("./Radius");
10
10
  const isEdge = (pos, b) => {
11
11
  "worklet";
12
12
 
13
- return pos.x === b.x || pos.y === b.y || pos.x === b.width || pos.y === b.height;
13
+ const right = b.x + b.width;
14
+ const bottom = b.y + b.height;
15
+ const withinX = pos.x >= b.x && pos.x <= right;
16
+ const withinY = pos.y >= b.y && pos.y <= bottom;
17
+ return withinY && (pos.x === b.x || pos.x === right) || withinX && (pos.y === b.y || pos.y === bottom);
14
18
  };
15
19
 
16
20
  // We have an issue to check property existence on JSI backed instances
@@ -1 +1 @@
1
- {"version":3,"names":["_Radius","require","isEdge","pos","b","x","y","width","height","exports","isRRectCtor","def","rect","undefined","isRectCtor","processRect","Skia","_def$x","_def$y","XYWHRect","processRRect","_def$r","_def$x2","_def$y2","r","processRadius","RRectXY","inflate","box","dx","dy","tx","ty","rx","ry","deflate"],"sources":["Rect.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-explicit-any */\nimport type { Skia, SkRect, SkRRect, Vector } from \"../../../skia/types\";\nimport type { RectCtor, RectDef, RRectCtor, RRectDef } from \"../../types\";\n\nimport { processRadius } from \"./Radius\";\n\nexport const isEdge = (pos: Vector, b: SkRect) => {\n \"worklet\";\n return (\n pos.x === b.x || pos.y === b.y || pos.x === b.width || pos.y === b.height\n );\n};\n\n// We have an issue to check property existence on JSI backed instances\nconst isRRectCtor = (def: RRectDef): def is RRectCtor => {\n \"worklet\";\n return (def as any).rect === undefined;\n};\n// We have an issue to check property existence on JSI backed instances\nconst isRectCtor = (def: RectDef): def is RectCtor => {\n \"worklet\";\n return (def as any).rect === undefined;\n};\n\nexport const processRect = (Skia: Skia, def: RectDef) => {\n \"worklet\";\n if (isRectCtor(def)) {\n return Skia.XYWHRect(def.x ?? 0, def.y ?? 0, def.width, def.height);\n } else {\n return def.rect;\n }\n};\n\nexport const processRRect = (Skia: Skia, def: RRectDef) => {\n \"worklet\";\n if (isRRectCtor(def)) {\n const r = processRadius(Skia, def.r ?? 0);\n return Skia.RRectXY(\n Skia.XYWHRect(def.x ?? 0, def.y ?? 0, def.width, def.height),\n r.x,\n r.y\n );\n } else {\n return def.rect;\n }\n};\n\nexport const inflate = (\n Skia: Skia,\n box: SkRRect,\n dx: number,\n dy: number,\n tx = 0,\n ty = 0\n) => {\n \"worklet\";\n return Skia.RRectXY(\n Skia.XYWHRect(\n box.rect.x - dx + tx,\n box.rect.y - dy + ty,\n box.rect.width + 2 * dx,\n box.rect.height + 2 * dy\n ),\n box.rx + dx,\n box.ry + dy\n );\n};\n\nexport const deflate = (\n Skia: Skia,\n box: SkRRect,\n dx: number,\n dy: number,\n tx = 0,\n ty = 0\n) => {\n \"worklet\";\n return inflate(Skia, box, -dx, -dy, tx, ty);\n};\n"],"mappings":";;;;;;AAIA,IAAAA,OAAA,GAAAC,OAAA;AAJA;;AAMO,MAAMC,MAAM,GAAGA,CAACC,GAAW,EAAEC,CAAS,KAAK;EAChD,SAAS;;EACT,OACED,GAAG,CAACE,CAAC,KAAKD,CAAC,CAACC,CAAC,IAAIF,GAAG,CAACG,CAAC,KAAKF,CAAC,CAACE,CAAC,IAAIH,GAAG,CAACE,CAAC,KAAKD,CAAC,CAACG,KAAK,IAAIJ,GAAG,CAACG,CAAC,KAAKF,CAAC,CAACI,MAAM;AAE7E,CAAC;;AAED;AAAAC,OAAA,CAAAP,MAAA,GAAAA,MAAA;AACA,MAAMQ,WAAW,GAAIC,GAAa,IAAuB;EACvD,SAAS;;EACT,OAAQA,GAAG,CAASC,IAAI,KAAKC,SAAS;AACxC,CAAC;AACD;AACA,MAAMC,UAAU,GAAIH,GAAY,IAAsB;EACpD,SAAS;;EACT,OAAQA,GAAG,CAASC,IAAI,KAAKC,SAAS;AACxC,CAAC;AAEM,MAAME,WAAW,GAAGA,CAACC,IAAU,EAAEL,GAAY,KAAK;EACvD,SAAS;;EACT,IAAIG,UAAU,CAACH,GAAG,CAAC,EAAE;IAAA,IAAAM,MAAA,EAAAC,MAAA;IACnB,OAAOF,IAAI,CAACG,QAAQ,EAAAF,MAAA,GAACN,GAAG,CAACN,CAAC,cAAAY,MAAA,cAAAA,MAAA,GAAI,CAAC,GAAAC,MAAA,GAAEP,GAAG,CAACL,CAAC,cAAAY,MAAA,cAAAA,MAAA,GAAI,CAAC,EAAEP,GAAG,CAACJ,KAAK,EAAEI,GAAG,CAACH,MAAM,CAAC;EACrE,CAAC,MAAM;IACL,OAAOG,GAAG,CAACC,IAAI;EACjB;AACF,CAAC;AAACH,OAAA,CAAAM,WAAA,GAAAA,WAAA;AAEK,MAAMK,YAAY,GAAGA,CAACJ,IAAU,EAAEL,GAAa,KAAK;EACzD,SAAS;;EACT,IAAID,WAAW,CAACC,GAAG,CAAC,EAAE;IAAA,IAAAU,MAAA,EAAAC,OAAA,EAAAC,OAAA;IACpB,MAAMC,CAAC,GAAG,IAAAC,qBAAa,EAACT,IAAI,GAAAK,MAAA,GAAEV,GAAG,CAACa,CAAC,cAAAH,MAAA,cAAAA,MAAA,GAAI,CAAC,CAAC;IACzC,OAAOL,IAAI,CAACU,OAAO,CACjBV,IAAI,CAACG,QAAQ,EAAAG,OAAA,GAACX,GAAG,CAACN,CAAC,cAAAiB,OAAA,cAAAA,OAAA,GAAI,CAAC,GAAAC,OAAA,GAAEZ,GAAG,CAACL,CAAC,cAAAiB,OAAA,cAAAA,OAAA,GAAI,CAAC,EAAEZ,GAAG,CAACJ,KAAK,EAAEI,GAAG,CAACH,MAAM,CAAC,EAC5DgB,CAAC,CAACnB,CAAC,EACHmB,CAAC,CAAClB,CACJ,CAAC;EACH,CAAC,MAAM;IACL,OAAOK,GAAG,CAACC,IAAI;EACjB;AACF,CAAC;AAACH,OAAA,CAAAW,YAAA,GAAAA,YAAA;AAEK,MAAMO,OAAO,GAAGA,CACrBX,IAAU,EACVY,GAAY,EACZC,EAAU,EACVC,EAAU,EACVC,EAAE,GAAG,CAAC,EACNC,EAAE,GAAG,CAAC,KACH;EACH,SAAS;;EACT,OAAOhB,IAAI,CAACU,OAAO,CACjBV,IAAI,CAACG,QAAQ,CACXS,GAAG,CAAChB,IAAI,CAACP,CAAC,GAAGwB,EAAE,GAAGE,EAAE,EACpBH,GAAG,CAAChB,IAAI,CAACN,CAAC,GAAGwB,EAAE,GAAGE,EAAE,EACpBJ,GAAG,CAAChB,IAAI,CAACL,KAAK,GAAG,CAAC,GAAGsB,EAAE,EACvBD,GAAG,CAAChB,IAAI,CAACJ,MAAM,GAAG,CAAC,GAAGsB,EACxB,CAAC,EACDF,GAAG,CAACK,EAAE,GAAGJ,EAAE,EACXD,GAAG,CAACM,EAAE,GAAGJ,EACX,CAAC;AACH,CAAC;AAACrB,OAAA,CAAAkB,OAAA,GAAAA,OAAA;AAEK,MAAMQ,OAAO,GAAGA,CACrBnB,IAAU,EACVY,GAAY,EACZC,EAAU,EACVC,EAAU,EACVC,EAAE,GAAG,CAAC,EACNC,EAAE,GAAG,CAAC,KACH;EACH,SAAS;;EACT,OAAOL,OAAO,CAACX,IAAI,EAAEY,GAAG,EAAE,CAACC,EAAE,EAAE,CAACC,EAAE,EAAEC,EAAE,EAAEC,EAAE,CAAC;AAC7C,CAAC;AAACvB,OAAA,CAAA0B,OAAA,GAAAA,OAAA","ignoreList":[]}
1
+ {"version":3,"names":["_Radius","require","isEdge","pos","b","right","x","width","bottom","y","height","withinX","withinY","exports","isRRectCtor","def","rect","undefined","isRectCtor","processRect","Skia","_def$x","_def$y","XYWHRect","processRRect","_def$r","_def$x2","_def$y2","r","processRadius","RRectXY","inflate","box","dx","dy","tx","ty","rx","ry","deflate"],"sources":["Rect.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-explicit-any */\nimport type { Skia, SkRect, SkRRect, Vector } from \"../../../skia/types\";\nimport type { RectCtor, RectDef, RRectCtor, RRectDef } from \"../../types\";\n\nimport { processRadius } from \"./Radius\";\n\nexport const isEdge = (pos: Vector, b: SkRect) => {\n \"worklet\";\n const right = b.x + b.width;\n const bottom = b.y + b.height;\n const withinX = pos.x >= b.x && pos.x <= right;\n const withinY = pos.y >= b.y && pos.y <= bottom;\n return (\n (withinY && (pos.x === b.x || pos.x === right)) ||\n (withinX && (pos.y === b.y || pos.y === bottom))\n );\n};\n\n// We have an issue to check property existence on JSI backed instances\nconst isRRectCtor = (def: RRectDef): def is RRectCtor => {\n \"worklet\";\n return (def as any).rect === undefined;\n};\n// We have an issue to check property existence on JSI backed instances\nconst isRectCtor = (def: RectDef): def is RectCtor => {\n \"worklet\";\n return (def as any).rect === undefined;\n};\n\nexport const processRect = (Skia: Skia, def: RectDef) => {\n \"worklet\";\n if (isRectCtor(def)) {\n return Skia.XYWHRect(def.x ?? 0, def.y ?? 0, def.width, def.height);\n } else {\n return def.rect;\n }\n};\n\nexport const processRRect = (Skia: Skia, def: RRectDef) => {\n \"worklet\";\n if (isRRectCtor(def)) {\n const r = processRadius(Skia, def.r ?? 0);\n return Skia.RRectXY(\n Skia.XYWHRect(def.x ?? 0, def.y ?? 0, def.width, def.height),\n r.x,\n r.y\n );\n } else {\n return def.rect;\n }\n};\n\nexport const inflate = (\n Skia: Skia,\n box: SkRRect,\n dx: number,\n dy: number,\n tx = 0,\n ty = 0\n) => {\n \"worklet\";\n return Skia.RRectXY(\n Skia.XYWHRect(\n box.rect.x - dx + tx,\n box.rect.y - dy + ty,\n box.rect.width + 2 * dx,\n box.rect.height + 2 * dy\n ),\n box.rx + dx,\n box.ry + dy\n );\n};\n\nexport const deflate = (\n Skia: Skia,\n box: SkRRect,\n dx: number,\n dy: number,\n tx = 0,\n ty = 0\n) => {\n \"worklet\";\n return inflate(Skia, box, -dx, -dy, tx, ty);\n};\n"],"mappings":"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","ignoreList":[]}
@@ -16,8 +16,12 @@ const copyFrameOnAndroid = currentFrame => {
16
16
  if (_Platform.Platform.OS === "android") {
17
17
  const tex = currentFrame.value;
18
18
  if (tex) {
19
- currentFrame.value = tex; //.makeNonTextureImage();
20
- tex.dispose();
19
+ const copy = tex.makeNonTextureImage();
20
+ if (copy) {
21
+ currentFrame.value = copy;
22
+ tex.dispose();
23
+ }
24
+ // If copy fails, keep the original frame rather than showing black
21
25
  }
22
26
  }
23
27
  };
@@ -26,6 +30,8 @@ const setFrame = (video, currentFrame) => {
26
30
 
27
31
  const img = video.nextImage();
28
32
  if (img) {
33
+ var _currentFrame$value;
34
+ (_currentFrame$value = currentFrame.value) === null || _currentFrame$value === void 0 || _currentFrame$value.dispose();
29
35
  currentFrame.value = img;
30
36
  copyFrameOnAndroid(currentFrame);
31
37
  }
@@ -1 +1 @@
1
- {"version":3,"names":["_react","require","_Platform","_ReanimatedProxy","_interopRequireDefault","_useVideoLoading","e","__esModule","default","copyFrameOnAndroid","currentFrame","Platform","OS","tex","value","dispose","setFrame","video","img","nextImage","defaultOptions","looping","paused","seek","volume","useOption","defaultValue","Rea","useSharedValue","isSharedValue","disposeVideo","useVideo","source","userOptions","_userOptions$paused","_userOptions$looping","_userOptions$seek","_userOptions$volume","useVideoLoading","isPaused","currentTime","duration","useMemo","_video$duration","framerate","_video$framerate","size","_video$size","width","height","rotation","_video$rotation","useAnimatedReaction","pause","play","setVolume","setLooping","useFrameCallback","_frameInfo","useEffect","runOnUI","exports"],"sources":["useVideo.ts"],"sourcesContent":["import type { SharedValue, FrameInfo } from \"react-native-reanimated\";\nimport { useEffect, useMemo } from \"react\";\n\nimport type { SkImage, Video } from \"../../skia/types\";\nimport { Platform } from \"../../Platform\";\n\nimport Rea from \"./ReanimatedProxy\";\nimport { useVideoLoading } from \"./useVideoLoading\";\n\ntype MaybeAnimated<T> = SharedValue<T> | T;\n\ninterface PlaybackOptions {\n looping: MaybeAnimated<boolean>;\n paused: MaybeAnimated<boolean>;\n seek: MaybeAnimated<number | null>;\n volume: MaybeAnimated<number>;\n}\n\nconst copyFrameOnAndroid = (currentFrame: SharedValue<SkImage | null>) => {\n \"worklet\";\n // on android we need to copy the texture before it's invalidated\n if (Platform.OS === \"android\") {\n const tex = currentFrame.value;\n if (tex) {\n currentFrame.value = tex; //.makeNonTextureImage();\n tex.dispose();\n }\n }\n};\n\nconst setFrame = (video: Video, currentFrame: SharedValue<SkImage | null>) => {\n \"worklet\";\n const img = video.nextImage();\n if (img) {\n currentFrame.value = img;\n copyFrameOnAndroid(currentFrame);\n }\n};\n\nconst defaultOptions = {\n looping: true,\n paused: false,\n seek: null,\n volume: 0,\n};\n\nconst useOption = <T>(value: MaybeAnimated<T>) => {\n \"worklet\";\n const defaultValue = Rea.useSharedValue(\n Rea.isSharedValue(value) ? value.value : value\n );\n return Rea.isSharedValue(value)\n ? (value as SharedValue<T>)\n : (defaultValue as SharedValue<T>);\n};\n\nconst disposeVideo = (video: Video | null) => {\n \"worklet\";\n video?.dispose();\n};\n\nexport const useVideo = (\n source: string | null,\n userOptions?: Partial<PlaybackOptions>\n) => {\n const video = useVideoLoading(source);\n const isPaused = useOption(userOptions?.paused ?? defaultOptions.paused);\n const looping = useOption(userOptions?.looping ?? defaultOptions.looping);\n const seek = useOption(userOptions?.seek ?? defaultOptions.seek);\n const volume = useOption(userOptions?.volume ?? defaultOptions.volume);\n const currentFrame = Rea.useSharedValue<null | SkImage>(null);\n const currentTime = Rea.useSharedValue(0);\n const duration = useMemo(() => video?.duration() ?? 0, [video]);\n const framerate = useMemo(\n () => (Platform.OS === \"web\" ? -1 : (video?.framerate() ?? 0)),\n [video]\n );\n const size = useMemo(() => video?.size() ?? { width: 0, height: 0 }, [video]);\n const rotation = useMemo(() => video?.rotation() ?? 0, [video]);\n\n // Handle pause/play state changes\n Rea.useAnimatedReaction(\n () => isPaused.value,\n (paused) => {\n if (paused) {\n video?.pause();\n } else {\n video?.play();\n }\n }\n );\n\n // Handle seek\n Rea.useAnimatedReaction(\n () => seek.value,\n (value) => {\n if (value !== null) {\n video?.seek(value);\n seek.value = null;\n }\n }\n );\n\n // Handle volume changes\n Rea.useAnimatedReaction(\n () => volume.value,\n (value) => {\n video?.setVolume(value);\n }\n );\n\n // Handle looping changes\n Rea.useAnimatedReaction(\n () => looping.value,\n (value) => {\n video?.setLooping(value);\n }\n );\n\n // Frame callback - simplified since native handles frame timing\n Rea.useFrameCallback((_frameInfo: FrameInfo) => {\n \"worklet\";\n if (!video) {\n return;\n }\n // Update current time from native player\n currentTime.value = video.currentTime();\n // Get the latest frame (native handles timing via CADisplayLink/etc)\n setFrame(video, currentFrame);\n });\n\n // Apply initial state when video becomes available\n useEffect(() => {\n if (video) {\n video.setLooping(looping.value);\n video.setVolume(volume.value);\n if (isPaused.value) {\n video.pause();\n } else {\n video.play();\n }\n }\n return () => {\n Rea.runOnUI(disposeVideo)(video);\n };\n }, [video, isPaused, looping, volume]);\n\n return {\n currentFrame,\n currentTime,\n duration,\n framerate,\n rotation,\n size,\n };\n};\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"names":["_react","require","_Platform","_ReanimatedProxy","_interopRequireDefault","_useVideoLoading","e","__esModule","default","copyFrameOnAndroid","currentFrame","Platform","OS","tex","value","copy","makeNonTextureImage","dispose","setFrame","video","img","nextImage","_currentFrame$value","defaultOptions","looping","paused","seek","volume","useOption","defaultValue","Rea","useSharedValue","isSharedValue","disposeVideo","useVideo","source","userOptions","_userOptions$paused","_userOptions$looping","_userOptions$seek","_userOptions$volume","useVideoLoading","isPaused","currentTime","duration","useMemo","_video$duration","framerate","_video$framerate","size","_video$size","width","height","rotation","_video$rotation","useAnimatedReaction","pause","play","setVolume","setLooping","useFrameCallback","_frameInfo","useEffect","runOnUI","exports"],"sources":["useVideo.ts"],"sourcesContent":["import type { SharedValue, FrameInfo } from \"react-native-reanimated\";\nimport { useEffect, useMemo } from \"react\";\n\nimport type { SkImage, Video } from \"../../skia/types\";\nimport { Platform } from \"../../Platform\";\n\nimport Rea from \"./ReanimatedProxy\";\nimport { useVideoLoading } from \"./useVideoLoading\";\n\ntype MaybeAnimated<T> = SharedValue<T> | T;\n\ninterface PlaybackOptions {\n looping: MaybeAnimated<boolean>;\n paused: MaybeAnimated<boolean>;\n seek: MaybeAnimated<number | null>;\n volume: MaybeAnimated<number>;\n}\n\nconst copyFrameOnAndroid = (currentFrame: SharedValue<SkImage | null>) => {\n \"worklet\";\n // on android we need to copy the texture before it's invalidated\n if (Platform.OS === \"android\") {\n const tex = currentFrame.value;\n if (tex) {\n const copy = tex.makeNonTextureImage();\n if (copy) {\n currentFrame.value = copy;\n tex.dispose();\n }\n // If copy fails, keep the original frame rather than showing black\n }\n }\n};\n\nconst setFrame = (video: Video, currentFrame: SharedValue<SkImage | null>) => {\n \"worklet\";\n const img = video.nextImage();\n if (img) {\n currentFrame.value?.dispose();\n currentFrame.value = img;\n copyFrameOnAndroid(currentFrame);\n }\n};\n\nconst defaultOptions = {\n looping: true,\n paused: false,\n seek: null,\n volume: 0,\n};\n\nconst useOption = <T>(value: MaybeAnimated<T>) => {\n \"worklet\";\n const defaultValue = Rea.useSharedValue(\n Rea.isSharedValue(value) ? value.value : value\n );\n return Rea.isSharedValue(value)\n ? (value as SharedValue<T>)\n : (defaultValue as SharedValue<T>);\n};\n\nconst disposeVideo = (video: Video | null) => {\n \"worklet\";\n video?.dispose();\n};\n\nexport const useVideo = (\n source: string | null,\n userOptions?: Partial<PlaybackOptions>\n) => {\n const video = useVideoLoading(source);\n const isPaused = useOption(userOptions?.paused ?? defaultOptions.paused);\n const looping = useOption(userOptions?.looping ?? defaultOptions.looping);\n const seek = useOption(userOptions?.seek ?? defaultOptions.seek);\n const volume = useOption(userOptions?.volume ?? defaultOptions.volume);\n const currentFrame = Rea.useSharedValue<null | SkImage>(null);\n const currentTime = Rea.useSharedValue(0);\n const duration = useMemo(() => video?.duration() ?? 0, [video]);\n const framerate = useMemo(\n () => (Platform.OS === \"web\" ? -1 : (video?.framerate() ?? 0)),\n [video]\n );\n const size = useMemo(() => video?.size() ?? { width: 0, height: 0 }, [video]);\n const rotation = useMemo(() => video?.rotation() ?? 0, [video]);\n\n // Handle pause/play state changes\n Rea.useAnimatedReaction(\n () => isPaused.value,\n (paused) => {\n if (paused) {\n video?.pause();\n } else {\n video?.play();\n }\n }\n );\n\n // Handle seek\n Rea.useAnimatedReaction(\n () => seek.value,\n (value) => {\n if (value !== null) {\n video?.seek(value);\n seek.value = null;\n }\n }\n );\n\n // Handle volume changes\n Rea.useAnimatedReaction(\n () => volume.value,\n (value) => {\n video?.setVolume(value);\n }\n );\n\n // Handle looping changes\n Rea.useAnimatedReaction(\n () => looping.value,\n (value) => {\n video?.setLooping(value);\n }\n );\n\n // Frame callback - simplified since native handles frame timing\n Rea.useFrameCallback((_frameInfo: FrameInfo) => {\n \"worklet\";\n if (!video) {\n return;\n }\n // Update current time from native player\n currentTime.value = video.currentTime();\n // Get the latest frame (native handles timing via CADisplayLink/etc)\n setFrame(video, currentFrame);\n });\n\n // Apply initial state when video becomes available\n useEffect(() => {\n if (video) {\n video.setLooping(looping.value);\n video.setVolume(volume.value);\n if (isPaused.value) {\n video.pause();\n } else {\n video.play();\n }\n }\n return () => {\n Rea.runOnUI(disposeVideo)(video);\n };\n }, [video, isPaused, looping, volume]);\n\n return {\n currentFrame,\n currentTime,\n duration,\n framerate,\n rotation,\n size,\n };\n};\n"],"mappings":"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","ignoreList":[]}
@@ -1,3 +1,4 @@
1
+ /// <reference lib="esnext.disposable" preserve="true" />
1
2
  export interface SkJSIInstance<T extends string> extends Disposable {
2
3
  __typename__: T;
3
4
  dispose(): void;
@@ -1 +1 @@
1
- {"version":3,"names":[],"sources":["JsiInstance.ts"],"sourcesContent":["export interface SkJSIInstance<T extends string> extends Disposable {\n __typename__: T;\n dispose(): void;\n}\n"],"mappings":"","ignoreList":[]}
1
+ {"version":3,"names":[],"sources":["JsiInstance.ts"],"sourcesContent":["/// <reference lib=\"esnext.disposable\" preserve=\"true\" />\n\nexport interface SkJSIInstance<T extends string> extends Disposable {\n __typename__: T;\n dispose(): void;\n}\n"],"mappings":"","ignoreList":[]}
@@ -31,7 +31,10 @@ const createDrawingContext = (Skia, paintPool, canvas) => {
31
31
  if (paintPool.length === 0) {
32
32
  paintPool.push(Skia.Paint());
33
33
  } else {
34
+ // reset() produces an anti-alias false paint, unlike the Skia.Paint()
35
+ // factory: restore the default so reused pools render like the first frame.
34
36
  paintPool[0].reset();
37
+ paintPool[0].setAntiAlias(true);
35
38
  }
36
39
  paints.push(paintPool[0]);
37
40
  opacities.push(1);
@@ -49,12 +52,14 @@ const createDrawingContext = (Skia, paintPool, canvas) => {
49
52
  nextPaintIndex++;
50
53
  };
51
54
 
52
- // Pushes an externally owned paint (the `paint` prop) onto the stack. It must
53
- // push an opacity alongside it: restorePaint() pops both, so pushing only the
54
- // paint would underflow the opacity stack and leak the enclosing group's
55
- // opacity onto everything drawn afterwards.
55
+ // Pushes an externally owned paint (the `paint` prop) onto the stack. It
56
+ // pushes a frame-scoped copy, like the C++ DrawingCtx: materializePaint()
57
+ // mutates the top of the stack, and those mutations must not leak into the
58
+ // user-owned paint. It must also push an opacity alongside it: restorePaint()
59
+ // pops both, so pushing only the paint would underflow the opacity stack and
60
+ // leak the enclosing group's opacity onto everything drawn afterwards.
56
61
  const pushPaint = paint => {
57
- paints.push(paint);
62
+ paints.push(track(paint.copy()));
58
63
  opacities.push(opacities[opacities.length - 1]);
59
64
  };
60
65
  const getOpacity = () => {
@@ -1 +1 @@
1
- {"version":3,"names":["_FrameScope","require","createDrawingContext","Skia","paintPool","canvas","frameSkia","track","dispose","createFrameScope","paints","colorFilters","shaders","imageFilters","pathEffects","paintDeclarations","opacities","nextPaintIndex","length","push","Paint","reset","savePaint","nextPaint","assign","getCurrentPaint","pushPaint","paint","getOpacity","setOpacity","newOpacity","Math","max","min","saveBackdropFilter","imageFilter","imgf","pop","cf","ImageFilter","MakeColorFilter","saveLayer","undefined","restore","restorePaint","materializePaint","setColorFilter","reduceRight","inner","outer","ColorFilter","MakeCompose","setShader","setImageFilter","setPathEffect","PathEffect","exports"],"sources":["DrawingContext.ts"],"sourcesContent":["import type {\n Skia,\n SkCanvas,\n SkColorFilter,\n SkPaint,\n SkShader,\n SkImageFilter,\n SkPathEffect,\n} from \"../../skia/types\";\n\nimport { createFrameScope } from \"./FrameScope\";\n\nexport const createDrawingContext = (\n Skia: Skia,\n paintPool: SkPaint[],\n canvas: SkCanvas\n) => {\n \"worklet\";\n\n // Everything the renderer creates through frameSkia is frame-scoped and\n // deleted by dispose() once the frame has been recorded. The paint pool is\n // recording-scoped and deliberately allocated with the raw Skia instance.\n const { Skia: frameSkia, track, dispose } = createFrameScope(Skia);\n\n // State (formerly class fields)\n const paints: SkPaint[] = [];\n const colorFilters: SkColorFilter[] = [];\n const shaders: SkShader[] = [];\n const imageFilters: SkImageFilter[] = [];\n const pathEffects: SkPathEffect[] = [];\n const paintDeclarations: SkPaint[] = [];\n const opacities: number[] = [];\n\n let nextPaintIndex = 1;\n\n // Initialize first paint and opacity\n if (paintPool.length === 0) {\n paintPool.push(Skia.Paint());\n } else {\n paintPool[0].reset();\n }\n paints.push(paintPool[0]);\n opacities.push(1);\n\n // Methods (formerly class methods)\n const savePaint = () => {\n // Get next available paint from pool or create new one if needed\n if (nextPaintIndex >= paintPool.length) {\n paintPool.push(Skia.Paint());\n }\n\n const nextPaint = paintPool[nextPaintIndex];\n nextPaint.assign(getCurrentPaint()); // Reuse allocation by copying properties\n paints.push(nextPaint);\n opacities.push(opacities[opacities.length - 1]);\n nextPaintIndex++;\n };\n\n // Pushes an externally owned paint (the `paint` prop) onto the stack. It must\n // push an opacity alongside it: restorePaint() pops both, so pushing only the\n // paint would underflow the opacity stack and leak the enclosing group's\n // opacity onto everything drawn afterwards.\n const pushPaint = (paint: SkPaint) => {\n paints.push(paint);\n opacities.push(opacities[opacities.length - 1]);\n };\n\n const getOpacity = () => {\n return opacities[opacities.length - 1];\n };\n\n const setOpacity = (newOpacity: number) => {\n opacities[opacities.length - 1] = Math.max(0, Math.min(1, newOpacity));\n };\n\n const saveBackdropFilter = () => {\n let imageFilter: SkImageFilter | null = null;\n const imgf = imageFilters.pop();\n if (imgf) {\n imageFilter = imgf;\n } else {\n const cf = colorFilters.pop();\n if (cf) {\n imageFilter = frameSkia.ImageFilter.MakeColorFilter(cf, null);\n }\n }\n canvas.saveLayer(undefined, null, imageFilter);\n canvas.restore();\n };\n\n // Equivalent to the `get paint()` getter in the original class\n const getCurrentPaint = () => {\n return paints[paints.length - 1];\n };\n\n const restorePaint = () => {\n opacities.pop();\n return paints.pop();\n };\n\n const materializePaint = () => {\n // Color Filters\n if (colorFilters.length > 0) {\n getCurrentPaint().setColorFilter(\n colorFilters.reduceRight((inner, outer) =>\n inner ? frameSkia.ColorFilter.MakeCompose(outer, inner) : outer\n )\n );\n }\n // Shaders\n if (shaders.length > 0) {\n getCurrentPaint().setShader(shaders[shaders.length - 1]);\n }\n // Image Filters\n if (imageFilters.length > 0) {\n getCurrentPaint().setImageFilter(\n imageFilters.reduceRight((inner, outer) =>\n inner ? frameSkia.ImageFilter.MakeCompose(outer, inner) : outer\n )\n );\n }\n\n // Path Effects\n if (pathEffects.length > 0) {\n getCurrentPaint().setPathEffect(\n pathEffects.reduceRight((inner, outer) =>\n inner ? frameSkia.PathEffect.MakeCompose(outer, inner) : outer\n )\n );\n }\n\n // Clear arrays\n colorFilters.length = 0;\n shaders.length = 0;\n imageFilters.length = 0;\n pathEffects.length = 0;\n };\n\n // Return an object containing the Skia reference, the canvas, and the methods\n return {\n // Public fields\n Skia: frameSkia,\n canvas,\n track,\n dispose,\n paints,\n colorFilters,\n shaders,\n imageFilters,\n pathEffects,\n paintDeclarations,\n paintPool,\n\n // Public methods\n savePaint,\n pushPaint,\n saveBackdropFilter,\n get paint() {\n return paints[paints.length - 1];\n }, // the \"getter\" for the current paint\n restorePaint,\n materializePaint,\n getOpacity,\n setOpacity,\n };\n};\n\nexport type DrawingContext = ReturnType<typeof createDrawingContext>;\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"names":["_FrameScope","require","createDrawingContext","Skia","paintPool","canvas","frameSkia","track","dispose","createFrameScope","paints","colorFilters","shaders","imageFilters","pathEffects","paintDeclarations","opacities","nextPaintIndex","length","push","Paint","reset","setAntiAlias","savePaint","nextPaint","assign","getCurrentPaint","pushPaint","paint","copy","getOpacity","setOpacity","newOpacity","Math","max","min","saveBackdropFilter","imageFilter","imgf","pop","cf","ImageFilter","MakeColorFilter","saveLayer","undefined","restore","restorePaint","materializePaint","setColorFilter","reduceRight","inner","outer","ColorFilter","MakeCompose","setShader","setImageFilter","setPathEffect","PathEffect","exports"],"sources":["DrawingContext.ts"],"sourcesContent":["import type {\n Skia,\n SkCanvas,\n SkColorFilter,\n SkPaint,\n SkShader,\n SkImageFilter,\n SkPathEffect,\n} from \"../../skia/types\";\n\nimport { createFrameScope } from \"./FrameScope\";\n\nexport const createDrawingContext = (\n Skia: Skia,\n paintPool: SkPaint[],\n canvas: SkCanvas\n) => {\n \"worklet\";\n\n // Everything the renderer creates through frameSkia is frame-scoped and\n // deleted by dispose() once the frame has been recorded. The paint pool is\n // recording-scoped and deliberately allocated with the raw Skia instance.\n const { Skia: frameSkia, track, dispose } = createFrameScope(Skia);\n\n // State (formerly class fields)\n const paints: SkPaint[] = [];\n const colorFilters: SkColorFilter[] = [];\n const shaders: SkShader[] = [];\n const imageFilters: SkImageFilter[] = [];\n const pathEffects: SkPathEffect[] = [];\n const paintDeclarations: SkPaint[] = [];\n const opacities: number[] = [];\n\n let nextPaintIndex = 1;\n\n // Initialize first paint and opacity\n if (paintPool.length === 0) {\n paintPool.push(Skia.Paint());\n } else {\n // reset() produces an anti-alias false paint, unlike the Skia.Paint()\n // factory: restore the default so reused pools render like the first frame.\n paintPool[0].reset();\n paintPool[0].setAntiAlias(true);\n }\n paints.push(paintPool[0]);\n opacities.push(1);\n\n // Methods (formerly class methods)\n const savePaint = () => {\n // Get next available paint from pool or create new one if needed\n if (nextPaintIndex >= paintPool.length) {\n paintPool.push(Skia.Paint());\n }\n\n const nextPaint = paintPool[nextPaintIndex];\n nextPaint.assign(getCurrentPaint()); // Reuse allocation by copying properties\n paints.push(nextPaint);\n opacities.push(opacities[opacities.length - 1]);\n nextPaintIndex++;\n };\n\n // Pushes an externally owned paint (the `paint` prop) onto the stack. It\n // pushes a frame-scoped copy, like the C++ DrawingCtx: materializePaint()\n // mutates the top of the stack, and those mutations must not leak into the\n // user-owned paint. It must also push an opacity alongside it: restorePaint()\n // pops both, so pushing only the paint would underflow the opacity stack and\n // leak the enclosing group's opacity onto everything drawn afterwards.\n const pushPaint = (paint: SkPaint) => {\n paints.push(track(paint.copy()));\n opacities.push(opacities[opacities.length - 1]);\n };\n\n const getOpacity = () => {\n return opacities[opacities.length - 1];\n };\n\n const setOpacity = (newOpacity: number) => {\n opacities[opacities.length - 1] = Math.max(0, Math.min(1, newOpacity));\n };\n\n const saveBackdropFilter = () => {\n let imageFilter: SkImageFilter | null = null;\n const imgf = imageFilters.pop();\n if (imgf) {\n imageFilter = imgf;\n } else {\n const cf = colorFilters.pop();\n if (cf) {\n imageFilter = frameSkia.ImageFilter.MakeColorFilter(cf, null);\n }\n }\n canvas.saveLayer(undefined, null, imageFilter);\n canvas.restore();\n };\n\n // Equivalent to the `get paint()` getter in the original class\n const getCurrentPaint = () => {\n return paints[paints.length - 1];\n };\n\n const restorePaint = () => {\n opacities.pop();\n return paints.pop();\n };\n\n const materializePaint = () => {\n // Color Filters\n if (colorFilters.length > 0) {\n getCurrentPaint().setColorFilter(\n colorFilters.reduceRight((inner, outer) =>\n inner ? frameSkia.ColorFilter.MakeCompose(outer, inner) : outer\n )\n );\n }\n // Shaders\n if (shaders.length > 0) {\n getCurrentPaint().setShader(shaders[shaders.length - 1]);\n }\n // Image Filters\n if (imageFilters.length > 0) {\n getCurrentPaint().setImageFilter(\n imageFilters.reduceRight((inner, outer) =>\n inner ? frameSkia.ImageFilter.MakeCompose(outer, inner) : outer\n )\n );\n }\n\n // Path Effects\n if (pathEffects.length > 0) {\n getCurrentPaint().setPathEffect(\n pathEffects.reduceRight((inner, outer) =>\n inner ? frameSkia.PathEffect.MakeCompose(outer, inner) : outer\n )\n );\n }\n\n // Clear arrays\n colorFilters.length = 0;\n shaders.length = 0;\n imageFilters.length = 0;\n pathEffects.length = 0;\n };\n\n // Return an object containing the Skia reference, the canvas, and the methods\n return {\n // Public fields\n Skia: frameSkia,\n canvas,\n track,\n dispose,\n paints,\n colorFilters,\n shaders,\n imageFilters,\n pathEffects,\n paintDeclarations,\n paintPool,\n\n // Public methods\n savePaint,\n pushPaint,\n saveBackdropFilter,\n get paint() {\n return paints[paints.length - 1];\n }, // the \"getter\" for the current paint\n restorePaint,\n materializePaint,\n getOpacity,\n setOpacity,\n };\n};\n\nexport type DrawingContext = ReturnType<typeof createDrawingContext>;\n"],"mappings":"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","ignoreList":[]}
@@ -289,21 +289,26 @@ const SkiaPictureView = props => {
289
289
  // - The renderer is created synchronously on mount (the canvas element is
290
290
  // guaranteed to exist in useLayoutEffect; a zero size at that point is
291
291
  // fine, the surface is created once the canvas is measurable).
292
- // - Redraw requests coalesce into a single animation frame. A picture
293
- // dispatched before the canvas has a size stays in pictureRef (drawing
294
- // while unmeasured is a no-op) and is painted by the resize path below
295
- // once the canvas becomes measurable, so it is never lost.
292
+ // - Redraw requests coalesce into a single microtask. A microtask (not an
293
+ // animation frame) so that a picture produced inside a rAF callback (the
294
+ // Reanimated mapper) is drawn before the current frame paints: deferring
295
+ // to the next rAF alternates between "flush pending in this frame" and
296
+ // "flush scheduled for the next frame", drawing on every other frame
297
+ // only and halving the effective frame rate. A picture dispatched before
298
+ // the canvas has a size stays in pictureRef (drawing while unmeasured is
299
+ // a no-op) and is painted by the resize path below once the canvas
300
+ // becomes measurable, so it is never lost.
296
301
  // - A ResizeObserver on the canvas itself recreates the surface and repaints
297
302
  // synchronously (its callbacks run after layout, before paint), and is
298
303
  // also the source of the user-facing onLayout event.
299
304
  const redrawPendingRef = (0, _react.useRef)(false);
300
- const frameRef = (0, _react.useRef)(0);
305
+ const flushScheduledRef = (0, _react.useRef)(false);
301
306
  const onLayoutRef = (0, _react.useRef)(onLayout);
302
307
  (0, _react.useLayoutEffect)(() => {
303
308
  onLayoutRef.current = onLayout;
304
309
  }, [onLayout]);
305
310
  const flushRedraw = (0, _react.useCallback)(() => {
306
- frameRef.current = 0;
311
+ flushScheduledRef.current = false;
307
312
  if (redrawPendingRef.current && rendererRef.current && pictureRef.current) {
308
313
  redrawPendingRef.current = false;
309
314
  rendererRef.current.draw(pictureRef.current);
@@ -313,8 +318,9 @@ const SkiaPictureView = props => {
313
318
  }, []);
314
319
  const redraw = (0, _react.useCallback)(() => {
315
320
  redrawPendingRef.current = true;
316
- if (frameRef.current === 0) {
317
- frameRef.current = requestAnimationFrame(flushRedraw);
321
+ if (!flushScheduledRef.current) {
322
+ flushScheduledRef.current = true;
323
+ queueMicrotask(flushRedraw);
318
324
  }
319
325
  }, [flushRedraw]);
320
326
  const getSize = (0, _react.useCallback)(() => {
@@ -449,12 +455,11 @@ const SkiaPictureView = props => {
449
455
  renderer.dispose();
450
456
  };
451
457
  }, [isStatic]);
452
- (0, _react.useEffect)(() => {
453
- return () => {
454
- cancelAnimationFrame(frameRef.current);
455
- frameRef.current = 0;
456
- };
457
- }, []);
458
+
459
+ // No flush cancellation is needed on unmount: a microtask queued before
460
+ // unmount runs within the same task, and flushRedraw no-ops once the
461
+ // layout-effect cleanup has nulled rendererRef.
462
+
458
463
  (0, _react.useImperativeHandle)(ref, () => ({
459
464
  setPicture,
460
465
  getSize,