@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.
- package/cpp/api/JsiSkFont.h +3 -2
- package/cpp/api/recorder/Paint.h +6 -2
- package/cpp/jsi/NativeObject.h +50 -32
- package/cpp/jsi/RuntimeAwareCache.cpp +1 -0
- package/cpp/jsi/RuntimeAwareCache.h +29 -0
- package/cpp/rnskia/RNSkManager.cpp +5 -0
- package/lib/commonjs/dom/nodes/datatypes/Rect.js +5 -1
- package/lib/commonjs/dom/nodes/datatypes/Rect.js.map +1 -1
- package/lib/commonjs/external/reanimated/useVideo.js +8 -2
- package/lib/commonjs/external/reanimated/useVideo.js.map +1 -1
- package/lib/commonjs/skia/types/JsiInstance.d.ts +1 -0
- package/lib/commonjs/skia/types/JsiInstance.js.map +1 -1
- package/lib/commonjs/sksg/Recorder/DrawingContext.js +10 -5
- package/lib/commonjs/sksg/Recorder/DrawingContext.js.map +1 -1
- package/lib/commonjs/views/SkiaPictureView.web.js +19 -14
- package/lib/commonjs/views/SkiaPictureView.web.js.map +1 -1
- package/lib/module/dom/nodes/datatypes/Rect.js +5 -1
- package/lib/module/dom/nodes/datatypes/Rect.js.map +1 -1
- package/lib/module/external/reanimated/useVideo.js +8 -2
- package/lib/module/external/reanimated/useVideo.js.map +1 -1
- package/lib/module/skia/types/JsiInstance.d.ts +1 -0
- package/lib/module/skia/types/JsiInstance.js.map +1 -1
- package/lib/module/sksg/Recorder/DrawingContext.js +10 -5
- package/lib/module/sksg/Recorder/DrawingContext.js.map +1 -1
- package/lib/module/views/SkiaPictureView.web.js +19 -14
- package/lib/module/views/SkiaPictureView.web.js.map +1 -1
- package/lib/typescript/src/skia/types/JsiInstance.d.ts +1 -0
- package/package.json +1 -1
- package/src/dom/nodes/datatypes/Rect.ts +6 -1
- package/src/external/reanimated/useVideo.ts +7 -2
- package/src/skia/types/JsiInstance.ts +2 -0
- package/src/sksg/Recorder/DrawingContext.ts +10 -5
- package/src/views/SkiaPictureView.web.tsx +17 -14
package/cpp/api/JsiSkFont.h
CHANGED
|
@@ -135,8 +135,9 @@ public:
|
|
|
135
135
|
bool isEmbolden() { return getObject()->isEmbolden(); }
|
|
136
136
|
|
|
137
137
|
std::shared_ptr<JsiSkTypeface> getTypeface() {
|
|
138
|
-
|
|
139
|
-
|
|
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) {
|
package/cpp/api/recorder/Paint.h
CHANGED
|
@@ -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
|
-
|
|
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()) {
|
package/cpp/jsi/NativeObject.h
CHANGED
|
@@ -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
|
|
55
|
-
* created for and allocates a new cache when the
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
RNJsi::RuntimeAwareCache<T> &get(jsi::Runtime &rt) {
|
|
65
|
-
auto
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
//
|
|
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 =
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
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,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
|
-
|
|
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","
|
|
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
|
-
|
|
20
|
-
|
|
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
|
|
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 +1 @@
|
|
|
1
|
-
{"version":3,"names":[],"sources":["JsiInstance.ts"],"sourcesContent":["
|
|
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
|
|
53
|
-
//
|
|
54
|
-
//
|
|
55
|
-
// opacity
|
|
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
|
|
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
|
|
293
|
-
//
|
|
294
|
-
//
|
|
295
|
-
//
|
|
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
|
|
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
|
-
|
|
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 (
|
|
317
|
-
|
|
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
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
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,
|