@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,162 @@
|
|
|
1
|
+
#include "EventLoop.h"
|
|
2
|
+
|
|
3
|
+
#include "HostContext.h"
|
|
4
|
+
#include "v8/V8Util.h"
|
|
5
|
+
|
|
6
|
+
#include <algorithm>
|
|
7
|
+
|
|
8
|
+
namespace next2d {
|
|
9
|
+
|
|
10
|
+
EventLoop* CurrentEventLoop(v8::Isolate* isolate)
|
|
11
|
+
{
|
|
12
|
+
v8::Local<v8::Context> ctx = isolate->GetCurrentContext();
|
|
13
|
+
if (!ctx.IsEmpty() &&
|
|
14
|
+
ctx->GetNumberOfEmbedderDataFields() > kEventLoopEmbedderSlot) {
|
|
15
|
+
void* p = ctx->GetAlignedPointerFromEmbedderData(kEventLoopEmbedderSlot);
|
|
16
|
+
if (p) {
|
|
17
|
+
return static_cast<EventLoop*>(p);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
HostContext* host = HostContext::From(isolate);
|
|
21
|
+
return host ? host->event_loop : nullptr;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
EventLoop::EventLoop(v8::Isolate* isolate)
|
|
25
|
+
: isolate_(isolate), start_(Clock::now())
|
|
26
|
+
{
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
EventLoop::~EventLoop() = default;
|
|
30
|
+
|
|
31
|
+
double EventLoop::Now() const
|
|
32
|
+
{
|
|
33
|
+
const auto elapsed = Clock::now() - start_;
|
|
34
|
+
return std::chrono::duration<double, std::milli>(elapsed).count();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
uint32_t EventLoop::SetTimer(v8::Local<v8::Function> callback, double delay_ms, bool repeat)
|
|
38
|
+
{
|
|
39
|
+
if (delay_ms < 0.0) {
|
|
40
|
+
delay_ms = 0.0;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const uint32_t id = next_timer_id_++;
|
|
44
|
+
TimerEntry entry;
|
|
45
|
+
entry.callback.Reset(isolate_, callback);
|
|
46
|
+
entry.due_ms = Now() + delay_ms;
|
|
47
|
+
entry.interval_ms = delay_ms;
|
|
48
|
+
entry.repeat = repeat;
|
|
49
|
+
entry.cancelled = false;
|
|
50
|
+
timers_.emplace(id, std::move(entry));
|
|
51
|
+
return id;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
void EventLoop::ClearTimer(uint32_t id)
|
|
55
|
+
{
|
|
56
|
+
auto it = timers_.find(id);
|
|
57
|
+
if (it != timers_.end()) {
|
|
58
|
+
// 実行ループ中の削除に備え cancelled フラグで無効化
|
|
59
|
+
it->second.cancelled = true;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
uint32_t EventLoop::RequestAnimationFrame(v8::Local<v8::Function> callback)
|
|
64
|
+
{
|
|
65
|
+
const uint32_t id = next_raf_id_++;
|
|
66
|
+
v8::Global<v8::Function> global;
|
|
67
|
+
global.Reset(isolate_, callback);
|
|
68
|
+
raf_callbacks_.emplace_back(id, std::move(global));
|
|
69
|
+
return id;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
void EventLoop::CancelAnimationFrame(uint32_t id)
|
|
73
|
+
{
|
|
74
|
+
raf_cancelled_.push_back(id);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
void EventLoop::PumpTimers()
|
|
78
|
+
{
|
|
79
|
+
const double now = Now();
|
|
80
|
+
|
|
81
|
+
v8::HandleScope handle_scope(isolate_);
|
|
82
|
+
v8::Local<v8::Context> ctx = isolate_->GetCurrentContext();
|
|
83
|
+
v8::Local<v8::Object> global = ctx->Global();
|
|
84
|
+
|
|
85
|
+
// 期限到来分を収集 (map の走査中の変更を避けるため id を先に集める)
|
|
86
|
+
std::vector<uint32_t> due;
|
|
87
|
+
for (auto& [id, entry] : timers_) {
|
|
88
|
+
if (!entry.cancelled && entry.due_ms <= now) {
|
|
89
|
+
due.push_back(id);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
for (uint32_t id : due) {
|
|
94
|
+
auto it = timers_.find(id);
|
|
95
|
+
if (it == timers_.end() || it->second.cancelled) {
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
v8::Local<v8::Function> fn = it->second.callback.Get(isolate_);
|
|
100
|
+
|
|
101
|
+
if (it->second.repeat) {
|
|
102
|
+
it->second.due_ms = now + it->second.interval_ms;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
v8::TryCatch try_catch(isolate_);
|
|
106
|
+
(void) fn->Call(ctx, global, 0, nullptr);
|
|
107
|
+
// 例外はコンソールに委譲 (V8Runtime のメッセージハンドラが拾う)
|
|
108
|
+
|
|
109
|
+
if (!it->second.repeat) {
|
|
110
|
+
// 単発は実行後に破棄
|
|
111
|
+
auto erase_it = timers_.find(id);
|
|
112
|
+
if (erase_it != timers_.end()) {
|
|
113
|
+
timers_.erase(erase_it);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// cancelled を掃除
|
|
119
|
+
for (auto it = timers_.begin(); it != timers_.end();) {
|
|
120
|
+
if (it->second.cancelled) {
|
|
121
|
+
it = timers_.erase(it);
|
|
122
|
+
} else {
|
|
123
|
+
++it;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
void EventLoop::RunAnimationFrame(double timestamp_ms)
|
|
129
|
+
{
|
|
130
|
+
if (raf_callbacks_.empty()) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// ブラウザ同様、今フレームで実行するのは「現時点で登録済み」のコールバックのみ。
|
|
135
|
+
// コールバック内で再登録された rAF は次フレームに回す。
|
|
136
|
+
auto current = std::move(raf_callbacks_);
|
|
137
|
+
raf_callbacks_.clear();
|
|
138
|
+
|
|
139
|
+
v8::HandleScope handle_scope(isolate_);
|
|
140
|
+
v8::Local<v8::Context> ctx = isolate_->GetCurrentContext();
|
|
141
|
+
v8::Local<v8::Object> global = ctx->Global();
|
|
142
|
+
v8::Local<v8::Value> arg = v8::Number::New(isolate_, timestamp_ms);
|
|
143
|
+
|
|
144
|
+
for (auto& [id, cb] : current) {
|
|
145
|
+
if (std::find(raf_cancelled_.begin(), raf_cancelled_.end(), id) != raf_cancelled_.end()) {
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
v8::Local<v8::Function> fn = cb.Get(isolate_);
|
|
149
|
+
v8::TryCatch try_catch(isolate_);
|
|
150
|
+
v8::Local<v8::Value> args[1] = { arg };
|
|
151
|
+
(void) fn->Call(ctx, global, 1, args);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
raf_cancelled_.clear();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
bool EventLoop::HasPendingWork() const
|
|
158
|
+
{
|
|
159
|
+
return !timers_.empty() || !raf_callbacks_.empty();
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
} // namespace next2d
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// EventLoop: setTimeout / setInterval / requestAnimationFrame を実装する非同期タスクキュー。
|
|
2
|
+
//
|
|
3
|
+
// ブラウザのイベントループを単一スレッドで模倣する。main.cpp のゲームループが毎フレーム
|
|
4
|
+
// PumpTimers() と RunAnimationFrame() を呼び出す。V8 のマイクロタスク(Promise)は
|
|
5
|
+
// V8Runtime 側で各タスク実行後にチェックポイントされる。
|
|
6
|
+
#pragma once
|
|
7
|
+
|
|
8
|
+
#include <v8.h>
|
|
9
|
+
#include <chrono>
|
|
10
|
+
#include <cstdint>
|
|
11
|
+
#include <map>
|
|
12
|
+
#include <vector>
|
|
13
|
+
|
|
14
|
+
namespace next2d {
|
|
15
|
+
|
|
16
|
+
// Context の embedder data slot に EventLoop* を格納するインデックス。
|
|
17
|
+
// main / 各 worker の Context がそれぞれ自分の EventLoop を登録し、
|
|
18
|
+
// Timers/rAF バインディングは実行中の Context の EventLoop を参照する。
|
|
19
|
+
constexpr int kEventLoopEmbedderSlot = 1;
|
|
20
|
+
|
|
21
|
+
class EventLoop;
|
|
22
|
+
|
|
23
|
+
// 現在の Context に紐づく EventLoop を返す (未登録なら HostContext の既定)。
|
|
24
|
+
EventLoop* CurrentEventLoop(v8::Isolate* isolate);
|
|
25
|
+
|
|
26
|
+
class EventLoop {
|
|
27
|
+
public:
|
|
28
|
+
explicit EventLoop(v8::Isolate* isolate);
|
|
29
|
+
~EventLoop();
|
|
30
|
+
|
|
31
|
+
using Clock = std::chrono::steady_clock;
|
|
32
|
+
|
|
33
|
+
// performance.now() 相当 (起動からの経過ミリ秒)
|
|
34
|
+
double Now() const;
|
|
35
|
+
|
|
36
|
+
// setTimeout / setInterval
|
|
37
|
+
uint32_t SetTimer(v8::Local<v8::Function> callback, double delay_ms, bool repeat);
|
|
38
|
+
void ClearTimer(uint32_t id);
|
|
39
|
+
|
|
40
|
+
// requestAnimationFrame / cancelAnimationFrame
|
|
41
|
+
uint32_t RequestAnimationFrame(v8::Local<v8::Function> callback);
|
|
42
|
+
void CancelAnimationFrame(uint32_t id);
|
|
43
|
+
|
|
44
|
+
// 期限の来たタイマーを実行する (毎フレーム冒頭)
|
|
45
|
+
void PumpTimers();
|
|
46
|
+
|
|
47
|
+
// 登録済み rAF コールバックをタイムスタンプ付きで実行する (毎フレーム)
|
|
48
|
+
void RunAnimationFrame(double timestamp_ms);
|
|
49
|
+
|
|
50
|
+
// 保留タスクが残っているか (終了判定用)
|
|
51
|
+
bool HasPendingWork() const;
|
|
52
|
+
|
|
53
|
+
private:
|
|
54
|
+
struct TimerEntry {
|
|
55
|
+
v8::Global<v8::Function> callback;
|
|
56
|
+
double due_ms;
|
|
57
|
+
double interval_ms;
|
|
58
|
+
bool repeat;
|
|
59
|
+
bool cancelled;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
v8::Isolate* isolate_;
|
|
63
|
+
Clock::time_point start_;
|
|
64
|
+
|
|
65
|
+
uint32_t next_timer_id_ = 1;
|
|
66
|
+
std::map<uint32_t, TimerEntry> timers_;
|
|
67
|
+
|
|
68
|
+
uint32_t next_raf_id_ = 1;
|
|
69
|
+
std::vector<std::pair<uint32_t, v8::Global<v8::Function>>> raf_callbacks_;
|
|
70
|
+
std::vector<uint32_t> raf_cancelled_;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
} // namespace next2d
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// HostContext: C++ 側のサブシステムを束ね、V8 バインディングから参照できるようにする。
|
|
2
|
+
//
|
|
3
|
+
// Isolate の data slot 0 に本ポインタを格納し、任意のバインディングコールバックから
|
|
4
|
+
// HostContext::From(isolate) で取り出す。これにより各バインディングは EventLoop /
|
|
5
|
+
// DawnContext / AssetLoader / GamepadManager 等へアクセスできる。
|
|
6
|
+
#pragma once
|
|
7
|
+
|
|
8
|
+
#include <v8.h>
|
|
9
|
+
|
|
10
|
+
namespace next2d {
|
|
11
|
+
|
|
12
|
+
class EventLoop;
|
|
13
|
+
class DawnContext;
|
|
14
|
+
class AssetLoader;
|
|
15
|
+
class GamepadManager;
|
|
16
|
+
class AudioEngine;
|
|
17
|
+
class WorkerRuntime;
|
|
18
|
+
|
|
19
|
+
// Isolate data slot index (V8Runtime が SetData で登録)
|
|
20
|
+
constexpr uint32_t kHostContextSlot = 0;
|
|
21
|
+
|
|
22
|
+
// グラフィックスバックエンド種別。
|
|
23
|
+
// Xbox は WebGPU(Dawn/D3D12)。Nintendo Switch 移植時は WebGL2(GL) を選ぶ。
|
|
24
|
+
// player は navigator.gpu の有無で自動的に webgpu / webgl2 経路を切り替える。
|
|
25
|
+
enum class GraphicsBackend {
|
|
26
|
+
WebGPU,
|
|
27
|
+
WebGL2,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
class HostContext {
|
|
31
|
+
public:
|
|
32
|
+
HostContext() = default;
|
|
33
|
+
|
|
34
|
+
EventLoop* event_loop = nullptr;
|
|
35
|
+
DawnContext* gpu = nullptr;
|
|
36
|
+
AssetLoader* assets = nullptr;
|
|
37
|
+
GamepadManager* gamepad = nullptr;
|
|
38
|
+
AudioEngine* audio = nullptr;
|
|
39
|
+
WorkerRuntime* workers = nullptr;
|
|
40
|
+
|
|
41
|
+
// 使用するグラフィックスバックエンド (既定は Xbox=WebGPU)
|
|
42
|
+
GraphicsBackend backend = GraphicsBackend::WebGPU;
|
|
43
|
+
|
|
44
|
+
// 入力イベント配送先の主要 canvas (Canvas.cpp が最後に生成したものを記録)
|
|
45
|
+
v8::Global<v8::Object> main_canvas;
|
|
46
|
+
|
|
47
|
+
// アプリのビューポート論理サイズ (canvas.width/height の初期値)
|
|
48
|
+
int viewport_width = 1920;
|
|
49
|
+
int viewport_height = 1080;
|
|
50
|
+
double device_pixel_ratio = 1.0;
|
|
51
|
+
|
|
52
|
+
static HostContext* From(v8::Isolate* isolate)
|
|
53
|
+
{
|
|
54
|
+
return static_cast<HostContext*>(isolate->GetData(kHostContextSlot));
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
} // namespace next2d
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
#include "Bindings.h"
|
|
2
|
+
|
|
3
|
+
#include "HostContext.h"
|
|
4
|
+
#include "AssetLoader.h"
|
|
5
|
+
#include "EventTarget.h"
|
|
6
|
+
#include "platform/AudioEngine.h"
|
|
7
|
+
#include "v8/V8Util.h"
|
|
8
|
+
|
|
9
|
+
#include <memory>
|
|
10
|
+
#include <vector>
|
|
11
|
+
|
|
12
|
+
namespace next2d {
|
|
13
|
+
|
|
14
|
+
using v8util::Str;
|
|
15
|
+
using v8util::ToStdString;
|
|
16
|
+
|
|
17
|
+
namespace {
|
|
18
|
+
|
|
19
|
+
// GainNode.gain.value を保持し、接続済みボイスへ音量を反映する。
|
|
20
|
+
struct GainParam {
|
|
21
|
+
float value = 1.0f;
|
|
22
|
+
std::shared_ptr<AudioVoice> voice; // source.connect(gain) 時にリンクされる
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
void ReleaseGainParam(const v8::WeakCallbackInfo<GainParam>& info)
|
|
26
|
+
{
|
|
27
|
+
delete info.GetParameter();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// 再生中(非ループ)の source ノード。毎フレーム IsFinished を見て "ended" を発火する。
|
|
31
|
+
std::vector<v8::Global<v8::Object>>& PlayingSources()
|
|
32
|
+
{
|
|
33
|
+
static std::vector<v8::Global<v8::Object>> sources;
|
|
34
|
+
return sources;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// JS オブジェクトに shared_ptr を保持させるためのホルダ。GC で解放する。
|
|
38
|
+
template <typename T>
|
|
39
|
+
struct Holder {
|
|
40
|
+
std::shared_ptr<T> ptr;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
template <typename T>
|
|
44
|
+
void ReleaseHolder(const v8::WeakCallbackInfo<Holder<T>>& info)
|
|
45
|
+
{
|
|
46
|
+
delete info.GetParameter();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
template <typename T>
|
|
50
|
+
void Attach(v8::Isolate* isolate, v8::Local<v8::Object> obj, std::shared_ptr<T> ptr)
|
|
51
|
+
{
|
|
52
|
+
auto* holder = new Holder<T>{std::move(ptr)};
|
|
53
|
+
obj->SetInternalField(0, v8::External::New(isolate, holder));
|
|
54
|
+
auto* handle = new v8::Global<v8::Object>(isolate, obj);
|
|
55
|
+
handle->SetWeak(holder, ReleaseHolder<T>, v8::WeakCallbackType::kParameter);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
template <typename T>
|
|
59
|
+
std::shared_ptr<T> Get(v8::Local<v8::Object> obj)
|
|
60
|
+
{
|
|
61
|
+
auto* holder = static_cast<Holder<T>*>(
|
|
62
|
+
obj->GetInternalField(0).As<v8::External>()->Value());
|
|
63
|
+
return holder->ptr;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
v8::Local<v8::ObjectTemplate> InternalTemplate(v8::Isolate* isolate)
|
|
67
|
+
{
|
|
68
|
+
v8::Local<v8::ObjectTemplate> tmpl = v8::ObjectTemplate::New(isolate);
|
|
69
|
+
tmpl->SetInternalFieldCount(1);
|
|
70
|
+
return tmpl;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// --- AudioBufferSourceNode ------------------------------------------------
|
|
74
|
+
void SourceStart(const v8::FunctionCallbackInfo<v8::Value>& args)
|
|
75
|
+
{
|
|
76
|
+
auto voice = Get<AudioVoice>(args.This());
|
|
77
|
+
bool loop = false;
|
|
78
|
+
v8::Local<v8::Value> loop_val;
|
|
79
|
+
if (args.This()->Get(args.GetIsolate()->GetCurrentContext(),
|
|
80
|
+
Str(args.GetIsolate(), "loop")).ToLocal(&loop_val)) {
|
|
81
|
+
loop = loop_val->BooleanValue(args.GetIsolate());
|
|
82
|
+
}
|
|
83
|
+
if (voice) {
|
|
84
|
+
voice->Start(loop);
|
|
85
|
+
// 非ループ再生は終了時に "ended" を発火するため登録する。
|
|
86
|
+
if (!loop) {
|
|
87
|
+
PlayingSources().emplace_back(args.GetIsolate(), args.This());
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
void SourceStop(const v8::FunctionCallbackInfo<v8::Value>& args)
|
|
93
|
+
{
|
|
94
|
+
auto voice = Get<AudioVoice>(args.This());
|
|
95
|
+
if (voice) {
|
|
96
|
+
voice->Stop();
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// GainNode の connect: 実際に音量を反映するのは source.connect(gain) 側。ここは no-op。
|
|
101
|
+
void GainConnect(const v8::FunctionCallbackInfo<v8::Value>& /*args*/) {}
|
|
102
|
+
|
|
103
|
+
// source.disconnect(): player は stop() でこれを呼ぶ。ボイスを停止し登録からも外す
|
|
104
|
+
// (PlayingSources の強参照 Global がノードを固定し続けるのを防ぐ)。
|
|
105
|
+
void SourceDisconnect(const v8::FunctionCallbackInfo<v8::Value>& args)
|
|
106
|
+
{
|
|
107
|
+
v8::Isolate* isolate = args.GetIsolate();
|
|
108
|
+
auto voice = Get<AudioVoice>(args.This());
|
|
109
|
+
if (voice) {
|
|
110
|
+
voice->Stop();
|
|
111
|
+
}
|
|
112
|
+
auto& sources = PlayingSources();
|
|
113
|
+
v8::Local<v8::Object> node = args.This();
|
|
114
|
+
for (size_t i = 0; i < sources.size();) {
|
|
115
|
+
if (sources[i].Get(isolate) == node) {
|
|
116
|
+
sources.erase(sources.begin() + i);
|
|
117
|
+
} else {
|
|
118
|
+
++i;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// source.connect(destination): destination が GainNode なら voice をリンクして音量を反映する。
|
|
124
|
+
void SourceConnect(const v8::FunctionCallbackInfo<v8::Value>& args)
|
|
125
|
+
{
|
|
126
|
+
v8::Isolate* isolate = args.GetIsolate();
|
|
127
|
+
v8::Local<v8::Context> ctx = isolate->GetCurrentContext();
|
|
128
|
+
auto voice = Get<AudioVoice>(args.This());
|
|
129
|
+
if (!voice || args.Length() < 1 || !args[0]->IsObject()) {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
// 接続先(GainNode)の gain オブジェクトから GainParam を取り出しリンクする。
|
|
133
|
+
v8::Local<v8::Object> dest = args[0].As<v8::Object>();
|
|
134
|
+
v8::Local<v8::Value> gain_val;
|
|
135
|
+
if (!dest->Get(ctx, Str(isolate, "gain")).ToLocal(&gain_val) || !gain_val->IsObject()) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
v8::Local<v8::Object> gain = gain_val.As<v8::Object>();
|
|
139
|
+
if (gain->InternalFieldCount() < 1) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
auto* param = static_cast<GainParam*>(
|
|
143
|
+
gain->GetInternalField(0).As<v8::External>()->Value());
|
|
144
|
+
if (param) {
|
|
145
|
+
param->voice = voice;
|
|
146
|
+
voice->SetVolume(param->value); // 接続時点の音量を即時反映
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// createBufferSource() -> AudioBufferSourceNode
|
|
151
|
+
void CreateBufferSource(const v8::FunctionCallbackInfo<v8::Value>& args)
|
|
152
|
+
{
|
|
153
|
+
v8::Isolate* isolate = args.GetIsolate();
|
|
154
|
+
v8::Local<v8::Context> ctx = isolate->GetCurrentContext();
|
|
155
|
+
AudioEngine* engine = HostContext::From(isolate)->audio;
|
|
156
|
+
|
|
157
|
+
v8::Local<v8::Object> node = InternalTemplate(isolate)->NewInstance(ctx).ToLocalChecked();
|
|
158
|
+
// buffer は後から setter 経由で設定する想定 (ここでは空ボイス)。
|
|
159
|
+
Attach<AudioVoice>(isolate, node, nullptr);
|
|
160
|
+
|
|
161
|
+
v8util::SetValue(isolate, node, "loop", v8::Boolean::New(isolate, false));
|
|
162
|
+
v8util::SetMethod(isolate, node, "start", SourceStart);
|
|
163
|
+
v8util::SetMethod(isolate, node, "stop", SourceStop);
|
|
164
|
+
v8util::SetMethod(isolate, node, "connect", SourceConnect);
|
|
165
|
+
// source.addEventListener("ended", ...) を player が使うため EventTarget を設置する。
|
|
166
|
+
InstallEventTarget(isolate, node);
|
|
167
|
+
v8util::SetMethod(isolate, node, "disconnect", SourceDisconnect);
|
|
168
|
+
|
|
169
|
+
// buffer プロパティ設定時に AudioVoice を生成するアクセサ
|
|
170
|
+
// (SetAccessor は V8 12.9 で削除。SetNativeDataProperty を使う)
|
|
171
|
+
node->SetNativeDataProperty(ctx, Str(isolate, "buffer"),
|
|
172
|
+
nullptr,
|
|
173
|
+
[](v8::Local<v8::Name>, v8::Local<v8::Value> value,
|
|
174
|
+
const v8::PropertyCallbackInfo<void>& info) {
|
|
175
|
+
v8::Isolate* iso = info.GetIsolate();
|
|
176
|
+
if (!value->IsObject()) {
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
auto pcm = Get<PcmBuffer>(value.As<v8::Object>());
|
|
180
|
+
AudioEngine* eng = HostContext::From(iso)->audio;
|
|
181
|
+
auto voice = eng->CreateVoice(pcm);
|
|
182
|
+
Attach<AudioVoice>(iso, info.This(), voice);
|
|
183
|
+
}).Check();
|
|
184
|
+
|
|
185
|
+
args.GetReturnValue().Set(node);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// --- GainNode -------------------------------------------------------------
|
|
189
|
+
void CreateGain(const v8::FunctionCallbackInfo<v8::Value>& args)
|
|
190
|
+
{
|
|
191
|
+
v8::Isolate* isolate = args.GetIsolate();
|
|
192
|
+
v8::Local<v8::Context> ctx = isolate->GetCurrentContext();
|
|
193
|
+
|
|
194
|
+
v8::Local<v8::Object> node = v8::Object::New(isolate);
|
|
195
|
+
|
|
196
|
+
// gain オブジェクトは内部フィールドに GainParam を持ち、value アクセサで
|
|
197
|
+
// リンク済みボイスへ音量を即時反映する (再生中のミュート/音量変更に対応)。
|
|
198
|
+
v8::Local<v8::Object> gain = InternalTemplate(isolate)->NewInstance(ctx).ToLocalChecked();
|
|
199
|
+
auto* param = new GainParam();
|
|
200
|
+
gain->SetInternalField(0, v8::External::New(isolate, param));
|
|
201
|
+
auto* handle = new v8::Global<v8::Object>(isolate, gain);
|
|
202
|
+
handle->SetWeak(param, ReleaseGainParam, v8::WeakCallbackType::kParameter);
|
|
203
|
+
|
|
204
|
+
gain->SetNativeDataProperty(ctx, Str(isolate, "value"),
|
|
205
|
+
[](v8::Local<v8::Name>, const v8::PropertyCallbackInfo<v8::Value>& info) {
|
|
206
|
+
auto* p = static_cast<GainParam*>(
|
|
207
|
+
info.This()->GetInternalField(0).As<v8::External>()->Value());
|
|
208
|
+
info.GetReturnValue().Set(v8::Number::New(info.GetIsolate(), p ? p->value : 1.0));
|
|
209
|
+
},
|
|
210
|
+
[](v8::Local<v8::Name>, v8::Local<v8::Value> value,
|
|
211
|
+
const v8::PropertyCallbackInfo<void>& info) {
|
|
212
|
+
auto* p = static_cast<GainParam*>(
|
|
213
|
+
info.This()->GetInternalField(0).As<v8::External>()->Value());
|
|
214
|
+
if (!p || !value->IsNumber()) return;
|
|
215
|
+
p->value = static_cast<float>(value.As<v8::Number>()->Value());
|
|
216
|
+
if (p->voice) p->voice->SetVolume(p->value); // 再生中も反映
|
|
217
|
+
}).Check();
|
|
218
|
+
|
|
219
|
+
v8util::SetValue(isolate, node, "gain", gain);
|
|
220
|
+
v8util::SetMethod(isolate, node, "connect", GainConnect);
|
|
221
|
+
v8util::SetMethod(isolate, node, "disconnect",
|
|
222
|
+
[](const v8::FunctionCallbackInfo<v8::Value>&) {});
|
|
223
|
+
args.GetReturnValue().Set(node);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// 毎フレーム呼ばれ、再生終了した source ノードへ "ended" を発火する (main.cpp のループから)。
|
|
227
|
+
} // namespace
|
|
228
|
+
|
|
229
|
+
void PumpAudioEvents(v8::Isolate* isolate)
|
|
230
|
+
{
|
|
231
|
+
auto& sources = PlayingSources();
|
|
232
|
+
if (sources.empty()) return;
|
|
233
|
+
v8::HandleScope scope(isolate);
|
|
234
|
+
v8::Local<v8::Context> ctx = isolate->GetCurrentContext();
|
|
235
|
+
for (size_t i = 0; i < sources.size();) {
|
|
236
|
+
v8::Local<v8::Object> node = sources[i].Get(isolate);
|
|
237
|
+
auto voice = Get<AudioVoice>(node);
|
|
238
|
+
if (!voice || voice->IsFinished()) {
|
|
239
|
+
if (voice) {
|
|
240
|
+
v8::Local<v8::Object> ev = v8::Object::New(isolate);
|
|
241
|
+
ev->Set(ctx, Str(isolate, "type"), Str(isolate, "ended")).Check();
|
|
242
|
+
DispatchEvent(isolate, node, ev);
|
|
243
|
+
}
|
|
244
|
+
sources.erase(sources.begin() + i);
|
|
245
|
+
} else {
|
|
246
|
+
++i;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
namespace {
|
|
252
|
+
|
|
253
|
+
// --- decodeAudioData ------------------------------------------------------
|
|
254
|
+
void DecodeAudioData(const v8::FunctionCallbackInfo<v8::Value>& args)
|
|
255
|
+
{
|
|
256
|
+
v8::Isolate* isolate = args.GetIsolate();
|
|
257
|
+
v8::Local<v8::Context> ctx = isolate->GetCurrentContext();
|
|
258
|
+
|
|
259
|
+
v8::Local<v8::Promise::Resolver> resolver =
|
|
260
|
+
v8::Promise::Resolver::New(ctx).ToLocalChecked();
|
|
261
|
+
args.GetReturnValue().Set(resolver->GetPromise());
|
|
262
|
+
|
|
263
|
+
std::vector<uint8_t> input;
|
|
264
|
+
if (args.Length() >= 1 && args[0]->IsArrayBuffer()) {
|
|
265
|
+
auto ab = args[0].As<v8::ArrayBuffer>();
|
|
266
|
+
auto store = ab->GetBackingStore();
|
|
267
|
+
input.assign(static_cast<uint8_t*>(store->Data()),
|
|
268
|
+
static_cast<uint8_t*>(store->Data()) + store->ByteLength());
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
auto pcm = std::make_shared<PcmBuffer>();
|
|
272
|
+
if (input.empty() || !AudioEngine::Decode(input, *pcm)) {
|
|
273
|
+
resolver->Reject(ctx, v8::Exception::Error(Str(isolate, "audio decode failed"))).Check();
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
v8::Local<v8::Object> buffer = InternalTemplate(isolate)->NewInstance(ctx).ToLocalChecked();
|
|
278
|
+
Attach<PcmBuffer>(isolate, buffer, pcm);
|
|
279
|
+
v8util::SetValue(isolate, buffer, "sampleRate", v8::Number::New(isolate, pcm->sample_rate));
|
|
280
|
+
v8util::SetValue(isolate, buffer, "numberOfChannels", v8::Integer::New(isolate, pcm->channels));
|
|
281
|
+
v8util::SetValue(isolate, buffer, "duration",
|
|
282
|
+
v8::Number::New(isolate, pcm->channels
|
|
283
|
+
? static_cast<double>(pcm->samples.size()) / pcm->channels / pcm->sample_rate
|
|
284
|
+
: 0.0));
|
|
285
|
+
resolver->Resolve(ctx, buffer).Check();
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// AudioContext コンストラクタ
|
|
289
|
+
void AudioContextConstructor(const v8::FunctionCallbackInfo<v8::Value>& args)
|
|
290
|
+
{
|
|
291
|
+
v8::Isolate* isolate = args.GetIsolate();
|
|
292
|
+
if (!args.IsConstructCall()) {
|
|
293
|
+
v8util::ThrowTypeError(isolate, "AudioContext must be called with new");
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
v8::Local<v8::Object> self = args.This();
|
|
297
|
+
v8util::SetValue(isolate, self, "sampleRate", v8::Number::New(isolate, 48000));
|
|
298
|
+
v8util::SetValue(isolate, self, "destination", v8::Object::New(isolate));
|
|
299
|
+
v8util::SetMethod(isolate, self, "createBufferSource", CreateBufferSource);
|
|
300
|
+
v8util::SetMethod(isolate, self, "createGain", CreateGain);
|
|
301
|
+
v8util::SetMethod(isolate, self, "decodeAudioData", DecodeAudioData);
|
|
302
|
+
args.GetReturnValue().Set(self);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
} // namespace
|
|
306
|
+
|
|
307
|
+
void InstallAudio(v8::Isolate* isolate, v8::Local<v8::Object> global, HostContext* /*host*/)
|
|
308
|
+
{
|
|
309
|
+
v8::Local<v8::Context> ctx = isolate->GetCurrentContext();
|
|
310
|
+
|
|
311
|
+
v8::Local<v8::FunctionTemplate> tmpl =
|
|
312
|
+
v8::FunctionTemplate::New(isolate, AudioContextConstructor);
|
|
313
|
+
tmpl->SetClassName(Str(isolate, "AudioContext"));
|
|
314
|
+
v8::Local<v8::Function> fn = tmpl->GetFunction(ctx).ToLocalChecked();
|
|
315
|
+
|
|
316
|
+
global->Set(ctx, Str(isolate, "AudioContext"), fn).Check();
|
|
317
|
+
global->Set(ctx, Str(isolate, "webkitAudioContext"), fn).Check();
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
} // namespace next2d
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// 各ブラウザ相当バインディングのインストール宣言。
|
|
2
|
+
// V8Runtime::Initialize から Context 生成後に InstallGlobalBindings が呼ばれる。
|
|
3
|
+
#pragma once
|
|
4
|
+
|
|
5
|
+
#include <v8.h>
|
|
6
|
+
#include <string>
|
|
7
|
+
|
|
8
|
+
namespace next2d {
|
|
9
|
+
|
|
10
|
+
class HostContext;
|
|
11
|
+
|
|
12
|
+
// console.* (log/info/warn/error/debug)
|
|
13
|
+
void InstallConsole(v8::Isolate* isolate, v8::Local<v8::Object> global);
|
|
14
|
+
|
|
15
|
+
// setTimeout / setInterval / clearTimeout / clearInterval /
|
|
16
|
+
// requestAnimationFrame / cancelAnimationFrame / performance.now
|
|
17
|
+
void InstallTimers(v8::Isolate* isolate, v8::Local<v8::Object> global, HostContext* host);
|
|
18
|
+
|
|
19
|
+
// fetch / XMLHttpRequest 最小実装 (assets/app からのローカル読み込み)
|
|
20
|
+
void InstallFetch(v8::Isolate* isolate, v8::Local<v8::Object> global, HostContext* host);
|
|
21
|
+
|
|
22
|
+
// Image / createImageBitmap (WIC デコード)
|
|
23
|
+
void InstallImage(v8::Isolate* isolate, v8::Local<v8::Object> global, HostContext* host);
|
|
24
|
+
|
|
25
|
+
// navigator.gpu (WebGPU -> Dawn)。backend==WebGPU のときのみ navigator.gpu を公開する。
|
|
26
|
+
void InstallWebGPU(v8::Isolate* isolate, v8::Local<v8::Object> global, HostContext* host);
|
|
27
|
+
|
|
28
|
+
// canvas.getContext('webgpu') が返す GPUCanvasContext を生成する (WebGPU.cpp)。
|
|
29
|
+
v8::Local<v8::Object> CreateWebGPUCanvasContext(v8::Isolate* isolate, HostContext* host);
|
|
30
|
+
|
|
31
|
+
// canvas.getContext('2d') / OffscreenCanvas.getContext('2d') を生成する (Canvas2D.cpp)。
|
|
32
|
+
v8::Local<v8::Object> CreateCanvas2DContext(v8::Isolate* isolate, HostContext* host,
|
|
33
|
+
int width, int height);
|
|
34
|
+
|
|
35
|
+
// navigator.getGamepads / gamepad イベント基盤 (GameInput)
|
|
36
|
+
void InstallGamepad(v8::Isolate* isolate, v8::Local<v8::Object> global, HostContext* host);
|
|
37
|
+
|
|
38
|
+
// AudioContext 最小実装 (XAudio2)
|
|
39
|
+
void InstallAudio(v8::Isolate* isolate, v8::Local<v8::Object> global, HostContext* host);
|
|
40
|
+
|
|
41
|
+
// 再生終了した AudioBufferSourceNode へ "ended" を発火する。main.cpp のループから毎フレーム呼ぶ。
|
|
42
|
+
void PumpAudioEvents(v8::Isolate* isolate);
|
|
43
|
+
|
|
44
|
+
// window / document / navigator / screen 等の DOM 相当スタブと canvas 生成
|
|
45
|
+
void InstallDomShims(v8::Isolate* isolate, v8::Local<v8::Object> global, HostContext* host);
|
|
46
|
+
|
|
47
|
+
// HTMLCanvasElement 相当を生成する。getContext は '2d'/'webgpu'/'webgl2' を
|
|
48
|
+
// backend に応じてディスパッチし、transferControlToOffscreen も備える (Canvas.cpp)。
|
|
49
|
+
v8::Local<v8::Object> CreateCanvasElement(v8::Isolate* isolate, HostContext* host,
|
|
50
|
+
int width, int height);
|
|
51
|
+
|
|
52
|
+
// OffscreenCanvas を生成する (Canvas.cpp)。worker/メッシュ生成/テキストで使用。
|
|
53
|
+
v8::Local<v8::Object> CreateOffscreenCanvas(v8::Isolate* isolate, HostContext* host,
|
|
54
|
+
int width, int height);
|
|
55
|
+
|
|
56
|
+
// window/worker グローバルへ Worker コンストラクタ等を設置する (Worker/WorkerRuntime)。
|
|
57
|
+
void InstallWorker(v8::Isolate* isolate, v8::Local<v8::Object> global, HostContext* host);
|
|
58
|
+
|
|
59
|
+
// XMLHttpRequest / Blob / URL.createObjectURL / ImageData (Network.cpp)
|
|
60
|
+
void InstallNetwork(v8::Isolate* isolate, v8::Local<v8::Object> global, HostContext* host);
|
|
61
|
+
|
|
62
|
+
// HTMLVideoElement を生成する (Video.cpp)。document.createElement("video") から使用。
|
|
63
|
+
v8::Local<v8::Object> CreateVideoElement(v8::Isolate* isolate, HostContext* host);
|
|
64
|
+
|
|
65
|
+
// video 要素の現在フレーム RGBA を取得する (Video.cpp)。GetImageSourcePixels から使用。
|
|
66
|
+
bool GetVideoFramePixels(v8::Isolate* isolate, v8::Local<v8::Object> obj,
|
|
67
|
+
const uint8_t** out_rgba, uint32_t* out_w, uint32_t* out_h);
|
|
68
|
+
|
|
69
|
+
// blob: URL のバイト列をテキストとして取得する (Network.cpp)。
|
|
70
|
+
bool ResolveObjectURL(const std::string& url, std::string* out_text);
|
|
71
|
+
|
|
72
|
+
// 上記をまとめてインストールする
|
|
73
|
+
void InstallGlobalBindings(v8::Isolate* isolate, v8::Local<v8::Object> global, HostContext* host);
|
|
74
|
+
|
|
75
|
+
} // namespace next2d
|