@next2d/builder 0.1.7 → 0.1.10

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 (75) hide show
  1. package/dist/cli.d.ts +59 -0
  2. package/dist/cli.js +141 -0
  3. package/dist/constants.d.ts +53 -0
  4. package/dist/constants.js +64 -0
  5. package/dist/context.d.ts +29 -0
  6. package/dist/context.js +19 -0
  7. package/dist/electron.d.ts +10 -0
  8. package/dist/electron.js +142 -0
  9. package/dist/index.js +18 -754
  10. package/dist/native.d.ts +27 -0
  11. package/dist/native.js +123 -0
  12. package/dist/utils.d.ts +27 -0
  13. package/dist/utils.js +40 -0
  14. package/dist/web.d.ts +18 -0
  15. package/dist/web.js +70 -0
  16. package/dist/xbox.d.ts +62 -0
  17. package/dist/xbox.js +426 -0
  18. package/package.json +12 -9
  19. package/templates/xbox/CMakeLists.txt +116 -11
  20. package/templates/xbox/README.md +34 -8
  21. package/templates/xbox/cmake/FetchDawn.cmake +47 -6
  22. package/templates/xbox/cmake/FindV8.cmake +15 -0
  23. package/templates/xbox/js/bootstrap.js +151 -28
  24. package/templates/xbox/js/selftest.js +740 -90
  25. package/templates/xbox/src/AssetLoader.cpp +115 -12
  26. package/templates/xbox/src/AssetLoader.h +4 -0
  27. package/templates/xbox/src/EmbeddedAssets.cpp +149 -0
  28. package/templates/xbox/src/EmbeddedAssets.h +60 -0
  29. package/templates/xbox/src/EventLoop.cpp +13 -1
  30. package/templates/xbox/src/EventLoop.h +12 -0
  31. package/templates/xbox/src/bindings/Audio.cpp +211 -31
  32. package/templates/xbox/src/bindings/Bindings.h +10 -0
  33. package/templates/xbox/src/bindings/Canvas.cpp +77 -2
  34. package/templates/xbox/src/bindings/Canvas2D.cpp +496 -43
  35. package/templates/xbox/src/bindings/Console.cpp +6 -1
  36. package/templates/xbox/src/bindings/DomShims.cpp +143 -0
  37. package/templates/xbox/src/bindings/EventTarget.cpp +11 -0
  38. package/templates/xbox/src/bindings/Fetch.cpp +2 -8
  39. package/templates/xbox/src/bindings/Image.cpp +243 -15
  40. package/templates/xbox/src/bindings/Polyfills.cpp +1226 -0
  41. package/templates/xbox/src/bindings/RasterCore.h +105 -1
  42. package/templates/xbox/src/bindings/Video.cpp +153 -10
  43. package/templates/xbox/src/bindings/webgpu/WebGPU.cpp +844 -18
  44. package/templates/xbox/src/bindings/webgpu/WebGPUCommon.h +7 -9
  45. package/templates/xbox/src/bindings/webgpu/WebGPUEnums.h +32 -0
  46. package/templates/xbox/src/gpu/DawnContext.cpp +245 -7
  47. package/templates/xbox/src/gpu/DawnContext.h +18 -0
  48. package/templates/xbox/src/main.cpp +459 -8
  49. package/templates/xbox/src/platform/AudioDecoder.cpp +121 -0
  50. package/templates/xbox/src/platform/AudioDecoder.h +18 -0
  51. package/templates/xbox/src/platform/AudioEngine.cpp +37 -6
  52. package/templates/xbox/src/platform/DecodeQueue.cpp +136 -0
  53. package/templates/xbox/src/platform/DecodeQueue.h +27 -0
  54. package/templates/xbox/src/platform/ImageDecoder.cpp +44 -0
  55. package/templates/xbox/src/platform/ImageDecoder.h +18 -0
  56. package/templates/xbox/src/platform/StbTextRasterizer.cpp +324 -0
  57. package/templates/xbox/src/platform/StbTextRasterizer.h +37 -0
  58. package/templates/xbox/src/platform/TextRasterizer.cpp +32 -0
  59. package/templates/xbox/src/platform/WicDecoder.cpp +19 -0
  60. package/templates/xbox/src/v8/CodeCache.h +223 -0
  61. package/templates/xbox/src/v8/V8Runtime.cpp +76 -11
  62. package/templates/xbox/src/v8/V8Util.h +79 -0
  63. package/templates/xbox/src/v8/WeakHandle.h +45 -0
  64. package/templates/xbox/src/worker/WorkerRuntime.cpp +432 -48
  65. package/templates/xbox/src/worker/WorkerRuntime.h +43 -13
  66. package/templates/xbox/tests/check_local.sh +7 -2
  67. package/templates/xbox/tests/gamecore_api_probe.cpp +172 -0
  68. package/templates/xbox/tests/raster_test.cpp +50 -0
  69. package/templates/xbox/tests/windows_platform_test.cpp +75 -3
  70. package/templates/xbox/third_party/LICENSES.md +14 -0
  71. package/templates/xbox/third_party/dr_mp3.h +5412 -0
  72. package/templates/xbox/third_party/dr_wav.h +9131 -0
  73. package/templates/xbox/third_party/stb_image.h +7988 -0
  74. package/templates/xbox/third_party/stb_truetype.h +5079 -0
  75. package/templates/xbox/third_party/stb_vorbis.c +5584 -0
