@next2d/builder 0.1.7 → 0.1.8
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/dist/index.js +62 -26
- package/package.json +3 -2
- package/templates/xbox/CMakeLists.txt +69 -0
- package/templates/xbox/README.md +16 -8
- package/templates/xbox/cmake/FetchDawn.cmake +47 -6
- package/templates/xbox/cmake/FindV8.cmake +15 -0
- package/templates/xbox/js/bootstrap.js +31 -28
- package/templates/xbox/js/selftest.js +740 -90
- package/templates/xbox/src/EventLoop.cpp +28 -1
- package/templates/xbox/src/EventLoop.h +12 -0
- package/templates/xbox/src/bindings/Audio.cpp +99 -15
- package/templates/xbox/src/bindings/Bindings.h +10 -0
- package/templates/xbox/src/bindings/Canvas.cpp +45 -0
- package/templates/xbox/src/bindings/Canvas2D.cpp +2 -5
- package/templates/xbox/src/bindings/DomShims.cpp +127 -0
- package/templates/xbox/src/bindings/EventTarget.cpp +2 -0
- package/templates/xbox/src/bindings/Fetch.cpp +2 -8
- package/templates/xbox/src/bindings/Image.cpp +212 -10
- package/templates/xbox/src/bindings/Polyfills.cpp +791 -0
- package/templates/xbox/src/bindings/Video.cpp +96 -7
- package/templates/xbox/src/bindings/webgpu/WebGPU.cpp +815 -15
- package/templates/xbox/src/bindings/webgpu/WebGPUCommon.h +3 -8
- package/templates/xbox/src/gpu/DawnContext.cpp +90 -3
- package/templates/xbox/src/gpu/DawnContext.h +18 -0
- package/templates/xbox/src/main.cpp +88 -6
- package/templates/xbox/src/platform/AudioEngine.cpp +9 -3
- package/templates/xbox/src/v8/V8Runtime.cpp +69 -5
- package/templates/xbox/src/v8/V8Util.h +29 -0
- package/templates/xbox/src/v8/WeakHandle.h +45 -0
- package/templates/xbox/src/worker/WorkerRuntime.cpp +325 -41
- package/templates/xbox/src/worker/WorkerRuntime.h +20 -10
- package/templates/xbox/tests/check_local.sh +2 -1
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
#include "EventLoop.h"
|
|
5
5
|
#include "AssetLoader.h"
|
|
6
6
|
#include "bindings/Bindings.h"
|
|
7
|
+
#include "bindings/ImageSource.h"
|
|
8
|
+
#include "platform/ImageTypes.h"
|
|
7
9
|
#include "v8/V8Util.h"
|
|
8
10
|
|
|
9
11
|
#include <cstdlib>
|
|
@@ -22,14 +24,108 @@ using v8util::ToStdString;
|
|
|
22
24
|
// ===========================================================================
|
|
23
25
|
namespace {
|
|
24
26
|
|
|
25
|
-
|
|
27
|
+
// ログ用: data:/blob: URL は長大になるため先頭だけに切り詰める
|
|
28
|
+
std::string ShortUrl(const std::string& url)
|
|
29
|
+
{
|
|
30
|
+
if (url.size() <= 64) {
|
|
31
|
+
return url;
|
|
32
|
+
}
|
|
33
|
+
return url.substr(0, 64) + "... (" + std::to_string(url.size()) + " chars)";
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// base64 デコード (data: URL 用)。不正文字があれば false。
|
|
37
|
+
bool DecodeBase64(const std::string& in, std::string* out)
|
|
38
|
+
{
|
|
39
|
+
auto val6 = [](unsigned char c) -> int {
|
|
40
|
+
if (c >= 'A' && c <= 'Z') return c - 'A';
|
|
41
|
+
if (c >= 'a' && c <= 'z') return c - 'a' + 26;
|
|
42
|
+
if (c >= '0' && c <= '9') return c - '0' + 52;
|
|
43
|
+
if (c == '+') return 62;
|
|
44
|
+
if (c == '/') return 63;
|
|
45
|
+
return -1;
|
|
46
|
+
};
|
|
47
|
+
out->clear();
|
|
48
|
+
out->reserve(in.size() * 3 / 4);
|
|
49
|
+
int val = 0;
|
|
50
|
+
int bits = -8;
|
|
51
|
+
for (unsigned char c : in) {
|
|
52
|
+
if (c == '=') {
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
if (c == '\r' || c == '\n' || c == ' ') {
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
const int d = val6(c);
|
|
59
|
+
if (d < 0) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
val = (val << 6) | d;
|
|
63
|
+
bits += 6;
|
|
64
|
+
if (bits >= 0) {
|
|
65
|
+
out->push_back(static_cast<char>((val >> bits) & 0xFF));
|
|
66
|
+
bits -= 8;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// %xx パーセントデコード (非 base64 の data: URL 用)
|
|
73
|
+
std::string PercentDecode(const std::string& s)
|
|
74
|
+
{
|
|
75
|
+
std::string out;
|
|
76
|
+
out.reserve(s.size());
|
|
77
|
+
for (size_t i = 0; i < s.size(); ++i) {
|
|
78
|
+
if (s[i] == '%' && i + 2 < s.size()) {
|
|
79
|
+
const auto hex = [](char c) -> int {
|
|
80
|
+
if (c >= '0' && c <= '9') return c - '0';
|
|
81
|
+
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
|
|
82
|
+
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
|
|
83
|
+
return -1;
|
|
84
|
+
};
|
|
85
|
+
const int hi = hex(s[i + 1]);
|
|
86
|
+
const int lo = hex(s[i + 2]);
|
|
87
|
+
if (hi >= 0 && lo >= 0) {
|
|
88
|
+
out.push_back(static_cast<char>((hi << 4) | lo));
|
|
89
|
+
i += 2;
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
out.push_back(s[i]);
|
|
94
|
+
}
|
|
95
|
+
return out;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
bool HasBoolMarker(v8::Isolate* isolate, v8::Local<v8::Object> obj, const char* name)
|
|
26
99
|
{
|
|
27
100
|
v8::Local<v8::Context> ctx = isolate->GetCurrentContext();
|
|
28
101
|
v8::Local<v8::Value> v;
|
|
29
|
-
return obj->Get(ctx, Str(isolate,
|
|
102
|
+
return obj->Get(ctx, Str(isolate, name)).ToLocal(&v) &&
|
|
30
103
|
v->IsBoolean() && v.As<v8::Boolean>()->Value();
|
|
31
104
|
}
|
|
32
105
|
|
|
106
|
+
bool IsOffscreen(v8::Isolate* isolate, v8::Local<v8::Object> obj)
|
|
107
|
+
{
|
|
108
|
+
return HasBoolMarker(isolate, obj, "__isOffscreenCanvas");
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
bool IsImageBitmapObject(v8::Isolate* isolate, v8::Local<v8::Object> obj)
|
|
112
|
+
{
|
|
113
|
+
return HasBoolMarker(isolate, obj, "__isImageBitmap");
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// WriteHostObject / ReadHostObject のタグ
|
|
117
|
+
constexpr uint32_t kHostObjectOffscreenCanvas = 1;
|
|
118
|
+
constexpr uint32_t kHostObjectImageBitmap = 2;
|
|
119
|
+
|
|
120
|
+
// メッセージトレース: 定常時のログ洪水を防ぐため、最初の 120 件のあとは
|
|
121
|
+
// 200 件ごとに 1 回だけ出力する (render は 60fps で双方向に流れる)。
|
|
122
|
+
bool TraceMessage()
|
|
123
|
+
{
|
|
124
|
+
static uint64_t count = 0;
|
|
125
|
+
++count;
|
|
126
|
+
return count <= 120 || count % 200 == 0;
|
|
127
|
+
}
|
|
128
|
+
|
|
33
129
|
class SerializerDelegate : public v8::ValueSerializer::Delegate {
|
|
34
130
|
public:
|
|
35
131
|
explicit SerializerDelegate(v8::Isolate* isolate) : isolate_(isolate) {}
|
|
@@ -39,18 +135,46 @@ public:
|
|
|
39
135
|
isolate_->ThrowException(v8::Exception::Error(message));
|
|
40
136
|
}
|
|
41
137
|
|
|
42
|
-
//
|
|
138
|
+
// すべてのオブジェクトで IsHostObject を照会させる。
|
|
139
|
+
// OffscreenCanvas は ObjectTemplate 由来ではない plain object のため、
|
|
140
|
+
// これが無いと V8 は IsHostObject を呼ばず、getContext 等の関数プロパティで
|
|
141
|
+
// 「could not be cloned」になる。
|
|
142
|
+
bool HasCustomHostObject(v8::Isolate*) override { return true; }
|
|
143
|
+
|
|
144
|
+
// OffscreenCanvas / ImageBitmap をホストオブジェクトとして WriteHostObject に回す。
|
|
43
145
|
v8::Maybe<bool> IsHostObject(v8::Isolate*, v8::Local<v8::Object> object) override
|
|
44
146
|
{
|
|
45
|
-
return v8::Just(IsOffscreen(isolate_, object)
|
|
147
|
+
return v8::Just(IsOffscreen(isolate_, object) ||
|
|
148
|
+
IsImageBitmapObject(isolate_, object));
|
|
46
149
|
}
|
|
47
150
|
|
|
48
151
|
v8::Maybe<bool> WriteHostObject(v8::Isolate*, v8::Local<v8::Object> object) override
|
|
49
152
|
{
|
|
50
153
|
v8::Local<v8::Context> ctx = isolate_->GetCurrentContext();
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
154
|
+
|
|
155
|
+
// ImageBitmap: ピクセルごと複製する (render メッセージの imageBitmaps 用)
|
|
156
|
+
if (IsImageBitmapObject(isolate_, object)) {
|
|
157
|
+
const uint8_t* px = nullptr;
|
|
158
|
+
uint32_t w = 0, h = 0;
|
|
159
|
+
if (!GetImageSourcePixels(isolate_, object, &px, &w, &h) || !px) {
|
|
160
|
+
isolate_->ThrowException(v8::Exception::Error(
|
|
161
|
+
Str(isolate_, "ImageBitmap clone failed")));
|
|
162
|
+
return v8::Nothing<bool>();
|
|
163
|
+
}
|
|
164
|
+
serializer->WriteUint32(kHostObjectImageBitmap);
|
|
165
|
+
serializer->WriteUint32(w);
|
|
166
|
+
serializer->WriteUint32(h);
|
|
167
|
+
serializer->WriteRawBytes(px, static_cast<size_t>(w) * h * 4);
|
|
168
|
+
return v8::Just(true);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// OffscreenCanvas
|
|
172
|
+
// Get 失敗時に空 Local を触らないようフォールバックを入れる
|
|
173
|
+
v8::Local<v8::Value> w = v8::Undefined(isolate_);
|
|
174
|
+
v8::Local<v8::Value> h = v8::Undefined(isolate_);
|
|
175
|
+
(void) object->Get(ctx, Str(isolate_, "width")).ToLocal(&w);
|
|
176
|
+
(void) object->Get(ctx, Str(isolate_, "height")).ToLocal(&h);
|
|
177
|
+
serializer->WriteUint32(kHostObjectOffscreenCanvas);
|
|
54
178
|
serializer->WriteUint32(w->IsNumber() ? static_cast<uint32_t>(w.As<v8::Number>()->Value()) : 0);
|
|
55
179
|
serializer->WriteUint32(h->IsNumber() ? static_cast<uint32_t>(h.As<v8::Number>()->Value()) : 0);
|
|
56
180
|
return v8::Just(true);
|
|
@@ -69,6 +193,26 @@ public:
|
|
|
69
193
|
|
|
70
194
|
v8::MaybeLocal<v8::Object> ReadHostObject(v8::Isolate*) override
|
|
71
195
|
{
|
|
196
|
+
uint32_t tag = 0;
|
|
197
|
+
if (!deserializer->ReadUint32(&tag)) {
|
|
198
|
+
return v8::MaybeLocal<v8::Object>();
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (tag == kHostObjectImageBitmap) {
|
|
202
|
+
uint32_t w = 0, h = 0;
|
|
203
|
+
const void* px = nullptr;
|
|
204
|
+
if (!deserializer->ReadUint32(&w) || !deserializer->ReadUint32(&h) ||
|
|
205
|
+
!deserializer->ReadRawBytes(static_cast<size_t>(w) * h * 4, &px)) {
|
|
206
|
+
return v8::MaybeLocal<v8::Object>();
|
|
207
|
+
}
|
|
208
|
+
auto* img = new DecodedImage();
|
|
209
|
+
img->width = w;
|
|
210
|
+
img->height = h;
|
|
211
|
+
img->rgba.assign(static_cast<const uint8_t*>(px),
|
|
212
|
+
static_cast<const uint8_t*>(px) + static_cast<size_t>(w) * h * 4);
|
|
213
|
+
return WrapImageBitmap(isolate_, img);
|
|
214
|
+
}
|
|
215
|
+
|
|
72
216
|
uint32_t w = 0, h = 0;
|
|
73
217
|
deserializer->ReadUint32(&w);
|
|
74
218
|
deserializer->ReadUint32(&h);
|
|
@@ -84,36 +228,69 @@ private:
|
|
|
84
228
|
|
|
85
229
|
} // namespace
|
|
86
230
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
231
|
+
WorkerMessage SerializeMessage(v8::Isolate* isolate, v8::Local<v8::Context> source_ctx,
|
|
232
|
+
v8::Local<v8::Value> message,
|
|
233
|
+
v8::Local<v8::Value> transfer_list)
|
|
90
234
|
{
|
|
91
235
|
v8::Context::Scope scope(source_ctx);
|
|
92
236
|
SerializerDelegate delegate(isolate);
|
|
93
237
|
v8::ValueSerializer serializer(isolate, &delegate);
|
|
94
238
|
delegate.serializer = &serializer;
|
|
95
239
|
|
|
240
|
+
// transfer_list の ArrayBuffer は中身を埋め込まず所有権を移譲する
|
|
241
|
+
// (player の render キューは数十 MB — 毎フレームのコピーは不可)。
|
|
242
|
+
std::vector<v8::Local<v8::ArrayBuffer>> transfer_abs;
|
|
243
|
+
if (!transfer_list.IsEmpty() && transfer_list->IsArray()) {
|
|
244
|
+
auto arr = transfer_list.As<v8::Array>();
|
|
245
|
+
for (uint32_t i = 0; i < arr->Length(); ++i) {
|
|
246
|
+
v8::Local<v8::Value> item;
|
|
247
|
+
if (arr->Get(source_ctx, i).ToLocal(&item) && item->IsArrayBuffer()) {
|
|
248
|
+
auto ab = item.As<v8::ArrayBuffer>();
|
|
249
|
+
if (ab->IsDetachable()) {
|
|
250
|
+
serializer.TransferArrayBuffer(
|
|
251
|
+
static_cast<uint32_t>(transfer_abs.size()), ab);
|
|
252
|
+
transfer_abs.push_back(ab);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
96
258
|
serializer.WriteHeader();
|
|
97
|
-
// 通常の ArrayBuffer は値としてシリアライズされる (transfer は将来最適化)。
|
|
98
259
|
if (serializer.WriteValue(source_ctx, message).IsNothing()) {
|
|
99
260
|
return {};
|
|
100
261
|
}
|
|
262
|
+
|
|
263
|
+
WorkerMessage result;
|
|
101
264
|
std::pair<uint8_t*, size_t> buffer = serializer.Release();
|
|
102
|
-
|
|
265
|
+
result.data.assign(buffer.first, buffer.first + buffer.second);
|
|
103
266
|
free(buffer.first);
|
|
104
|
-
|
|
267
|
+
|
|
268
|
+
// 成功後に detach (ブラウザの transferable と同じく送信側では使えなくなる)
|
|
269
|
+
for (auto& ab : transfer_abs) {
|
|
270
|
+
result.transfers.push_back(ab->GetBackingStore());
|
|
271
|
+
(void) ab->Detach(v8::Local<v8::Value>());
|
|
272
|
+
}
|
|
273
|
+
return result;
|
|
105
274
|
}
|
|
106
275
|
|
|
107
276
|
v8::MaybeLocal<v8::Value> DeserializeMessage(v8::Isolate* isolate,
|
|
108
277
|
v8::Local<v8::Context> target_ctx,
|
|
109
|
-
const
|
|
278
|
+
const WorkerMessage& message)
|
|
110
279
|
{
|
|
111
280
|
v8::Context::Scope scope(target_ctx);
|
|
112
281
|
HostContext* host = HostContext::From(isolate);
|
|
113
282
|
DeserializerDelegate delegate(isolate, host);
|
|
114
|
-
v8::ValueDeserializer deserializer(isolate,
|
|
283
|
+
v8::ValueDeserializer deserializer(isolate, message.data.data(),
|
|
284
|
+
message.data.size(), &delegate);
|
|
115
285
|
delegate.deserializer = &deserializer;
|
|
116
286
|
|
|
287
|
+
// transfer された ArrayBuffer を同じ backing store で再装着する (ゼロコピー)
|
|
288
|
+
for (size_t i = 0; i < message.transfers.size(); ++i) {
|
|
289
|
+
v8::Local<v8::ArrayBuffer> ab =
|
|
290
|
+
v8::ArrayBuffer::New(isolate, message.transfers[i]);
|
|
291
|
+
deserializer.TransferArrayBuffer(static_cast<uint32_t>(i), ab);
|
|
292
|
+
}
|
|
293
|
+
|
|
117
294
|
if (deserializer.ReadHeader(target_ctx).IsNothing()) {
|
|
118
295
|
return v8::MaybeLocal<v8::Value>();
|
|
119
296
|
}
|
|
@@ -140,6 +317,7 @@ void DispatchMessage(v8::Isolate* isolate, v8::Local<v8::Context> ctx,
|
|
|
140
317
|
if (target->Get(ctx, Str(isolate, "onmessage")).ToLocal(&on) && on->IsFunction()) {
|
|
141
318
|
v8::TryCatch tc(isolate);
|
|
142
319
|
(void) on.As<v8::Function>()->Call(ctx, target, 1, args);
|
|
320
|
+
v8util::ReportCaught(isolate, &tc, "onmessage");
|
|
143
321
|
}
|
|
144
322
|
// __messageListeners 配列
|
|
145
323
|
v8::Local<v8::Value> listeners;
|
|
@@ -151,6 +329,7 @@ void DispatchMessage(v8::Isolate* isolate, v8::Local<v8::Context> ctx,
|
|
|
151
329
|
if (arr->Get(ctx, i).ToLocal(&fn) && fn->IsFunction()) {
|
|
152
330
|
v8::TryCatch tc(isolate);
|
|
153
331
|
(void) fn.As<v8::Function>()->Call(ctx, target, 1, args);
|
|
332
|
+
v8util::ReportCaught(isolate, &tc, "message listener");
|
|
154
333
|
}
|
|
155
334
|
}
|
|
156
335
|
}
|
|
@@ -203,9 +382,13 @@ void WorkerSelfPostMessage(const v8::FunctionCallbackInfo<v8::Value>& args)
|
|
|
203
382
|
auto* instance = static_cast<WorkerInstance*>(args.Data().As<v8::External>()->Value());
|
|
204
383
|
v8::Local<v8::Value> msg = args.Length() > 0 ? args[0] : v8::Undefined(isolate).As<v8::Value>();
|
|
205
384
|
v8::Local<v8::Value> transfer = args.Length() > 1 ? args[1] : v8::Undefined(isolate).As<v8::Value>();
|
|
206
|
-
auto
|
|
207
|
-
if (
|
|
208
|
-
|
|
385
|
+
auto message = SerializeMessage(isolate, isolate->GetCurrentContext(), msg, transfer);
|
|
386
|
+
if (TraceMessage()) {
|
|
387
|
+
std::cerr << "[Worker] worker->main postMessage: " << message.data.size()
|
|
388
|
+
<< " bytes (+" << message.transfers.size() << " transfers)" << std::endl;
|
|
389
|
+
}
|
|
390
|
+
if (!message.data.empty()) {
|
|
391
|
+
instance->PostToMain(std::move(message));
|
|
209
392
|
}
|
|
210
393
|
}
|
|
211
394
|
|
|
@@ -236,9 +419,13 @@ void MainWorkerPostMessage(const v8::FunctionCallbackInfo<v8::Value>& args)
|
|
|
236
419
|
auto* instance = static_cast<WorkerInstance*>(args.Data().As<v8::External>()->Value());
|
|
237
420
|
v8::Local<v8::Value> msg = args.Length() > 0 ? args[0] : v8::Undefined(isolate).As<v8::Value>();
|
|
238
421
|
v8::Local<v8::Value> transfer = args.Length() > 1 ? args[1] : v8::Undefined(isolate).As<v8::Value>();
|
|
239
|
-
auto
|
|
240
|
-
if (
|
|
241
|
-
|
|
422
|
+
auto message = SerializeMessage(isolate, isolate->GetCurrentContext(), msg, transfer);
|
|
423
|
+
if (TraceMessage()) {
|
|
424
|
+
std::cerr << "[Worker] main->worker postMessage: " << message.data.size()
|
|
425
|
+
<< " bytes (+" << message.transfers.size() << " transfers)" << std::endl;
|
|
426
|
+
}
|
|
427
|
+
if (!message.data.empty()) {
|
|
428
|
+
instance->PostToWorker(std::move(message));
|
|
242
429
|
}
|
|
243
430
|
}
|
|
244
431
|
|
|
@@ -246,10 +433,12 @@ void MainWorkerPostMessage(const v8::FunctionCallbackInfo<v8::Value>& args)
|
|
|
246
433
|
|
|
247
434
|
bool WorkerInstance::Start()
|
|
248
435
|
{
|
|
436
|
+
std::cerr << "[Worker] start: " << ShortUrl(url_) << std::endl;
|
|
249
437
|
v8::HandleScope handle_scope(isolate_);
|
|
250
438
|
|
|
251
439
|
v8::Local<v8::Context> context = v8::Context::New(isolate_);
|
|
252
440
|
context_.Reset(isolate_, context);
|
|
441
|
+
std::cerr << "[Worker] context created" << std::endl;
|
|
253
442
|
|
|
254
443
|
// worker 専用 EventLoop を Context の embedder slot に登録
|
|
255
444
|
loop_ = std::make_unique<EventLoop>(isolate_);
|
|
@@ -260,6 +449,7 @@ bool WorkerInstance::Start()
|
|
|
260
449
|
|
|
261
450
|
// ブラウザ相当環境一式 (console/timers/fetch/image/audio/webgpu/canvas/dom)
|
|
262
451
|
InstallGlobalBindings(isolate_, global, HostContext::From(isolate_));
|
|
452
|
+
std::cerr << "[Worker] bindings installed" << std::endl;
|
|
263
453
|
|
|
264
454
|
// worker スコープには document/window を出さない (ライブラリの環境判定対策)。
|
|
265
455
|
global->Delete(context, Str(isolate_, "document")).Check();
|
|
@@ -278,46 +468,112 @@ bool WorkerInstance::Start()
|
|
|
278
468
|
runtime_->InstallOnGlobal(global);
|
|
279
469
|
|
|
280
470
|
// ワーカースクリプトを読み込んで評価 (クラシック。ESM worker は «EXTEND»)。
|
|
281
|
-
// Vite の `?worker&inline` は
|
|
282
|
-
//
|
|
471
|
+
// Vite の `?worker&inline` は data:application/javascript;base64,... を渡す
|
|
472
|
+
// (バージョンによっては blob: URL)。data: / blob: / assets の順で解決する。
|
|
283
473
|
HostContext* host = HostContext::From(isolate_);
|
|
284
474
|
std::string source;
|
|
285
|
-
if (url_.rfind("
|
|
475
|
+
if (url_.rfind("data:", 0) == 0) {
|
|
476
|
+
const auto comma = url_.find(',');
|
|
477
|
+
if (comma == std::string::npos) {
|
|
478
|
+
std::cerr << "[Worker] malformed data url: " << ShortUrl(url_) << std::endl;
|
|
479
|
+
return false;
|
|
480
|
+
}
|
|
481
|
+
const std::string meta = url_.substr(5, comma - 5); // 例: application/javascript;base64
|
|
482
|
+
const std::string payload = url_.substr(comma + 1);
|
|
483
|
+
if (meta.find(";base64") != std::string::npos) {
|
|
484
|
+
if (!DecodeBase64(payload, &source)) {
|
|
485
|
+
std::cerr << "[Worker] base64 decode failed: " << ShortUrl(url_) << std::endl;
|
|
486
|
+
return false;
|
|
487
|
+
}
|
|
488
|
+
} else {
|
|
489
|
+
source = PercentDecode(payload);
|
|
490
|
+
}
|
|
491
|
+
} else if (url_.rfind("blob:", 0) == 0) {
|
|
286
492
|
if (!ResolveObjectURL(url_, &source)) {
|
|
287
|
-
std::cerr << "[Worker] blob url not found: " << url_ << std::endl;
|
|
493
|
+
std::cerr << "[Worker] blob url not found: " << ShortUrl(url_) << std::endl;
|
|
288
494
|
return false;
|
|
289
495
|
}
|
|
290
496
|
} else {
|
|
291
497
|
auto src = host->assets->ReadText(url_);
|
|
292
498
|
if (!src) {
|
|
293
|
-
std::cerr << "[Worker] script not found: " << url_ << std::endl;
|
|
499
|
+
std::cerr << "[Worker] script not found: " << ShortUrl(url_) << std::endl;
|
|
294
500
|
return false;
|
|
295
501
|
}
|
|
296
502
|
source = std::move(*src);
|
|
297
503
|
}
|
|
504
|
+
std::cerr << "[Worker] source resolved (" << source.size() << " bytes)" << std::endl;
|
|
505
|
+
|
|
506
|
+
// «診断» レンダラ worker のフレームサイクル (frameStarted / mainTexture) を追跡する。
|
|
507
|
+
// 黒画面問題: getCurrentTexture がフレーム 3 回目以降呼ばれなくなる現象の原因特定用。
|
|
508
|
+
// minify 後も this.プロパティ名は保持されるためパターンは安定。見つからなければ無変更。
|
|
509
|
+
if (source.find("getCurrentTexture") != std::string::npos) {
|
|
510
|
+
const auto patch = [&source](const std::string& from, const std::string& to) {
|
|
511
|
+
const auto pos = source.find(from);
|
|
512
|
+
if (pos != std::string::npos) {
|
|
513
|
+
source.replace(pos, from.size(), to);
|
|
514
|
+
} else {
|
|
515
|
+
std::cerr << "[Worker] trace patch not applied: " << from.substr(0, 40) << std::endl;
|
|
516
|
+
}
|
|
517
|
+
};
|
|
518
|
+
source.insert(0,
|
|
519
|
+
"var __n2dT={};function __n2dTrace(k,s){var n=(__n2dT[k]=(__n2dT[k]||0)+1);"
|
|
520
|
+
"if(n<=30||n%300===0)console.info(\"[trace]\",k,\"#\"+n,s||\"\")}\n");
|
|
521
|
+
patch("clearTransferBounds(){this.beginFrame()}",
|
|
522
|
+
"clearTransferBounds(){__n2dTrace(\"CTB\",\"fs=\"+this.frameStarted+\" mt=\"+!!this.mainTexture);this.beginFrame()}");
|
|
523
|
+
patch("ensureMainTexture(){this.mainTexture||",
|
|
524
|
+
"ensureMainTexture(){__n2dTrace(\"EMT\",\"mt=\"+!!this.mainTexture+\" rc=\"+this.$needsReconfigure);this.mainTexture||");
|
|
525
|
+
patch("endFrame(){if(this.frameStarted){",
|
|
526
|
+
"endFrame(){__n2dTrace(\"EF\",\"fs=\"+this.frameStarted+\" enc=\"+!!this.commandEncoder);if(this.frameStarted){");
|
|
527
|
+
patch("transferMainCanvas(){if(!this.$mainAttachmentObject||!this.$mainAttachmentObject.texture)",
|
|
528
|
+
"transferMainCanvas(){__n2dTrace(\"TMC\",\"att=\"+!!(this.$mainAttachmentObject&&this.$mainAttachmentObject.texture));if(!this.$mainAttachmentObject||!this.$mainAttachmentObject.texture)");
|
|
529
|
+
// «診断/自己修復» CommandController.execute() は try/catch を持たないため、
|
|
530
|
+
// 描画中の例外が 1 度起きると this.state が "active" のまま固まり、
|
|
531
|
+
// 以後 onmessage は queue に積むだけで execute を再開せず worker が永久停止する
|
|
532
|
+
// (画面が ~15 フレームで凍結する現象の直接原因)。元の execute をリネームし、
|
|
533
|
+
// 例外を捕捉して message+stack を出力しつつ state を戻すラッパを被せる。
|
|
534
|
+
// これで「実際に何が throw しているか」を CI ログで確定でき、かつ凍結も防げる。
|
|
535
|
+
patch("async execute(){for(this.state=\"active\"",
|
|
536
|
+
"async execute(){try{await this.__n2dExec()}catch(__ee){"
|
|
537
|
+
"var __n=(globalThis.__rerr=(globalThis.__rerr||0)+1);"
|
|
538
|
+
"if(__n<=8)console.error(\"[rendererr] #\"+__n+\" \"+(__ee&&__ee.message||__ee)+"
|
|
539
|
+
"\"\\n\"+(__ee&&__ee.stack||\"\"));this.state=\"deactivate\"}}"
|
|
540
|
+
"async __n2dExec(){for(this.state=\"active\"");
|
|
541
|
+
}
|
|
298
542
|
v8::TryCatch tc(isolate_);
|
|
299
543
|
v8::ScriptOrigin origin(Str(isolate_, url_));
|
|
300
544
|
v8::Local<v8::String> code = Str(isolate_, source);
|
|
301
545
|
v8::Local<v8::Script> script;
|
|
302
546
|
if (!v8::Script::Compile(context, code, &origin).ToLocal(&script) ||
|
|
303
547
|
script->Run(context).IsEmpty()) {
|
|
304
|
-
std::cerr << "[Worker] script eval failed: " << url_ << std::endl;
|
|
548
|
+
std::cerr << "[Worker] script eval failed: " << ShortUrl(url_) << std::endl;
|
|
549
|
+
if (tc.HasCaught()) {
|
|
550
|
+
v8::String::Utf8Value exception(isolate_, tc.Exception());
|
|
551
|
+
std::cerr << "[Worker] exception: " << (*exception ? *exception : "?") << std::endl;
|
|
552
|
+
v8::Local<v8::Message> message = tc.Message();
|
|
553
|
+
if (!message.IsEmpty()) {
|
|
554
|
+
std::cerr << "[Worker] at line "
|
|
555
|
+
<< message->GetLineNumber(context).FromMaybe(0) << std::endl;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
305
558
|
return false;
|
|
306
559
|
}
|
|
307
|
-
|
|
560
|
+
// NOTE: ここで PerformMicrotaskCheckpoint は呼ばない。Start は JS コールバック
|
|
561
|
+
// (new Worker) の最中に呼ばれるため、JS 実行中のチェックポイントは V8 の規約違反。
|
|
562
|
+
// マイクロタスクはメインループの PumpMicrotasks で処理される。
|
|
563
|
+
std::cerr << "[Worker] script evaluated" << std::endl;
|
|
308
564
|
return true;
|
|
309
565
|
}
|
|
310
566
|
|
|
311
567
|
void WorkerInstance::Terminate() { terminated_ = true; }
|
|
312
568
|
|
|
313
|
-
void WorkerInstance::PostToWorker(
|
|
569
|
+
void WorkerInstance::PostToWorker(WorkerMessage message)
|
|
314
570
|
{
|
|
315
|
-
to_worker_.push_back(
|
|
571
|
+
to_worker_.push_back(std::move(message));
|
|
316
572
|
}
|
|
317
573
|
|
|
318
|
-
void WorkerInstance::PostToMain(
|
|
574
|
+
void WorkerInstance::PostToMain(WorkerMessage message)
|
|
319
575
|
{
|
|
320
|
-
to_main_.push_back(
|
|
576
|
+
to_main_.push_back(std::move(message));
|
|
321
577
|
}
|
|
322
578
|
|
|
323
579
|
void WorkerInstance::DeliverToWorker(double now_ms)
|
|
@@ -329,9 +585,19 @@ void WorkerInstance::DeliverToWorker(double now_ms)
|
|
|
329
585
|
while (!to_worker_.empty()) {
|
|
330
586
|
WorkerMessage m = std::move(to_worker_.front());
|
|
331
587
|
to_worker_.pop_front();
|
|
588
|
+
const bool trace = TraceMessage();
|
|
589
|
+
if (trace) {
|
|
590
|
+
std::cerr << "[Worker] deliver main->worker (" << m.data.size() << " bytes)" << std::endl;
|
|
591
|
+
}
|
|
332
592
|
v8::Local<v8::Value> data;
|
|
333
|
-
if (DeserializeMessage(isolate_, ctx, m
|
|
593
|
+
if (DeserializeMessage(isolate_, ctx, m).ToLocal(&data)) {
|
|
334
594
|
DispatchMessage(isolate_, ctx, ctx->Global(), data);
|
|
595
|
+
if (trace) {
|
|
596
|
+
std::cerr << "[Worker] worker onmessage done" << std::endl;
|
|
597
|
+
}
|
|
598
|
+
} else {
|
|
599
|
+
std::cerr << "[Worker] main->worker deserialize failed ("
|
|
600
|
+
<< m.data.size() << " bytes)" << std::endl;
|
|
335
601
|
}
|
|
336
602
|
}
|
|
337
603
|
|
|
@@ -353,9 +619,19 @@ void WorkerInstance::DeliverToMain()
|
|
|
353
619
|
while (!to_main_.empty()) {
|
|
354
620
|
WorkerMessage m = std::move(to_main_.front());
|
|
355
621
|
to_main_.pop_front();
|
|
622
|
+
const bool trace = TraceMessage();
|
|
623
|
+
if (trace) {
|
|
624
|
+
std::cerr << "[Worker] deliver worker->main (" << m.data.size() << " bytes)" << std::endl;
|
|
625
|
+
}
|
|
356
626
|
v8::Local<v8::Value> data;
|
|
357
|
-
if (DeserializeMessage(isolate_, main_ctx, m
|
|
627
|
+
if (DeserializeMessage(isolate_, main_ctx, m).ToLocal(&data)) {
|
|
358
628
|
DispatchMessage(isolate_, main_ctx, worker_obj, data);
|
|
629
|
+
if (trace) {
|
|
630
|
+
std::cerr << "[Worker] main onmessage done" << std::endl;
|
|
631
|
+
}
|
|
632
|
+
} else {
|
|
633
|
+
std::cerr << "[Worker] worker->main deserialize failed ("
|
|
634
|
+
<< m.data.size() << " bytes)" << std::endl;
|
|
359
635
|
}
|
|
360
636
|
}
|
|
361
637
|
}
|
|
@@ -389,11 +665,17 @@ v8::Local<v8::Object> WorkerRuntime::CreateWorker(const std::string& url)
|
|
|
389
665
|
worker->Set(ctx, Str(isolate_, "postMessage"),
|
|
390
666
|
v8::Function::New(ctx, MainWorkerPostMessage, ext).ToLocalChecked()).Check();
|
|
391
667
|
SetMethod(isolate_, worker, "addEventListener", AddEventListener);
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
668
|
+
// terminate は WorkerInstance* を data (External) で受け取る必要があるため
|
|
669
|
+
// SetMethod (data なし) ではなく Function::New で設置する。
|
|
670
|
+
worker->Set(ctx, Str(isolate_, "terminate"),
|
|
671
|
+
v8::Function::New(ctx,
|
|
672
|
+
[](const v8::FunctionCallbackInfo<v8::Value>& a) {
|
|
673
|
+
if (!a.Data()->IsExternal()) {
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
676
|
+
auto* w = static_cast<WorkerInstance*>(a.Data().As<v8::External>()->Value());
|
|
677
|
+
w->Terminate();
|
|
678
|
+
}, ext).ToLocalChecked()).Check();
|
|
397
679
|
|
|
398
680
|
ptr->main_worker_object.Reset(isolate_, worker);
|
|
399
681
|
|
|
@@ -437,8 +719,10 @@ void WorkerRuntime::InstallOnGlobal(v8::Local<v8::Object> global)
|
|
|
437
719
|
|
|
438
720
|
void WorkerRuntime::Pump(double now_ms)
|
|
439
721
|
{
|
|
440
|
-
|
|
441
|
-
|
|
722
|
+
// 配送中の JS が new Worker() を呼ぶと workers_ に push_back され
|
|
723
|
+
// vector が再割当されるため、イテレータではなく添字で回す。
|
|
724
|
+
for (size_t i = 0; i < workers_.size(); ++i) {
|
|
725
|
+
workers_[i]->Deliver(now_ms);
|
|
442
726
|
}
|
|
443
727
|
}
|
|
444
728
|
|
|
@@ -25,9 +25,12 @@ class HostContext;
|
|
|
25
25
|
class EventLoop;
|
|
26
26
|
class WorkerRuntime;
|
|
27
27
|
|
|
28
|
-
// 1 メッセージ = ValueSerializer
|
|
28
|
+
// 1 メッセージ = ValueSerializer 出力バイト列 + transfer された ArrayBuffer の実体。
|
|
29
|
+
// transfer_list の ArrayBuffer は中身を bytes に埋め込まず所有権ごと移す
|
|
30
|
+
// (render キューは数十 MB になるため、コピーするとフレームレートが崩壊する)。
|
|
29
31
|
struct WorkerMessage {
|
|
30
32
|
std::vector<uint8_t> data;
|
|
33
|
+
std::vector<std::shared_ptr<v8::BackingStore>> transfers;
|
|
31
34
|
};
|
|
32
35
|
|
|
33
36
|
class WorkerInstance {
|
|
@@ -39,8 +42,8 @@ public:
|
|
|
39
42
|
void Terminate();
|
|
40
43
|
|
|
41
44
|
// main→worker / worker→main の投函
|
|
42
|
-
void PostToWorker(
|
|
43
|
-
void PostToMain(
|
|
45
|
+
void PostToWorker(WorkerMessage message);
|
|
46
|
+
void PostToMain(WorkerMessage message);
|
|
44
47
|
|
|
45
48
|
// 配送 + タイマー/rAF 前進 (WorkerRuntime::Pump から呼ぶ)
|
|
46
49
|
void Deliver(double now_ms);
|
|
@@ -88,21 +91,28 @@ public:
|
|
|
88
91
|
|
|
89
92
|
bool HasWorkers() const { return !workers_.empty(); }
|
|
90
93
|
|
|
94
|
+
// 全 WorkerInstance を破棄する。WorkerInstance は v8::Global (context_ /
|
|
95
|
+
// main_worker_object 等) を保持するため、Isolate::Dispose より前に必ず呼ぶこと
|
|
96
|
+
// (呼ばずにスタック巻き戻しで破棄されると Isolate 破棄後の Global::Reset で fail-fast)。
|
|
97
|
+
void Shutdown() { workers_.clear(); }
|
|
98
|
+
|
|
91
99
|
private:
|
|
92
100
|
v8::Isolate* isolate_;
|
|
93
101
|
HostContext* host_;
|
|
94
102
|
std::vector<std::unique_ptr<WorkerInstance>> workers_;
|
|
95
103
|
};
|
|
96
104
|
|
|
97
|
-
// ValueSerializer/Deserializer による structured clone
|
|
98
|
-
//
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
105
|
+
// ValueSerializer/Deserializer による structured clone
|
|
106
|
+
// (OffscreenCanvas / ImageBitmap のホストオブジェクト対応 + ArrayBuffer transfer)。
|
|
107
|
+
// 失敗時は data が空の WorkerMessage を返す。transfer_list 内の ArrayBuffer は
|
|
108
|
+
// detach され、backing store が message.transfers へ移る。
|
|
109
|
+
WorkerMessage SerializeMessage(v8::Isolate* isolate, v8::Local<v8::Context> source_ctx,
|
|
110
|
+
v8::Local<v8::Value> message,
|
|
111
|
+
v8::Local<v8::Value> transfer_list);
|
|
102
112
|
|
|
103
|
-
//
|
|
113
|
+
// message を target_ctx で復元する (transfer された ArrayBuffer は同一実体を再装着)。
|
|
104
114
|
v8::MaybeLocal<v8::Value> DeserializeMessage(v8::Isolate* isolate,
|
|
105
115
|
v8::Local<v8::Context> target_ctx,
|
|
106
|
-
const
|
|
116
|
+
const WorkerMessage& message);
|
|
107
117
|
|
|
108
118
|
} // namespace next2d
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
set -euo pipefail
|
|
11
11
|
cd "$(dirname "$0")/.."
|
|
12
12
|
|
|
13
|
-
V8_TAG="13.
|
|
13
|
+
V8_TAG="13.7.152.19"
|
|
14
14
|
V8INC="${1:-}"
|
|
15
15
|
|
|
16
16
|
echo "== 1/3 RasterCore unit tests =="
|
|
@@ -41,6 +41,7 @@ FILES=(
|
|
|
41
41
|
src/bindings/Console.cpp
|
|
42
42
|
src/bindings/Timers.cpp
|
|
43
43
|
src/bindings/EventTarget.cpp
|
|
44
|
+
src/bindings/Polyfills.cpp
|
|
44
45
|
src/bindings/Network.cpp
|
|
45
46
|
src/bindings/Fetch.cpp
|
|
46
47
|
src/bindings/Canvas.cpp
|