@next2d/builder 0.1.5 → 0.1.6

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 (55) hide show
  1. package/README.md +11 -0
  2. package/dist/index.js +247 -3
  3. package/package.json +10 -8
  4. package/templates/xbox/CMakeLists.txt +121 -0
  5. package/templates/xbox/MicrosoftGame.config +36 -0
  6. package/templates/xbox/README.md +319 -0
  7. package/templates/xbox/cmake/FetchDawn.cmake +42 -0
  8. package/templates/xbox/cmake/FindV8.cmake +62 -0
  9. package/templates/xbox/js/bootstrap.js +143 -0
  10. package/templates/xbox/js/selftest.js +515 -0
  11. package/templates/xbox/src/AssetLoader.cpp +106 -0
  12. package/templates/xbox/src/AssetLoader.h +35 -0
  13. package/templates/xbox/src/EventLoop.cpp +162 -0
  14. package/templates/xbox/src/EventLoop.h +73 -0
  15. package/templates/xbox/src/HostContext.h +58 -0
  16. package/templates/xbox/src/bindings/Audio.cpp +320 -0
  17. package/templates/xbox/src/bindings/Bindings.h +75 -0
  18. package/templates/xbox/src/bindings/Canvas.cpp +171 -0
  19. package/templates/xbox/src/bindings/Canvas2D.cpp +462 -0
  20. package/templates/xbox/src/bindings/Console.cpp +52 -0
  21. package/templates/xbox/src/bindings/DomShims.cpp +221 -0
  22. package/templates/xbox/src/bindings/EventTarget.cpp +127 -0
  23. package/templates/xbox/src/bindings/EventTarget.h +17 -0
  24. package/templates/xbox/src/bindings/Fetch.cpp +149 -0
  25. package/templates/xbox/src/bindings/Gamepad.cpp +79 -0
  26. package/templates/xbox/src/bindings/Image.cpp +160 -0
  27. package/templates/xbox/src/bindings/ImageSource.h +26 -0
  28. package/templates/xbox/src/bindings/Network.cpp +303 -0
  29. package/templates/xbox/src/bindings/RasterCore.h +265 -0
  30. package/templates/xbox/src/bindings/Timers.cpp +87 -0
  31. package/templates/xbox/src/bindings/Video.cpp +235 -0
  32. package/templates/xbox/src/bindings/webgpu/WebGPU.cpp +1318 -0
  33. package/templates/xbox/src/bindings/webgpu/WebGPUCommon.h +119 -0
  34. package/templates/xbox/src/bindings/webgpu/WebGPUEnums.h +245 -0
  35. package/templates/xbox/src/gpu/DawnContext.cpp +170 -0
  36. package/templates/xbox/src/gpu/DawnContext.h +64 -0
  37. package/templates/xbox/src/main.cpp +368 -0
  38. package/templates/xbox/src/platform/AudioEngine.cpp +184 -0
  39. package/templates/xbox/src/platform/AudioEngine.h +65 -0
  40. package/templates/xbox/src/platform/GamepadManager.cpp +83 -0
  41. package/templates/xbox/src/platform/GamepadManager.h +40 -0
  42. package/templates/xbox/src/platform/ImageTypes.h +17 -0
  43. package/templates/xbox/src/platform/TextRasterizer.cpp +183 -0
  44. package/templates/xbox/src/platform/TextRasterizer.h +35 -0
  45. package/templates/xbox/src/platform/WicDecoder.cpp +63 -0
  46. package/templates/xbox/src/platform/WicDecoder.h +16 -0
  47. package/templates/xbox/src/v8/V8Runtime.cpp +284 -0
  48. package/templates/xbox/src/v8/V8Runtime.h +68 -0
  49. package/templates/xbox/src/v8/V8Util.h +56 -0
  50. package/templates/xbox/src/worker/WorkerRuntime.cpp +453 -0
  51. package/templates/xbox/src/worker/WorkerRuntime.h +108 -0
  52. package/templates/xbox/tests/check_local.sh +56 -0
  53. package/templates/xbox/tests/raster_test.bin +0 -0
  54. package/templates/xbox/tests/raster_test.cpp +279 -0
  55. package/templates/xbox/tests/windows_platform_test.cpp +187 -0
