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