@next2d/builder 0.1.8 → 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 (67) 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 -790
  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 +11 -9
  19. package/templates/xbox/CMakeLists.txt +47 -11
  20. package/templates/xbox/README.md +18 -0
  21. package/templates/xbox/js/bootstrap.js +120 -0
  22. package/templates/xbox/src/AssetLoader.cpp +115 -12
  23. package/templates/xbox/src/AssetLoader.h +4 -0
  24. package/templates/xbox/src/EmbeddedAssets.cpp +149 -0
  25. package/templates/xbox/src/EmbeddedAssets.h +60 -0
  26. package/templates/xbox/src/EventLoop.cpp +0 -15
  27. package/templates/xbox/src/bindings/Audio.cpp +118 -22
  28. package/templates/xbox/src/bindings/Canvas.cpp +32 -2
  29. package/templates/xbox/src/bindings/Canvas2D.cpp +495 -39
  30. package/templates/xbox/src/bindings/Console.cpp +6 -1
  31. package/templates/xbox/src/bindings/DomShims.cpp +18 -2
  32. package/templates/xbox/src/bindings/EventTarget.cpp +9 -0
  33. package/templates/xbox/src/bindings/Image.cpp +83 -57
  34. package/templates/xbox/src/bindings/Polyfills.cpp +671 -236
  35. package/templates/xbox/src/bindings/RasterCore.h +105 -1
  36. package/templates/xbox/src/bindings/Video.cpp +57 -3
  37. package/templates/xbox/src/bindings/webgpu/WebGPU.cpp +29 -3
  38. package/templates/xbox/src/bindings/webgpu/WebGPUCommon.h +4 -1
  39. package/templates/xbox/src/bindings/webgpu/WebGPUEnums.h +32 -0
  40. package/templates/xbox/src/gpu/DawnContext.cpp +167 -16
  41. package/templates/xbox/src/main.cpp +385 -16
  42. package/templates/xbox/src/platform/AudioDecoder.cpp +121 -0
  43. package/templates/xbox/src/platform/AudioDecoder.h +18 -0
  44. package/templates/xbox/src/platform/AudioEngine.cpp +28 -3
  45. package/templates/xbox/src/platform/DecodeQueue.cpp +136 -0
  46. package/templates/xbox/src/platform/DecodeQueue.h +27 -0
  47. package/templates/xbox/src/platform/ImageDecoder.cpp +44 -0
  48. package/templates/xbox/src/platform/ImageDecoder.h +18 -0
  49. package/templates/xbox/src/platform/StbTextRasterizer.cpp +324 -0
  50. package/templates/xbox/src/platform/StbTextRasterizer.h +37 -0
  51. package/templates/xbox/src/platform/TextRasterizer.cpp +32 -0
  52. package/templates/xbox/src/platform/WicDecoder.cpp +19 -0
  53. package/templates/xbox/src/v8/CodeCache.h +223 -0
  54. package/templates/xbox/src/v8/V8Runtime.cpp +34 -33
  55. package/templates/xbox/src/v8/V8Util.h +54 -4
  56. package/templates/xbox/src/worker/WorkerRuntime.cpp +165 -65
  57. package/templates/xbox/src/worker/WorkerRuntime.h +23 -3
  58. package/templates/xbox/tests/check_local.sh +5 -1
  59. package/templates/xbox/tests/gamecore_api_probe.cpp +172 -0
  60. package/templates/xbox/tests/raster_test.cpp +50 -0
  61. package/templates/xbox/tests/windows_platform_test.cpp +75 -3
  62. package/templates/xbox/third_party/LICENSES.md +14 -0
  63. package/templates/xbox/third_party/dr_mp3.h +5412 -0
  64. package/templates/xbox/third_party/dr_wav.h +9131 -0
  65. package/templates/xbox/third_party/stb_image.h +7988 -0
  66. package/templates/xbox/third_party/stb_truetype.h +5079 -0
  67. package/templates/xbox/third_party/stb_vorbis.c +5584 -0
@@ -7,9 +7,11 @@
7
7
  #include "bindings/ImageSource.h"
8
8
  #include "platform/ImageTypes.h"