@@ -4,10 +4,14 @@
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"
10
+ #include "v8/CodeCache.h"
8
11
 
9
12
  #include <cstdlib>
10
13
  #include <cstring>
14
+ #include <filesystem>
11
15
  #include <iostream>
12
16
 
13
17
  namespace next2d {
@@ -22,14 +26,99 @@ using v8util::ToStdString;
22
26
  // ===========================================================================
23
27
  namespace {
24
28
 
25
- bool IsOffscreen(v8::Isolate* isolate, v8::Local<v8::Object> obj)
29
+ // ログ用: data:/blob: URL は長大になるため先頭だけに切り詰める
30
+ std::string ShortUrl(const std::string& url)
31
+ {
32
+ if (url.size() <= 64) {
33
+ return url;
34
+ }
35
+ return url.substr(0, 64) + "... (" + std::to_string(url.size()) + " chars)";
36
+ }
37
+
38
+ // base64 デコード (data: URL 用)。不正文字があれば false。
39
+ bool DecodeBase64(const std::string& in, std::string* out)
40
+ {
41
+ auto val6 = [](unsigned char c) -> int {
42
+ if (c >= 'A' && c <= 'Z') return c - 'A';
43
+ if (c >= 'a' && c <= 'z') return c - 'a' + 26;
44
+ if (c >= '0' && c <= '9') return c - '0' + 52;
45
+ if (c == '+') return 62;
46
+ if (c == '/') return 63;
47
+ return -1;
48
+ };
49
+ out->clear();
50
+ out->reserve(in.size() * 3 / 4);
51
+ int val = 0;
52
+ int bits = -8;
53
+ for (unsigned char c : in) {
54
+ if (c == '=') {
55
+ break;
56
+ }
57
+ if (c == '\r' || c == '\n' || c == ' ') {
58
+ continue;
59
+ }
60
+ const int d = val6(c);
61
+ if (d < 0) {
62
+ return false;
63
+ }
64
+ val = (val << 6) | d;
65
+ bits += 6;
66
+ if (bits >= 0) {
67
+ out->push_back(static_cast<char>((val >> bits) & 0xFF));
68
+ bits -= 8;
69
+ }
70
+ }
71
+ return true;
72
+ }
73
+
74
+ // %xx パーセントデコード (非 base64 の data: URL 用)
75
+ std::string PercentDecode(const std::string& s)
76
+ {
77
+ std::string out;
78
+ out.reserve(s.size());
79
+ for (size_t i = 0; i < s.size(); ++i) {
80
+ if (s[i] == '%' && i + 2 < s.size()) {
81
+ const auto hex = [](char c) -> int {
82
+ if (c >= '0' && c <= '9') return c - '0';
83
+ if (c >= 'a' && c <= 'f') return c - 'a' + 10;
84
+ if (c >= 'A' && c <= 'F') return c - 'A' + 10;
85
+ return -1;
86
+ };
87
+ const int hi = hex(s[i + 1]);
88
+ const int lo = hex(s[i + 2]);
89
+ if (hi >= 0 && lo >= 0) {
90
+ out.push_back(static_cast<char>((hi << 4) | lo));
91
+ i += 2;
92
+ continue;
93
+ }
94
+ }
95
+ out.push_back(s[i]);
96
+ }
97
+ return out;
98
+ }
99
+
100
+ bool HasBoolMarker(v8::Isolate* isolate, v8::Local<v8::Object> obj, const char* name)
26
101
  {
27
102
  v8::Local<v8::Context> ctx = isolate->GetCurrentContext();
28
103
  v8::Local<v8::Value> v;
29
- return obj->Get(ctx, Str(isolate, "__isOffscreenCanvas")).ToLocal(&v) &&
104
+ return obj->Get(ctx, Str(isolate, name)).ToLocal(&v) &&
30
105
  v->IsBoolean() && v.As<v8::Boolean>()->Value();
31
106
  }
32
107
 
108
+ bool IsOffscreen(v8::Isolate* isolate, v8::Local<v8::Object> obj)
109
+ {
110
+ return HasBoolMarker(isolate, obj, "__isOffscreenCanvas");
111
+ }
112
+
113
+ bool IsImageBitmapObject(v8::Isolate* isolate, v8::Local<v8::Object> obj)
114
+ {
115
+ return HasBoolMarker(isolate, obj, "__isImageBitmap");
116
+ }
117
+
118
+ // WriteHostObject / ReadHostObject のタグ
119
+ constexpr uint32_t kHostObjectOffscreenCanvas = 1;
120
+ constexpr uint32_t kHostObjectImageBitmap = 2;
121
+
33
122
  class SerializerDelegate : public v8::ValueSerializer::Delegate {
34
123
  public:
35
124
  explicit SerializerDelegate(v8::Isolate* isolate) : isolate_(isolate) {}
@@ -39,18 +128,46 @@ public:
39
128
  isolate_->ThrowException(v8::Exception::Error(message));
40
129
  }
41
130
 
42
- // OffscreenCanvas をホストオブジェクトとして扱い WriteHostObject に回す。
131
+ // すべてのオブジェクトで IsHostObject を照会させる。
132
+ // OffscreenCanvas は ObjectTemplate 由来ではない plain object のため、
133
+ // これが無いと V8 は IsHostObject を呼ばず、getContext 等の関数プロパティで
134
+ // 「could not be cloned」になる。
135
+ bool HasCustomHostObject(v8::Isolate*) override { return true; }
136
+
137
+ // OffscreenCanvas / ImageBitmap をホストオブジェクトとして WriteHostObject に回す。
43
138
  v8::Maybe<bool> IsHostObject(v8::Isolate*, v8::Local<v8::Object> object) override
44
139
  {
45
- return v8::Just(IsOffscreen(isolate_, object));
140
+ return v8::Just(IsOffscreen(isolate_, object) ||
141
+ IsImageBitmapObject(isolate_, object));
46
142
  }
47
143
 
48
144
  v8::Maybe<bool> WriteHostObject(v8::Isolate*, v8::Local<v8::Object> object) override
49
145
  {
50
146
  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);
147
+
148
+ // ImageBitmap: ピクセルごと複製する (render メッセージの imageBitmaps 用)
149
+ if (IsImageBitmapObject(isolate_, object)) {
150
+ const uint8_t* px = nullptr;
151
+ uint32_t w = 0, h = 0;
152
+ if (!GetImageSourcePixels(isolate_, object, &px, &w, &h) || !px) {
153
+ isolate_->ThrowException(v8::Exception::Error(
154
+ Str(isolate_, "ImageBitmap clone failed")));
155
+ return v8::Nothing<bool>();
156
+ }
157
+ serializer->WriteUint32(kHostObjectImageBitmap);
158
+ serializer->WriteUint32(w);
159
+ serializer->WriteUint32(h);
160
+ serializer->WriteRawBytes(px, static_cast<size_t>(w) * h * 4);
161
+ return v8::Just(true);
162
+ }
163
+
164
+ // OffscreenCanvas
165
+ // Get 失敗時に空 Local を触らないようフォールバックを入れる
166
+ v8::Local<v8::Value> w = v8::Undefined(isolate_);
167
+ v8::Local<v8::Value> h = v8::Undefined(isolate_);
168
+ (void) object->Get(ctx, Str(isolate_, "width")).ToLocal(&w);
169
+ (void) object->Get(ctx, Str(isolate_, "height")).ToLocal(&h);
170
+ serializer->WriteUint32(kHostObjectOffscreenCanvas);
54
171
  serializer->WriteUint32(w->IsNumber() ? static_cast<uint32_t>(w.As<v8::Number>()->Value()) : 0);
55
172
  serializer->WriteUint32(h->IsNumber() ? static_cast<uint32_t>(h.As<v8::Number>()->Value()) : 0);
56
173
  return v8::Just(true);
@@ -69,6 +186,26 @@ public:
69
186
 
70
187
  v8::MaybeLocal<v8::Object> ReadHostObject(v8::Isolate*) override
71
188
  {
189
+ uint32_t tag = 0;
190
+ if (!deserializer->ReadUint32(&tag)) {
191
+ return v8::MaybeLocal<v8::Object>();
192
+ }
193
+
194
+ if (tag == kHostObjectImageBitmap) {
195
+ uint32_t w = 0, h = 0;
196
+ const void* px = nullptr;
197
+ if (!deserializer->ReadUint32(&w) || !deserializer->ReadUint32(&h) ||
198
+ !deserializer->ReadRawBytes(static_cast<size_t>(w) * h * 4, &px)) {
199
+ return v8::MaybeLocal<v8::Object>();
200
+ }
201
+ auto* img = new DecodedImage();
202
+ img->width = w;
203
+ img->height = h;
204
+ img->rgba.assign(static_cast<const uint8_t*>(px),
205
+ static_cast<const uint8_t*>(px) + static_cast<size_t>(w) * h * 4);
206
+ return WrapImageBitmap(isolate_, img);
207
+ }
208
+
72
209
  uint32_t w = 0, h = 0;
73
210
  deserializer->ReadUint32(&w);
74
211
  deserializer->ReadUint32(&h);
@@ -84,36 +221,69 @@ private:
84
221
 
85
222
  } // namespace
86
223
 
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*/)
224
+ WorkerMessage SerializeMessage(v8::Isolate* isolate, v8::Local<v8::Context> source_ctx,
225
+ v8::Local<v8::Value> message,
226
+ v8::Local<v8::Value> transfer_list)
90
227
  {
91
228
  v8::Context::Scope scope(source_ctx);
92
229
  SerializerDelegate delegate(isolate);
93
230
  v8::ValueSerializer serializer(isolate, &delegate);
94
231
  delegate.serializer = &serializer;
95
232
 
233
+ // transfer_list の ArrayBuffer は中身を埋め込まず所有権を移譲する
234
+ // (player の render キューは数十 MB — 毎フレームのコピーは不可)。
235
+ std::vector<v8::Local<v8::ArrayBuffer>> transfer_abs;
236
+ if (!transfer_list.IsEmpty() && transfer_list->IsArray()) {
237
+ auto arr = transfer_list.As<v8::Array>();
238
+ for (uint32_t i = 0; i < arr->Length(); ++i) {
239
+ v8::Local<v8::Value> item;
240
+ if (arr->Get(source_ctx, i).ToLocal(&item) && item->IsArrayBuffer()) {
241
+ auto ab = item.As<v8::ArrayBuffer>();
242
+ if (ab->IsDetachable()) {
243
+ serializer.TransferArrayBuffer(
244
+ static_cast<uint32_t>(transfer_abs.size()), ab);
245
+ transfer_abs.push_back(ab);
246
+ }
247
+ }
248
+ }
249
+ }
250
+
96
251
  serializer.WriteHeader();
97
- // 通常の ArrayBuffer は値としてシリアライズされる (transfer は将来最適化)。
98
252
  if (serializer.WriteValue(source_ctx, message).IsNothing()) {
99
253
  return {};
100
254
  }
255
+
256
+ WorkerMessage result;
101
257
  std::pair<uint8_t*, size_t> buffer = serializer.Release();
102
- std::vector<uint8_t> bytes(buffer.first, buffer.first + buffer.second);
258
+ result.data.assign(buffer.first, buffer.first + buffer.second);
103
259
  free(buffer.first);
104
- return bytes;
260
+
261
+ // 成功後に detach (ブラウザの transferable と同じく送信側では使えなくなる)
262
+ for (auto& ab : transfer_abs) {
263
+ result.transfers.push_back(ab->GetBackingStore());
264
+ (void) ab->Detach(v8::Local<v8::Value>());
265
+ }
266
+ return result;
105
267
  }
106
268
 
107
269
  v8::MaybeLocal<v8::Value> DeserializeMessage(v8::Isolate* isolate,
108
270
  v8::Local<v8::Context> target_ctx,
109
- const std::vector<uint8_t>& bytes)
271
+ const WorkerMessage& message)
110
272
  {
111
273
  v8::Context::Scope scope(target_ctx);
112
274
  HostContext* host = HostContext::From(isolate);
113
275
  DeserializerDelegate delegate(isolate, host);
114
- v8::ValueDeserializer deserializer(isolate, bytes.data(), bytes.size(), &delegate);
276
+ v8::ValueDeserializer deserializer(isolate, message.data.data(),
277
+ message.data.size(), &delegate);
115
278
  delegate.deserializer = &deserializer;
116
279
 
280
+ // transfer された ArrayBuffer を同じ backing store で再装着する (ゼロコピー)
281
+ for (size_t i = 0; i < message.transfers.size(); ++i) {
282
+ v8::Local<v8::ArrayBuffer> ab =
283
+ v8::ArrayBuffer::New(isolate, message.transfers[i]);
284
+ deserializer.TransferArrayBuffer(static_cast<uint32_t>(i), ab);
285
+ }
286
+
117
287
  if (deserializer.ReadHeader(target_ctx).IsNothing()) {
118
288
  return v8::MaybeLocal<v8::Value>();
119
289
  }
@@ -140,6 +310,7 @@ void DispatchMessage(v8::Isolate* isolate, v8::Local<v8::Context> ctx,
140
310
  if (target->Get(ctx, Str(isolate, "onmessage")).ToLocal(&on) && on->IsFunction()) {
141
311
  v8::TryCatch tc(isolate);
142
312
  (void) on.As<v8::Function>()->Call(ctx, target, 1, args);
313
+ v8util::ReportCaught(isolate, &tc, "onmessage");
143
314
  }
144
315
  // __messageListeners 配列
145
316
  v8::Local<v8::Value> listeners;
@@ -151,6 +322,7 @@ void DispatchMessage(v8::Isolate* isolate, v8::Local<v8::Context> ctx,
151
322
  if (arr->Get(ctx, i).ToLocal(&fn) && fn->IsFunction()) {
152
323
  v8::TryCatch tc(isolate);
153
324
  (void) fn.As<v8::Function>()->Call(ctx, target, 1, args);
325
+ v8util::ReportCaught(isolate, &tc, "message listener");
154
326
  }
155
327
  }
156
328
  }
@@ -182,11 +354,35 @@ void AddEventListener(const v8::FunctionCallbackInfo<v8::Value>& args)
182
354
  // ===========================================================================
183
355
  // WorkerInstance
184
356
  // ===========================================================================
185
- WorkerInstance::WorkerInstance(WorkerRuntime* runtime, v8::Isolate* isolate, std::string url)
186
- : runtime_(runtime), isolate_(isolate), url_(std::move(url))
357
+ WorkerInstance::WorkerInstance(WorkerRuntime* runtime, v8::Isolate* isolate, std::string url,
358
+ bool is_module)
359
+ : runtime_(runtime), isolate_(isolate), url_(std::move(url)), module_(is_module)
187
360
  {
188
361
  }
189
362
 
363
+ namespace {
364
+
365
+ // import 指定子を referrer の URL からの相対で解決する。
366
+ // data:/blob: の referrer(インライン worker)は基準ディレクトリを持たないため
367
+ // 指定子をそのまま返す(assets 参照は失敗するが、インライン ESM は import を持たない)。
368
+ std::string ResolveModulePath(const std::string& referrer_url, const std::string& specifier)
369
+ {
370
+ // 絶対 URL / bare specifier はそのまま
371
+ if (specifier.rfind("/", 0) == 0 ||
372
+ specifier.rfind("http:", 0) == 0 || specifier.rfind("https:", 0) == 0 ||
373
+ specifier.rfind("data:", 0) == 0 || specifier.rfind("blob:", 0) == 0) {
374
+ return specifier;
375
+ }
376
+ if (referrer_url.rfind("data:", 0) == 0 || referrer_url.rfind("blob:", 0) == 0) {
377
+ return specifier;
378
+ }
379
+ namespace fs = std::filesystem;
380
+ const fs::path dir = fs::path(referrer_url).parent_path();
381
+ return (dir / specifier).lexically_normal().generic_string();
382
+ }
383
+
384
+ } // namespace
385
+
190
386
  WorkerInstance::~WorkerInstance() = default;
191
387
 
192
388
  v8::Local<v8::Context> WorkerInstance::context() const
@@ -203,9 +399,9 @@ void WorkerSelfPostMessage(const v8::FunctionCallbackInfo<v8::Value>& args)
203
399
  auto* instance = static_cast<WorkerInstance*>(args.Data().As<v8::External>()->Value());
204
400
  v8::Local<v8::Value> msg = args.Length() > 0 ? args[0] : v8::Undefined(isolate).As<v8::Value>();
205
401
  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));
