@next2d/builder 0.1.5 → 0.1.7
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.
- package/README.md +15 -0
- package/dist/index.js +315 -3
- package/package.json +10 -8
- package/templates/xbox/CMakeLists.txt +124 -0
- package/templates/xbox/MicrosoftGame.config +36 -0
- package/templates/xbox/README.md +372 -0
- package/templates/xbox/cmake/FetchDawn.cmake +42 -0
- package/templates/xbox/cmake/FindV8.cmake +68 -0
- package/templates/xbox/js/bootstrap.js +143 -0
- package/templates/xbox/js/selftest.js +515 -0
- package/templates/xbox/src/AssetLoader.cpp +106 -0
- package/templates/xbox/src/AssetLoader.h +35 -0
- package/templates/xbox/src/EventLoop.cpp +162 -0
- package/templates/xbox/src/EventLoop.h +73 -0
- package/templates/xbox/src/HostContext.h +58 -0
- package/templates/xbox/src/bindings/Audio.cpp +320 -0
- package/templates/xbox/src/bindings/Bindings.h +75 -0
- package/templates/xbox/src/bindings/Canvas.cpp +171 -0
- package/templates/xbox/src/bindings/Canvas2D.cpp +462 -0
- package/templates/xbox/src/bindings/Console.cpp +52 -0
- package/templates/xbox/src/bindings/DomShims.cpp +221 -0
- package/templates/xbox/src/bindings/EventTarget.cpp +127 -0
- package/templates/xbox/src/bindings/EventTarget.h +17 -0
- package/templates/xbox/src/bindings/Fetch.cpp +149 -0
- package/templates/xbox/src/bindings/Gamepad.cpp +79 -0
- package/templates/xbox/src/bindings/Image.cpp +160 -0
- package/templates/xbox/src/bindings/ImageSource.h +26 -0
- package/templates/xbox/src/bindings/Network.cpp +303 -0
- package/templates/xbox/src/bindings/RasterCore.h +265 -0
- package/templates/xbox/src/bindings/Timers.cpp +87 -0
- package/templates/xbox/src/bindings/Video.cpp +235 -0
- package/templates/xbox/src/bindings/webgpu/WebGPU.cpp +1318 -0
- package/templates/xbox/src/bindings/webgpu/WebGPUCommon.h +119 -0
- package/templates/xbox/src/bindings/webgpu/WebGPUEnums.h +245 -0
- package/templates/xbox/src/gpu/DawnContext.cpp +170 -0
- package/templates/xbox/src/gpu/DawnContext.h +64 -0
- package/templates/xbox/src/main.cpp +368 -0
- package/templates/xbox/src/platform/AudioEngine.cpp +184 -0
- package/templates/xbox/src/platform/AudioEngine.h +65 -0
- package/templates/xbox/src/platform/GamepadManager.cpp +83 -0
- package/templates/xbox/src/platform/GamepadManager.h +40 -0
- package/templates/xbox/src/platform/ImageTypes.h +17 -0
- package/templates/xbox/src/platform/TextRasterizer.cpp +183 -0
- package/templates/xbox/src/platform/TextRasterizer.h +35 -0
- package/templates/xbox/src/platform/WicDecoder.cpp +63 -0
- package/templates/xbox/src/platform/WicDecoder.h +16 -0
- package/templates/xbox/src/v8/V8Runtime.cpp +285 -0
- package/templates/xbox/src/v8/V8Runtime.h +68 -0
- package/templates/xbox/src/v8/V8Util.h +56 -0
- package/templates/xbox/src/worker/WorkerRuntime.cpp +453 -0
- package/templates/xbox/src/worker/WorkerRuntime.h +108 -0
- package/templates/xbox/tests/check_local.sh +56 -0
- package/templates/xbox/tests/raster_test.cpp +279 -0
- 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
|