9
9
  #include "v8/V8Util.h"
10
+ #include "v8/CodeCache.h"
10
11
 
11
12
  #include <cstdlib>
12
13
  #include <cstring>
14
+ #include <filesystem>
13
15
  #include <iostream>
14
16
 
15
17
  namespace next2d {
@@ -117,15 +119,6 @@ bool IsImageBitmapObject(v8::Isolate* isolate, v8::Local<v8::Object> obj)
117
119
  constexpr uint32_t kHostObjectOffscreenCanvas = 1;
118
120
  constexpr uint32_t kHostObjectImageBitmap = 2;
119
121
 
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
-
129
122
  class SerializerDelegate : public v8::ValueSerializer::Delegate {
130
123
  public:
131
124
  explicit SerializerDelegate(v8::Isolate* isolate) : isolate_(isolate) {}
@@ -361,11 +354,35 @@ void AddEventListener(const v8::FunctionCallbackInfo<v8::Value>& args)
361
354
  // ===========================================================================
362
355
  // WorkerInstance
363
356
  // ===========================================================================
364
- WorkerInstance::WorkerInstance(WorkerRuntime* runtime, v8::Isolate* isolate, std::string url)
365
- : 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)
366
360
  {
367
361
  }
368
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
+
369
386
  WorkerInstance::~WorkerInstance() = default;
370
387
 
371
388
  v8::Local<v8::Context> WorkerInstance::context() const
@@ -383,10 +400,6 @@ void WorkerSelfPostMessage(const v8::FunctionCallbackInfo<v8::Value>& args)
383
400
  v8::Local<v8::Value> msg = args.Length() > 0 ? args[0] : v8::Undefined(isolate).As<v8::Value>();
384
401
  v8::Local<v8::Value> transfer = args.Length() > 1 ? args[1] : v8::Undefined(isolate).As<v8::Value>();
385
402
  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
403
  if (!message.data.empty()) {
391
404
  instance->PostToMain(std::move(message));
392
405
  }
@@ -420,10 +433,6 @@ void MainWorkerPostMessage(const v8::FunctionCallbackInfo<v8::Value>& args)
420
433
  v8::Local<v8::Value> msg = args.Length() > 0 ? args[0] : v8::Undefined(isolate).As<v8::Value>();
421
434
  v8::Local<v8::Value> transfer = args.Length() > 1 ? args[1] : v8::Undefined(isolate).As<v8::Value>();
422
435
  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
436
  if (!message.data.empty()) {
428
437
  instance->PostToWorker(std::move(message));
429
438
  }
@@ -433,23 +442,22 @@ void MainWorkerPostMessage(const v8::FunctionCallbackInfo<v8::Value>& args)
433
442
 
434
443
  bool WorkerInstance::Start()
435
444
  {
436
- std::cerr << "[Worker] start: " << ShortUrl(url_) << std::endl;
437
445
  v8::HandleScope handle_scope(isolate_);
438
446
 
439
447
  v8::Local<v8::Context> context = v8::Context::New(isolate_);
440
448
  context_.Reset(isolate_, context);
441
- std::cerr << "[Worker] context created" << std::endl;
442
449
 
443
450
  // worker 専用 EventLoop を Context の embedder slot に登録
444
451
  loop_ = std::make_unique<EventLoop>(isolate_);
445
452
  context->SetAlignedPointerInEmbedderData(kEventLoopEmbedderSlot, loop_.get());
453
+ // ESM の ResolveModule コールバックが context から自身を引けるように登録
454
+ context->SetAlignedPointerInEmbedderData(kWorkerInstanceEmbedderSlot, this);
446
455
 
447
456
  v8::Context::Scope context_scope(context);
448
457
  v8::Local<v8::Object> global = context->Global();
449
458
 
450
459
  // ブラウザ相当環境一式 (console/timers/fetch/image/audio/webgpu/canvas/dom)
451
460
  InstallGlobalBindings(isolate_, global, HostContext::From(isolate_));
452
- std::cerr << "[Worker] bindings installed" << std::endl;
453
461
 
454
462
  // worker スコープには document/window を出さない (ライブラリの環境判定対策)。
455
463
  global->Delete(context, Str(isolate_, "document")).Check();
@@ -467,7 +475,7 @@ bool WorkerInstance::Start()
467
475
  // 入れ子 Worker を許可 (ZlibInflate 等)。
468
476
  runtime_->InstallOnGlobal(global);
469
477
 
470
- // ワーカースクリプトを読み込んで評価 (クラシック。ESM worker は «EXTEND»)。
478
+ // ワーカースクリプトを読み込む (クラシック / ESM は module_ で分岐)。
471
479
  // Vite の `?worker&inline` は data:application/javascript;base64,... を渡す
472
480
  // (バージョンによっては blob: URL)。data: / blob: / assets の順で解決する。
473
481
  HostContext* host = HostContext::From(isolate_);
@@ -501,37 +509,23 @@ bool WorkerInstance::Start()
501
509
  }
502
510
  source = std::move(*src);
503
511
  }