402
+ auto message = SerializeMessage(isolate, isolate->GetCurrentContext(), msg, transfer);
403
+ if (!message.data.empty()) {
404
+ instance->PostToMain(std::move(message));
209
405
  }
210
406
  }
211
407
 
@@ -236,9 +432,9 @@ void MainWorkerPostMessage(const v8::FunctionCallbackInfo<v8::Value>& args)
236
432
  auto* instance = static_cast<WorkerInstance*>(args.Data().As<v8::External>()->Value());
237
433
  v8::Local<v8::Value> msg = args.Length() > 0 ? args[0] : v8::Undefined(isolate).As<v8::Value>();
238
434
  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));
435
+ auto message = SerializeMessage(isolate, isolate->GetCurrentContext(), msg, transfer);
436
+ if (!message.data.empty()) {
437
+ instance->PostToWorker(std::move(message));
242
438
  }
243
439
  }
244
440
 
@@ -254,6 +450,8 @@ bool WorkerInstance::Start()
254
450
  // worker 専用 EventLoop を Context の embedder slot に登録
255
451
  loop_ = std::make_unique<EventLoop>(isolate_);
256
452
  context->SetAlignedPointerInEmbedderData(kEventLoopEmbedderSlot, loop_.get());
453
+ // ESM の ResolveModule コールバックが context から自身を引けるように登録
454
+ context->SetAlignedPointerInEmbedderData(kWorkerInstanceEmbedderSlot, this);
257
455
 
