@next2d/builder 0.1.6 → 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.
Files changed (34) hide show
  1. package/README.md +5 -1
  2. package/dist/index.js +140 -36
  3. package/package.json +3 -2
  4. package/templates/xbox/CMakeLists.txt +74 -2
  5. package/templates/xbox/README.md +73 -12
  6. package/templates/xbox/cmake/FetchDawn.cmake +47 -6
  7. package/templates/xbox/cmake/FindV8.cmake +22 -1
  8. package/templates/xbox/js/bootstrap.js +31 -28
  9. package/templates/xbox/js/selftest.js +740 -90
  10. package/templates/xbox/src/EventLoop.cpp +28 -1
  11. package/templates/xbox/src/EventLoop.h +12 -0
  12. package/templates/xbox/src/bindings/Audio.cpp +99 -15
  13. package/templates/xbox/src/bindings/Bindings.h +10 -0
  14. package/templates/xbox/src/bindings/Canvas.cpp +45 -0
  15. package/templates/xbox/src/bindings/Canvas2D.cpp +2 -5
  16. package/templates/xbox/src/bindings/DomShims.cpp +127 -0
  17. package/templates/xbox/src/bindings/EventTarget.cpp +2 -0
  18. package/templates/xbox/src/bindings/Fetch.cpp +2 -8
  19. package/templates/xbox/src/bindings/Image.cpp +212 -10
  20. package/templates/xbox/src/bindings/Polyfills.cpp +791 -0
  21. package/templates/xbox/src/bindings/Video.cpp +96 -7
  22. package/templates/xbox/src/bindings/webgpu/WebGPU.cpp +815 -15
  23. package/templates/xbox/src/bindings/webgpu/WebGPUCommon.h +3 -8
  24. package/templates/xbox/src/gpu/DawnContext.cpp +90 -3
  25. package/templates/xbox/src/gpu/DawnContext.h +18 -0
  26. package/templates/xbox/src/main.cpp +88 -6
  27. package/templates/xbox/src/platform/AudioEngine.cpp +9 -3
  28. package/templates/xbox/src/v8/V8Runtime.cpp +69 -4
  29. package/templates/xbox/src/v8/V8Util.h +29 -0
  30. package/templates/xbox/src/v8/WeakHandle.h +45 -0
  31. package/templates/xbox/src/worker/WorkerRuntime.cpp +325 -41
  32. package/templates/xbox/src/worker/WorkerRuntime.h +20 -10
  33. package/templates/xbox/tests/check_local.sh +3 -2
  34. package/templates/xbox/tests/raster_test.bin +0 -0
@@ -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
- bool IsOffscreen(v8::Isolate* isolate, v8::Local<v8::Object> obj)
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, "__isOffscreenCanvas")).ToLocal(&v) &&
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
- // OffscreenCanvas をホストオブジェクトとして扱い WriteHostObject に回す。
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
- v8::Local<v8::Value> w, h;
52
- object->Get(ctx, Str(isolate_, "width")).ToLocal(&w);
53
- object->Get(ctx, Str(isolate_, "height")).ToLocal(&h);
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
- std::vector<uint8_t> SerializeMessage(v8::Isolate* isolate, v8::Local<v8::Context> source_ctx,
88
- v8::Local<v8::Value> message,
89
- v8::Local<v8::Value> /*transfer_list*/)
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
- std::vector<uint8_t> bytes(buffer.first, buffer.first + buffer.second);
265
+ result.data.assign(buffer.first, buffer.first + buffer.second);
103
266
  free(buffer.first);
104
- return bytes;
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 std::vector<uint8_t>& bytes)
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, bytes.data(), bytes.size(), &delegate);
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 bytes = SerializeMessage(isolate, isolate->GetCurrentContext(), msg, transfer);
207
- if (!bytes.empty()) {
208
- instance->PostToMain(std::move(bytes));
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 bytes = SerializeMessage(isolate, isolate->GetCurrentContext(), msg, transfer);
240
- if (!bytes.empty()) {
241
- instance->PostToWorker(std::move(bytes));
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` は blob: URL(= URL.createObjectURL(new Blob([code]))) を渡すため、
282
- // まず object URL レジストリ、次に assets を参照する。
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("blob:", 0) == 0) {
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
- isolate_->PerformMicrotaskCheckpoint();
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(std::vector<uint8_t> bytes)
569
+ void WorkerInstance::PostToWorker(WorkerMessage message)
314
570
  {
315
- to_worker_.push_back(WorkerMessage{std::move(bytes)});
571
+ to_worker_.push_back(std::move(message));
316
572
  }
317
573
 
318
- void WorkerInstance::PostToMain(std::vector<uint8_t> bytes)
574
+ void WorkerInstance::PostToMain(WorkerMessage message)
319
575
  {
320
- to_main_.push_back(WorkerMessage{std::move(bytes)});
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.data).ToLocal(&data)) {
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.data).ToLocal(&data)) {
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
- SetMethod(isolate_, worker, "terminate",
393
- [](const v8::FunctionCallbackInfo<v8::Value>& a) {
394
- auto* w = static_cast<WorkerInstance*>(a.Data().As<v8::External>()->Value());
395
- w->Terminate();
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
- for (auto& w : workers_) {
441
- w->Deliver(now_ms);
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(std::vector<uint8_t> bytes);
43
- void PostToMain(std::vector<uint8_t> bytes);
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 (OffscreenCanvas transfer 対応)。
98
- // 失敗時は空を返す。source_ctx で直列化し、bytes を得る。
99
- std::vector<uint8_t> SerializeMessage(v8::Isolate* isolate, v8::Local<v8::Context> source_ctx,
100
- v8::Local<v8::Value> message,
101
- v8::Local<v8::Value> transfer_list);
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
- // bytes を target_ctx で復元する。
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 std::vector<uint8_t>& bytes);
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.6.233.17"
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
@@ -49,7 +50,7 @@ FILES=(
49
50
  )
50
51
  for f in "${FILES[@]}"; do
51
52
  c++ -fsyntax-only -std=c++20 -Isrc -isystem "${V8INC}" \
52
- -DV8_COMPRESS_POINTERS -DV8_ENABLE_SANDBOX "$f"
53
+ -DV8_COMPRESS_POINTERS "$f"
53
54
  echo "OK $f"
54
55
  done
55
56