@@ -0,0 +1,68 @@
1
+ // V8Runtime: V8 プラットフォーム / Isolate / Context のライフサイクルを管理し、
2
+ // スクリプト(クラシック/ESM)の評価とマイクロタスクのポンプを提供する。
3
+ #pragma once
4
+
5
+ #include <v8.h>
6
+ #include <libplatform/libplatform.h>
7
+
8
+ #include <map>
9
+ #include <memory>
10
+ #include <string>
11
+
12
+ namespace next2d {
13
+
14
+ class HostContext;
15
+
16
+ class V8Runtime {
17
+ public:
18
+ V8Runtime();
19
+ ~V8Runtime();
20
+
21
+ // process 全体で一度だけ呼ぶ (argv[0] を渡す)
22
+ static void InitializeProcess(const char* exec_path);
23
+ static void ShutdownProcess();
24
+
25
+ // Isolate + Context を生成し、HostContext を data slot に紐付ける
26
+ bool Initialize(HostContext* host);
27
+ void Dispose();
28
+
29
+ v8::Isolate* isolate() const { return isolate_; }
30
+ v8::Local<v8::Context> context() const;
31
+
32
+ // クラシックスクリプトを評価 (bootstrap.js 等)。失敗時 false。
33
+ bool RunScript(const std::string& source, const std::string& name);
34
+
35
+ // ES モジュールを評価 (Next2D アプリ本体)。base_dir は相対 import 解決用。
36
+ bool RunModule(const std::string& source, const std::string& path);
37
+
38
+ // Promise 等のマイクロタスクを処理する (タスク実行の直後に呼ぶ)
39
+ void PumpMicrotasks();
40
+
41
+ // 保留中のフォアグラウンドタスク(V8 プラットフォーム)を処理する
42
+ void PumpPlatformTasks();
43
+
44
+ private:
45
+ // ESM 解決
46
+ v8::MaybeLocal<v8::Module> LoadModule(const std::string& path, const std::string& referrer_dir);
47
+ static v8::MaybeLocal<v8::Module> ResolveModuleCallback(
48
+ v8::Local<v8::Context> context,
49
+ v8::Local<v8::String> specifier,
50
+ v8::Local<v8::FixedArray> import_assertions,
51
+ v8::Local<v8::Module> referrer);
52
+
53
+ void ReportException(v8::TryCatch* try_catch);
54
+
55
+ static std::unique_ptr<v8::Platform> platform_;
56
+
57
+ v8::Isolate* isolate_ = nullptr;
58
+ v8::Isolate::CreateParams create_params_;
59
+ v8::Global<v8::Context> context_;
60
+ HostContext* host_ = nullptr;
61
+
62
+ // ESM キャッシュ: 絶対パス -> Module。resolve コールバックから参照する。
63
+ std::map<std::string, v8::Global<v8::Module>> module_cache_;
64
+ // Module identity hash -> 絶対パス (相対 import 解決の referrer 特定に使用)
65
+ std::map<int, std::string> module_paths_;
66
+ };
67
+
68
+ } // namespace next2d
@@ -0,0 +1,56 @@
1
+ // V8 バインディング用の小さなヘルパー群。
2
+ #pragma once
3
+
4
+ #include <v8.h>
5
+ #include <string>
6
+ #include <string_view>
7
+
8
+ namespace next2d::v8util {
9
+
10
+ // std::string -> v8::String (UTF-8)
11
+ inline v8::Local<v8::String> Str(v8::Isolate* isolate, std::string_view s)
12
+ {
13
+ return v8::String::NewFromUtf8(
14
+ isolate, s.data(), v8::NewStringType::kNormal,
15
+ static_cast<int>(s.size())
16
+ ).ToLocalChecked();
17
+ }
18
+
19
+ // v8::Value -> std::string (UTF-8)
20
+ inline std::string ToStdString(v8::Isolate* isolate, v8::Local<v8::Value> value)
21
+ {
22
+ v8::String::Utf8Value utf8(isolate, value);
23
+ return (*utf8) ? std::string(*utf8, utf8.length()) : std::string();
24
+ }
25
+
26
+ // object[name] = fn
27
+ inline void SetMethod(v8::Isolate* isolate,
28
+ v8::Local<v8::Object> target,
29
+ std::string_view name,
30
+ v8::FunctionCallback callback,
31
+ v8::Local<v8::Value> data = v8::Local<v8::Value>())
32
+ {
33
+ v8::Local<v8::Context> ctx = isolate->GetCurrentContext();
34
+ v8::Local<v8::Function> fn =
35
+ v8::Function::New(ctx, callback, data).ToLocalChecked();
36
+ fn->SetName(Str(isolate, name));
37
+ target->Set(ctx, Str(isolate, name), fn).Check();
38
+ }
39
+
40
+ // object[name] = value
41
+ inline void SetValue(v8::Isolate* isolate,
42
+ v8::Local<v8::Object> target,
43
+ std::string_view name,
44
+ v8::Local<v8::Value> value)
45
+ {
46
+ v8::Local<v8::Context> ctx = isolate->GetCurrentContext();
47
+ target->Set(ctx, Str(isolate, name), value).Check();
48
+ }
49
+
50
+ // TypeError を投げる (バインディング内の引数検証で使用)
51
+ inline void ThrowTypeError(v8::Isolate* isolate, std::string_view message)
52
+ {
53
+ isolate->ThrowException(v8::Exception::TypeError(Str(isolate, message)));
54
+ }
55
+
56
+ } // namespace next2d::v8util
@@ -0,0 +1,453 @@
1
+ #include "WorkerRuntime.h"
2
+
3
+ #include "HostContext.h"
4
+ #include "EventLoop.h"
5
+ #include "AssetLoader.h"
6
+ #include "bindings/Bindings.h"
7
+ #include "v8/V8Util.h"
8
+
9
+ #include <cstdlib>
10
+ #include <cstring>
11
+ #include <iostream>
12
+
13
+ namespace next2d {
14
+
15
+ using v8util::Str;
16
+ using v8util::SetMethod;
17
+ using v8util::SetValue;
18
+ using v8util::ToStdString;
19
+
20
+ // ===========================================================================
21
+ // structured clone (ValueSerializer / ValueDeserializer)
22
+ // ===========================================================================
23
+ namespace {
24
+
25
+ bool IsOffscreen(v8::Isolate* isolate, v8::Local<v8::Object> obj)
26
+ {
27
+ v8::Local<v8::Context> ctx = isolate->GetCurrentContext();
28
+ v8::Local<v8::Value> v;
29
+ return obj->Get(ctx, Str(isolate, "__isOffscreenCanvas")).ToLocal(&v) &&
30
+ v->IsBoolean() && v.As<v8::Boolean>()->Value();
31
+ }
32
+
33
+ class SerializerDelegate : public v8::ValueSerializer::Delegate {
34
+ public:
35
+ explicit SerializerDelegate(v8::Isolate* isolate) : isolate_(isolate) {}
36
+
37
+ void ThrowDataCloneError(v8::Local<v8::String> message) override
38
+ {
39
+ isolate_->ThrowException(v8::Exception::Error(message));
40
+ }
41
+
42
+ // OffscreenCanvas をホストオブジェクトとして扱い WriteHostObject に回す。
43
+ v8::Maybe<bool> IsHostObject(v8::Isolate*, v8::Local<v8::Object> object) override
44
+ {
45
+ return v8::Just(IsOffscreen(isolate_, object));
46
+ }
47
+
48
+ v8::Maybe<bool> WriteHostObject(v8::Isolate*, v8::Local<v8::Object> object) override
49
+ {
50
+ 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);
54
+ serializer->WriteUint32(w->IsNumber() ? static_cast<uint32_t>(w.As<v8::Number>()->Value()) : 0);
55
+ serializer->WriteUint32(h->IsNumber() ? static_cast<uint32_t>(h.As<v8::Number>()->Value()) : 0);
56
+ return v8::Just(true);
57
+ }
58
+
59
+ v8::ValueSerializer* serializer = nullptr;
60
+
61
+ private:
62
+ v8::Isolate* isolate_;
63
+ };
64
+
65
+ class DeserializerDelegate : public v8::ValueDeserializer::Delegate {
66
+ public:
67
+ DeserializerDelegate(v8::Isolate* isolate, HostContext* host)
68
+ : isolate_(isolate), host_(host) {}
69
+
70
+ v8::MaybeLocal<v8::Object> ReadHostObject(v8::Isolate*) override
71
+ {
72
+ uint32_t w = 0, h = 0;
73
+ deserializer->ReadUint32(&w);
74
+ deserializer->ReadUint32(&h);
75
+ return CreateOffscreenCanvas(isolate_, host_, static_cast<int>(w), static_cast<int>(h));
76
+ }
77
+
78
+ v8::ValueDeserializer* deserializer = nullptr;
79
+
80
+ private:
81
+ v8::Isolate* isolate_;
82
+ HostContext* host_;
83
+ };
84
+
85
+ } // namespace
86
+
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*/)
90
+ {
91
+ v8::Context::Scope scope(source_ctx);
92
+ SerializerDelegate delegate(isolate);
93
+ v8::ValueSerializer serializer(isolate, &delegate);
94
+ delegate.serializer = &serializer;
95
+
96
+ serializer.WriteHeader();
97
+ // 通常の ArrayBuffer は値としてシリアライズされる (transfer は将来最適化)。
98
+ if (serializer.WriteValue(source_ctx, message).IsNothing()) {
99
+ return {};
100
+ }
101
+ std::pair<uint8_t*, size_t> buffer = serializer.Release();
102
+ std::vector<uint8_t> bytes(buffer.first, buffer.first + buffer.second);
103
+ free(buffer.first);
104
+ return bytes;
105
+ }
106
+
107
+ v8::MaybeLocal<v8::Value> DeserializeMessage(v8::Isolate* isolate,
108
+ v8::Local<v8::Context> target_ctx,
109
+ const std::vector<uint8_t>& bytes)
110
+ {
111
+ v8::Context::Scope scope(target_ctx);
112
+ HostContext* host = HostContext::From(isolate);
113
+ DeserializerDelegate delegate(isolate, host);
114
+ v8::ValueDeserializer deserializer(isolate, bytes.data(), bytes.size(), &delegate);
115
+ delegate.deserializer = &deserializer;
116
+
117
+ if (deserializer.ReadHeader(target_ctx).IsNothing()) {
118
+ return v8::MaybeLocal<v8::Value>();
119
+ }
120
+ return deserializer.ReadValue(target_ctx);
121
+ }
122
+
123
+ // ===========================================================================
124
+ // メッセージ配送ヘルパー
125
+ // ===========================================================================
126
+ namespace {
127
+
128
+ // target(self / worker) の onmessage と addEventListener('message') 登録分へ配送する。
129
+ void DispatchMessage(v8::Isolate* isolate, v8::Local<v8::Context> ctx,
130
+ v8::Local<v8::Object> target, v8::Local<v8::Value> data)
131
+ {
132
+ v8::Context::Scope scope(ctx);
133
+ v8::Local<v8::Object> event = v8::Object::New(isolate);
134
+ SetValue(isolate, event, "data", data);
135
+ SetValue(isolate, event, "type", Str(isolate, "message"));
136
+ v8::Local<v8::Value> args[1] = { event };
137
+
138
+ // onmessage プロパティ
139
+ v8::Local<v8::Value> on;
140
+ if (target->Get(ctx, Str(isolate, "onmessage")).ToLocal(&on) && on->IsFunction()) {
141
+ v8::TryCatch tc(isolate);
142
+ (void) on.As<v8::Function>()->Call(ctx, target, 1, args);
143
+ }
144
+ // __messageListeners 配列
145
+ v8::Local<v8::Value> listeners;
146
+ if (target->Get(ctx, Str(isolate, "__messageListeners")).ToLocal(&listeners) &&
147
+ listeners->IsArray()) {
148
+ auto arr = listeners.As<v8::Array>();
149
+ for (uint32_t i = 0; i < arr->Length(); ++i) {
150
+ v8::Local<v8::Value> fn;
151
+ if (arr->Get(ctx, i).ToLocal(&fn) && fn->IsFunction()) {
152
+ v8::TryCatch tc(isolate);
153
+ (void) fn.As<v8::Function>()->Call(ctx, target, 1, args);
154
+ }
155
+ }
156
+ }
157
+ }
158
+
159
+ // addEventListener(type, fn): type=='message' のみ __messageListeners へ蓄積。
160
+ void AddEventListener(const v8::FunctionCallbackInfo<v8::Value>& args)
161
+ {
162
+ v8::Isolate* isolate = args.GetIsolate();
163
+ v8::Local<v8::Context> ctx = isolate->GetCurrentContext();
164
+ if (args.Length() < 2 || !args[1]->IsFunction()) return;
165
+ if (ToStdString(isolate, args[0]) != "message") return;
166
+
167
+ v8::Local<v8::Object> self = args.This();
168
+ v8::Local<v8::Value> listeners;
169
+ v8::Local<v8::Array> arr;
170
+ if (self->Get(ctx, Str(isolate, "__messageListeners")).ToLocal(&listeners) &&
171
+ listeners->IsArray()) {
172
+ arr = listeners.As<v8::Array>();
173
+ } else {
174
+ arr = v8::Array::New(isolate, 0);
175
+ SetValue(isolate, self, "__messageListeners", arr);
176
+ }
177
+ arr->Set(ctx, arr->Length(), args[1]).Check();
178
+ }
179
+
180
+ } // namespace
181
+
182
+ // ===========================================================================
183
+ // WorkerInstance
184
+ // ===========================================================================
185
+ WorkerInstance::WorkerInstance(WorkerRuntime* runtime, v8::Isolate* isolate, std::string url)
186
+ : runtime_(runtime), isolate_(isolate), url_(std::move(url))
187
+ {
188
+ }
189
+
190
+ WorkerInstance::~WorkerInstance() = default;
191
+
192
+ v8::Local<v8::Context> WorkerInstance::context() const
193
+ {
194
+ return context_.Get(isolate_);
195
+ }
196
+
197
+ namespace {
198
+
199
+ // worker 側 self.postMessage: worker→main へ投函する。
200
+ void WorkerSelfPostMessage(const v8::FunctionCallbackInfo<v8::Value>& args)
201
+ {
202
+ v8::Isolate* isolate = args.GetIsolate();
203
+ auto* instance = static_cast<WorkerInstance*>(args.Data().As<v8::External>()->Value());
204
+ v8::Local<v8::Value> msg = args.Length() > 0 ? args[0] : v8::Undefined(isolate).As<v8::Value>();
205
+ 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));
209
+ }
210
+ }
211
+
212
+ // worker 側 importScripts(url...): 同期でクラシックスクリプトを評価する。
213
+ void WorkerImportScripts(const v8::FunctionCallbackInfo<v8::Value>& args)
214
+ {
215
+ v8::Isolate* isolate = args.GetIsolate();
216
+ v8::Local<v8::Context> ctx = isolate->GetCurrentContext();
217
+ HostContext* host = HostContext::From(isolate);
218
+ for (int i = 0; i < args.Length(); ++i) {
219
+ const std::string url = ToStdString(isolate, args[i]);
220
+ auto src = host->assets->ReadText(url);
221
+ if (!src) continue;
222
+ v8::TryCatch tc(isolate);
223
+ v8::ScriptOrigin origin(Str(isolate, url));
224
+ v8::Local<v8::String> code = Str(isolate, *src);
225
+ v8::Local<v8::Script> script;
226
+ if (v8::Script::Compile(ctx, code, &origin).ToLocal(&script)) {
227
+ (void) script->Run(ctx);
228
+ }
229
+ }
230
+ }
231
+
232
+ // main 側 worker.postMessage: main→worker へ投函する。
233
+ void MainWorkerPostMessage(const v8::FunctionCallbackInfo<v8::Value>& args)
234
+ {
235
+ v8::Isolate* isolate = args.GetIsolate();
236
+ auto* instance = static_cast<WorkerInstance*>(args.Data().As<v8::External>()->Value());
237
+ v8::Local<v8::Value> msg = args.Length() > 0 ? args[0] : v8::Undefined(isolate).As<v8::Value>();
238
+ 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));
242
+ }
243
+ }
244
+
245
+ } // namespace
246
+
247
+ bool WorkerInstance::Start()
248
+ {
249
+ v8::HandleScope handle_scope(isolate_);
250
+
251
+ v8::Local<v8::Context> context = v8::Context::New(isolate_);
252
+ context_.Reset(isolate_, context);
253
+
254
+ // worker 専用 EventLoop を Context の embedder slot に登録
255
+ loop_ = std::make_unique<EventLoop>(isolate_);
256
+ context->SetAlignedPointerInEmbedderData(kEventLoopEmbedderSlot, loop_.get());
257
+
258
+ v8::Context::Scope context_scope(context);
259
+ v8::Local<v8::Object> global = context->Global();
260
+
261
+ // ブラウザ相当環境一式 (console/timers/fetch/image/audio/webgpu/canvas/dom)
262
+ InstallGlobalBindings(isolate_, global, HostContext::From(isolate_));
263
+
264
+ // worker スコープには document/window を出さない (ライブラリの環境判定対策)。
265
+ global->Delete(context, Str(isolate_, "document")).Check();
266
+ global->Delete(context, Str(isolate_, "window")).Check();
267
+
268
+ // worker 固有: self / postMessage / importScripts / close / addEventListener
269
+ v8::Local<v8::External> self_ext = v8::External::New(isolate_, this);
270
+ SetValue(isolate_, global, "self", global);
271
+ global->Set(context, Str(isolate_, "postMessage"),
272
+ v8::Function::New(context, WorkerSelfPostMessage, self_ext).ToLocalChecked()).Check();
273
+ SetMethod(isolate_, global, "importScripts", WorkerImportScripts);
274
+ SetMethod(isolate_, global, "addEventListener", AddEventListener);
275
+ SetMethod(isolate_, global, "close", [](const v8::FunctionCallbackInfo<v8::Value>&) {});
276
+
277
+ // 入れ子 Worker を許可 (ZlibInflate 等)。
278
+ runtime_->InstallOnGlobal(global);
279
+
280
+ // ワーカースクリプトを読み込んで評価 (クラシック。ESM worker は «EXTEND»)。
281
+ // Vite の `?worker&inline` は blob: URL(= URL.createObjectURL(new Blob([code]))) を渡すため、
282
+ // まず object URL レジストリ、次に assets を参照する。
283
+ HostContext* host = HostContext::From(isolate_);
284
+ std::string source;
285
+ if (url_.rfind("blob:", 0) == 0) {
286
+ if (!ResolveObjectURL(url_, &source)) {
287
+ std::cerr << "[Worker] blob url not found: " << url_ << std::endl;
288
+ return false;
289
+ }
290
+ } else {
291
+ auto src = host->assets->ReadText(url_);
292
+ if (!src) {
293
+ std::cerr << "[Worker] script not found: " << url_ << std::endl;
294
+ return false;
295
+ }
296
+ source = std::move(*src);
297
+ }
298
+ v8::TryCatch tc(isolate_);
299
+ v8::ScriptOrigin origin(Str(isolate_, url_));
300
+ v8::Local<v8::String> code = Str(isolate_, source);
301
+ v8::Local<v8::Script> script;
302
+ if (!v8::Script::Compile(context, code, &origin).ToLocal(&script) ||
303
+ script->Run(context).IsEmpty()) {
304
+ std::cerr << "[Worker] script eval failed: " << url_ << std::endl;
305
+ return false;
306
+ }
307
+ isolate_->PerformMicrotaskCheckpoint();
308
+ return true;
309
+ }
310
+
311
+ void WorkerInstance::Terminate() { terminated_ = true; }
312
+
313
+ void WorkerInstance::PostToWorker(std::vector<uint8_t> bytes)
314
+ {
315
+ to_worker_.push_back(WorkerMessage{std::move(bytes)});
316
+ }
317
+
318
+ void WorkerInstance::PostToMain(std::vector<uint8_t> bytes)
319
+ {
320
+ to_main_.push_back(WorkerMessage{std::move(bytes)});
321
+ }
322
+
323
+ void WorkerInstance::DeliverToWorker(double now_ms)
324
+ {
325
+ if (terminated_) { to_worker_.clear(); return; }
326
+ v8::HandleScope hs(isolate_);
327
+ v8::Local<v8::Context> ctx = context();
328
+
329
+ while (!to_worker_.empty()) {
330
+ WorkerMessage m = std::move(to_worker_.front());
331
+ to_worker_.pop_front();
332
+ v8::Local<v8::Value> data;
333
+ if (DeserializeMessage(isolate_, ctx, m.data).ToLocal(&data)) {
334
+ DispatchMessage(isolate_, ctx, ctx->Global(), data);
335
+ }
336
+ }
337
+
338
+ // worker のタイマー / rAF (レンダラ worker はここで描画コマンドを積む)
339
+ v8::Context::Scope cs(ctx);
340
+ loop_->PumpTimers();
341
+ isolate_->PerformMicrotaskCheckpoint();
342
+ loop_->RunAnimationFrame(now_ms);
343
+ isolate_->PerformMicrotaskCheckpoint();
344
+ }
345
+
346
+ void WorkerInstance::DeliverToMain()
347
+ {
348
+ if (main_worker_object.IsEmpty()) { to_main_.clear(); return; }
349
+ v8::HandleScope hs(isolate_);
350
+ v8::Local<v8::Object> worker_obj = main_worker_object.Get(isolate_);
351
+ v8::Local<v8::Context> main_ctx = worker_obj->GetCreationContextChecked();
352
+
353
+ while (!to_main_.empty()) {
354
+ WorkerMessage m = std::move(to_main_.front());
355
+ to_main_.pop_front();
356
+ v8::Local<v8::Value> data;
357
+ if (DeserializeMessage(isolate_, main_ctx, m.data).ToLocal(&data)) {
358
+ DispatchMessage(isolate_, main_ctx, worker_obj, data);
359
+ }
360
+ }
361
+ }
362
+
363
+ void WorkerInstance::Deliver(double now_ms)
364
+ {
365
+ DeliverToWorker(now_ms);
366
+ DeliverToMain();
367
+ }
368
+
369
+ // ===========================================================================
370
+ // WorkerRuntime
371
+ // ===========================================================================
372
+ WorkerRuntime::WorkerRuntime(v8::Isolate* isolate, HostContext* host)
373
+ : isolate_(isolate), host_(host)
374
+ {
375
+ }
376
+
377
+ WorkerRuntime::~WorkerRuntime() = default;
378
+
379
+ v8::Local<v8::Object> WorkerRuntime::CreateWorker(const std::string& url)
380
+ {
381
+ v8::Local<v8::Context> ctx = isolate_->GetCurrentContext();
382
+
383
+ auto instance = std::make_unique<WorkerInstance>(this, isolate_, url);
384
+ WorkerInstance* ptr = instance.get();
385
+
386
+ // main 側 Worker オブジェクト
387
+ v8::Local<v8::Object> worker = v8::Object::New(isolate_);
388
+ v8::Local<v8::External> ext = v8::External::New(isolate_, ptr);
389
+ worker->Set(ctx, Str(isolate_, "postMessage"),
390
+ v8::Function::New(ctx, MainWorkerPostMessage, ext).ToLocalChecked()).Check();
391
+ 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
+ });
397
+
398
+ ptr->main_worker_object.Reset(isolate_, worker);
399
+
400
+ if (!ptr->Start()) {
401
+ // 起動失敗でも Worker オブジェクトは返す (エラーは配送されない)
402
+ }
403
+ workers_.push_back(std::move(instance));
404
+ return worker;
405
+ }
406
+
407
+ namespace {
408
+
409
+ // new Worker(url, options)
410
+ void WorkerConstructor(const v8::FunctionCallbackInfo<v8::Value>& args)
411
+ {
412
+ v8::Isolate* isolate = args.GetIsolate();
413
+ if (!args.IsConstructCall()) {
414
+ v8util::ThrowTypeError(isolate, "Worker must be called with new");
415
+ return;
416
+ }
417
+ auto* runtime = static_cast<WorkerRuntime*>(args.Data().As<v8::External>()->Value());
418
+ std::string url;
419
+ if (args.Length() > 0) {
420
+ // new Worker(new URL(...)) にも対応するため toString で URL 文字列化
421
+ url = ToStdString(isolate, args[0]);
422
+ }
423
+ args.GetReturnValue().Set(runtime->CreateWorker(url));
424
+ }
425
+
426
+ } // namespace
427
+
428
+ void WorkerRuntime::InstallOnGlobal(v8::Local<v8::Object> global)
429
+ {
430
+ v8::Local<v8::Context> ctx = isolate_->GetCurrentContext();
431
+ v8::Local<v8::External> ext = v8::External::New(isolate_, this);
432
+ v8::Local<v8::Function> ctor =
433
+ v8::Function::New(ctx, WorkerConstructor, ext).ToLocalChecked();
434
+ ctor->SetName(Str(isolate_, "Worker"));
435
+ global->Set(ctx, Str(isolate_, "Worker"), ctor).Check();
436
+ }
437
+
438
+ void WorkerRuntime::Pump(double now_ms)
439
+ {
440
+ for (auto& w : workers_) {
441
+ w->Deliver(now_ms);
442
+ }
443
+ }
444
+
445
+ // Bindings.h の InstallWorker: WorkerRuntime を使って Worker を設置する。
446
+ void InstallWorker(v8::Isolate* isolate, v8::Local<v8::Object> global, HostContext* host)
447
+ {
448
+ if (host->workers) {
449
+ host->workers->InstallOnGlobal(global);
450
+ }
451
+ }
452
+
453
+ } // namespace next2d
@@ -0,0 +1,108 @@
1
+ // WorkerRuntime: Web Worker を「同一 Isolate 内の別 Context を協調スケジューリング」する
2
+ // 単一スレッドモデルで実装する。
3
+ //
4
+ // player はレンダラを Worker + transferControlToOffscreen(OffscreenCanvas) で動かし、
5
+ // メインスレッドから postMessage で描画コマンドを送る。真のマルチスレッド化は
6
+ // Dawn/GPU のスレッド安全性を要するため、まず GPU を 1 スレッドに保てる協調モデルを採る。
7
+ //
8
+ // - new Worker(url) : 別 Context を生成しワーカースクリプトを評価
9
+ // - worker.postMessage(m,t) : main→worker のメッセージキューへ (ValueSerializer)
10
+ // - self.postMessage(m,t) : worker→main のメッセージキューへ
11
+ // - Pump() : 双方向のメッセージ配送 + 各 worker の EventLoop 前進
12
+ #pragma once
13
+
14
+ #include <v8.h>
15
+
16
+ #include <cstdint>
17
+ #include <deque>
18
+ #include <memory>
19
+ #include <string>
20
+ #include <vector>
21
+
22
+ namespace next2d {
23
+
24
+ class HostContext;
25
+ class EventLoop;
26
+ class WorkerRuntime;
27
+
28
+ // 1 メッセージ = ValueSerializer 出力バイト列。
29
+ struct WorkerMessage {
30
+ std::vector<uint8_t> data;
31
+ };
32
+
33
+ class WorkerInstance {
34
+ public:
35
+ WorkerInstance(WorkerRuntime* runtime, v8::Isolate* isolate, std::string url);
36
+ ~WorkerInstance();
37
+
38
+ bool Start(); // Context 生成 + ワーカースクリプト評価
39
+ void Terminate();
40
+
41
+ // main→worker / worker→main の投函
42
+ void PostToWorker(std::vector<uint8_t> bytes);
43
+ void PostToMain(std::vector<uint8_t> bytes);
44
+
45
+ // 配送 + タイマー/rAF 前進 (WorkerRuntime::Pump から呼ぶ)
46
+ void Deliver(double now_ms);
47
+
48
+ v8::Local<v8::Context> context() const;
49
+ EventLoop* event_loop() const { return loop_.get(); }
50
+
51
+ // main 側の Worker オブジェクト (onmessage を保持)
52
+ v8::Global<v8::Object> main_worker_object;
53
+ // worker 側 self.onmessage
54
+ v8::Global<v8::Function> worker_onmessage;
55
+ // main 側 worker.onmessage
56
+ v8::Global<v8::Function> main_onmessage;
57
+
58
+ private:
59
+ void DeliverToWorker(double now_ms);
60
+ void DeliverToMain();
61
+
62
+ WorkerRuntime* runtime_;
63
+ v8::Isolate* isolate_;
64
+ std::string url_;
65
+ v8::Global<v8::Context> context_;
66
+ std::unique_ptr<EventLoop> loop_;
67
+ std::deque<WorkerMessage> to_worker_;
68
+ std::deque<WorkerMessage> to_main_;
69
+ bool terminated_ = false;
70
+ };
71
+
72
+ class WorkerRuntime {
73
+ public:
74
+ WorkerRuntime(v8::Isolate* isolate, HostContext* host);
75
+ ~WorkerRuntime();
76
+
77
+ v8::Isolate* isolate() const { return isolate_; }
78
+ HostContext* host() const { return host_; }
79
+
80
+ // main / worker グローバルに Worker コンストラクタ等を設置する。
81
+ void InstallOnGlobal(v8::Local<v8::Object> global);
82
+
83
+ // new Worker(url) の実体
84
+ v8::Local<v8::Object> CreateWorker(const std::string& url);
85
+
86
+ // 全 worker のメッセージ配送とイベントループ前進
87
+ void Pump(double now_ms);
88
+
89
+ bool HasWorkers() const { return !workers_.empty(); }
90
+
91
+ private:
92
+ v8::Isolate* isolate_;
93
+ HostContext* host_;
94
+ std::vector<std::unique_ptr<WorkerInstance>> workers_;
95
+ };
96
+
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);
102
+
103
+ // bytes を target_ctx で復元する。
104
+ v8::MaybeLocal<v8::Value> DeserializeMessage(v8::Isolate* isolate,
105
+ v8::Local<v8::Context> target_ctx,
106
+ const std::vector<uint8_t>& bytes);
107
+
108
+ } // namespace next2d