258
456
  v8::Context::Scope context_scope(context);
259
457
  v8::Local<v8::Object> global = context->Global();
@@ -277,47 +475,209 @@ bool WorkerInstance::Start()
277
475
  // 入れ子 Worker を許可 (ZlibInflate 等)。
278
476
  runtime_->InstallOnGlobal(global);
279
477
 
280
- // ワーカースクリプトを読み込んで評価 (クラシック。ESM worker は «EXTEND»)。
281
- // Vite の `?worker&inline` は blob: URL(= URL.createObjectURL(new Blob([code]))) を渡すため、
282
- // まず object URL レジストリ、次に assets を参照する。
478
+ // ワーカースクリプトを読み込む (クラシック / ESM は module_ で分岐)。
479
+ // Vite の `?worker&inline` は data:application/javascript;base64,... を渡す
480
+ // (バージョンによっては blob: URL)。data: / blob: / assets の順で解決する。
283
481
  HostContext* host = HostContext::From(isolate_);
284
482
  std::string source;
285
- if (url_.rfind("blob:", 0) == 0) {
483
+ if (url_.rfind("data:", 0) == 0) {
484
+ const auto comma = url_.find(',');
485
+ if (comma == std::string::npos) {
486
+ std::cerr << "[Worker] malformed data url: " << ShortUrl(url_) << std::endl;
487
+ return false;
488
+ }
489
+ const std::string meta = url_.substr(5, comma - 5); // 例: application/javascript;base64
490
+ const std::string payload = url_.substr(comma + 1);
491
+ if (meta.find(";base64") != std::string::npos) {
492
+ if (!DecodeBase64(payload, &source)) {
493
+ std::cerr << "[Worker] base64 decode failed: " << ShortUrl(url_) << std::endl;
494
+ return false;
495
+ }
496
+ } else {
497
+ source = PercentDecode(payload);
498
+ }
499
+ } else if (url_.rfind("blob:", 0) == 0) {
286
500
  if (!ResolveObjectURL(url_, &source)) {
287
- std::cerr << "[Worker] blob url not found: " << url_ << std::endl;
501
+ std::cerr << "[Worker] blob url not found: " << ShortUrl(url_) << std::endl;
288
502
  return false;
289
503
  }
290
504
  } else {
291
505
  auto src = host->assets->ReadText(url_);
292
506
  if (!src) {
293
- std::cerr << "[Worker] script not found: " << url_ << std::endl;
507
+ std::cerr << "[Worker] script not found: " << ShortUrl(url_) << std::endl;
294
508
  return false;
295
509
  }
296
510
  source = std::move(*src);
297
511
  }
512
+
513
+ // ESM worker (new Worker(url, { type: "module" }))。モジュールとして評価する。
514
+ if (module_) {
515
+ return EvaluateModule(context, source);
516
+ }
517
+
518
+ // «自己修復» CommandController.execute() は try/catch を持たないため、描画中の例外が
519
+ // 1 度起きると this.state が "active" のまま固まり、以後 onmessage は queue に積むだけで
520
+ // execute を再開せず worker が永久停止する。元の execute をリネームし、例外を捕捉して
521
+ // state を戻すラッパを被せて凍結を防ぐ (例外は [rendererr] で出力)。安全網として残す。
522
+ if (source.find("getCurrentTexture") != std::string::npos) {
523
+ const auto patch = [&source](const std::string& from, const std::string& to) {
524
+ const auto pos = source.find(from);
525
+ if (pos != std::string::npos) {
526
+ source.replace(pos, from.size(), to);
527
+ }
528
+ };
529
+ patch("async execute(){for(this.state=\"active\"",
530
+ "async execute(){try{await this.__n2dExec()}catch(__ee){"
531
+ "var __n=(globalThis.__rerr=(globalThis.__rerr||0)+1);"
532
+ "if(__n<=8)console.error(\"[rendererr] #\"+__n+\" \"+(__ee&&__ee.message||__ee)+"
533
+ "\"\\n\"+(__ee&&__ee.stack||\"\"));this.state=\"deactivate\"}}"
534
+ "async __n2dExec(){for(this.state=\"active\"");
535
+ }
298
536
  v8::TryCatch tc(isolate_);
299
537
  v8::ScriptOrigin origin(Str(isolate_, url_));
300
538
  v8::Local<v8::String> code = Str(isolate_, source);
539
+ // バイトコードキャッシュ込みでコンパイル。レンダラ worker (player の描画バンドル)
540
+ // が最大のパース対象。キーは自己修復パッチ適用後のソースなので整合する。
301
541
  v8::Local<v8::Script> script;
302
- if (!v8::Script::Compile(context, code, &origin).ToLocal(&script) ||
542
+ if (!codecache::Compile(context, code, origin, url_, source).ToLocal(&script) ||
303
543
  script->Run(context).IsEmpty()) {
304
- std::cerr << "[Worker] script eval failed: " << url_ << std::endl;
544
+ std::cerr << "[Worker] script eval failed: " << ShortUrl(url_) << std::endl;
545
+ if (tc.HasCaught()) {
546
+ v8::String::Utf8Value exception(isolate_, tc.Exception());
547
+ std::cerr << "[Worker] exception: " << (*exception ? *exception : "?") << std::endl;
548
+ v8::Local<v8::Message> message = tc.Message();
549
+ if (!message.IsEmpty()) {
550
+ std::cerr << "[Worker] at line "
551
+ << message->GetLineNumber(context).FromMaybe(0) << std::endl;
552
+ }
553
+ }
554
+ return false;
555
+ }
556
+ // NOTE: ここで PerformMicrotaskCheckpoint は呼ばない。Start は JS コールバック
557
+ // (new Worker) の最中に呼ばれるため、JS 実行中のチェックポイントは V8 の規約違反。
558
+ // マイクロタスクはメインループの PumpMicrotasks で処理される。
559
+ return true;
560
+ }
561
+
562
+ // --- ESM worker ------------------------------------------------------------
563
+ // 依存モジュールを assets から読み、コンパイルして再帰ロードする。
564
+ v8::MaybeLocal<v8::Module> WorkerInstance::LoadModule(const std::string& url)
565
+ {
566
+ auto cached = modules_.find(url);
567
+ if (cached != modules_.end()) {
568
+ return cached->second.Get(isolate_);
569
+ }
570
+ HostContext* host = HostContext::From(isolate_);
571
+ auto src = host->assets->ReadText(url);
572
+ if (!src) {
573
+ std::cerr << "[Worker] module not found: " << ShortUrl(url) << std::endl;
574
+ return v8::MaybeLocal<v8::Module>();
575
+ }
576
+
577
+ v8::ScriptOrigin origin(Str(isolate_, url), 0, 0, false, -1,
578
+ v8::Local<v8::Value>(), false, false, /*is_module*/ true);
579
+ v8::Local<v8::Module> module;
580
+ if (!codecache::CompileModule(isolate_, Str(isolate_, *src), origin, url, *src)
581
+ .ToLocal(&module)) {
582
+ std::cerr << "[Worker] module compile failed: " << ShortUrl(url) << std::endl;
583
+ return v8::MaybeLocal<v8::Module>();
584
+ }
585
+ modules_[url].Reset(isolate_, module);
586
+ module_paths_[module->GetIdentityHash()] = url;
587
+
588
+ v8::Local<v8::Context> ctx = context();
589
+ v8::Local<v8::FixedArray> requests = module->GetModuleRequests();
590
+ for (int i = 0; i < requests->Length(); ++i) {
591
+ v8::Local<v8::ModuleRequest> request =
592
+ requests->Get(ctx, i).As<v8::ModuleRequest>();
593
+ const std::string specifier = ToStdString(isolate_, request->GetSpecifier());
594
+ if (LoadModule(ResolveModulePath(url, specifier)).IsEmpty()) {
595
+ return v8::MaybeLocal<v8::Module>();
596
+ }
597
+ }
598
+ return module;
599
+ }
600
+
601
+ v8::MaybeLocal<v8::Module> WorkerInstance::ResolveModule(
602
+ v8::Local<v8::Context> context,
603
+ v8::Local<v8::String> specifier,
604
+ v8::Local<v8::FixedArray> /*import_attributes*/,
605
+ v8::Local<v8::Module> referrer)
606
+ {
607
+ v8::Isolate* isolate = context->GetIsolate();
608
+ auto* self = static_cast<WorkerInstance*>(
609
+ context->GetAlignedPointerFromEmbedderData(kWorkerInstanceEmbedderSlot));
610
+ if (!self) {
611
+ return v8::MaybeLocal<v8::Module>();
612
+ }
613
+ auto it = self->module_paths_.find(referrer->GetIdentityHash());
614
+ const std::string referrer_url =
615
+ (it != self->module_paths_.end()) ? it->second : self->url_;
616
+ const std::string spec = ToStdString(isolate, specifier);
617
+ return self->LoadModule(ResolveModulePath(referrer_url, spec));
618
+ }
619
+
620
+ bool WorkerInstance::EvaluateModule(v8::Local<v8::Context> context, const std::string& source)
621
+ {
622
+ v8::TryCatch tc(isolate_);
623
+
624
+ // 入口モジュールはインライン(data:/blob:)の可能性があるため、渡された source を
625
+ // 直接コンパイルする(assets 経由の LoadModule は依存モジュール用)。
626
+ v8::ScriptOrigin origin(Str(isolate_, url_), 0, 0, false, -1,
627
+ v8::Local<v8::Value>(), false, false, /*is_module*/ true);
628
+ v8::Local<v8::Module> module;
629
+ if (!codecache::CompileModule(isolate_, Str(isolate_, source), origin, url_, source)
630
+ .ToLocal(&module)) {
631
+ std::cerr << "[Worker] module compile failed: " << ShortUrl(url_) << std::endl;
632
+ return false;
633
+ }
634
+ modules_[url_].Reset(isolate_, module);
635
+ module_paths_[module->GetIdentityHash()] = url_;
636
+
637
+ // 静的 import を再帰ロード(インライン worker は import 無しでスキップ)。
638
+ v8::Local<v8::FixedArray> requests = module->GetModuleRequests();
639
+ for (int i = 0; i < requests->Length(); ++i) {
640
+ v8::Local<v8::ModuleRequest> request =
641
+ requests->Get(context, i).As<v8::ModuleRequest>();
642
+ const std::string specifier = ToStdString(isolate_, request->GetSpecifier());
643
+ if (LoadModule(ResolveModulePath(url_, specifier)).IsEmpty()) {
644
+ return false;
645
+ }
646
+ }
647
+
648
+ if (module->InstantiateModule(context, ResolveModule).IsNothing()) {
649
+ std::cerr << "[Worker] module instantiate failed: " << ShortUrl(url_) << std::endl;
650
+ if (tc.HasCaught()) {
651
+ v8::String::Utf8Value ex(isolate_, tc.Exception());
652
+ std::cerr << "[Worker] exception: " << (*ex ? *ex : "?") << std::endl;
653
+ }
654
+ return false;
655
+ }
656
+
657
+ // NOTE: Start と同じく PerformMicrotaskCheckpoint は呼ばない(new Worker の JS 実行中)。
658
+ // トップレベル await が返す Promise はメインループの PumpMicrotasks で解決される。
659
+ v8::Local<v8::Value> result;
660
+ if (!module->Evaluate(context).ToLocal(&result)) {
661
+ std::cerr << "[Worker] module evaluate failed: " << ShortUrl(url_) << std::endl;
662
+ if (tc.HasCaught()) {
663
+ v8::String::Utf8Value ex(isolate_, tc.Exception());
664
+ std::cerr << "[Worker] exception: " << (*ex ? *ex : "?") << std::endl;
665
+ }
305
666
  return false;
306
667
  }
307
- isolate_->PerformMicrotaskCheckpoint();
308
668
  return true;
309
669
  }
310
670
 
311
671
  void WorkerInstance::Terminate() { terminated_ = true; }
312
672
 
313
- void WorkerInstance::PostToWorker(std::vector<uint8_t> bytes)
673
+ void WorkerInstance::PostToWorker(WorkerMessage message)
314
674
  {
315
- to_worker_.push_back(WorkerMessage{std::move(bytes)});
675
+ to_worker_.push_back(std::move(message));
316
676
  }
317
677
 
318
- void WorkerInstance::PostToMain(std::vector<uint8_t> bytes)
678
+ void WorkerInstance::PostToMain(WorkerMessage message)
319
679
  {
320
- to_main_.push_back(WorkerMessage{std::move(bytes)});
680
+ to_main_.push_back(std::move(message));
321
681
  }
322
682
 
323
683
  void WorkerInstance::DeliverToWorker(double now_ms)
@@ -330,8 +690,11 @@ void WorkerInstance::DeliverToWorker(double now_ms)
330
690
  WorkerMessage m = std::move(to_worker_.front());
331
691
  to_worker_.pop_front();
332
692
  v8::Local<v8::Value> data;
333
- if (DeserializeMessage(isolate_, ctx, m.data).ToLocal(&data)) {
693
+ if (DeserializeMessage(isolate_, ctx, m).ToLocal(&data)) {
334
694
  DispatchMessage(isolate_, ctx, ctx->Global(), data);
695
+ } else {
696
+ std::cerr << "[Worker] main->worker deserialize failed ("
697
+ << m.data.size() << " bytes)" << std::endl;
335
698
  }
336
699
  }
337
700
 
@@ -354,8 +717,11 @@ void WorkerInstance::DeliverToMain()
354
717
  WorkerMessage m = std::move(to_main_.front());
355
718
  to_main_.pop_front();
356
719
  v8::Local<v8::Value> data;
357
- if (DeserializeMessage(isolate_, main_ctx, m.data).ToLocal(&data)) {
720
+ if (DeserializeMessage(isolate_, main_ctx, m).ToLocal(&data)) {
358
721
  DispatchMessage(isolate_, main_ctx, worker_obj, data);
722
+ } else {
723
+ std::cerr << "[Worker] worker->main deserialize failed ("
724
+ << m.data.size() << " bytes)" << std::endl;
359
725
  }
360
726
  }
361
727
  }
