@shopify/react-native-skia 2.3.6 → 2.3.7
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/apple/SkiaCVPixelBufferUtils.mm +4 -8
- package/cpp/api/JsiSkCanvas.h +4 -2
- package/cpp/api/JsiSkTextStyle.h +1 -1
- package/cpp/api/recorder/Drawings.h +1 -30
- package/cpp/api/recorder/RNRecorder.h +17 -2
- package/cpp/jsi/RuntimeLifecycleMonitor.cpp +27 -12
- package/package.json +1 -1
- package/scripts/install-skia.mjs +74 -49
|
@@ -30,16 +30,12 @@
|
|
|
30
30
|
#include <TargetConditionals.h>
|
|
31
31
|
#if TARGET_RT_BIG_ENDIAN
|
|
32
32
|
#define FourCC2Str(fourcc) \
|
|
33
|
-
(const char[])
|
|
34
|
-
|
|
35
|
-
*(((char *)&fourcc) + 3), 0 \
|
|
36
|
-
}
|
|
33
|
+
(const char[]){*((char *)&fourcc), *(((char *)&fourcc) + 1), \
|
|
34
|
+
*(((char *)&fourcc) + 2), *(((char *)&fourcc) + 3), 0}
|
|
37
35
|
#else
|
|
38
36
|
#define FourCC2Str(fourcc) \
|
|
39
|
-
(const char[])
|
|
40
|
-
|
|
41
|
-
*(((char *)&fourcc) + 1), *(((char *)&fourcc) + 0), 0 \
|
|
42
|
-
}
|
|
37
|
+
(const char[]){*(((char *)&fourcc) + 3), *(((char *)&fourcc) + 2), \
|
|
38
|
+
*(((char *)&fourcc) + 1), *(((char *)&fourcc) + 0), 0}
|
|
43
39
|
#endif
|
|
44
40
|
|
|
45
41
|
// pragma MARK: TextureHolder
|
package/cpp/api/JsiSkCanvas.h
CHANGED
|
@@ -242,8 +242,10 @@ public:
|
|
|
242
242
|
}
|
|
243
243
|
|
|
244
244
|
JSI_HOST_FUNCTION(getTotalMatrix) {
|
|
245
|
-
auto matrix =
|
|
246
|
-
|
|
245
|
+
auto matrix =
|
|
246
|
+
std::make_shared<JsiSkMatrix>(getContext(), _canvas->getTotalMatrix());
|
|
247
|
+
return JSI_CREATE_HOST_OBJECT_WITH_MEMORY_PRESSURE(runtime, matrix,
|
|
248
|
+
getContext());
|
|
247
249
|
}
|
|
248
250
|
|
|
249
251
|
JSI_HOST_FUNCTION(drawPoints) {
|
package/cpp/api/JsiSkTextStyle.h
CHANGED
|
@@ -137,7 +137,7 @@ public:
|
|
|
137
137
|
}
|
|
138
138
|
if (object.hasProperty(runtime, "halfLeading")) {
|
|
139
139
|
auto propValue = object.getProperty(runtime, "halfLeading");
|
|
140
|
-
retVal.setHalfLeading(propValue.
|
|
140
|
+
retVal.setHalfLeading(propValue.getBool());
|
|
141
141
|
}
|
|
142
142
|
if (object.hasProperty(runtime, "letterSpacing")) {
|
|
143
143
|
auto propValue = object.getProperty(runtime, "letterSpacing");
|
|
@@ -486,17 +486,6 @@ public:
|
|
|
486
486
|
convertProperty(runtime, object, "rect", props.rect, variables);
|
|
487
487
|
}
|
|
488
488
|
|
|
489
|
-
~ImageCmd() {
|
|
490
|
-
if (props.image.has_value()) {
|
|
491
|
-
auto image = props.image.value();
|
|
492
|
-
if (image) {
|
|
493
|
-
_context->runOnMainThread([image]() {
|
|
494
|
-
// Image will be deleted when this lambda is destroyed on main thread
|
|
495
|
-
});
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
}
|
|
499
|
-
|
|
500
489
|
void draw(DrawingCtx *ctx) {
|
|
501
490
|
auto [x, y, width, height, rect, fit, image, sampling] = props;
|
|
502
491
|
if (image.has_value()) {
|
|
@@ -806,15 +795,6 @@ public:
|
|
|
806
795
|
convertProperty(runtime, object, "picture", props.picture, variables);
|
|
807
796
|
}
|
|
808
797
|
|
|
809
|
-
~PictureCmd() {
|
|
810
|
-
auto picture = props.picture;
|
|
811
|
-
if (picture) {
|
|
812
|
-
_context->runOnMainThread([picture]() {
|
|
813
|
-
// Picture will be deleted when this lambda is destroyed on main thread
|
|
814
|
-
});
|
|
815
|
-
}
|
|
816
|
-
}
|
|
817
|
-
|
|
818
798
|
void draw(DrawingCtx *ctx) { ctx->canvas->drawPicture(props.picture); }
|
|
819
799
|
};
|
|
820
800
|
|
|
@@ -951,15 +931,6 @@ public:
|
|
|
951
931
|
convertProperty(runtime, object, "sampling", props.sampling, variables);
|
|
952
932
|
}
|
|
953
933
|
|
|
954
|
-
~AtlasCmd() {
|
|
955
|
-
auto image = props.image;
|
|
956
|
-
if (image) {
|
|
957
|
-
_context->runOnMainThread([image]() {
|
|
958
|
-
// Image will be deleted when this lambda is destroyed on main thread
|
|
959
|
-
});
|
|
960
|
-
}
|
|
961
|
-
}
|
|
962
|
-
|
|
963
934
|
void draw(DrawingCtx *ctx) {
|
|
964
935
|
if (props.image) {
|
|
965
936
|
// Validate transforms and sprites have the same size
|
|
@@ -990,4 +961,4 @@ public:
|
|
|
990
961
|
}
|
|
991
962
|
};
|
|
992
963
|
|
|
993
|
-
} // namespace RNSkia
|
|
964
|
+
} // namespace RNSkia
|
|
@@ -29,7 +29,22 @@ public:
|
|
|
29
29
|
Variables variables;
|
|
30
30
|
|
|
31
31
|
Recorder() = default;
|
|
32
|
-
~Recorder()
|
|
32
|
+
~Recorder() {
|
|
33
|
+
if (!_context || commands.empty()) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
auto context = _context;
|
|
38
|
+
using CommandList = std::vector<std::unique_ptr<Command>>;
|
|
39
|
+
auto pendingCommands = std::make_shared<CommandList>(std::move(commands));
|
|
40
|
+
|
|
41
|
+
context->runOnMainThread(
|
|
42
|
+
[pendingCommands = std::move(pendingCommands)]() mutable {
|
|
43
|
+
// Destroy the recorded commands on the main thread to ensure GPU
|
|
44
|
+
// backed resources release safely.
|
|
45
|
+
pendingCommands->clear();
|
|
46
|
+
});
|
|
47
|
+
}
|
|
33
48
|
|
|
34
49
|
void savePaint(jsi::Runtime &runtime, const jsi::Object &props,
|
|
35
50
|
bool standalone) {
|
|
@@ -652,4 +667,4 @@ public:
|
|
|
652
667
|
}
|
|
653
668
|
};
|
|
654
669
|
|
|
655
|
-
} // namespace RNSkia
|
|
670
|
+
} // namespace RNSkia
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#include "RuntimeLifecycleMonitor.h"
|
|
2
2
|
|
|
3
|
+
#include <mutex>
|
|
3
4
|
#include <unordered_map>
|
|
4
5
|
#include <unordered_set>
|
|
5
6
|
#include <utility>
|
|
@@ -9,25 +10,43 @@ namespace RNJsi {
|
|
|
9
10
|
static std::unordered_map<jsi::Runtime *,
|
|
10
11
|
std::unordered_set<RuntimeLifecycleListener *>>
|
|
11
12
|
listeners;
|
|
13
|
+
static std::mutex listenersMutex;
|
|
12
14
|
|
|
13
15
|
struct RuntimeLifecycleMonitorObject : public jsi::HostObject {
|
|
14
16
|
jsi::Runtime *_rt;
|
|
15
17
|
explicit RuntimeLifecycleMonitorObject(jsi::Runtime *rt) : _rt(rt) {}
|
|
16
18
|
~RuntimeLifecycleMonitorObject() {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
std::unordered_set<RuntimeLifecycleListener *> listenersCopy;
|
|
20
|
+
{
|
|
21
|
+
std::lock_guard<std::mutex> lock(listenersMutex);
|
|
22
|
+
auto listenersSet = listeners.find(_rt);
|
|
23
|
+
if (listenersSet != listeners.end()) {
|
|
24
|
+
listenersCopy = listenersSet->second;
|
|
25
|
+
listeners.erase(listenersSet);
|
|
21
26
|
}
|
|
22
|
-
|
|
27
|
+
}
|
|
28
|
+
for (auto listener : listenersCopy) {
|
|
29
|
+
listener->onRuntimeDestroyed(_rt);
|
|
23
30
|
}
|
|
24
31
|
}
|
|
25
32
|
};
|
|
26
33
|
|
|
27
34
|
void RuntimeLifecycleMonitor::addListener(jsi::Runtime &rt,
|
|
28
35
|
RuntimeLifecycleListener *listener) {
|
|
29
|
-
|
|
30
|
-
|
|
36
|
+
bool shouldInstallMonitor = false;
|
|
37
|
+
{
|
|
38
|
+
std::lock_guard<std::mutex> lock(listenersMutex);
|
|
39
|
+
auto listenersSet = listeners.find(&rt);
|
|
40
|
+
if (listenersSet == listeners.end()) {
|
|
41
|
+
std::unordered_set<RuntimeLifecycleListener *> newSet;
|
|
42
|
+
newSet.insert(listener);
|
|
43
|
+
listeners.emplace(&rt, std::move(newSet));
|
|
44
|
+
shouldInstallMonitor = true;
|
|
45
|
+
} else {
|
|
46
|
+
listenersSet->second.insert(listener);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (shouldInstallMonitor) {
|
|
31
50
|
// We install a global host object in the provided runtime, this way we can
|
|
32
51
|
// use that host object destructor to get notified when the runtime is being
|
|
33
52
|
// terminated. We use a unique name for the object as it gets saved with the
|
|
@@ -36,16 +55,12 @@ void RuntimeLifecycleMonitor::addListener(jsi::Runtime &rt,
|
|
|
36
55
|
rt, "__rnskia_rt_lifecycle_monitor",
|
|
37
56
|
jsi::Object::createFromHostObject(
|
|
38
57
|
rt, std::make_shared<RuntimeLifecycleMonitorObject>(&rt)));
|
|
39
|
-
std::unordered_set<RuntimeLifecycleListener *> newSet;
|
|
40
|
-
newSet.insert(listener);
|
|
41
|
-
listeners.emplace(&rt, std::move(newSet));
|
|
42
|
-
} else {
|
|
43
|
-
listenersSet->second.insert(listener);
|
|
44
58
|
}
|
|
45
59
|
}
|
|
46
60
|
|
|
47
61
|
void RuntimeLifecycleMonitor::removeListener(
|
|
48
62
|
jsi::Runtime &rt, RuntimeLifecycleListener *listener) {
|
|
63
|
+
std::lock_guard<std::mutex> lock(listenersMutex);
|
|
49
64
|
auto listenersSet = listeners.find(&rt);
|
|
50
65
|
if (listenersSet == listeners.end()) {
|
|
51
66
|
// nothing to do here
|
package/package.json
CHANGED
package/scripts/install-skia.mjs
CHANGED
|
@@ -77,64 +77,89 @@ const runCommand = (command, args) => {
|
|
|
77
77
|
});
|
|
78
78
|
};
|
|
79
79
|
|
|
80
|
-
const
|
|
80
|
+
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
|
81
|
+
|
|
82
|
+
const downloadToFile = (url, destPath, maxRetries = 5) => {
|
|
81
83
|
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
res.
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
84
|
+
|
|
85
|
+
const attemptDownload = (retryCount = 0) => {
|
|
86
|
+
return new Promise((resolve, reject) => {
|
|
87
|
+
const request = (currentUrl) => {
|
|
88
|
+
https
|
|
89
|
+
.get(currentUrl, { headers: { "User-Agent": "node" } }, (res) => {
|
|
90
|
+
if (
|
|
91
|
+
res.statusCode &&
|
|
92
|
+
[301, 302, 303, 307, 308].includes(res.statusCode)
|
|
93
|
+
) {
|
|
94
|
+
const { location } = res.headers;
|
|
95
|
+
if (location) {
|
|
96
|
+
res.resume();
|
|
97
|
+
request(location);
|
|
98
|
+
} else {
|
|
99
|
+
reject(new Error(`Redirect without location for ${currentUrl}`));
|
|
100
|
+
}
|
|
101
|
+
return;
|
|
96
102
|
}
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
103
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
new Error(
|
|
104
|
+
if (res.statusCode !== 200) {
|
|
105
|
+
const error = new Error(
|
|
103
106
|
`Failed to download: ${res.statusCode} ${res.statusMessage}`
|
|
104
|
-
)
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
const fileStream = fs.createWriteStream(destPath);
|
|
111
|
-
res.pipe(fileStream);
|
|
107
|
+
);
|
|
108
|
+
error.statusCode = res.statusCode;
|
|
109
|
+
reject(error);
|
|
110
|
+
res.resume();
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
112
113
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
114
|
+
const fileStream = fs.createWriteStream(destPath);
|
|
115
|
+
res.pipe(fileStream);
|
|
116
|
+
|
|
117
|
+
fileStream.on("finish", () => {
|
|
118
|
+
fileStream.close((err) => {
|
|
119
|
+
if (err) {
|
|
120
|
+
// If closing the stream errors, perform the same cleanup and reject.
|
|
121
|
+
fileStream.destroy();
|
|
122
|
+
fs.unlink(destPath, () => reject(err));
|
|
123
|
+
} else {
|
|
124
|
+
resolve();
|
|
125
|
+
}
|
|
126
|
+
});
|
|
122
127
|
});
|
|
123
|
-
});
|
|
124
128
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
+
const cleanup = (error) => {
|
|
130
|
+
fileStream.destroy();
|
|
131
|
+
fs.unlink(destPath, () => reject(error));
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
res.on("error", cleanup);
|
|
135
|
+
fileStream.on("error", cleanup);
|
|
136
|
+
})
|
|
137
|
+
.on("error", reject);
|
|
138
|
+
};
|
|
129
139
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
.on("error", reject);
|
|
134
|
-
};
|
|
140
|
+
request(url);
|
|
141
|
+
});
|
|
142
|
+
};
|
|
135
143
|
|
|
136
|
-
|
|
137
|
-
|
|
144
|
+
const downloadWithRetry = async (retryCount = 0) => {
|
|
145
|
+
try {
|
|
146
|
+
await attemptDownload(retryCount);
|
|
147
|
+
} catch (error) {
|
|
148
|
+
const isRateLimit = error.statusCode === 403 || error.message.includes('rate limit');
|
|
149
|
+
const shouldRetry = retryCount < maxRetries && (isRateLimit || error.code === 'ECONNRESET' || error.code === 'ETIMEDOUT');
|
|
150
|
+
|
|
151
|
+
if (shouldRetry) {
|
|
152
|
+
const delay = Math.pow(2, retryCount) * 1000; // 1s, 2s, 4s, 8s, 16s
|
|
153
|
+
console.log(` ⚠️ Download failed (${error.message}), retrying in ${delay/1000}s... (attempt ${retryCount + 1}/${maxRetries})`);
|
|
154
|
+
await sleep(delay);
|
|
155
|
+
return downloadWithRetry(retryCount + 1);
|
|
156
|
+
} else {
|
|
157
|
+
throw error;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
return downloadWithRetry();
|
|
138
163
|
};
|
|
139
164
|
|
|
140
165
|
const extractTarGz = async (archivePath, destDir) => {
|