504
- std::cerr << "[Worker] source resolved (" << source.size() << " bytes)" << std::endl;
505
512
 
506
- // «診断» レンダラ worker のフレームサイクル (frameStarted / mainTexture) を追跡する。
507
- // 黒画面問題: getCurrentTexture がフレーム 3 回目以降呼ばれなくなる現象の原因特定用。
508
- // minify 後も this.プロパティ名は保持されるためパターンは安定。見つからなければ無変更。
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] で出力)。安全網として残す。
509
522
  if (source.find("getCurrentTexture") != std::string::npos) {
510
523
  const auto patch = [&source](const std::string& from, const std::string& to) {
511
524
  const auto pos = source.find(from);
512
525
  if (pos != std::string::npos) {
513
526
  source.replace(pos, from.size(), to);
514
- } else {
515
- std::cerr << "[Worker] trace patch not applied: " << from.substr(0, 40) << std::endl;
516
527
  }
517
528
  };
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
529
  patch("async execute(){for(this.state=\"active\"",
536
530
  "async execute(){try{await this.__n2dExec()}catch(__ee){"
537
531
  "var __n=(globalThis.__rerr=(globalThis.__rerr||0)+1);"
@@ -542,8 +536,10 @@ bool WorkerInstance::Start()
542
536
  v8::TryCatch tc(isolate_);
543
537
  v8::ScriptOrigin origin(Str(isolate_, url_));
544
538
  v8::Local<v8::String> code = Str(isolate_, source);
539
+ // バイトコードキャッシュ込みでコンパイル。レンダラ worker (player の描画バンドル)
540
+ // が最大のパース対象。キーは自己修復パッチ適用後のソースなので整合する。
545
541
  v8::Local<v8::Script> script;
546
- if (!v8::Script::Compile(context, code, &origin).ToLocal(&script) ||
542
+ if (!codecache::Compile(context, code, origin, url_, source).ToLocal(&script) ||
547
543
  script->Run(context).IsEmpty()) {
548
544
  std::cerr << "[Worker] script eval failed: " << ShortUrl(url_) << std::endl;
549
545
  if (tc.HasCaught()) {
@@ -560,7 +556,115 @@ bool WorkerInstance::Start()
560
556
  // NOTE: ここで PerformMicrotaskCheckpoint は呼ばない。Start は JS コールバック
561
557
  // (new Worker) の最中に呼ばれるため、JS 実行中のチェックポイントは V8 の規約違反。
562
558
  // マイクロタスクはメインループの PumpMicrotasks で処理される。
563
- std::cerr << "[Worker] script evaluated" << std::endl;
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
+ }
666
+ return false;
667
+ }
564
668
  return true;
565
669
  }
566
670
 
@@ -585,16 +689,9 @@ void WorkerInstance::DeliverToWorker(double now_ms)
585
689
  while (!to_worker_.empty()) {
586
690
  WorkerMessage m = std::move(to_worker_.front());
587
691
  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
- }
592
692
  v8::Local<v8::Value> data;
593
693
  if (DeserializeMessage(isolate_, ctx, m).ToLocal(&data)) {
594
694
  DispatchMessage(isolate_, ctx, ctx->Global(), data);
595
- if (trace) {
596
- std::cerr << "[Worker] worker onmessage done" << std::endl;
597
- }
598
695
  } else {
599
696
  std::cerr << "[Worker] main->worker deserialize failed ("
600
697
  << m.data.size() << " bytes)" << std::endl;
@@ -619,16 +716,9 @@ void WorkerInstance::DeliverToMain()
619
716
  while (!to_main_.empty()) {
620
717
  WorkerMessage m = std::move(to_main_.front());
621
718
  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
- }
626
719
  v8::Local<v8::Value> data;
627
720
  if (DeserializeMessage(isolate_, main_ctx, m).ToLocal(&data)) {
628
721
  DispatchMessage(isolate_, main_ctx, worker_obj, data);
629
- if (trace) {
630
- std::cerr << "[Worker] main onmessage done" << std::endl;
631
- }
632
722
  } else {
633
723
  std::cerr << "[Worker] worker->main deserialize failed ("
634
724
  << m.data.size() << " bytes)" << std::endl;
@@ -652,11 +742,11 @@ WorkerRuntime::WorkerRuntime(v8::Isolate* isolate, HostContext* host)
652
742
 
653
743
  WorkerRuntime::~WorkerRuntime() = default;
654
744
 
655
- v8::Local<v8::Object> WorkerRuntime::CreateWorker(const std::string& url)
745
+ v8::Local<v8::Object> WorkerRuntime::CreateWorker(const std::string& url, bool is_module)
656
746
  {
657
747
  v8::Local<v8::Context> ctx = isolate_->GetCurrentContext();
658
748
 
659
- auto instance = std::make_unique<WorkerInstance>(this, isolate_, url);
749
+ auto instance = std::make_unique<WorkerInstance>(this, isolate_, url, is_module);
660
750
  WorkerInstance* ptr = instance.get();
661
751
 
662
752
  // main 側 Worker オブジェクト
@@ -702,7 +792,17 @@ void WorkerConstructor(const v8::FunctionCallbackInfo<v8::Value>& args)
702
792
  // new Worker(new URL(...)) にも対応するため toString で URL 文字列化
703
793
  url = ToStdString(isolate, args[0]);
704
794
  }
705
- 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));
706
806
  }
707
807
 
708
808
  } // namespace