@@ -376,11 +742,11 @@ WorkerRuntime::WorkerRuntime(v8::Isolate* isolate, HostContext* host)
376
742
 
377
743
  WorkerRuntime::~WorkerRuntime() = default;
378
744
 
379
- v8::Local<v8::Object> WorkerRuntime::CreateWorker(const std::string& url)
745
+ v8::Local<v8::Object> WorkerRuntime::CreateWorker(const std::string& url, bool is_module)
380
746
  {
381
747
  v8::Local<v8::Context> ctx = isolate_->GetCurrentContext();
382
748
 
383
- auto instance = std::make_unique<WorkerInstance>(this, isolate_, url);
749
+ auto instance = std::make_unique<WorkerInstance>(this, isolate_, url, is_module);
384
750
  WorkerInstance* ptr = instance.get();
385
751
 
386
752
  // main 側 Worker オブジェクト
@@ -389,11 +755,17 @@ v8::Local<v8::Object> WorkerRuntime::CreateWorker(const std::string& url)
389
755
  worker->Set(ctx, Str(isolate_, "postMessage"),
390
756
  v8::Function::New(ctx, MainWorkerPostMessage, ext).ToLocalChecked()).Check();
391
757
  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
- });
758
+ // terminate は WorkerInstance* を data (External) で受け取る必要があるため
759
+ // SetMethod (data なし) ではなく Function::New で設置する。
760
+ worker->Set(ctx, Str(isolate_, "terminate"),
761
+ v8::Function::New(ctx,
762
+ [](const v8::FunctionCallbackInfo<v8::Value>& a) {
763
+ if (!a.Data()->IsExternal()) {
764
+ return;
765
+ }
766
+ auto* w = static_cast<WorkerInstance*>(a.Data().As<v8::External>()->Value());
767
+ w->Terminate();
768
+ }, ext).ToLocalChecked()).Check();
397
769
 
