@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
@@ -63,6 +63,22 @@ void EventLoop::ClearTimer(uint32_t id)
63
63
  uint32_t EventLoop::RequestAnimationFrame(v8::Local<v8::Function> callback)
64
64
  {
65
65
  const uint32_t id = next_raf_id_++;
66
+
67
+ // 診断: 誰が rAF を登録しているか (関数名 + minify 後スクリプト位置)。
68
+ // 最初の 40 件 + 以後 300 件毎にサンプルし、凍結後も生きている登録元を特定する。
69
+ static uint64_t reg_count = 0;
70
+ ++reg_count;
71
+ if (reg_count <= 40 || reg_count % 300 == 0) {
72
+ const std::string name =
73
+ v8util::ToStdString(isolate_, callback->GetDebugName());
74
+ v8::Local<v8::Value> res = callback->GetScriptOrigin().ResourceName();
75
+ std::cerr << "[Loop] rAF register #" << reg_count
76
+ << " fn=" << (name.empty() ? "(anon)" : name)
77
+ << " at " << v8util::ToStdString(isolate_, res)
78
+ << ":" << callback->GetScriptLineNumber() + 1
79
+ << ":" << callback->GetScriptColumnNumber() + 1 << std::endl;
80
+ }
81
+
66
82
  v8::Global<v8::Function> global;
67
83
  global.Reset(isolate_, callback);
68
84
  raf_callbacks_.emplace_back(id, std::move(global));
@@ -71,6 +87,16 @@ uint32_t EventLoop::RequestAnimationFrame(v8::Local<v8::Function> callback)
71
87
 
72
88
  void EventLoop::CancelAnimationFrame(uint32_t id)
73
89
  {
90
+ // 未実行分は即座に取り除く。raf_cancelled_ だけに頼ると、RunAnimationFrame の
91
+ // 実行中に登録されたコールバックへのキャンセルが同フレーム末尾の clear で失われ、
92
+ // キャンセル済みのはずの rAF が翌フレームに実行されてしまう (ticker/Tween の多重化)。
93
+ for (auto it = raf_callbacks_.begin(); it != raf_callbacks_.end(); ++it) {
94
+ if (it->first == id) {
95
+ raf_callbacks_.erase(it);
96
+ return;
97
+ }
98
+ }
99
+ // 実行中バッチ (RunAnimationFrame の current) 内のものはフラグで無効化する
74
100
  raf_cancelled_.push_back(id);
75
101
  }
76
102
 
@@ -104,7 +130,7 @@ void EventLoop::PumpTimers()
104
130
 
105
131
  v8::TryCatch try_catch(isolate_);
106
132
  (void) fn->Call(ctx, global, 0, nullptr);
107
- // 例外はコンソールに委譲 (V8Runtime のメッセージハンドラが拾う)
133
+ v8util::ReportCaught(isolate_, &try_catch, "setTimeout/setInterval callback");
108
134
 
109
135
  if (!it->second.repeat) {
110
136
  // 単発は実行後に破棄
@@ -149,6 +175,7 @@ void EventLoop::RunAnimationFrame(double timestamp_ms)
149
175
  v8::TryCatch try_catch(isolate_);
150
176
  v8::Local<v8::Value> args[1] = { arg };
151
177
  (void) fn->Call(ctx, global, 1, args);
178
+ v8util::ReportCaught(isolate_, &try_catch, "requestAnimationFrame callback");
152
179
  }
153
180
 
154
181
  raf_cancelled_.clear();
@@ -50,6 +50,18 @@ public:
50
50
  // 保留タスクが残っているか (終了判定用)
51
51
  bool HasPendingWork() const;
52
52
 
53
+ // 診断用: 登録中の rAF コールバック数 / タイマー数
54
+ size_t PendingAnimationFrameCount() const { return raf_callbacks_.size(); }
55
+ size_t PendingTimerCount() const { return timers_.size(); }
56
+
57
+ // 全タイマー/rAF コールバック (v8::Global) を解放する。Isolate::Dispose より
58
+ // 前に必ず呼ぶこと (スタック巻き戻しでの破棄は Isolate 破棄後になり fail-fast)。
59
+ void Shutdown() {
60
+ timers_.clear();
61
+ raf_callbacks_.clear();
62
+ raf_cancelled_.clear();
63
+ }
64
+
53
65
  private:
54
66
  struct TimerEntry {
55
67
  v8::Global<v8::Function> callback;
@@ -5,6 +5,7 @@
5
5
  #include "EventTarget.h"
6
6
  #include "platform/AudioEngine.h"
7
7
  #include "v8/V8Util.h"
8
+ #include "v8/WeakHandle.h"
8
9
 
9
10
  #include <memory>
10
11
  #include <vector>
@@ -22,11 +23,6 @@ struct GainParam {
22
23
  std::shared_ptr<AudioVoice> voice; // source.connect(gain) 時にリンクされる
23
24
  };
24
25
 
25
- void ReleaseGainParam(const v8::WeakCallbackInfo<GainParam>& info)
26
- {
27
- delete info.GetParameter();
28
- }
29
-
30
26
  // 再生中(非ループ)の source ノード。毎フレーム IsFinished を見て "ended" を発火する。
31
27
  std::vector<v8::Global<v8::Object>>& PlayingSources()
32
28
  {
@@ -40,19 +36,12 @@ struct Holder {
40
36
  std::shared_ptr<T> ptr;
41
37
  };
42
38
 
43
- template <typename T>
44
- void ReleaseHolder(const v8::WeakCallbackInfo<Holder<T>>& info)
45
- {
46
- delete info.GetParameter();
47
- }
48
-
49
39
  template <typename T>
50
40
  void Attach(v8::Isolate* isolate, v8::Local<v8::Object> obj, std::shared_ptr<T> ptr)
51
41
  {
52
42
  auto* holder = new Holder<T>{std::move(ptr)};
53
43
  obj->SetInternalField(0, v8::External::New(isolate, holder));
54
- auto* handle = new v8::Global<v8::Object>(isolate, obj);
55
- handle->SetWeak(holder, ReleaseHolder<T>, v8::WeakCallbackType::kParameter);
44
+ v8util::AttachWeak(isolate, obj, holder);
56
45
  }
57
46
 
58
47
  template <typename T>
@@ -198,8 +187,7 @@ void CreateGain(const v8::FunctionCallbackInfo<v8::Value>& args)
198
187
  v8::Local<v8::Object> gain = InternalTemplate(isolate)->NewInstance(ctx).ToLocalChecked();
199
188
  auto* param = new GainParam();
200
189
  gain->SetInternalField(0, v8::External::New(isolate, param));
201
- auto* handle = new v8::Global<v8::Object>(isolate, gain);
202
- handle->SetWeak(param, ReleaseGainParam, v8::WeakCallbackType::kParameter);
190
+ v8util::AttachWeak(isolate, gain, param);
203
191
 
204
192
  gain->SetNativeDataProperty(ctx, Str(isolate, "value"),
205
193
  [](v8::Local<v8::Name>, const v8::PropertyCallbackInfo<v8::Value>& info) {
@@ -226,6 +214,16 @@ void CreateGain(const v8::FunctionCallbackInfo<v8::Value>& args)
226
214
  // 毎フレーム呼ばれ、再生終了した source ノードへ "ended" を発火する (main.cpp のループから)。
227
215
  } // namespace
228
216
 
217
+ // V8 破棄前に呼ぶ。再生追跡リストの v8::Global を解放する。
218
+ // (放置すると static デストラクタが V8 破棄後に走りアクセス違反になる)
219
+ void ShutdownAudioEvents()
220
+ {
221
+ for (auto& g : PlayingSources()) {
222
+ g.Reset();
223
+ }
224
+ PlayingSources().clear();
225
+ }
226
+
229
227
  void PumpAudioEvents(v8::Isolate* isolate)
230
228
  {
231
229
  auto& sources = PlayingSources();
@@ -285,6 +283,83 @@ void DecodeAudioData(const v8::FunctionCallbackInfo<v8::Value>& args)
285
283
  resolver->Resolve(ctx, buffer).Check();
286
284
  }
287
285
 
286
+ // --- Audio (HTMLAudioElement 最小実装) -------------------------------------
287
+ // ゲームコードが `new Audio(url)` で効果音/BGM を再生する経路。
288
+ // play/pause/paused/loop/volume/currentTime/preload をサポートする。
289
+ // pause→play は先頭からの再生になる (位置レジュームは未対応の近似)。
290
+
291
+ void AudioElementPlay(const v8::FunctionCallbackInfo<v8::Value>& args)
292
+ {
293
+ v8::Isolate* isolate = args.GetIsolate();
294
+ v8::Local<v8::Context> ctx = isolate->GetCurrentContext();
295
+ v8::Local<v8::Object> self = args.This();
296
+
297
+ auto resolver = v8::Promise::Resolver::New(ctx).ToLocalChecked();
298
+ args.GetReturnValue().Set(resolver->GetPromise());
299
+
300
+ auto voice = Get<AudioVoice>(self);
301
+ if (voice) {
302
+ bool loop = false;
303
+ double volume = 1.0;
304
+ v8::Local<v8::Value> v;
305
+ if (self->Get(ctx, Str(isolate, "loop")).ToLocal(&v)) {
306
+ loop = v->BooleanValue(isolate);
307
+ }
308
+ if (self->Get(ctx, Str(isolate, "volume")).ToLocal(&v) && v->IsNumber()) {
309
+ volume = v.As<v8::Number>()->Value();
310
+ }
311
+ voice->SetVolume(static_cast<float>(volume));
312
+ voice->Start(loop);
313
+ }
314
+ v8util::SetValue(isolate, self, "paused", v8::Boolean::New(isolate, false));
315
+ resolver->Resolve(ctx, v8::Undefined(isolate)).Check();
316
+ }
317
+
318
+ void AudioElementPause(const v8::FunctionCallbackInfo<v8::Value>& args)
319
+ {
320
+ v8::Isolate* isolate = args.GetIsolate();
321
+ auto voice = Get<AudioVoice>(args.This());
322
+ if (voice) {
323
+ voice->Stop();
324
+ }
325
+ v8util::SetValue(isolate, args.This(), "paused", v8::Boolean::New(isolate, true));
326
+ }
327
+
328
+ void AudioElementConstructor(const v8::FunctionCallbackInfo<v8::Value>& args)
329
+ {
330
+ v8::Isolate* isolate = args.GetIsolate();
331
+ if (!args.IsConstructCall()) {
332
+ v8util::ThrowTypeError(isolate, "Audio must be called with new");
333
+ return;
334
+ }
335
+ v8::Local<v8::Object> self = args.This();
336
+ Attach<AudioVoice>(isolate, self, nullptr);
337
+
338
+ // URL 指定があれば同期でロード+デコードして voice を作る (失敗時は無音の no-op)
339
+ if (args.Length() > 0 && args[0]->IsString()) {
340
+ const std::string src = ToStdString(isolate, args[0]);
341
+ v8util::SetValue(isolate, self, "src", args[0]);
342
+ HostContext* host = HostContext::From(isolate);
343
+ auto bytes = host->assets->ReadBinary(src);
344
+ if (bytes) {
345
+ auto pcm = std::make_shared<PcmBuffer>();
346
+ if (AudioEngine::Decode(*bytes, *pcm)) {
347
+ Attach<AudioVoice>(isolate, self, host->audio->CreateVoice(pcm));
348
+ }
349
+ }
350
+ }
351
+
352
+ v8util::SetValue(isolate, self, "loop", v8::Boolean::New(isolate, false));
353
+ v8util::SetValue(isolate, self, "volume", v8::Number::New(isolate, 1.0));
354
+ v8util::SetValue(isolate, self, "paused", v8::Boolean::New(isolate, true));
355
+ v8util::SetValue(isolate, self, "preload", Str(isolate, "auto"));
356
+ v8util::SetValue(isolate, self, "currentTime", v8::Number::New(isolate, 0));
357
+ v8util::SetMethod(isolate, self, "play", AudioElementPlay);
358
+ v8util::SetMethod(isolate, self, "pause", AudioElementPause);
359
+ InstallEventTarget(isolate, self);
360
+ args.GetReturnValue().Set(self);
361
+ }
362
+
288
363
  // AudioContext コンストラクタ
289
364
  void AudioContextConstructor(const v8::FunctionCallbackInfo<v8::Value>& args)
290
365
  {
@@ -315,6 +390,15 @@ void InstallAudio(v8::Isolate* isolate, v8::Local<v8::Object> global, HostContex
315
390
 
316
391
  global->Set(ctx, Str(isolate, "AudioContext"), fn).Check();
317
392
  global->Set(ctx, Str(isolate, "webkitAudioContext"), fn).Check();
393
+
394
+ // Audio (HTMLAudioElement 最小): ゲームコードの `new Audio(url)` 用。
395
+ // インスタンスは内部フィールドに AudioVoice ホルダを持つ。
396
+ v8::Local<v8::FunctionTemplate> audio_tmpl =
397
+ v8::FunctionTemplate::New(isolate, AudioElementConstructor);
398
+ audio_tmpl->SetClassName(Str(isolate, "Audio"));
399
+ audio_tmpl->InstanceTemplate()->SetInternalFieldCount(1);
400
+ global->Set(ctx, Str(isolate, "Audio"),
401
+ audio_tmpl->GetFunction(ctx).ToLocalChecked()).Check();
318
402
  }
319
403
 
320
404
  } // namespace next2d
@@ -41,6 +41,12 @@ void InstallAudio(v8::Isolate* isolate, v8::Local<v8::Object> global, HostContex
41
41
  // 再生終了した AudioBufferSourceNode へ "ended" を発火する。main.cpp のループから毎フレーム呼ぶ。
42
42
  void PumpAudioEvents(v8::Isolate* isolate);
43
43
 
44
+ // V8 破棄前に呼ぶ。音声の再生追跡リスト (v8::Global) を解放する。
45
+ void ShutdownAudioEvents();
46
+
47
+ // V8 破棄前に呼ぶ。WebGPU テンプレートの v8::Global (static) を解放する。
48
+ void ShutdownWebGPU();
49
+
44
50
  // window / document / navigator / screen 等の DOM 相当スタブと canvas 生成
45
51
  void InstallDomShims(v8::Isolate* isolate, v8::Local<v8::Object> global, HostContext* host);
46
52
 
@@ -69,6 +75,10 @@ bool GetVideoFramePixels(v8::Isolate* isolate, v8::Local<v8::Object> obj,
69
75
  // blob: URL のバイト列をテキストとして取得する (Network.cpp)。
70
76
  bool ResolveObjectURL(const std::string& url, std::string* out_text);
71
77
 
78
+ // TextEncoder/TextDecoder/queueMicrotask 等の JS ポリフィルを評価する (Polyfills.cpp)。
79
+ // bootstrap.js はメインのみのため、worker を含む全コンテキストにはこちらで注入する。
80
+ void InstallPolyfills(v8::Isolate* isolate, v8::Local<v8::Context> context);
81
+
72
82
  // 上記をまとめてインストールする
73
83
  void InstallGlobalBindings(v8::Isolate* isolate, v8::Local<v8::Object> global, HostContext* host);
74
84
 
@@ -135,6 +135,9 @@ v8::Local<v8::Object> CreateCanvasElement(v8::Isolate* isolate, HostContext* hos
135
135
  int width, int height)
136
136
  {
137
137
  v8::Local<v8::Object> canvas = v8::Object::New(isolate);
138
+ // player の canvasSetPositionService は children[0].localName === "canvas" を確認する
139
+ SetValue(isolate, canvas, "tagName", Str(isolate, "CANVAS"));
140
+ SetValue(isolate, canvas, "localName", Str(isolate, "canvas"));
138
141
  SetValue(isolate, canvas, "width",
139
142
  v8::Integer::New(isolate, width ? width : host->viewport_width));
140
143
  SetValue(isolate, canvas, "height",
@@ -146,6 +149,48 @@ v8::Local<v8::Object> CreateCanvasElement(v8::Isolate* isolate, HostContext* hos
146
149
  SetMethod(isolate, canvas, "transferControlToOffscreen", TransferControlToOffscreen);
147
150
  InstallEventTarget(isolate, canvas); // pointer/wheel リスナを保持
148
151
  SetMethod(isolate, canvas, "getBoundingClientRect", GetBoundingClientRect);
152
+
153
+ // player の CanvasInitializeService が canvas.setAttribute("style", ...) を呼ぶ。
154
+ // 属性は __attrs に保存する (見た目の CSS はネイティブ描画では不要)。
155
+ SetMethod(isolate, canvas, "setAttribute",
156
+ [](const v8::FunctionCallbackInfo<v8::Value>& a) {
157
+ if (a.Length() < 2) return;
158
+ v8::Isolate* iso = a.GetIsolate();
159
+ v8::Local<v8::Context> c = iso->GetCurrentContext();
160
+ v8::Local<v8::Value> attrs;
161
+ v8::Local<v8::Object> store;
162
+ if (a.This()->Get(c, v8util::Str(iso, "__attrs")).ToLocal(&attrs) && attrs->IsObject()) {
163
+ store = attrs.As<v8::Object>();
164
+ } else {
165
+ store = v8::Object::New(iso);
166
+ v8util::SetValue(iso, a.This(), "__attrs", store);
167
+ }
168
+ store->Set(c, a[0], a[1]).Check();
169
+ });
170
+ SetMethod(isolate, canvas, "getAttribute",
171
+ [](const v8::FunctionCallbackInfo<v8::Value>& a) {
172
+ a.GetReturnValue().SetNull();
173
+ if (a.Length() < 1) return;
174
+ v8::Isolate* iso = a.GetIsolate();
175
+ v8::Local<v8::Context> c = iso->GetCurrentContext();
176
+ v8::Local<v8::Value> attrs;
177
+ if (a.This()->Get(c, v8util::Str(iso, "__attrs")).ToLocal(&attrs) && attrs->IsObject()) {
178
+ v8::Local<v8::Value> v;
179
+ if (attrs.As<v8::Object>()->Get(c, a[0]).ToLocal(&v) && !v->IsUndefined()) {
180
+ a.GetReturnValue().Set(v);
181
+ }
182
+ }
183
+ });
184
+ SetMethod(isolate, canvas, "removeAttribute",
185
+ [](const v8::FunctionCallbackInfo<v8::Value>& a) {
186
+ if (a.Length() < 1) return;
187
+ v8::Isolate* iso = a.GetIsolate();
188
+ v8::Local<v8::Context> c = iso->GetCurrentContext();
189
+ v8::Local<v8::Value> attrs;
190
+ if (a.This()->Get(c, v8util::Str(iso, "__attrs")).ToLocal(&attrs) && attrs->IsObject()) {
191
+ (void) attrs.As<v8::Object>()->Delete(c, a[0]);
192
+ }
193
+ });
149
194
  // 主要 canvas を HostContext から参照できるよう記録 (WndProc の入力配送先)
150
195
  HostContext::From(isolate)->main_canvas.Reset(isolate, canvas);
151
196
  return canvas;
@@ -11,6 +11,7 @@
11
11
  #include "RasterCore.h"
12
12
  #include "platform/TextRasterizer.h"
13
13
  #include "v8/V8Util.h"
14
+ #include "v8/WeakHandle.h"
14
15
 
15
16
  #include <Windows.h>
16
17
 
@@ -352,9 +353,6 @@ void GetImageData(const v8::FunctionCallbackInfo<v8::Value>& a)
352
353
  a.GetReturnValue().Set(image_data);
353
354
  }
354
355
 
355
- // GC 時に Canvas2D 実体を解放
356
- void ReleaseCanvas2D(const v8::WeakCallbackInfo<Canvas2D>& info) { delete info.GetParameter(); }
357
-
358
356
  // fillStyle / strokeStyle / lineWidth / globalAlpha / font アクセサ
359
357
  void InstallAccessors(v8::Isolate* isolate, v8::Local<v8::Object> obj)
360
358
  {
@@ -424,8 +422,7 @@ v8::Local<v8::Object> CreateCanvas2DContext(v8::Isolate* isolate, HostContext* /
424
422
 
425
423
  auto* impl = new Canvas2D(width > 0 ? width : 1, height > 0 ? height : 1);
426
424
  obj->SetInternalField(0, v8::External::New(isolate, impl));
427
- auto* handle = new v8::Global<v8::Object>(isolate, obj);
428
- handle->SetWeak(impl, ReleaseCanvas2D, v8::WeakCallbackType::kParameter);
425
+ v8util::AttachWeak(isolate, obj, impl);
429
426
 
430
427
  SetMethod(isolate, obj, "beginPath", BeginPath);
431
428
  SetMethod(isolate, obj, "moveTo", MoveTo);
@@ -5,6 +5,10 @@
5
5
  #include "v8/V8Util.h"
6
6
 
7
7
  #include <Windows.h>
8
+ #include <bcrypt.h>
9
+ #include <cstdio>
10
+
11
+ #pragma comment(lib, "bcrypt.lib")
8
12
 
9
13
  namespace next2d {
10
14
 
@@ -107,6 +111,109 @@ void ClipboardWriteText(const v8::FunctionCallbackInfo<v8::Value>& args)
107
111
  resolver->Resolve(ctx, v8::Undefined(isolate)).Check();
108
112
  }
109
113
 
114
+ // crypto.getRandomValues(typedArray): OS 乱数で埋めて同じ view を返す
115
+ void CryptoGetRandomValues(const v8::FunctionCallbackInfo<v8::Value>& args)
116
+ {
117
+ v8::Isolate* isolate = args.GetIsolate();
118
+ if (args.Length() < 1 || !args[0]->IsArrayBufferView()) {
119
+ v8util::ThrowTypeError(isolate, "getRandomValues requires a TypedArray");
120
+ return;
121
+ }
122
+ auto view = args[0].As<v8::ArrayBufferView>();
123
+ std::vector<uint8_t> bytes(view->ByteLength());
124
+ if (!bytes.empty()) {
125
+ BCryptGenRandom(nullptr, bytes.data(), static_cast<ULONG>(bytes.size()),
126
+ BCRYPT_USE_SYSTEM_PREFERRED_RNG);
127
+ // view のバッキングへ書き戻す
128
+ auto ab = view->Buffer();
129
+ auto* dst = static_cast<uint8_t*>(ab->GetBackingStore()->Data()) + view->ByteOffset();
130
+ memcpy(dst, bytes.data(), bytes.size());
131
+ }
132
+ args.GetReturnValue().Set(args[0]);
133
+ }
134
+
135
+ // crypto.randomUUID(): RFC 4122 v4
136
+ void CryptoRandomUUID(const v8::FunctionCallbackInfo<v8::Value>& args)
137
+ {
138
+ v8::Isolate* isolate = args.GetIsolate();
139
+ uint8_t b[16] = {};
140
+ BCryptGenRandom(nullptr, b, sizeof(b), BCRYPT_USE_SYSTEM_PREFERRED_RNG);
141
+ b[6] = static_cast<uint8_t>((b[6] & 0x0f) | 0x40); // version 4
142
+ b[8] = static_cast<uint8_t>((b[8] & 0x3f) | 0x80); // variant
143
+ char buf[37];
144
+ std::snprintf(buf, sizeof(buf),
145
+ "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x",
146
+ b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7],
147
+ b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15]);
148
+ args.GetReturnValue().Set(Str(isolate, buf));
149
+ }
150
+
151
+ // --- 名前付きストレージの永続化バックエンド ---------------------------------
152
+ // localStorage / indexedDB のセマンティクスは Polyfills.cpp の JS 側が実装し、
153
+ // ここは名前ごとのファイル I/O のみを担う。
154
+ // 保存先: %LOCALAPPDATA%\Next2D\<name>.json
155
+ // «EXTEND» コンソール実機では XGameSave への置き換えが必要 (devkit 後の作業)。
156
+ std::wstring StorageFilePath(const std::string& name)
157
+ {
158
+ // ファイル名として安全な文字だけを残す
159
+ std::wstring safe;
160
+ for (char c : name) {
161
+ const bool ok = (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
162
+ (c >= '0' && c <= '9') || c == '_' || c == '-';
163
+ safe.push_back(ok ? static_cast<wchar_t>(c) : L'_');
164
+ }
165
+ if (safe.empty()) {
166
+ safe = L"storage";
167
+ }
168
+ wchar_t base[MAX_PATH] = {};
169
+ if (!GetEnvironmentVariableW(L"LOCALAPPDATA", base, MAX_PATH)) {
170
+ return safe + L".json"; // フォールバック: カレント
171
+ }
172
+ std::wstring dir = std::wstring(base) + L"\\Next2D";
173
+ CreateDirectoryW(dir.c_str(), nullptr);
174
+ return dir + L"\\" + safe + L".json";
175
+ }
176
+
177
+ // __next2d_storage_load(name) -> string
178
+ void StorageLoad(const v8::FunctionCallbackInfo<v8::Value>& args)
179
+ {
180
+ v8::Isolate* isolate = args.GetIsolate();
181
+ const std::string name = args.Length() > 0
182
+ ? v8util::ToStdString(isolate, args[0]) : "localStorage";
183
+ HANDLE h = CreateFileW(StorageFilePath(name).c_str(), GENERIC_READ, FILE_SHARE_READ,
184
+ nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
185
+ if (h == INVALID_HANDLE_VALUE) {
186
+ args.GetReturnValue().Set(Str(isolate, ""));
187
+ return;
188
+ }
189
+ DWORD size = GetFileSize(h, nullptr);
190
+ std::string data(size, '\0');
191
+ DWORD read = 0;
192
+ ReadFile(h, data.data(), size, &read, nullptr);
193
+ CloseHandle(h);
194
+ data.resize(read);
195
+ args.GetReturnValue().Set(Str(isolate, data));
196
+ }
197
+
198
+ // __next2d_storage_save(name, data)
199
+ void StorageSave(const v8::FunctionCallbackInfo<v8::Value>& args)
200
+ {
201
+ if (args.Length() < 2) {
202
+ return;
203
+ }
204
+ v8::Isolate* isolate = args.GetIsolate();
205
+ const std::string name = v8util::ToStdString(isolate, args[0]);
206
+ const std::string data = v8util::ToStdString(isolate, args[1]);
207
+ HANDLE h = CreateFileW(StorageFilePath(name).c_str(), GENERIC_WRITE, 0,
208
+ nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
209
+ if (h == INVALID_HANDLE_VALUE) {
210
+ return;
211
+ }
212
+ DWORD written = 0;
213
+ WriteFile(h, data.data(), static_cast<DWORD>(data.size()), &written, nullptr);
214
+ CloseHandle(h);
215
+ }
216
+
110
217
  // new OffscreenCanvas(width, height)
111
218
  void OffscreenCanvasConstructor(const v8::FunctionCallbackInfo<v8::Value>& args)
112
219
  {
@@ -144,6 +251,19 @@ void InstallDomShims(v8::Isolate* isolate, v8::Local<v8::Object> global, HostCon
144
251
  SetValue(isolate, global, "self", global);
145
252
  NoopEventTarget(isolate, global);
146
253
 
254
+ // crypto (getRandomValues / randomUUID — OS 乱数)。
255
+ // player の WebGLUtil が ID 生成に crypto.randomUUID を使う。
256
+ {
257
+ v8::Local<v8::Object> crypto = v8::Object::New(isolate);
258
+ SetMethod(isolate, crypto, "getRandomValues", CryptoGetRandomValues);
259
+ SetMethod(isolate, crypto, "randomUUID", CryptoRandomUUID);
260
+ SetValue(isolate, global, "crypto", crypto);
261
+ }
262
+
263
+ // localStorage のファイル I/O バックエンド (Polyfills.cpp の JS が利用)
264
+ SetMethod(isolate, global, "__next2d_storage_load", StorageLoad);
265
+ SetMethod(isolate, global, "__next2d_storage_save", StorageSave);
266
+
147
267
  // devicePixelRatio
148
268
  SetValue(isolate, global, "devicePixelRatio",
149
269
  v8::Number::New(isolate, host->device_pixel_ratio));
@@ -176,6 +296,8 @@ void InstallDomShims(v8::Isolate* isolate, v8::Local<v8::Object> global, HostCon
176
296
  // document
177
297
  v8::Local<v8::Object> document = v8::Object::New(isolate);
178
298
  SetMethod(isolate, document, "createElement", CreateElement);
299
+ // 起動時点で読み込み完了扱い (ゲームの boot 分岐が readyState === "loading" で待機しないように)
300
+ SetValue(isolate, document, "readyState", Str(isolate, "complete"));
179
301
  SetMethod(isolate, document, "getElementById",
180
302
  [](const v8::FunctionCallbackInfo<v8::Value>& a) { a.GetReturnValue().SetNull(); });
181
303
  SetMethod(isolate, document, "querySelector",
@@ -216,6 +338,11 @@ void InstallGlobalBindings(v8::Isolate* isolate, v8::Local<v8::Object> global, H
216
338
  InstallAudio(isolate, global, host);
217
339
  InstallWebGPU(isolate, global, host); // navigator.gpu
218
340
  InstallGamepad(isolate, global, host); // navigator.getGamepads
341
+
342
+ // TextEncoder/TextDecoder/queueMicrotask。bootstrap.js はメインコンテキスト
343
+ // でのみ実行されるため、worker を含む全コンテキストにはここで注入する
344
+ // (Vite バンドルの worker は評価時に TextDecoder を要求する)。
345
+ InstallPolyfills(isolate, isolate->GetCurrentContext());
219
346
  }
220
347
 
221
348
  } // namespace next2d
@@ -104,6 +104,7 @@ void DispatchEvent(v8::Isolate* isolate, v8::Local<v8::Object> target,
104
104
  if (target->Get(ctx, Str(isolate, onname.c_str())).ToLocal(&on) && on->IsFunction()) {
105
105
  v8::TryCatch tc(isolate);
106
106
  (void) on.As<v8::Function>()->Call(ctx, target, 1, args1);
107
+ v8util::ReportCaught(isolate, &tc, onname.c_str());
107
108
  }
108
109
 
109
110
  // __listeners[type]
@@ -118,6 +119,7 @@ void DispatchEvent(v8::Isolate* isolate, v8::Local<v8::Object> target,
118
119
  if (arr->Get(ctx, i).ToLocal(&fn) && fn->IsFunction()) {
119
120
  v8::TryCatch tc(isolate);
120
121
  (void) fn.As<v8::Function>()->Call(ctx, target, 1, args1);
122
+ v8util::ReportCaught(isolate, &tc, ("listener:" + type).c_str());
121
123
  }
122
124
  }
123
125
  }
@@ -3,6 +3,7 @@
3
3
  #include "HostContext.h"
4
4
  #include "AssetLoader.h"
5
5
  #include "v8/V8Util.h"
6
+ #include "v8/WeakHandle.h"
6
7
 
7
8
  #include <cstring>
8
9
  #include <vector>
@@ -84,12 +85,6 @@ void ResponseJson(const v8::FunctionCallbackInfo<v8::Value>& args)
84
85
  args.GetReturnValue().Set(resolver->GetPromise());
85
86
  }
86
87
 
87
- // External に持たせた vector を解放するための弱参照コールバック
88
- void ReleaseResponseData(const v8::WeakCallbackInfo<std::vector<uint8_t>>& info)
89
- {
90
- delete info.GetParameter();
91
- }
92
-
93
88
  v8::Local<v8::Object> MakeResponse(v8::Isolate* isolate, v8::Local<v8::Context> ctx,
94
89
  std::vector<uint8_t>&& bytes, bool ok, int status)
95
90
  {
@@ -101,8 +96,7 @@ v8::Local<v8::Object> MakeResponse(v8::Isolate* isolate, v8::Local<v8::Context>
101
96
  response->SetInternalField(0, v8::External::New(isolate, data));
102
97
 
103
98
  // GC 時に解放
104
- auto* handle = new v8::Global<v8::Object>(isolate, response);
105
- handle->SetWeak(data, ReleaseResponseData, v8::WeakCallbackType::kParameter);
99
+ v8util::AttachWeak(isolate, response, data);
106
100
 
107
101
  v8util::SetValue(isolate, response, "ok", v8::Boolean::New(isolate, ok));
108
102
  v8util::SetValue(isolate, response, "status", v8::Integer::New(isolate, status));