@@ -15,6 +15,7 @@
15
15
 
16
16
  #include <cstdint>
17
17
  #include <deque>
18
+ #include <map>
18
19
  #include <memory>
19
20
  #include <string>
20
21
  #include <vector>
@@ -25,6 +26,10 @@ class HostContext;
25
26
  class EventLoop;
26
27
  class WorkerRuntime;
27
28
 
29
+ // worker context の embedder slot。1 は EventLoop (kEventLoopEmbedderSlot) が使用。
30
+ // ESM の ResolveModule コールバックが context から WorkerInstance を引くために使う。
31
+ constexpr int kWorkerInstanceEmbedderSlot = 2;
32
+
28
33
  // 1 メッセージ = ValueSerializer 出力バイト列 + transfer された ArrayBuffer の実体。
29
34
  // transfer_list の ArrayBuffer は中身を bytes に埋め込まず所有権ごと移す
30
35
  // (render キューは数十 MB になるため、コピーするとフレームレートが崩壊する)。
@@ -35,7 +40,8 @@ struct WorkerMessage {
35
40
 
36
41
  class WorkerInstance {
37
42
  public:
38
- WorkerInstance(WorkerRuntime* runtime, v8::Isolate* isolate, std::string url);
43
+ WorkerInstance(WorkerRuntime* runtime, v8::Isolate* isolate, std::string url,
44
+ bool is_module = false);
39
45
  ~WorkerInstance();
40
46
 
41
47
  bool Start(); // Context 生成 + ワーカースクリプト評価
@@ -62,14 +68,28 @@ private:
62
68
  void DeliverToWorker(double now_ms);
63
69
  void DeliverToMain();
64
70
 
71
+ // ESM worker (type: "module")。分類スクリプトは Script、モジュールは Module として評価。
72
+ bool EvaluateModule(v8::Local<v8::Context> context, const std::string& source);
73
+ v8::MaybeLocal<v8::Module> LoadModule(const std::string& url);
74
+ static v8::MaybeLocal<v8::Module> ResolveModule(
75
+ v8::Local<v8::Context> context,
76
+ v8::Local<v8::String> specifier,
77
+ v8::Local<v8::FixedArray> import_attributes,
78
+ v8::Local<v8::Module> referrer);
79
+
65
80
  WorkerRuntime* runtime_;
66
81
  v8::Isolate* isolate_;
67
82
  std::string url_;
83
+ bool module_ = false;
68
84
  v8::Global<v8::Context> context_;
69
85
  std::unique_ptr<EventLoop> loop_;
70
86
  std::deque<WorkerMessage> to_worker_;
71
87
  std::deque<WorkerMessage> to_main_;
72
88
  bool terminated_ = false;
89
+
90
+ // ESM 解決: url -> Module、Module identity hash -> url (referrer 特定用)。
91
+ std::map<std::string, v8::Global<v8::Module>> modules_;
92
+ std::map<int, std::string> module_paths_;
73
93
  };
74
94
 
75
95
  class WorkerRuntime {
@@ -83,8 +103,8 @@ public:
83
103
  // main / worker グローバルに Worker コンストラクタ等を設置する。
84
104
  void InstallOnGlobal(v8::Local<v8::Object> global);
85
105
 
86
- // new Worker(url) の実体
87
- v8::Local<v8::Object> CreateWorker(const std::string& url);
106
+ // new Worker(url, { type }) の実体
107
+ v8::Local<v8::Object> CreateWorker(const std::string& url, bool is_module = false);
88
108
 
89
109
  // 全 worker のメッセージ配送とイベントループ前進
90
110
  void Pump(double now_ms);
@@ -47,9 +47,13 @@ FILES=(
47
47
  src/bindings/Canvas.cpp
48
48
  src/bindings/Audio.cpp
49
49
  src/bindings/Gamepad.cpp
50
+ src/platform/DecodeQueue.cpp
51
+ src/platform/AudioDecoder.cpp
52
+ src/platform/ImageDecoder.cpp
53
+ src/platform/StbTextRasterizer.cpp
50
54
  )
51
55
  for f in "${FILES[@]}"; do
52
- c++ -fsyntax-only -std=c++20 -Isrc -isystem "${V8INC}" \
56
+ c++ -fsyntax-only -std=c++20 -Isrc -I. -isystem "${V8INC}" \
53
57
  -DV8_COMPRESS_POINTERS "$f"
54
58
  echo "OK $f"
55
59
  done
@@ -0,0 +1,172 @@
1
+ // Game Core (Xbox コンソール) API パーティション監査プローブ。
2
+ //
3
+ // Windows SDK (19041+) は各 API 宣言を WINAPI_PARTITION_* でガードしており、
4
+ // `cl /DWINAPI_FAMILY=WINAPI_FAMILY_GAMES` でコンパイルすると GAMES パーティションに
5
+ // 属さない API は「未宣言」エラーになる。= NDA 資料なしで、公開 SDK だけを使って
6
+ // 「コンソールで使えない API」を正確に列挙できる (プリプロセッサが公式のゲート
7
+ // ロジックそのものを評価するため、テキスト解析より確実)。
8
+ //
9
+ // 使い方 (xbox-host-ci の gamecore-api-audit ジョブが実行):
10
+ // cl /c /DWINAPI_FAMILY=WINAPI_FAMILY_GAMES tests\gamecore_api_probe.cpp
11
+ // -> ホストがコンソールで使う API の存在検証 (エラー = 直ちに修正が必要)
12
+ // cl /c /DWINAPI_FAMILY=WINAPI_FAMILY_GAMES /DPROBE_V8 tests\gamecore_api_probe.cpp
13
+ // -> V8 の platform 層 (src/base/platform/*-win32) が使う API の存在検証
14
+ // (エラー = V8 Game Core 移植時に置換が必要な TODO リスト。情報提供扱い)
15
+ //
16
+ // 注意: これは「宣言がパーティションに含まれるか」の静的検査。実機での挙動
17
+ // (リンク先 lib の有無・実行時の権限) は devkit での検証が別途必要。
18
+ //
19
+ // WIN32_LEAN_AND_MEAN は必須: GAMES ファミリではフル Windows.h が引き込む
20
+ // 周辺ヘッダ (mmsystem/ole2/winsock 等) に GAMES 非対応部分があり
21
+ // コンパイルが壊れる (vcpkg の Xbox トリプレットも同様の前提)。
22
+ // 必要なヘッダは個別に include する。
23
+ #define WIN32_LEAN_AND_MEAN
24
+ #define NOMINMAX
25
+ #include <Windows.h>
26
+
27
+ #include <objbase.h> // CoInitializeEx (DecodeQueue のスレッド COM 初期化)
28
+ #include <bcrypt.h> // BCryptGenRandom (crypto.getRandomValues)
29
+ #include <xaudio2.h> // XAudio2Create (音声出力)
30
+
31
+ #ifdef PROBE_V8
32
+ #include <timeapi.h> // timeGetTime (V8 time.cc — GAMES 外の想定 = TODO 検出対象)
33
+ #include <psapi.h> // GetProcessMemoryInfo (V8 OS::GetPeakMemoryUsageKb)
34
+ #include <process.h> // _beginthreadex (V8 Thread)
35
+ #endif
36
+
37
+ // 関数アドレスを取るだけの配列。宣言が無ければコンパイルエラーになる。
38
+ #define PROBE(fn) reinterpret_cast<void*>(&fn)
39
+
40
+ // ---------------------------------------------------------------------------
41
+ // ホストがコンソールビルドでも使う API (NEXT2D_XBOX_CONSOLE ガード外のもの)。
42
+ // ここのエラーはホスト側の修正が必要 (ジョブを fail させる)。
43
+ // ---------------------------------------------------------------------------
44
+ void* const g_host_apis[] = {
45
+ // ウィンドウ / メッセージループ (main.cpp。GDK コンソールサンプルも同構成)
46
+ PROBE(RegisterClassExW),
47
+ PROBE(CreateWindowExW),
48
+ PROBE(DefWindowProcW),
49
+ PROBE(PeekMessageW),
50
+ PROBE(TranslateMessage),
51
+ PROBE(DispatchMessageW),
52
+ PROBE(PostQuitMessage),
53
+ PROBE(LoadCursorW),
54
+ PROBE(GetClientRect),
55
+
56
+ // 時刻 / ペーシング (EventLoop::Now, メインループ)
57
+ PROBE(QueryPerformanceCounter),
58
+ PROBE(QueryPerformanceFrequency),
59
+ PROBE(Sleep),
60
+
61
+ // モジュール / 環境 (ExeDir, DomShims の storage パス解決)
62
+ PROBE(GetModuleFileNameW),
63
+ PROBE(GetModuleHandleW),
64
+ PROBE(GetEnvironmentVariableW),
65
+ PROBE(GetLastError),
66
+
67
+ // COM (DecodeQueue プールスレッド。コンソールでは WIC/MF は使わないが
68
+ // CoInitializeEx 自体は呼ぶ)
69
+ PROBE(CoInitializeEx),
70
+ PROBE(CoUninitialize),
71
+
72
+ // 乱数 (crypto.getRandomValues)
73
+ PROBE(BCryptGenRandom),
74
+
75
+ // 音声 (XAudio2 はコンソールでは xaudio2_9)
76
+ PROBE(XAudio2Create),
77
+
78
+ // リソース (EmbeddedAssets の RCDATA 読み込み)
79
+ PROBE(FindResourceW),
80
+ PROBE(LoadResource),
81
+ PROBE(LockResource),
82
+ PROBE(SizeofResource),
83
+ };
84
+
85
+ #ifdef PROBE_V8
86
+ // ---------------------------------------------------------------------------
87
+ // V8 13.7 の platform 層 (src/base/platform/{platform-win32,time,mutex,
88
+ // condition-variable,semaphore}.cc) から機械抽出した Win32 API。
89
+ // ここのエラー = V8 Game Core 移植時の置換 TODO (情報提供、ジョブは fail しない)。
90
+ // ---------------------------------------------------------------------------
91
+ void* const g_v8_apis[] = {
92
+ // 仮想メモリ (焦点: コンソールでは XMemVirtualAlloc への置換が推奨とされる)
93
+ PROBE(VirtualAlloc),
94
+ PROBE(VirtualFree),
95
+ PROBE(VirtualProtect),
96
+ PROBE(VirtualQuery),
97
+ PROBE(DiscardVirtualMemory),
98
+ // VirtualAlloc2 / MapViewOfFile3 / UnmapViewOfFile2 は V8 が GetProcAddress で
99
+ // 動的解決するが、宣言の有無がパーティション対応の指標になるため含める
100
+ PROBE(VirtualAlloc2),
101
+ PROBE(MapViewOfFile3),
102
+ PROBE(UnmapViewOfFile2),
103
+
104
+ // ファイルマッピング (OS::MemoryMappedFile)
105
+ PROBE(CreateFileMappingW),
106
+ PROBE(CreateFileW),
107
+ PROBE(MapViewOfFile),
108
+ PROBE(MapViewOfFileEx),
109
+ PROBE(UnmapViewOfFile),
110
+ PROBE(GetFileSize),
111
+ PROBE(GetFileType),
112
+ PROBE(DeleteFileA),
113
+ PROBE(GetTempPathA),
114
+ PROBE(GetTempFileNameA),
115
+
116
+ // スレッド / TLS / 同期
117
+ PROBE(GetCurrentThread),
118
+ PROBE(GetCurrentThreadId),
119
+ PROBE(SetThreadPriority),
120
+ PROBE(TlsAlloc),
121
+ PROBE(TlsFree),
122
+ PROBE(TlsGetValue),
123
+ PROBE(TlsSetValue),
124
+ PROBE(CreateSemaphoreA),
125
+ PROBE(ReleaseSemaphore),
126
+ PROBE(WaitForSingleObject),
127
+ PROBE(CloseHandle),
128
+ PROBE(CreateWaitableTimerExW),
129
+ PROBE(SetWaitableTimer),
130
+
131
+ // 時刻 (焦点: timeGetTime は winmm — デスクトップ専用の可能性が高い)
132
+ PROBE(timeGetTime),
133
+ PROBE(GetSystemTime),
134
+ PROBE(GetSystemTimeAsFileTime),
135
+ PROBE(SystemTimeToFileTime),
136
+ PROBE(GetThreadTimes),
137
+ PROBE(GetTimeZoneInformation),
138
+
139
+ // プロセス / システム情報
140
+ PROBE(GetCurrentProcess),
141
+ PROBE(GetCurrentProcessId),
142
+ PROBE(GetSystemInfo),
143
+ PROBE(ExitProcess),
144
+ PROBE(TerminateProcess),
145
+ PROBE(GetProcessMemoryInfo),
146
+ PROBE(SetErrorMode),
147
+ PROBE(GetStdHandle),
148
+
149
+ // 文字列変換 / デバッグ / モジュール
150
+ PROBE(MultiByteToWideChar),
151
+ PROBE(WideCharToMultiByte),
152
+ PROBE(OutputDebugStringA),
153
+ PROBE(DebugBreak),
154
+ PROBE(IsDebuggerPresent),
155
+ PROBE(LoadLibraryW),
156
+ PROBE(GetProcAddress),
157
+ PROBE(VerifyVersionInfoW), // IsWindows10OrGreater (versionhelpers) の実体
158
+
159
+ // CRT (参考: パーティション対象外のはずだが確認)
160
+ reinterpret_cast<void*>(&_beginthreadex),
161
+ };
162
+ #endif // PROBE_V8
163
+
164
+ // リンク不要 (cl /c のみ)。配列が最適化で消えないよう参照を残す。
165
+ void* GameCoreApiProbeAnchor()
166
+ {
167
+ void* acc = const_cast<void**>(g_host_apis)[0];
168
+ #ifdef PROBE_V8
169
+ acc = const_cast<void**>(g_v8_apis)[0];
170
+ #endif
171
+ return acc;
172
+ }