398
770
  ptr->main_worker_object.Reset(isolate_, worker);
399
771
 
@@ -420,7 +792,17 @@ void WorkerConstructor(const v8::FunctionCallbackInfo<v8::Value>& args)
420
792
  // new Worker(new URL(...)) にも対応するため toString で URL 文字列化
421
793
  url = ToStdString(isolate, args[0]);
422
794
  }
423
- args.GetReturnValue().Set(runtime->CreateWorker(url));
795
+ // new Worker(url, { type: "module" }) → ESM として評価する
796
+ bool is_module = false;
797
+ if (args.Length() > 1 && args[1]->IsObject()) {
798
+ v8::Local<v8::Context> ctx = isolate->GetCurrentContext();
799
+ v8::Local<v8::Value> type;
800
+ if (args[1].As<v8::Object>()->Get(ctx, Str(isolate, "type")).ToLocal(&type) &&
801
+ type->IsString()) {
802
+ is_module = (ToStdString(isolate, type) == "module");
803
+ }
804
+ }
805
+ args.GetReturnValue().Set(runtime->CreateWorker(url, is_module));
424
806
  }
425
807
 
426
808
  } // namespace
@@ -437,8 +819,10 @@ void WorkerRuntime::InstallOnGlobal(v8::Local<v8::Object> global)
437
819
 
438
820
  void WorkerRuntime::Pump(double now_ms)
439
821
  {
440
- for (auto& w : workers_) {
441
- w->Deliver(now_ms);
822
+ // 配送中の JS が new Worker() を呼ぶと workers_ に push_back され
823
+ // vector が再割当されるため、イテレータではなく添字で回す。
824
+ for (size_t i = 0; i < workers_.size(); ++i) {
825
+ workers_[i]->Deliver(now_ms);
442
826
  }
443
827
  }
444
828