feat: updated engine version to 4.4-rc1
This commit is contained in:
parent
ee00efde1f
commit
21ba8e33af
5459 changed files with 1128836 additions and 198305 deletions
|
|
@ -295,10 +295,10 @@ const InternalConfig = function (initConfig) { // eslint-disable-line no-unused-
|
|||
'locateFile': function (path) {
|
||||
if (!path.startsWith('godot.')) {
|
||||
return path;
|
||||
} else if (path.endsWith('.worker.js')) {
|
||||
return `${loadPath}.worker.js`;
|
||||
} else if (path.endsWith('.audio.worklet.js')) {
|
||||
return `${loadPath}.audio.worklet.js`;
|
||||
} else if (path.endsWith('.audio.position.worklet.js')) {
|
||||
return `${loadPath}.audio.position.worklet.js`;
|
||||
} else if (path.endsWith('.js')) {
|
||||
return `${loadPath}.js`;
|
||||
} else if (path in gdext) {
|
||||
|
|
|
|||
|
|
@ -241,7 +241,11 @@ const Engine = (function () {
|
|||
*/
|
||||
installServiceWorker: function () {
|
||||
if (this.config.serviceWorker && 'serviceWorker' in navigator) {
|
||||
return navigator.serviceWorker.register(this.config.serviceWorker);
|
||||
try {
|
||||
return navigator.serviceWorker.register(this.config.serviceWorker);
|
||||
} catch (e) {
|
||||
return Promise.reject(e);
|
||||
}
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
|
|
|
|||
69
engine/platform/web/js/libs/audio.position.worklet.js
Normal file
69
engine/platform/web/js/libs/audio.position.worklet.js
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
/**************************************************************************/
|
||||
/* godot.audio.position.worklet.js */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
const POST_THRESHOLD_S = 0.1;
|
||||
|
||||
class GodotPositionReportingProcessor extends AudioWorkletProcessor {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
this.lastPostTime = currentTime;
|
||||
this.position = 0;
|
||||
this.ended = false;
|
||||
|
||||
this.port.onmessage = (event) => {
|
||||
if (event?.data?.type === 'ended') {
|
||||
this.ended = true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
process(inputs, _outputs, _parameters) {
|
||||
if (this.ended) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (inputs.length > 0) {
|
||||
const input = inputs[0];
|
||||
if (input.length > 0) {
|
||||
this.position += input[0].length;
|
||||
}
|
||||
}
|
||||
|
||||
// Posting messages is expensive. Let's limit the number of posts.
|
||||
if (currentTime - this.lastPostTime > POST_THRESHOLD_S) {
|
||||
this.lastPostTime = currentTime;
|
||||
this.port.postMessage({ type: 'position', data: this.position });
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
registerProcessor('godot-position-reporting-processor', GodotPositionReportingProcessor);
|
||||
|
|
@ -77,7 +77,7 @@ class Sample {
|
|||
* Creates a `Sample` based on the params. Will register it to the
|
||||
* `GodotAudio.samples` registry.
|
||||
* @param {SampleParams} params Base params
|
||||
* @param {SampleOptions} [options={{}}] Optional params
|
||||
* @param {SampleOptions | undefined} options Optional params.
|
||||
* @returns {Sample}
|
||||
*/
|
||||
static create(params, options = {}) {
|
||||
|
|
@ -98,7 +98,7 @@ class Sample {
|
|||
/**
|
||||
* `Sample` constructor.
|
||||
* @param {SampleParams} params Base params
|
||||
* @param {SampleOptions} [options={{}}] Optional params
|
||||
* @param {SampleOptions | undefined} options Optional params.
|
||||
*/
|
||||
constructor(params, options = {}) {
|
||||
/** @type {string} */
|
||||
|
|
@ -328,8 +328,10 @@ class SampleNodeBus {
|
|||
* offset?: number
|
||||
* playbackRate?: number
|
||||
* startTime?: number
|
||||
* pitchScale?: number
|
||||
* loopMode?: LoopMode
|
||||
* volume?: Float32Array
|
||||
* start?: boolean
|
||||
* }} SampleNodeOptions
|
||||
*/
|
||||
|
||||
|
|
@ -391,7 +393,7 @@ class SampleNode {
|
|||
* Creates a `SampleNode` based on the params. Will register the `SampleNode` to
|
||||
* the `GodotAudio.sampleNodes` regisery.
|
||||
* @param {SampleNodeParams} params Base params.
|
||||
* @param {SampleNodeOptions} options Optional params.
|
||||
* @param {SampleNodeOptions | undefined} options Optional params.
|
||||
* @returns {SampleNode}
|
||||
*/
|
||||
static create(params, options = {}) {
|
||||
|
|
@ -411,7 +413,7 @@ class SampleNode {
|
|||
|
||||
/**
|
||||
* @param {SampleNodeParams} params Base params
|
||||
* @param {SampleNodeOptions} [options={{}}] Optional params
|
||||
* @param {SampleNodeOptions | undefined} options Optional params.
|
||||
*/
|
||||
constructor(params, options = {}) {
|
||||
/** @type {string} */
|
||||
|
|
@ -421,9 +423,15 @@ class SampleNode {
|
|||
/** @type {number} */
|
||||
this.offset = options.offset ?? 0;
|
||||
/** @type {number} */
|
||||
this._playbackPosition = options.offset;
|
||||
/** @type {number} */
|
||||
this.startTime = options.startTime ?? 0;
|
||||
/** @type {boolean} */
|
||||
this.isPaused = false;
|
||||
/** @type {boolean} */
|
||||
this.isStarted = false;
|
||||
/** @type {boolean} */
|
||||
this.isCanceled = false;
|
||||
/** @type {number} */
|
||||
this.pauseTime = 0;
|
||||
/** @type {number} */
|
||||
|
|
@ -431,7 +439,7 @@ class SampleNode {
|
|||
/** @type {LoopMode} */
|
||||
this.loopMode = options.loopMode ?? this.getSample().loopMode ?? 'disabled';
|
||||
/** @type {number} */
|
||||
this._pitchScale = 1;
|
||||
this._pitchScale = options.pitchScale ?? 1;
|
||||
/** @type {number} */
|
||||
this._sourceStartTime = 0;
|
||||
/** @type {Map<Bus, SampleNodeBus>} */
|
||||
|
|
@ -440,6 +448,8 @@ class SampleNode {
|
|||
this._source = GodotAudio.ctx.createBufferSource();
|
||||
|
||||
this._onended = null;
|
||||
/** @type {AudioWorkletNode | null} */
|
||||
this._positionWorklet = null;
|
||||
|
||||
this.setPlaybackRate(options.playbackRate ?? 44100);
|
||||
this._source.buffer = this.getSample().getAudioBuffer();
|
||||
|
|
@ -449,6 +459,12 @@ class SampleNode {
|
|||
const bus = GodotAudio.Bus.getBus(params.busIndex);
|
||||
const sampleNodeBus = this.getSampleNodeBus(bus);
|
||||
sampleNodeBus.setVolume(options.volume);
|
||||
|
||||
this.connectPositionWorklet(options.start).catch((err) => {
|
||||
const newErr = new Error('Failed to create PositionWorklet.');
|
||||
newErr.cause = err;
|
||||
GodotRuntime.error(newErr);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -459,6 +475,14 @@ class SampleNode {
|
|||
return this._playbackRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the playback position.
|
||||
* @returns {number}
|
||||
*/
|
||||
getPlaybackPosition() {
|
||||
return this._playbackPosition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the playback rate.
|
||||
* @param {number} val Value to set.
|
||||
|
|
@ -508,8 +532,12 @@ class SampleNode {
|
|||
* @returns {void}
|
||||
*/
|
||||
start() {
|
||||
if (this.isStarted) {
|
||||
return;
|
||||
}
|
||||
this._resetSourceStartTime();
|
||||
this._source.start(this.startTime, this.offset);
|
||||
this.isStarted = true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -584,18 +612,60 @@ class SampleNode {
|
|||
return this._sampleNodeBuses.get(bus);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up and connects the source to the GodotPositionReportingProcessor
|
||||
* If the worklet module is not loaded in, it will be added
|
||||
*/
|
||||
async connectPositionWorklet(start) {
|
||||
await GodotAudio.audioPositionWorkletPromise;
|
||||
if (this.isCanceled) {
|
||||
return;
|
||||
}
|
||||
this._source.connect(this.getPositionWorklet());
|
||||
if (start) {
|
||||
this.start();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a AudioWorkletProcessor
|
||||
* @returns {AudioWorkletNode}
|
||||
*/
|
||||
getPositionWorklet() {
|
||||
if (this._positionWorklet != null) {
|
||||
return this._positionWorklet;
|
||||
}
|
||||
this._positionWorklet = new AudioWorkletNode(
|
||||
GodotAudio.ctx,
|
||||
'godot-position-reporting-processor'
|
||||
);
|
||||
this._positionWorklet.port.onmessage = (event) => {
|
||||
switch (event.data['type']) {
|
||||
case 'position':
|
||||
this._playbackPosition = (parseInt(event.data.data, 10) / this.getSample().sampleRate) + this.offset;
|
||||
break;
|
||||
default:
|
||||
// Do nothing.
|
||||
}
|
||||
};
|
||||
return this._positionWorklet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the `SampleNode`.
|
||||
* @returns {void}
|
||||
*/
|
||||
clear() {
|
||||
this.isCanceled = true;
|
||||
this.isPaused = false;
|
||||
this.pauseTime = 0;
|
||||
|
||||
if (this._source != null) {
|
||||
this._source.removeEventListener('ended', this._onended);
|
||||
this._onended = null;
|
||||
this._source.stop();
|
||||
if (this.isStarted) {
|
||||
this._source.stop();
|
||||
}
|
||||
this._source.disconnect();
|
||||
this._source = null;
|
||||
}
|
||||
|
|
@ -605,6 +675,13 @@ class SampleNode {
|
|||
}
|
||||
this._sampleNodeBuses.clear();
|
||||
|
||||
if (this._positionWorklet) {
|
||||
this._positionWorklet.disconnect();
|
||||
this._positionWorklet.port.onmessage = null;
|
||||
this._positionWorklet.port.postMessage({ type: 'ended' });
|
||||
this._positionWorklet = null;
|
||||
}
|
||||
|
||||
GodotAudio.SampleNode.delete(this.id);
|
||||
}
|
||||
|
||||
|
|
@ -645,7 +722,12 @@ class SampleNode {
|
|||
const pauseTime = this.isPaused
|
||||
? this.pauseTime
|
||||
: 0;
|
||||
if (this._positionWorklet != null) {
|
||||
this._positionWorklet.port.postMessage({ type: 'clear' });
|
||||
this._source.connect(this._positionWorklet);
|
||||
}
|
||||
this._source.start(this.startTime, this.offset + pauseTime);
|
||||
this.isStarted = true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -653,6 +735,9 @@ class SampleNode {
|
|||
* @returns {void}
|
||||
*/
|
||||
_pause() {
|
||||
if (!this.isStarted) {
|
||||
return;
|
||||
}
|
||||
this.isPaused = true;
|
||||
this.pauseTime = (GodotAudio.ctx.currentTime - this._sourceStartTime) / this.getPlaybackRate();
|
||||
this._source.stop();
|
||||
|
|
@ -780,7 +865,10 @@ class Bus {
|
|||
* @returns {void}
|
||||
*/
|
||||
static move(fromIndex, toIndex) {
|
||||
const movedBus = GodotAudio.Bus.getBus(fromIndex);
|
||||
const movedBus = GodotAudio.Bus.getBusOrNull(fromIndex);
|
||||
if (movedBus == null) {
|
||||
return;
|
||||
}
|
||||
const buses = GodotAudio.buses.filter((_, i) => i !== fromIndex);
|
||||
// Inserts at index.
|
||||
buses.splice(toIndex - 1, 0, movedBus);
|
||||
|
|
@ -1108,6 +1196,9 @@ const _GodotAudio = {
|
|||
driver: null,
|
||||
interval: 0,
|
||||
|
||||
/** @type {Promise} */
|
||||
audioPositionWorkletPromise: null,
|
||||
|
||||
/**
|
||||
* Converts linear volume to Db.
|
||||
* @param {number} linear Linear value to convert.
|
||||
|
|
@ -1174,6 +1265,10 @@ const _GodotAudio = {
|
|||
onlatencyupdate(computed_latency);
|
||||
}, 1000);
|
||||
GodotOS.atexit(GodotAudio.close_async);
|
||||
|
||||
const path = GodotConfig.locate_file('godot.audio.position.worklet.js');
|
||||
GodotAudio.audioPositionWorkletPromise = ctx.audioWorklet.addModule(path);
|
||||
|
||||
return ctx.destination.channelCount;
|
||||
},
|
||||
|
||||
|
|
@ -1252,7 +1347,7 @@ const _GodotAudio = {
|
|||
* @param {string} playbackObjectId The unique id of the sample playback
|
||||
* @param {string} streamObjectId The unique id of the stream
|
||||
* @param {number} busIndex Index of the bus currently binded to the sample playback
|
||||
* @param {SampleNodeOptions} startOptions Optional params
|
||||
* @param {SampleNodeOptions | undefined} startOptions Optional params.
|
||||
* @returns {void}
|
||||
*/
|
||||
start_sample: function (
|
||||
|
|
@ -1262,7 +1357,7 @@ const _GodotAudio = {
|
|||
startOptions
|
||||
) {
|
||||
GodotAudio.SampleNode.stopSampleNode(playbackObjectId);
|
||||
const sampleNode = GodotAudio.SampleNode.create(
|
||||
GodotAudio.SampleNode.create(
|
||||
{
|
||||
busIndex,
|
||||
id: playbackObjectId,
|
||||
|
|
@ -1270,7 +1365,6 @@ const _GodotAudio = {
|
|||
},
|
||||
startOptions
|
||||
);
|
||||
sampleNode.start();
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
@ -1337,7 +1431,10 @@ const _GodotAudio = {
|
|||
* @returns {void}
|
||||
*/
|
||||
remove_sample_bus: function (index) {
|
||||
const bus = GodotAudio.Bus.getBus(index);
|
||||
const bus = GodotAudio.Bus.getBusOrNull(index);
|
||||
if (bus == null) {
|
||||
return;
|
||||
}
|
||||
bus.clear();
|
||||
},
|
||||
|
||||
|
|
@ -1367,8 +1464,17 @@ const _GodotAudio = {
|
|||
* @returns {void}
|
||||
*/
|
||||
set_sample_bus_send: function (busIndex, sendIndex) {
|
||||
const bus = GodotAudio.Bus.getBus(busIndex);
|
||||
bus.setSend(GodotAudio.Bus.getBus(sendIndex));
|
||||
const bus = GodotAudio.Bus.getBusOrNull(busIndex);
|
||||
if (bus == null) {
|
||||
// Cannot send from an invalid bus.
|
||||
return;
|
||||
}
|
||||
let targetBus = GodotAudio.Bus.getBusOrNull(sendIndex);
|
||||
if (targetBus == null) {
|
||||
// Send to master.
|
||||
targetBus = GodotAudio.Bus.getBus(0);
|
||||
}
|
||||
bus.setSend(targetBus);
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
@ -1378,7 +1484,10 @@ const _GodotAudio = {
|
|||
* @returns {void}
|
||||
*/
|
||||
set_sample_bus_volume_db: function (busIndex, volumeDb) {
|
||||
const bus = GodotAudio.Bus.getBus(busIndex);
|
||||
const bus = GodotAudio.Bus.getBusOrNull(busIndex);
|
||||
if (bus == null) {
|
||||
return;
|
||||
}
|
||||
bus.setVolumeDb(volumeDb);
|
||||
},
|
||||
|
||||
|
|
@ -1389,7 +1498,10 @@ const _GodotAudio = {
|
|||
* @returns {void}
|
||||
*/
|
||||
set_sample_bus_solo: function (busIndex, enable) {
|
||||
const bus = GodotAudio.Bus.getBus(busIndex);
|
||||
const bus = GodotAudio.Bus.getBusOrNull(busIndex);
|
||||
if (bus == null) {
|
||||
return;
|
||||
}
|
||||
bus.solo(enable);
|
||||
},
|
||||
|
||||
|
|
@ -1400,7 +1512,10 @@ const _GodotAudio = {
|
|||
* @returns {void}
|
||||
*/
|
||||
set_sample_bus_mute: function (busIndex, enable) {
|
||||
const bus = GodotAudio.Bus.getBus(busIndex);
|
||||
const bus = GodotAudio.Bus.getBusOrNull(busIndex);
|
||||
if (bus == null) {
|
||||
return;
|
||||
}
|
||||
bus.mute(enable);
|
||||
},
|
||||
},
|
||||
|
|
@ -1562,13 +1677,14 @@ const _GodotAudio = {
|
|||
},
|
||||
|
||||
godot_audio_sample_start__proxy: 'sync',
|
||||
godot_audio_sample_start__sig: 'viiiii',
|
||||
godot_audio_sample_start__sig: 'viiiifi',
|
||||
/**
|
||||
* Starts a sample.
|
||||
* @param {number} playbackObjectIdStrPtr Playback object id pointer
|
||||
* @param {number} streamObjectIdStrPtr Stream object id pointer
|
||||
* @param {number} busIndex Bus index
|
||||
* @param {number} offset Sample offset
|
||||
* @param {number} pitchScale Pitch scale
|
||||
* @param {number} volumePtr Volume pointer
|
||||
* @returns {void}
|
||||
*/
|
||||
|
|
@ -1577,6 +1693,7 @@ const _GodotAudio = {
|
|||
streamObjectIdStrPtr,
|
||||
busIndex,
|
||||
offset,
|
||||
pitchScale,
|
||||
volumePtr
|
||||
) {
|
||||
/** @type {string} */
|
||||
|
|
@ -1585,11 +1702,13 @@ const _GodotAudio = {
|
|||
const streamObjectId = GodotRuntime.parseString(streamObjectIdStrPtr);
|
||||
/** @type {Float32Array} */
|
||||
const volume = GodotRuntime.heapSub(HEAPF32, volumePtr, 8);
|
||||
/** @type {SampleNodeConstructorOptions} */
|
||||
/** @type {SampleNodeOptions} */
|
||||
const startOptions = {
|
||||
offset,
|
||||
volume,
|
||||
playbackRate: 1,
|
||||
pitchScale,
|
||||
start: true,
|
||||
};
|
||||
GodotAudio.start_sample(
|
||||
playbackObjectId,
|
||||
|
|
@ -1635,6 +1754,22 @@ const _GodotAudio = {
|
|||
return Number(GodotAudio.sampleNodes.has(playbackObjectId));
|
||||
},
|
||||
|
||||
godot_audio_get_sample_playback_position__proxy: 'sync',
|
||||
godot_audio_get_sample_playback_position__sig: 'di',
|
||||
/**
|
||||
* Returns the position of the playback position.
|
||||
* @param {number} playbackObjectIdStrPtr Playback object id pointer
|
||||
* @returns {number}
|
||||
*/
|
||||
godot_audio_get_sample_playback_position: function (playbackObjectIdStrPtr) {
|
||||
const playbackObjectId = GodotRuntime.parseString(playbackObjectIdStrPtr);
|
||||
const sampleNode = GodotAudio.SampleNode.getSampleNodeOrNull(playbackObjectId);
|
||||
if (sampleNode == null) {
|
||||
return 0;
|
||||
}
|
||||
return sampleNode.getPlaybackPosition();
|
||||
},
|
||||
|
||||
godot_audio_sample_update_pitch_scale__proxy: 'sync',
|
||||
godot_audio_sample_update_pitch_scale__sig: 'vii',
|
||||
/**
|
||||
|
|
@ -2029,7 +2164,7 @@ autoAddDeps(GodotAudioWorklet, '$GodotAudioWorklet');
|
|||
mergeInto(LibraryManager.library, GodotAudioWorklet);
|
||||
|
||||
/*
|
||||
* The ScriptProcessorNode API, used when threads are disabled.
|
||||
* The ScriptProcessorNode API, used as a fallback if AudioWorklet is not available.
|
||||
*/
|
||||
const GodotAudioScript = {
|
||||
$GodotAudioScript__deps: ['$GodotAudio'],
|
||||
|
|
|
|||
|
|
@ -59,7 +59,12 @@ const GodotFetch = {
|
|||
});
|
||||
obj.status = response.status;
|
||||
obj.response = response;
|
||||
obj.reader = response.body.getReader();
|
||||
// `body` can be null per spec (for example, in cases where the request method is HEAD).
|
||||
// As of the time of writing, Chromium (127.0.6533.72) does not follow the spec but Firefox (131.0.3) does.
|
||||
// See godotengine/godot#76825 for more information.
|
||||
// See Chromium revert (of the change to follow the spec):
|
||||
// https://chromium.googlesource.com/chromium/src/+/135354b7bdb554cd03c913af7c90aceead03c4d4
|
||||
obj.reader = response.body?.getReader();
|
||||
obj.chunked = chunked;
|
||||
},
|
||||
|
||||
|
|
@ -121,6 +126,10 @@ const GodotFetch = {
|
|||
}
|
||||
obj.reading = true;
|
||||
obj.reader.read().then(GodotFetch.onread.bind(null, id)).catch(GodotFetch.onerror.bind(null, id));
|
||||
} else if (obj.reader == null && obj.response.body == null) {
|
||||
// Emulate a stream closure to maintain the request lifecycle.
|
||||
obj.reading = true;
|
||||
GodotFetch.onread(id, { value: undefined, done: true });
|
||||
}
|
||||
},
|
||||
},
|
||||
|
|
@ -159,7 +168,10 @@ const GodotFetch = {
|
|||
if (!obj.response) {
|
||||
return 0;
|
||||
}
|
||||
if (obj.reader) {
|
||||
// If the reader is nullish, but there is no body, and the request is not marked as done,
|
||||
// the same status should be returned as though the request is currently being read
|
||||
// so that the proper lifecycle closure can be handled in `read()`.
|
||||
if (obj.reader || (obj.response.body == null && !obj.done)) {
|
||||
return 1;
|
||||
}
|
||||
if (obj.done) {
|
||||
|
|
|
|||
|
|
@ -38,41 +38,57 @@ const GodotIME = {
|
|||
$GodotIME: {
|
||||
ime: null,
|
||||
active: false,
|
||||
focusTimerIntervalId: -1,
|
||||
|
||||
getModifiers: function (evt) {
|
||||
return (evt.shiftKey + 0) + ((evt.altKey + 0) << 1) + ((evt.ctrlKey + 0) << 2) + ((evt.metaKey + 0) << 3);
|
||||
},
|
||||
|
||||
ime_active: function (active) {
|
||||
function focus_timer() {
|
||||
GodotIME.active = true;
|
||||
function clearFocusTimerInterval() {
|
||||
clearInterval(GodotIME.focusTimerIntervalId);
|
||||
GodotIME.focusTimerIntervalId = -1;
|
||||
}
|
||||
|
||||
function focusTimer() {
|
||||
if (GodotIME.ime == null) {
|
||||
clearFocusTimerInterval();
|
||||
return;
|
||||
}
|
||||
GodotIME.ime.focus();
|
||||
}
|
||||
|
||||
if (GodotIME.ime) {
|
||||
if (active) {
|
||||
GodotIME.ime.style.display = 'block';
|
||||
setInterval(focus_timer, 100);
|
||||
} else {
|
||||
GodotIME.ime.style.display = 'none';
|
||||
GodotConfig.canvas.focus();
|
||||
GodotIME.active = false;
|
||||
}
|
||||
if (GodotIME.focusTimerIntervalId > -1) {
|
||||
clearFocusTimerInterval();
|
||||
}
|
||||
|
||||
if (GodotIME.ime == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
GodotIME.active = active;
|
||||
if (active) {
|
||||
GodotIME.ime.style.display = 'block';
|
||||
GodotIME.focusTimerIntervalId = setInterval(focusTimer, 100);
|
||||
} else {
|
||||
GodotIME.ime.style.display = 'none';
|
||||
GodotConfig.canvas.focus();
|
||||
}
|
||||
},
|
||||
|
||||
ime_position: function (x, y) {
|
||||
if (GodotIME.ime) {
|
||||
const canvas = GodotConfig.canvas;
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
const rw = canvas.width / rect.width;
|
||||
const rh = canvas.height / rect.height;
|
||||
const clx = (x / rw) + rect.x;
|
||||
const cly = (y / rh) + rect.y;
|
||||
|
||||
GodotIME.ime.style.left = `${clx}px`;
|
||||
GodotIME.ime.style.top = `${cly}px`;
|
||||
if (GodotIME.ime == null) {
|
||||
return;
|
||||
}
|
||||
const canvas = GodotConfig.canvas;
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
const rw = canvas.width / rect.width;
|
||||
const rh = canvas.height / rect.height;
|
||||
const clx = (x / rw) + rect.x;
|
||||
const cly = (y / rh) + rect.y;
|
||||
|
||||
GodotIME.ime.style.left = `${clx}px`;
|
||||
GodotIME.ime.style.top = `${cly}px`;
|
||||
},
|
||||
|
||||
init: function (ime_cb, key_cb, code, key) {
|
||||
|
|
@ -84,20 +100,27 @@ const GodotIME = {
|
|||
evt.preventDefault();
|
||||
}
|
||||
function ime_event_cb(event) {
|
||||
if (GodotIME.ime) {
|
||||
if (event.type === 'compositionstart') {
|
||||
ime_cb(0, null);
|
||||
GodotIME.ime.innerHTML = '';
|
||||
} else if (event.type === 'compositionupdate') {
|
||||
const ptr = GodotRuntime.allocString(event.data);
|
||||
ime_cb(1, ptr);
|
||||
GodotRuntime.free(ptr);
|
||||
} else if (event.type === 'compositionend') {
|
||||
const ptr = GodotRuntime.allocString(event.data);
|
||||
ime_cb(2, ptr);
|
||||
GodotRuntime.free(ptr);
|
||||
GodotIME.ime.innerHTML = '';
|
||||
}
|
||||
if (GodotIME.ime == null) {
|
||||
return;
|
||||
}
|
||||
switch (event.type) {
|
||||
case 'compositionstart':
|
||||
ime_cb(0, null);
|
||||
GodotIME.ime.innerHTML = '';
|
||||
break;
|
||||
case 'compositionupdate': {
|
||||
const ptr = GodotRuntime.allocString(event.data);
|
||||
ime_cb(1, ptr);
|
||||
GodotRuntime.free(ptr);
|
||||
} break;
|
||||
case 'compositionend': {
|
||||
const ptr = GodotRuntime.allocString(event.data);
|
||||
ime_cb(2, ptr);
|
||||
GodotRuntime.free(ptr);
|
||||
GodotIME.ime.innerHTML = '';
|
||||
} break;
|
||||
default:
|
||||
// Do nothing.
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -133,10 +156,15 @@ const GodotIME = {
|
|||
},
|
||||
|
||||
clear: function () {
|
||||
if (GodotIME.ime) {
|
||||
GodotIME.ime.remove();
|
||||
GodotIME.ime = null;
|
||||
if (GodotIME.ime == null) {
|
||||
return;
|
||||
}
|
||||
if (GodotIME.focusTimerIntervalId > -1) {
|
||||
clearInterval(GodotIME.focusTimerIntervalId);
|
||||
GodotIME.focusTimerIntervalId = -1;
|
||||
}
|
||||
GodotIME.ime.remove();
|
||||
GodotIME.ime = null;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -127,6 +127,10 @@ const GodotJSWrapper = {
|
|||
GodotRuntime.setHeapValue(p_exchange, id, 'i64');
|
||||
return 24; // OBJECT
|
||||
},
|
||||
|
||||
isBuffer: function (obj) {
|
||||
return obj instanceof ArrayBuffer || ArrayBuffer.isView(obj);
|
||||
},
|
||||
},
|
||||
|
||||
godot_js_wrapper_interface_get__proxy: 'sync',
|
||||
|
|
@ -303,6 +307,34 @@ const GodotJSWrapper = {
|
|||
return -1;
|
||||
}
|
||||
},
|
||||
|
||||
godot_js_wrapper_object_is_buffer__proxy: 'sync',
|
||||
godot_js_wrapper_object_is_buffer__sig: 'ii',
|
||||
godot_js_wrapper_object_is_buffer: function (p_id) {
|
||||
const obj = GodotJSWrapper.get_proxied_value(p_id);
|
||||
return GodotJSWrapper.isBuffer(obj)
|
||||
? 1
|
||||
: 0;
|
||||
},
|
||||
|
||||
godot_js_wrapper_object_transfer_buffer__proxy: 'sync',
|
||||
godot_js_wrapper_object_transfer_buffer__sig: 'viiii',
|
||||
godot_js_wrapper_object_transfer_buffer: function (p_id, p_byte_arr, p_byte_arr_write, p_callback) {
|
||||
let obj = GodotJSWrapper.get_proxied_value(p_id);
|
||||
if (!GodotJSWrapper.isBuffer(obj)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ArrayBuffer.isView(obj) && !(obj instanceof Uint8Array)) {
|
||||
obj = new Uint8Array(obj.buffer);
|
||||
} else if (obj instanceof ArrayBuffer) {
|
||||
obj = new Uint8Array(obj);
|
||||
}
|
||||
|
||||
const resizePackedByteArrayAndOpenWrite = GodotRuntime.get_func(p_callback);
|
||||
const bytesPtr = resizePackedByteArrayAndOpenWrite(p_byte_arr, p_byte_arr_write, obj.length);
|
||||
HEAPU8.set(obj, bytesPtr);
|
||||
},
|
||||
};
|
||||
|
||||
autoAddDeps(GodotJSWrapper, '$GodotJSWrapper');
|
||||
|
|
|
|||
|
|
@ -441,8 +441,12 @@ const GodotPWA = {
|
|||
godot_js_pwa_cb__sig: 'vi',
|
||||
godot_js_pwa_cb: function (p_update_cb) {
|
||||
if ('serviceWorker' in navigator) {
|
||||
const cb = GodotRuntime.get_func(p_update_cb);
|
||||
navigator.serviceWorker.getRegistration().then(GodotPWA.updateState.bind(null, cb));
|
||||
try {
|
||||
const cb = GodotRuntime.get_func(p_update_cb);
|
||||
navigator.serviceWorker.getRegistration().then(GodotPWA.updateState.bind(null, cb));
|
||||
} catch (e) {
|
||||
GodotRuntime.error('Failed to assign PWA callback', e);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -450,12 +454,17 @@ const GodotPWA = {
|
|||
godot_js_pwa_update__sig: 'i',
|
||||
godot_js_pwa_update: function () {
|
||||
if ('serviceWorker' in navigator && GodotPWA.hasUpdate) {
|
||||
navigator.serviceWorker.getRegistration().then(function (reg) {
|
||||
if (!reg || !reg.waiting) {
|
||||
return;
|
||||
}
|
||||
reg.waiting.postMessage('update');
|
||||
});
|
||||
try {
|
||||
navigator.serviceWorker.getRegistration().then(function (reg) {
|
||||
if (!reg || !reg.waiting) {
|
||||
return;
|
||||
}
|
||||
reg.waiting.postMessage('update');
|
||||
});
|
||||
} catch (e) {
|
||||
GodotRuntime.error(e);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
|
|
|
|||
94
engine/platform/web/js/libs/library_godot_webmidi.js
Normal file
94
engine/platform/web/js/libs/library_godot_webmidi.js
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
/**************************************************************************/
|
||||
/* library_godot_webmidi.js */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
const GodotWebMidi = {
|
||||
|
||||
$GodotWebMidi__deps: ['$GodotRuntime'],
|
||||
$GodotWebMidi: {
|
||||
abortControllers: [],
|
||||
isListening: false,
|
||||
},
|
||||
|
||||
godot_js_webmidi_open_midi_inputs__deps: ['$GodotWebMidi'],
|
||||
godot_js_webmidi_open_midi_inputs__proxy: 'sync',
|
||||
godot_js_webmidi_open_midi_inputs__sig: 'iiii',
|
||||
godot_js_webmidi_open_midi_inputs: function (pSetInputNamesCb, pOnMidiMessageCb, pDataBuffer, dataBufferLen) {
|
||||
if (GodotWebMidi.is_listening) {
|
||||
return 0; // OK
|
||||
}
|
||||
if (!navigator.requestMIDIAccess) {
|
||||
return 2; // ERR_UNAVAILABLE
|
||||
}
|
||||
const setInputNamesCb = GodotRuntime.get_func(pSetInputNamesCb);
|
||||
const onMidiMessageCb = GodotRuntime.get_func(pOnMidiMessageCb);
|
||||
|
||||
GodotWebMidi.isListening = true;
|
||||
navigator.requestMIDIAccess().then((midi) => {
|
||||
const inputs = [...midi.inputs.values()];
|
||||
const inputNames = inputs.map((input) => input.name);
|
||||
|
||||
const c_ptr = GodotRuntime.allocStringArray(inputNames);
|
||||
setInputNamesCb(inputNames.length, c_ptr);
|
||||
GodotRuntime.freeStringArray(c_ptr, inputNames.length);
|
||||
|
||||
inputs.forEach((input, i) => {
|
||||
const abortController = new AbortController();
|
||||
GodotWebMidi.abortControllers.push(abortController);
|
||||
input.addEventListener('midimessage', (event) => {
|
||||
const status = event.data[0];
|
||||
const data = event.data.slice(1);
|
||||
const size = data.length;
|
||||
|
||||
if (size > dataBufferLen) {
|
||||
throw new Error(`data too big ${size} > ${dataBufferLen}`);
|
||||
}
|
||||
HEAPU8.set(data, pDataBuffer);
|
||||
|
||||
onMidiMessageCb(i, status, pDataBuffer, data.length);
|
||||
}, { signal: abortController.signal });
|
||||
});
|
||||
});
|
||||
|
||||
return 0; // OK
|
||||
},
|
||||
|
||||
godot_js_webmidi_close_midi_inputs__deps: ['$GodotWebMidi'],
|
||||
godot_js_webmidi_close_midi_inputs__proxy: 'sync',
|
||||
godot_js_webmidi_close_midi_inputs__sig: 'v',
|
||||
godot_js_webmidi_close_midi_inputs: function () {
|
||||
for (const abortController of GodotWebMidi.abortControllers) {
|
||||
abortController.abort();
|
||||
}
|
||||
GodotWebMidi.abortControllers = [];
|
||||
GodotWebMidi.isListening = false;
|
||||
},
|
||||
};
|
||||
|
||||
mergeInto(LibraryManager.library, GodotWebMidi);
|
||||
Loading…
Add table
Add a link
Reference in a new issue