diff options
Diffstat (limited to 'platform/javascript/js/libs')
| -rw-r--r-- | platform/javascript/js/libs/audio.worklet.js | 186 | ||||
| -rw-r--r-- | platform/javascript/js/libs/library_godot_audio.js | 340 | ||||
| -rw-r--r-- | platform/javascript/js/libs/library_godot_display.js | 477 | ||||
| -rw-r--r-- | platform/javascript/js/libs/library_godot_editor_tools.js | 56 | ||||
| -rw-r--r-- | platform/javascript/js/libs/library_godot_eval.js | 87 | ||||
| -rw-r--r-- | platform/javascript/js/libs/library_godot_http_request.js | 143 | ||||
| -rw-r--r-- | platform/javascript/js/libs/library_godot_os.js | 280 | ||||
| -rw-r--r-- | platform/javascript/js/libs/library_godot_runtime.js | 120 | 
8 files changed, 1689 insertions, 0 deletions
diff --git a/platform/javascript/js/libs/audio.worklet.js b/platform/javascript/js/libs/audio.worklet.js new file mode 100644 index 0000000000..a27035ef22 --- /dev/null +++ b/platform/javascript/js/libs/audio.worklet.js @@ -0,0 +1,186 @@ +/*************************************************************************/ +/*  audio.worklet.js                                                     */ +/*************************************************************************/ +/*                       This file is part of:                           */ +/*                           GODOT ENGINE                                */ +/*                      https://godotengine.org                          */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md).   */ +/*                                                                       */ +/* 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.                */ +/*************************************************************************/ + +class RingBuffer { +	constructor(p_buffer, p_state) { +		this.buffer = p_buffer; +		this.avail = p_state; +		this.rpos = 0; +		this.wpos = 0; +	} + +	data_left() { +		return Atomics.load(this.avail, 0); +	} + +	space_left() { +		return this.buffer.length - this.data_left(); +	} + +	read(output) { +		const size = this.buffer.length; +		let from = 0; +		let to_write = output.length; +		if (this.rpos + to_write > size) { +			const high = size - this.rpos; +			output.set(this.buffer.subarray(this.rpos, size)); +			from = high; +			to_write -= high; +			this.rpos = 0; +		} +		output.set(this.buffer.subarray(this.rpos, this.rpos + to_write), from); +		this.rpos += to_write; +		Atomics.add(this.avail, 0, -output.length); +		Atomics.notify(this.avail, 0); +	} + +	write(p_buffer) { +		const to_write = p_buffer.length; +		const mw = this.buffer.length - this.wpos; +		if (mw >= to_write) { +			this.buffer.set(p_buffer, this.wpos); +		} else { +			const high = p_buffer.subarray(0, to_write - mw); +			const low = p_buffer.subarray(to_write - mw); +			this.buffer.set(high, this.wpos); +			this.buffer.set(low); +		} +		let diff = to_write; +		if (this.wpos + diff >= this.buffer.length) { +			diff -= this.buffer.length; +		} +		this.wpos += diff; +		Atomics.add(this.avail, 0, to_write); +		Atomics.notify(this.avail, 0); +	} +} + +class GodotProcessor extends AudioWorkletProcessor { +	constructor() { +		super(); +		this.running = true; +		this.lock = null; +		this.notifier = null; +		this.output = null; +		this.output_buffer = new Float32Array(); +		this.input = null; +		this.input_buffer = new Float32Array(); +		this.port.onmessage = (event) => { +			const cmd = event.data['cmd']; +			const data = event.data['data']; +			this.parse_message(cmd, data); +		}; +	} + +	process_notify() { +		Atomics.add(this.notifier, 0, 1); +		Atomics.notify(this.notifier, 0); +	} + +	parse_message(p_cmd, p_data) { +		if (p_cmd === "start" && p_data) { +			const state = p_data[0]; +			let idx = 0; +			this.lock = state.subarray(idx, ++idx); +			this.notifier = state.subarray(idx, ++idx); +			const avail_in = state.subarray(idx, ++idx); +			const avail_out = state.subarray(idx, ++idx); +			this.input = new RingBuffer(p_data[1], avail_in); +			this.output = new RingBuffer(p_data[2], avail_out); +		} else if (p_cmd === "stop") { +			this.runing = false; +			this.output = null; +			this.input = null; +		} +	} + +	static array_has_data(arr) { +		return arr.length && arr[0].length && arr[0][0].length; +	} + +	process(inputs, outputs, parameters) { +		if (!this.running) { +			return false; // Stop processing. +		} +		if (this.output === null) { +			return true; // Not ready yet, keep processing. +		} +		const process_input = GodotProcessor.array_has_data(inputs); +		if (process_input) { +			const input = inputs[0]; +			const chunk = input[0].length * input.length; +			if (this.input_buffer.length !== chunk) { +				this.input_buffer = new Float32Array(chunk); +			} +			if (this.input.space_left() >= chunk) { +				GodotProcessor.write_input(this.input_buffer, input); +				this.input.write(this.input_buffer); +			} else { +				this.port.postMessage("Input buffer is full! Skipping input frame."); +			} +		} +		const process_output = GodotProcessor.array_has_data(outputs); +		if (process_output) { +			const output = outputs[0]; +			const chunk = output[0].length * output.length; +			if (this.output_buffer.length !== chunk) { +				this.output_buffer = new Float32Array(chunk); +			} +			if (this.output.data_left() >= chunk) { +				this.output.read(this.output_buffer); +				GodotProcessor.write_output(output, this.output_buffer); +			} else { +				this.port.postMessage("Output buffer has not enough frames! Skipping output frame."); +			} +		} +		this.process_notify(); +		return true; +	} + +	static write_output(dest, source) { +		const channels = dest.length; +		for (let ch = 0; ch < channels; ch++) { +			for (let sample = 0; sample < dest[ch].length; sample++) { +				dest[ch][sample] = source[sample * channels + ch]; +			} +		} +	} + +	static write_input(dest, source) { +		const channels = source.length; +		for (let ch = 0; ch < channels; ch++) { +			for (let sample = 0; sample < source[ch].length; sample++) { +				dest[sample * channels + ch] = source[ch][sample]; +			} +		} +	} +} + +registerProcessor('godot-processor', GodotProcessor); diff --git a/platform/javascript/js/libs/library_godot_audio.js b/platform/javascript/js/libs/library_godot_audio.js new file mode 100644 index 0000000000..a657d0a125 --- /dev/null +++ b/platform/javascript/js/libs/library_godot_audio.js @@ -0,0 +1,340 @@ +/*************************************************************************/ +/*  library_godot_audio.js                                               */ +/*************************************************************************/ +/*                       This file is part of:                           */ +/*                           GODOT ENGINE                                */ +/*                      https://godotengine.org                          */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md).   */ +/*                                                                       */ +/* 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 GodotAudio = { +	$GodotAudio__deps: ['$GodotRuntime', '$GodotOS'], +	$GodotAudio: { +		ctx: null, +		input: null, +		driver: null, +		interval: 0, + +		init: function(mix_rate, latency, onstatechange, onlatencyupdate) { +			const ctx = new (window.AudioContext || window.webkitAudioContext)({ +				sampleRate: mix_rate, +				// latencyHint: latency / 1000 // Do not specify, leave 'interactive' for good performance. +			}); +			GodotAudio.ctx = ctx; +			ctx.onstatechange = function() { +				let state = 0; +				switch (ctx.state) { +					case 'suspended': +						state = 0; +						break; +					case 'running': +						state = 1; +						break; +					case 'closed': +						state = 2; +						break; + +					// no default +				} +				onstatechange(state); +			} +			ctx.onstatechange(); // Immeditately notify state. +			// Update computed latency +			GodotAudio.interval = setInterval(function() { +				let computed_latency = 0; +				if (ctx.baseLatency) { +					computed_latency += GodotAudio.ctx.baseLatency; +				} +				if (ctx.outputLatency) { +					computed_latency += GodotAudio.ctx.outputLatency; +				} +				onlatencyupdate(computed_latency); +			}, 1000); +			GodotOS.atexit(GodotAudio.close_async); +			return ctx.destination.channelCount; +		}, + +		create_input: function(callback) { +			if (GodotAudio.input) { +				return; // Already started. +			} +			function gotMediaInput(stream) { +				GodotAudio.input = GodotAudio.ctx.createMediaStreamSource(stream); +				callback(GodotAudio.input) +			} +			if (navigator.mediaDevices.getUserMedia) { +				navigator.mediaDevices.getUserMedia({ +					"audio": true +				}).then(gotMediaInput, function(e) { GodotRuntime.print(e) }); +			} else { +				if (!navigator.getUserMedia) { +					navigator.getUserMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia; +				} +				navigator.getUserMedia({ +					"audio": true +				}, gotMediaInput, function(e) { GodotRuntime.print(e) }); +			} +		}, + +		close_async: function(resolve, reject) { +			const ctx = GodotAudio.ctx; +			GodotAudio.ctx = null; +			// Audio was not initialized. +			if (!ctx) { +				resolve(); +				return; +			} +			// Remove latency callback +			if (GodotAudio.interval) { +				clearInterval(GodotAudio.interval); +				GodotAudio.interval = 0; +			} +			// Disconnect input, if it was started. +			if (GodotAudio.input) { +				GodotAudio.input.disconnect(); +				GodotAudio.input = null; +			} +			// Disconnect output +			let closed = Promise.resolve(); +			if (GodotAudio.driver) { +				closed = GodotAudio.driver.close(); +			} +			closed.then(function() { +				return ctx.close(); +			}).then(function() { +				ctx.onstatechange = null; +				resolve(); +			}).catch(function(e) { +				ctx.onstatechange = null; +				GodotRuntime.error("Error closing AudioContext", e); +				resolve(); +			}); +		}, +	}, + +	godot_audio_is_available__proxy: 'sync', +	godot_audio_is_available: function () { +		if (!(window.AudioContext || window.webkitAudioContext)) { +			return 0; +		} +		return 1; +	}, + +	godot_audio_init: function(p_mix_rate, p_latency, p_state_change, p_latency_update) { +		const statechange = GodotRuntime.get_func(p_state_change); +		const latencyupdate = GodotRuntime.get_func(p_latency_update); +		return GodotAudio.init(p_mix_rate, p_latency, statechange, latencyupdate); +	}, + +	godot_audio_resume: function() { +		if (GodotAudio.ctx && GodotAudio.ctx.state !== 'running') { +			GodotAudio.ctx.resume(); +		} +	}, + +	godot_audio_capture_start__proxy: 'sync', +	godot_audio_capture_start: function() { +		if (GodotAudio.input) { +			return; // Already started. +		} +		GodotAudio.create_input(function(input) { +			input.connect(GodotAudio.driver.get_node()); +		}); +	}, + +	godot_audio_capture_stop__proxy: 'sync', +	godot_audio_capture_stop: function() { +		if (GodotAudio.input) { +			const tracks = GodotAudio.input['mediaStream']['getTracks'](); +			for (let i = 0; i < tracks.length; i++) { +				tracks[i]['stop'](); +			} +			GodotAudio.input.disconnect(); +			GodotAudio.input = null; +		} +	}, +}; + +autoAddDeps(GodotAudio, "$GodotAudio"); +mergeInto(LibraryManager.library, GodotAudio); + +/** + * The AudioWorklet API driver, used when threads are available. + */ +const GodotAudioWorklet = { +	$GodotAudioWorklet__deps: ['$GodotAudio', '$GodotConfig'], +	$GodotAudioWorklet: { +		promise: null, +		worklet: null, + +		create: function(channels) { +			const path = GodotConfig.locate_file('godot.audio.worklet.js'); +			GodotAudioWorklet.promise = GodotAudio.ctx.audioWorklet.addModule(path).then(function() { +				GodotAudioWorklet.worklet = new AudioWorkletNode( +					GodotAudio.ctx, +					'godot-processor', +					{ +						'outputChannelCount': [channels] +					} +				); +				return Promise.resolve(); +			}); +			GodotAudio.driver = GodotAudioWorklet; +		}, + +		start: function(in_buf, out_buf, state) { +			GodotAudioWorklet.promise.then(function() { +				const node = GodotAudioWorklet.worklet; +				node.connect(GodotAudio.ctx.destination); +				node.port.postMessage({ +					'cmd': 'start', +					'data': [state, in_buf, out_buf], +				}); +				node.port.onmessage = function(event) { +					GodotRuntime.error(event.data); +				}; +			}); +		}, + +		get_node: function() { +			return GodotAudioWorklet.worklet; +		}, + +		close: function() { +			return new Promise(function(resolve, reject) { +				GodotAudioWorklet.promise.then(function() { +					GodotAudioWorklet.worklet.port.postMessage({ +						'cmd': 'stop', +						'data': null, +					}); +					GodotAudioWorklet.worklet.disconnect(); +					GodotAudioWorklet.worklet = null; +					GodotAudioWorklet.promise = null; +					resolve(); +				}); +			}); +		}, +	}, + +	godot_audio_worklet_create: function(channels) { +		GodotAudioWorklet.create(channels); +	}, + +	godot_audio_worklet_start: function(p_in_buf, p_in_size, p_out_buf, p_out_size, p_state) { +		const out_buffer = GodotRuntime.heapSub(HEAPF32, p_out_buf, p_out_size); +		const in_buffer = GodotRuntime.heapSub(HEAPF32, p_in_buf, p_in_size); +		const state = GodotRuntime.heapSub(HEAP32, p_state, 4); +		GodotAudioWorklet.start(in_buffer, out_buffer, state); +	}, + +	godot_audio_worklet_state_wait: function(p_state, p_idx, p_expected, p_timeout) { +		Atomics.wait(HEAP32, (p_state >> 2) + p_idx, p_expected, p_timeout); +		return Atomics.load(HEAP32, (p_state >> 2) + p_idx); +	}, + +	godot_audio_worklet_state_add: function(p_state, p_idx, p_value) { +		return Atomics.add(HEAP32, (p_state >> 2) + p_idx, p_value); +	}, + +	godot_audio_worklet_state_get: function(p_state, p_idx) { +		return Atomics.load(HEAP32, (p_state >> 2) + p_idx); +	}, +}; + +autoAddDeps(GodotAudioWorklet, "$GodotAudioWorklet"); +mergeInto(LibraryManager.library, GodotAudioWorklet); + +/* + * The deprecated ScriptProcessorNode API, used when threads are disabled. + */ +const GodotAudioScript = { +	$GodotAudioScript__deps: ['$GodotAudio'], +	$GodotAudioScript: { +		script: null, + +		create: function(buffer_length, channel_count) { +			GodotAudioScript.script = GodotAudio.ctx.createScriptProcessor(buffer_length, 2, channel_count); +			GodotAudio.driver = GodotAudioScript; +			return GodotAudioScript.script.bufferSize; +		}, + +		start: function(p_in_buf, p_in_size, p_out_buf, p_out_size, onprocess) { +			GodotAudioScript.script.onaudioprocess = function(event) { +				// Read input +				const inb = GodotRuntime.heapSub(HEAPF32, p_in_buf, p_in_size); +				const input = event.inputBuffer; +				if (GodotAudio.input) { +					const inlen = input.getChannelData(0).length; +					for (let ch = 0; ch < 2; ch++) { +						const data = input.getChannelData(ch); +						for (let s = 0; s < inlen; s++) { +							inb[s * 2 + ch] = data[s]; +						} +					} +				} + +				// Let Godot process the input/output. +				onprocess(); + +				// Write the output. +				const outb = GodotRuntime.heapSub(HEAPF32, p_out_buf, p_out_size); +				const output = event.outputBuffer; +				const channels = output.numberOfChannels; +				for (let ch = 0; ch < channels; ch++) { +					const data = output.getChannelData(ch); +					// Loop through samples and assign computed values. +					for (let sample = 0; sample < data.length; sample++) { +						data[sample] = outb[sample * channels + ch]; +					} +				} +			}; +			GodotAudioScript.script.connect(GodotAudio.ctx.destination); +		}, + +		get_node: function() { +			return GodotAudioScript.script; +		}, + +		close: function() { +			return new Promise(function(resolve, reject) { +				GodotAudioScript.script.disconnect(); +				GodotAudioScript.script.onaudioprocess = null; +				GodotAudioScript.script = null; +				resolve(); +			}); +		}, +	}, + +	godot_audio_script_create: function(buffer_length, channel_count) { +		return GodotAudioScript.create(buffer_length, channel_count); +	}, + +	godot_audio_script_start: function(p_in_buf, p_in_size, p_out_buf, p_out_size, p_cb) { +		const onprocess = GodotRuntime.get_func(p_cb); +		GodotAudioScript.start(p_in_buf, p_in_size, p_out_buf, p_out_size, onprocess); +	}, +}; + +autoAddDeps(GodotAudioScript, "$GodotAudioScript"); +mergeInto(LibraryManager.library, GodotAudioScript); diff --git a/platform/javascript/js/libs/library_godot_display.js b/platform/javascript/js/libs/library_godot_display.js new file mode 100644 index 0000000000..28f63ba557 --- /dev/null +++ b/platform/javascript/js/libs/library_godot_display.js @@ -0,0 +1,477 @@ +/*************************************************************************/ +/*  library_godot_display.js                                             */ +/*************************************************************************/ +/*                       This file is part of:                           */ +/*                           GODOT ENGINE                                */ +/*                      https://godotengine.org                          */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md).   */ +/*                                                                       */ +/* 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.                */ +/*************************************************************************/ + +/* + * Display Server listeners. + * Keeps track of registered event listeners so it can remove them on shutdown. + */ +const GodotDisplayListeners = { +	$GodotDisplayListeners__deps: ['$GodotOS'], +	$GodotDisplayListeners__postset: 'GodotOS.atexit(function(resolve, reject) { GodotDisplayListeners.clear(); resolve(); });', +	$GodotDisplayListeners: { +		handlers: [], + +		has: function(target, event, method, capture) { +			return GodotDisplayListeners.handlers.findIndex(function(e) { +				return e.target === target && e.event === event && e.method === method && e.capture === capture; +			}) !== -1; +		}, + +		add: function(target, event, method, capture) { +			if (GodotDisplayListeners.has(target, event, method, capture)) { +				return; +			} +			function Handler(p_target, p_event, p_method, p_capture) { +				this.target = p_target; +				this.event = p_event; +				this.method = p_method; +				this.capture = p_capture; +			}; +			GodotDisplayListeners.handlers.push(new Handler(target, event, method, capture)); +			target.addEventListener(event, method, capture); +		}, + +		clear: function() { +			GodotDisplayListeners.handlers.forEach(function(h) { +				h.target.removeEventListener(h.event, h.method, h.capture); +			}); +			GodotDisplayListeners.handlers.length = 0; +		}, +	}, +}; +mergeInto(LibraryManager.library, GodotDisplayListeners); + +/* + * Drag and drop handler. + * This is pretty big, but basically detect dropped files on GodotConfig.canvas, + * process them one by one (recursively for directories), and copies them to + * the temporary FS path '/tmp/drop-[random]/' so it can be emitted as a godot + * event (that requires a string array of paths). + * + * NOTE: The temporary files are removed after the callback. This means that + * deferred callbacks won't be able to access the files. + */ +const GodotDisplayDragDrop = { +	$GodotDisplayDragDrop__deps: ['$FS', '$GodotFS'], +	$GodotDisplayDragDrop: { +		promises: [], +		pending_files: [], + +		add_entry: function(entry) { +			if (entry.isDirectory) { +				GodotDisplayDragDrop.add_dir(entry); +			} else if (entry.isFile) { +				GodotDisplayDragDrop.add_file(entry); +			} else { +				GodotRuntime.error("Unrecognized entry...", entry); +			} +		}, + +		add_dir: function(entry) { +			GodotDisplayDragDrop.promises.push(new Promise(function(resolve, reject) { +				const reader = entry.createReader(); +				reader.readEntries(function(entries) { +					for (let i = 0; i < entries.length; i++) { +						GodotDisplayDragDrop.add_entry(entries[i]); +					} +					resolve(); +				}); +			})); +		}, + +		add_file: function(entry) { +			GodotDisplayDragDrop.promises.push(new Promise(function(resolve, reject) { +				entry.file(function(file) { +					const reader = new FileReader(); +					reader.onload = function() { +						const f = { +							"path": file.relativePath || file.webkitRelativePath, +							"name": file.name, +							"type": file.type, +							"size": file.size, +							"data": reader.result +						}; +						if (!f['path']) { +							f['path'] = f['name']; +						} +						GodotDisplayDragDrop.pending_files.push(f); +						resolve() +					}; +					reader.onerror = function() { +						GodotRuntime.print("Error reading file"); +						reject(); +					} +					reader.readAsArrayBuffer(file); +				}, function(err) { +					GodotRuntime.print("Error!"); +					reject(); +				}); +			})); +		}, + +		process: function(resolve, reject) { +			if (GodotDisplayDragDrop.promises.length === 0) { +				resolve(); +				return; +			} +			GodotDisplayDragDrop.promises.pop().then(function() { +				setTimeout(function() { +					GodotDisplayDragDrop.process(resolve, reject); +				}, 0); +			}); +		}, + +		_process_event: function(ev, callback) { +			ev.preventDefault(); +			if (ev.dataTransfer.items) { +				// Use DataTransferItemList interface to access the file(s) +				for (let i = 0; i < ev.dataTransfer.items.length; i++) { +					const item = ev.dataTransfer.items[i]; +					let entry = null; +					if ("getAsEntry" in item) { +						entry = item.getAsEntry(); +					} else if ("webkitGetAsEntry" in item) { +						entry = item.webkitGetAsEntry(); +					} +					if (entry) { +						GodotDisplayDragDrop.add_entry(entry); +					} +				} +			} else { +				GodotRuntime.error("File upload not supported"); +			} +			new Promise(GodotDisplayDragDrop.process).then(function() { +				const DROP = "/tmp/drop-" + parseInt(Math.random() * (1 << 30), 10) + "/"; +				const drops = []; +				const files = []; +				FS.mkdir(DROP); +				GodotDisplayDragDrop.pending_files.forEach((elem) => { +					const path = elem['path']; +					GodotFS.copy_to_fs(DROP + path, elem['data']); +					let idx = path.indexOf("/"); +					if (idx === -1) { +						// Root file +						drops.push(DROP + path); +					} else { +						// Subdir +						const sub = path.substr(0, idx); +						idx = sub.indexOf("/"); +						if (idx < 0 && drops.indexOf(DROP + sub) === -1) { +							drops.push(DROP + sub); +						} +					} +					files.push(DROP + path); +				}); +				GodotDisplayDragDrop.promises = []; +				GodotDisplayDragDrop.pending_files = []; +				callback(drops); +				const dirs = [DROP.substr(0, DROP.length -1)]; +				// Remove temporary files +				files.forEach(function (file) { +					FS.unlink(file); +					let dir = file.replace(DROP, ""); +					let idx = dir.lastIndexOf("/"); +					while (idx > 0) { +						dir = dir.substr(0, idx); +						if (dirs.indexOf(DROP + dir) === -1) { +							dirs.push(DROP + dir); +						} +						idx = dir.lastIndexOf("/"); +					} +				}); +				// Remove dirs. +				dirs.sort(function(a, b) { +					const al = (a.match(/\//g) || []).length; +					const bl = (b.match(/\//g) || []).length; +					if (al > bl) +						return -1; +					else if (al < bl) +						return 1; +					return 0; +				}).forEach(function(dir) { +					FS.rmdir(dir); +				}); +			}); +		}, + +		handler: function(callback) { +			return function(ev) { +				GodotDisplayDragDrop._process_event(ev, callback); +			}; +		}, +	}, +}; +mergeInto(LibraryManager.library, GodotDisplayDragDrop); + +/* + * Display server cursor helper. + * Keeps track of cursor status and custom shapes. + */ +const GodotDisplayCursor = { +	$GodotDisplayCursor__deps: ['$GodotOS', '$GodotConfig'], +	$GodotDisplayCursor__postset: 'GodotOS.atexit(function(resolve, reject) { GodotDisplayCursor.clear(); resolve(); });', +	$GodotDisplayCursor: { +		shape: 'auto', +		visible: true, +		cursors: {}, +		set_style: function(style) { +			GodotConfig.canvas.style.cursor = style; +		}, +		set_shape: function(shape) { +			GodotDisplayCursor.shape = shape; +			let css = shape; +			if (shape in GodotDisplayCursor.cursors) { +				const c = GodotDisplayCursor.cursors[shape]; +				css = 'url("' + c.url + '") ' + c.x + ' ' + c.y + ', auto'; +			} +			if (GodotDisplayCursor.visible) { +				GodotDisplayCursor.set_style(css); +			} +		}, +		clear: function() { +			GodotDisplayCursor.set_style(''); +			GodotDisplayCursor.shape = 'auto'; +			GodotDisplayCursor.visible = true; +			Object.keys(GodotDisplayCursor.cursors).forEach(function(key) { +				URL.revokeObjectURL(GodotDisplayCursor.cursors[key]); +				delete GodotDisplayCursor.cursors[key]; +			}); +		}, +	}, +}; +mergeInto(LibraryManager.library, GodotDisplayCursor); + +/** + * Display server interface. + * + * Exposes all the functions needed by DisplayServer implementation. + */ +const GodotDisplay = { +	$GodotDisplay__deps: ['$GodotConfig', '$GodotRuntime', '$GodotDisplayCursor', '$GodotDisplayListeners', '$GodotDisplayDragDrop'], +	$GodotDisplay: { +		window_icon: '', +	}, + +	godot_js_display_is_swap_ok_cancel: function() { +		const win = (['Windows', 'Win64', 'Win32', 'WinCE']); +		const plat = navigator.platform || ""; +		if (win.indexOf(plat) !== -1) { +			return 1; +		} +		return 0; +	}, + +	godot_js_display_alert: function(p_text) { +		window.alert(GodotRuntime.parseString(p_text)); // eslint-disable-line no-alert +	}, + +	godot_js_display_pixel_ratio_get: function() { +		return window.devicePixelRatio || 1; +	}, + +	/* +	 * Canvas +	 */ +	godot_js_display_canvas_focus: function() { +		GodotConfig.canvas.focus(); +	}, + +	godot_js_display_canvas_is_focused: function() { +		return document.activeElement === GodotConfig.canvas; +	}, + +	godot_js_display_canvas_bounding_rect_position_get: function(r_x, r_y) { +		const brect = GodotConfig.canvas.getBoundingClientRect(); +		GodotRuntime.setHeapValue(r_x, brect.x, 'i32'); +		GodotRuntime.setHeapValue(r_y, brect.y, 'i32'); +	}, + +	/* +	 * Touchscreen +	 */ +	godot_js_display_touchscreen_is_available: function() { +		return 'ontouchstart' in window; +	}, + +	/* +	 * Clipboard +	 */ +	godot_js_display_clipboard_set: function(p_text) { +		const text = GodotRuntime.parseString(p_text); +		if (!navigator.clipboard || !navigator.clipboard.writeText) { +			return 1; +		} +		navigator.clipboard.writeText(text).catch(function(e) { +			// Setting OS clipboard is only possible from an input callback. +			GodotRuntime.error("Setting OS clipboard is only possible from an input callback for the HTML5 plafrom. Exception:", e); +		}); +		return 0; +	}, + +	godot_js_display_clipboard_get: function(callback) { +		const func = GodotRuntime.get_func(callback); +		try { +			navigator.clipboard.readText().then(function (result) { +				const ptr = GodotRuntime.allocString(result); +				func(ptr); +				GodotRuntime.free(ptr); +			}).catch(function (e) { +				// Fail graciously. +			}); +		} catch (e) { +			// Fail graciously. +		} +	}, + +	/* +	 * Window +	 */ +	godot_js_display_window_request_fullscreen: function() { +		const canvas = GodotConfig.canvas; +		(canvas.requestFullscreen || canvas.msRequestFullscreen || +			canvas.mozRequestFullScreen || canvas.mozRequestFullscreen || +			canvas.webkitRequestFullscreen +		).call(canvas); +	}, + +	godot_js_display_window_title_set: function(p_data) { +		document.title = GodotRuntime.parseString(p_data); +	}, + +	godot_js_display_window_icon_set: function(p_ptr, p_len) { +		let link = document.getElementById('-gd-engine-icon'); +		if (link === null) { +			link = document.createElement('link'); +			link.rel = 'icon'; +			link.id = '-gd-engine-icon'; +			document.head.appendChild(link); +		} +		const old_icon = GodotDisplay.window_icon; +		const png = new Blob([GodotRuntime.heapCopy(HEAPU8, p_ptr, p_len)], { type: "image/png" }); +		GodotDisplay.window_icon = URL.createObjectURL(png); +		link.href = GodotDisplay.window_icon; +		if (old_icon) { +			URL.revokeObjectURL(old_icon); +		} +	}, + +	/* +	 * Cursor +	 */ +	godot_js_display_cursor_set_visible: function(p_visible) { +		const visible = p_visible !== 0; +		if (visible === GodotDisplayCursor.visible) { +			return; +		} +		GodotDisplayCursor.visible = visible; +		if (visible) { +			GodotDisplayCursor.set_shape(GodotDisplayCursor.shape); +		} else { +			GodotDisplayCursor.set_style('none'); +		} +	}, + +	godot_js_display_cursor_is_hidden: function() { +		return !GodotDisplayCursor.visible; +	}, + +	godot_js_display_cursor_set_shape: function(p_string) { +		GodotDisplayCursor.set_shape(GodotRuntime.parseString(p_string)); +	}, + +	godot_js_display_cursor_set_custom_shape: function(p_shape, p_ptr, p_len, p_hotspot_x, p_hotspot_y) { +		const shape = GodotRuntime.parseString(p_shape); +		const old_shape = GodotDisplayCursor.cursors[shape]; +		if (p_len > 0) { +			const png = new Blob([GodotRuntime.heapCopy(HEAPU8, p_ptr, p_len)], { type: 'image/png' }); +			const url = URL.createObjectURL(png); +			GodotDisplayCursor.cursors[shape] = { +				url: url, +				x: p_hotspot_x, +				y: p_hotspot_y, +			}; +		} else { +			delete GodotDisplayCursor.cursors[shape]; +		} +		if (shape === GodotDisplayCursor.shape) { +			GodotDisplayCursor.set_shape(GodotDisplayCursor.shape); +		} +		if (old_shape) { +			URL.revokeObjectURL(old_shape.url); +		} +	}, + +	/* +	 * Listeners +	 */ +	godot_js_display_notification_cb: function(callback, p_enter, p_exit, p_in, p_out) { +		const canvas = GodotConfig.canvas; +		const func = GodotRuntime.get_func(callback); +		const notif = [p_enter, p_exit, p_in, p_out]; +		['mouseover', 'mouseleave', 'focus', 'blur'].forEach(function(evt_name, idx) { +			GodotDisplayListeners.add(canvas, evt_name, function() { +				func.bind(null, notif[idx]); +			}, true); +		}); +	}, + +	godot_js_display_paste_cb: function(callback) { +		const func = GodotRuntime.get_func(callback); +		GodotDisplayListeners.add(window, 'paste', function(evt) { +			const text = evt.clipboardData.getData('text'); +			const ptr = GodotRuntime.allocString(text); +			func(ptr); +			GodotRuntime.free(ptr); +		}, false); +	}, + +	godot_js_display_drop_files_cb: function(callback) { +		const func = GodotRuntime.get_func(callback) +		const dropFiles = function(files) { +			const args = files || []; +			if (!args.length) { +				return; +			} +			const argc = args.length; +			const argv = GodotRuntime.allocStringArray(args); +			func(argv, argc); +			GodotRuntime.freeStringArray(argv, argc); +		}; +		const canvas = GodotConfig.canvas; +		GodotDisplayListeners.add(canvas, 'dragover', function(ev) { +			// Prevent default behavior (which would try to open the file(s)) +			ev.preventDefault(); +		}, false); +		GodotDisplayListeners.add(canvas, 'drop', GodotDisplayDragDrop.handler(dropFiles)); +	}, +}; + +autoAddDeps(GodotDisplay, '$GodotDisplay'); +mergeInto(LibraryManager.library, GodotDisplay); diff --git a/platform/javascript/js/libs/library_godot_editor_tools.js b/platform/javascript/js/libs/library_godot_editor_tools.js new file mode 100644 index 0000000000..21e40185ae --- /dev/null +++ b/platform/javascript/js/libs/library_godot_editor_tools.js @@ -0,0 +1,56 @@ +/*************************************************************************/ +/*  library_godot_editor_tools.js                                        */ +/*************************************************************************/ +/*                       This file is part of:                           */ +/*                           GODOT ENGINE                                */ +/*                      https://godotengine.org                          */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md).   */ +/*                                                                       */ +/* 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 GodotEditorTools = { +	godot_js_editor_download_file__deps: ['$FS'], +	godot_js_editor_download_file: function(p_path, p_name, p_mime) { +		const path = GodotRuntime.parseString(p_path); +		const name = GodotRuntime.parseString(p_name); +		const mime = GodotRuntime.parseString(p_mime); +		const size = FS.stat(path)['size']; +		const buf = new Uint8Array(size); +		const fd = FS.open(path, 'r'); +		FS.read(fd, buf, 0, size); +		FS.close(fd); +		FS.unlink(path); +		const blob = new Blob([buf], { type: mime }); +		const url = window.URL.createObjectURL(blob); +		const a = document.createElement('a'); +		a.href = url; +		a.download = name; +		a.style.display = 'none'; +		document.body.appendChild(a); +		a.click(); +		a.remove(); +		window.URL.revokeObjectURL(url); +	}, +}; + +mergeInto(LibraryManager.library, GodotEditorTools); diff --git a/platform/javascript/js/libs/library_godot_eval.js b/platform/javascript/js/libs/library_godot_eval.js new file mode 100644 index 0000000000..4064938d3e --- /dev/null +++ b/platform/javascript/js/libs/library_godot_eval.js @@ -0,0 +1,87 @@ +/*************************************************************************/ +/*  library_godot_eval.js                                                */ +/*************************************************************************/ +/*                       This file is part of:                           */ +/*                           GODOT ENGINE                                */ +/*                      https://godotengine.org                          */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md).   */ +/*                                                                       */ +/* 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 GodotEval = { +	godot_js_eval__deps: ['$GodotRuntime'], +	godot_js_eval: function(p_js, p_use_global_ctx, p_union_ptr, p_byte_arr, p_byte_arr_write, p_callback) { +		const js_code = GodotRuntime.parseString(p_js); +		let eval_ret = null; +		try { +			if (p_use_global_ctx) { +				// indirect eval call grants global execution context +				const global_eval = eval; // eslint-disable-line no-eval +				eval_ret = global_eval(js_code); +			} else { +				eval_ret = eval(js_code); // eslint-disable-line no-eval +			} +		} catch (e) { +			GodotRuntime.error(e); +		} + +		switch (typeof eval_ret) { + +			case 'boolean': +				GodotRuntime.setHeapValue(p_union_ptr, eval_ret, 'i32'); +				return 1; // BOOL + +			case 'number': +				GodotRuntime.setHeapValue(p_union_ptr, eval_ret, 'double'); +				return 3; // REAL + +			case 'string': +				GodotRuntime.setHeapValue(p_union_ptr, GodotRuntime.allocString(eval_ret), '*'); +				return 4; // STRING + +			case 'object': +				if (eval_ret === null) { +					break; +				} + +				if (ArrayBuffer.isView(eval_ret) && !(eval_ret instanceof Uint8Array)) { +					eval_ret = new Uint8Array(eval_ret.buffer); +				} +				else if (eval_ret instanceof ArrayBuffer) { +					eval_ret = new Uint8Array(eval_ret); +				} +				if (eval_ret instanceof Uint8Array) { +					const func = GodotRuntime.get_func(p_callback); +					const bytes_ptr = func(p_byte_arr, p_byte_arr_write,  eval_ret.length); +					HEAPU8.set(eval_ret, bytes_ptr); +					return 20; // POOL_BYTE_ARRAY +				} +				break; + +			// no default +		} +		return 0; // NIL +	}, +} + +mergeInto(LibraryManager.library, GodotEval); diff --git a/platform/javascript/js/libs/library_godot_http_request.js b/platform/javascript/js/libs/library_godot_http_request.js new file mode 100644 index 0000000000..6f80f3b958 --- /dev/null +++ b/platform/javascript/js/libs/library_godot_http_request.js @@ -0,0 +1,143 @@ +/*************************************************************************/ +/*  http_request.js                                                      */ +/*************************************************************************/ +/*                       This file is part of:                           */ +/*                           GODOT ENGINE                                */ +/*                      https://godotengine.org                          */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md).   */ +/*                                                                       */ +/* 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 GodotHTTPRequest = { +	$GodotHTTPRequest__deps: ['$GodotRuntime'], +	$GodotHTTPRequest: { +		requests: [], + +		getUnusedRequestId: function() { +			var idMax = GodotHTTPRequest.requests.length; +			for (var potentialId = 0; potentialId < idMax; ++potentialId) { +				if (GodotHTTPRequest.requests[potentialId] instanceof XMLHttpRequest) { +					continue; +				} +				return potentialId; +			} +			GodotHTTPRequest.requests.push(null) +			return idMax; +		}, + +		setupRequest: function(xhr) { +			xhr.responseType = 'arraybuffer'; +		}, +	}, + +	godot_xhr_new: function() { +		var newId = GodotHTTPRequest.getUnusedRequestId(); +		GodotHTTPRequest.requests[newId] = new XMLHttpRequest; +		GodotHTTPRequest.setupRequest(GodotHTTPRequest.requests[newId]); +		return newId; +	}, + +	godot_xhr_reset: function(xhrId) { +		GodotHTTPRequest.requests[xhrId] = new XMLHttpRequest; +		GodotHTTPRequest.setupRequest(GodotHTTPRequest.requests[xhrId]); +	}, + +	godot_xhr_free: function(xhrId) { +		GodotHTTPRequest.requests[xhrId].abort(); +		GodotHTTPRequest.requests[xhrId] = null; +	}, + +	godot_xhr_open: function(xhrId, method, url, p_user, p_password) { +		const user = p_user > 0 ? GodotRuntime.parseString(p_user) : null; +		const password = p_password > 0 ? GodotRuntime.parseString(p_password) : null; +		GodotHTTPRequest.requests[xhrId].open(GodotRuntime.parseString(method), GodotRuntime.parseString(url), true, user, password); +	}, + +	godot_xhr_set_request_header: function(xhrId, header, value) { +		GodotHTTPRequest.requests[xhrId].setRequestHeader(GodotRuntime.parseString(header), GodotRuntime.parseString(value)); +	}, + +	godot_xhr_send_null: function(xhrId) { +		GodotHTTPRequest.requests[xhrId].send(); +	}, + +	godot_xhr_send_string: function(xhrId, strPtr) { +		if (!strPtr) { +			GodotRuntime.error("Failed to send string per XHR: null pointer"); +			return; +		} +		GodotHTTPRequest.requests[xhrId].send(GodotRuntime.parseString(strPtr)); +	}, + +	godot_xhr_send_data: function(xhrId, ptr, len) { +		if (!ptr) { +			GodotRuntime.error("Failed to send data per XHR: null pointer"); +			return; +		} +		if (len < 0) { +			GodotRuntime.error("Failed to send data per XHR: buffer length less than 0"); +			return; +		} +		GodotHTTPRequest.requests[xhrId].send(HEAPU8.subarray(ptr, ptr + len)); +	}, + +	godot_xhr_abort: function(xhrId) { +		GodotHTTPRequest.requests[xhrId].abort(); +	}, + +	godot_xhr_get_status: function(xhrId) { +		return GodotHTTPRequest.requests[xhrId].status; +	}, + +	godot_xhr_get_ready_state: function(xhrId) { +		return GodotHTTPRequest.requests[xhrId].readyState; +	}, + +	godot_xhr_get_response_headers_length: function(xhrId) { +		var headers = GodotHTTPRequest.requests[xhrId].getAllResponseHeaders(); +		return headers === null ? 0 : GodotRuntime.strlen(headers); +	}, + +	godot_xhr_get_response_headers: function(xhrId, dst, len) { +		var str = GodotHTTPRequest.requests[xhrId].getAllResponseHeaders(); +		if (str === null) +			return; +		GodotRuntime.stringToHeap(str, dst, len); +	}, + +	godot_xhr_get_response_length: function(xhrId) { +		var body = GodotHTTPRequest.requests[xhrId].response; +		return body === null ? 0 : body.byteLength; +	}, + +	godot_xhr_get_response: function(xhrId, dst, len) { +		var buf = GodotHTTPRequest.requests[xhrId].response; +		if (buf === null) +			return; +		buf = new Uint8Array(buf).subarray(0, len); +		HEAPU8.set(buf, dst); +	}, +}; + +autoAddDeps(GodotHTTPRequest, "$GodotHTTPRequest"); +mergeInto(LibraryManager.library, GodotHTTPRequest); diff --git a/platform/javascript/js/libs/library_godot_os.js b/platform/javascript/js/libs/library_godot_os.js new file mode 100644 index 0000000000..582f04cb1b --- /dev/null +++ b/platform/javascript/js/libs/library_godot_os.js @@ -0,0 +1,280 @@ +/*************************************************************************/ +/*  library_godot_os.js                                                  */ +/*************************************************************************/ +/*                       This file is part of:                           */ +/*                           GODOT ENGINE                                */ +/*                      https://godotengine.org                          */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md).   */ +/*                                                                       */ +/* 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 IDHandler = { +	$IDHandler: { +		_last_id: 0, +		_references: {}, + +		get: function(p_id) { +			return IDHandler._references[p_id]; +		}, + +		add: function(p_data) { +			const id = ++IDHandler._last_id; +			IDHandler._references[id] = p_data; +			return id; +		}, + +		remove: function(p_id) { +			delete IDHandler._references[p_id]; +		}, +	}, +}; + +autoAddDeps(IDHandler, "$IDHandler"); +mergeInto(LibraryManager.library, IDHandler); + +const GodotConfig = { +	$GodotConfig__postset: 'Module["initConfig"] = GodotConfig.init_config;', +	$GodotConfig__deps: ['$GodotRuntime'], +	$GodotConfig: { +		canvas: null, +		locale: "en", +		resize_on_start: false, +		on_execute: null, + +		init_config: function(p_opts) { +			GodotConfig.resize_on_start = p_opts['resizeCanvasOnStart'] ? true : false; +			GodotConfig.canvas = p_opts['canvas']; +			GodotConfig.locale = p_opts['locale'] || GodotConfig.locale; +			GodotConfig.on_execute = p_opts['onExecute']; +			// This is called by emscripten, even if undocumented. +			Module['onExit'] = p_opts['onExit']; // eslint-disable-line no-undef +		}, + +		locate_file: function(file) { +			return Module["locateFile"](file); // eslint-disable-line no-undef +		}, +	}, + +	godot_js_config_canvas_id_get: function(p_ptr, p_ptr_max) { +		GodotRuntime.stringToHeap('#' + GodotConfig.canvas.id, p_ptr, p_ptr_max); +	}, + +	godot_js_config_locale_get: function(p_ptr, p_ptr_max) { +		GodotRuntime.stringToHeap(GodotConfig.locale, p_ptr, p_ptr_max); +	}, + +	godot_js_config_is_resize_on_start: function() { +		return GodotConfig.resize_on_start ? 1 : 0; +	}, +}; + +autoAddDeps(GodotConfig, '$GodotConfig'); +mergeInto(LibraryManager.library, GodotConfig); + + +const GodotFS = { +	$GodotFS__deps: ['$FS', '$IDBFS', '$GodotRuntime'], +	$GodotFS__postset: [ +		'Module["initFS"] = GodotFS.init;', +		'Module["deinitFS"] = GodotFS.deinit;', +		'Module["copyToFS"] = GodotFS.copy_to_fs;', +	].join(''), +	$GodotFS: { +		_idbfs: false, +		_syncing: false, +		_mount_points: [], + +		is_persistent: function() { +			return GodotFS._idbfs ? 1 : 0; +		}, + +		// Initialize godot file system, setting up persistent paths. +		// Returns a promise that resolves when the FS is ready. +		// We keep track of mount_points, so that we can properly close the IDBFS +		// since emscripten is not doing it by itself. (emscripten GH#12516). +		init: function(persistentPaths) { +			GodotFS._idbfs = false; +			if (!Array.isArray(persistentPaths)) { +				return Promise.reject(new Error('Persistent paths must be an array')); +			} +			if (!persistentPaths.length) { +				return Promise.resolve(); +			} +			GodotFS._mount_points = persistentPaths.slice(); + +			function createRecursive(dir) { +				try { +					FS.stat(dir); +				} catch (e) { +					if (e.errno !== ERRNO_CODES.ENOENT) { +						throw e; +					} +					FS.mkdirTree(dir); +				} +			} + +			GodotFS._mount_points.forEach(function(path) { +				createRecursive(path); +				FS.mount(IDBFS, {}, path); +			}); +			return new Promise(function(resolve, reject) { +				FS.syncfs(true, function(err) { +					if (err) { +						GodotFS._mount_points = []; +						GodotFS._idbfs = false; +						GodotRuntime.print("IndexedDB not available: " + err.message); +					} else { +						GodotFS._idbfs = true; +					} +					resolve(err); +				}); +			}); +		}, + +		// Deinit godot file system, making sure to unmount file systems, and close IDBFS(s). +		deinit: function() { +			GodotFS._mount_points.forEach(function(path) { +				try { +					FS.unmount(path); +				} catch (e) { +					GodotRuntime.print("Already unmounted", e); +				} +				if (GodotFS._idbfs && IDBFS.dbs[path]) { +					IDBFS.dbs[path].close(); +					delete IDBFS.dbs[path]; +				} +			}); +			GodotFS._mount_points = []; +			GodotFS._idbfs = false; +			GodotFS._syncing = false; +		}, + +		sync: function() { +			if (GodotFS._syncing) { +				GodotRuntime.error('Already syncing!'); +				return Promise.resolve(); +			} +			GodotFS._syncing = true; +			return new Promise(function (resolve, reject) { +				FS.syncfs(false, function(error) { +					if (error) { +						GodotRuntime.error('Failed to save IDB file system: ' + error.message); +					} +					GodotFS._syncing = false; +					resolve(error); +				}); +			}); +		}, + +		// Copies a buffer to the internal file system. Creating directories recursively. +		copy_to_fs: function(path, buffer) { +			const idx = path.lastIndexOf("/"); +			let dir = "/"; +			if (idx > 0) { +				dir = path.slice(0, idx); +			} +			try { +				FS.stat(dir); +			} catch (e) { +				if (e.errno !== ERRNO_CODES.ENOENT) { +					throw e; +				} +				FS.mkdirTree(dir); +			} +			FS.writeFile(path, new Uint8Array(buffer), {'flags': 'wx+'}); +		}, +	}, +}; +mergeInto(LibraryManager.library, GodotFS); + +const GodotOS = { +	$GodotOS__deps: ['$GodotFS', '$GodotRuntime'], +	$GodotOS__postset: [ +		'Module["request_quit"] = function() { GodotOS.request_quit() };', +		'GodotOS._fs_sync_promise = Promise.resolve();', +	].join(''), +	$GodotOS: { +		request_quit: function() {}, +		_async_cbs: [], +		_fs_sync_promise: null, + +		atexit: function(p_promise_cb) { +			GodotOS._async_cbs.push(p_promise_cb); +		}, + +		finish_async: function(callback) { +			GodotOS._fs_sync_promise.then(function(err) { +				const promises = []; +				GodotOS._async_cbs.forEach(function(cb) { +					promises.push(new Promise(cb)); +				}); +				return Promise.all(promises); +			}).then(function() { +				return GodotFS.sync(); // Final FS sync. +			}).then(function(err) { +				// Always deferred. +				setTimeout(function() { +					callback(); +				}, 0); +			}); +		}, +	}, + +	godot_js_os_finish_async: function(p_callback) { +		const func = GodotRuntime.get_func(p_callback); +		GodotOS.finish_async(func); +	}, + +	godot_js_os_request_quit_cb: function(p_callback) { +		GodotOS.request_quit = GodotRuntime.get_func(p_callback); +	}, + +	godot_js_os_fs_is_persistent: function() { +		return GodotFS.is_persistent(); +	}, + +	godot_js_os_fs_sync: function(callback) { +		const func = GodotRuntime.get_func(callback); +		GodotOS._fs_sync_promise = GodotFS.sync(); +		GodotOS._fs_sync_promise.then(function(err) { +			func(); +		}); +	}, + +	godot_js_os_execute: function(p_json) { +		const json_args = GodotRuntime.parseString(p_json); +		const args = JSON.parse(json_args); +		if (GodotConfig.on_execute) { +			GodotConfig.on_execute(args); +			return 0; +		} +		return 1; +	}, + +	godot_js_os_shell_open: function(p_uri) { +		window.open(GodotRuntime.parseString(p_uri), '_blank'); +	}, +}; + +autoAddDeps(GodotOS, '$GodotOS'); +mergeInto(LibraryManager.library, GodotOS); diff --git a/platform/javascript/js/libs/library_godot_runtime.js b/platform/javascript/js/libs/library_godot_runtime.js new file mode 100644 index 0000000000..1769f83623 --- /dev/null +++ b/platform/javascript/js/libs/library_godot_runtime.js @@ -0,0 +1,120 @@ +/*************************************************************************/ +/*  library_godot_runtime.js                                             */ +/*************************************************************************/ +/*                       This file is part of:                           */ +/*                           GODOT ENGINE                                */ +/*                      https://godotengine.org                          */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md).   */ +/*                                                                       */ +/* 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 GodotRuntime = { +	$GodotRuntime: { +		/* +		 * Functions +		 */ +		get_func: function(ptr) { +			return wasmTable.get(ptr); // eslint-disable-line no-undef +		}, + +		/* +		 * Prints +		 */ +		error: function() { +			err.apply(null, Array.from(arguments)); // eslint-disable-line no-undef +		}, + +		print: function() { +			out.apply(null, Array.from(arguments)); // eslint-disable-line no-undef +		}, + +		/* +		 * Memory +		 */ +		malloc: function(p_size) { +			return _malloc(p_size); // eslint-disable-line no-undef +		}, + +		free: function(p_ptr) { +			_free(p_ptr); // eslint-disable-line no-undef +		}, + +		getHeapValue: function (p_ptr, p_type) { +			return getValue(p_ptr, p_type); // eslint-disable-line no-undef +		}, + +		setHeapValue: function(p_ptr, p_value, p_type) { +			setValue(p_ptr, p_value, p_type); // eslint-disable-line no-undef +		}, + +		heapSub: function(p_heap, p_ptr, p_len) { +			const bytes = p_heap.BYTES_PER_ELEMENT; +			return p_heap.subarray(p_ptr / bytes, p_ptr / bytes + p_len); +		}, + +		heapCopy: function(p_heap, p_ptr, p_len) { +			const bytes = p_heap.BYTES_PER_ELEMENT; +			return p_heap.slice(p_ptr / bytes, p_ptr / bytes + p_len); +		}, + +		/* +		 * Strings +		 */ +		parseString: function(p_ptr) { +			return UTF8ToString(p_ptr); // eslint-disable-line no-undef +		}, + +		strlen: function(p_str) { +			return lengthBytesUTF8(p_str); // eslint-disable-line no-undef +		}, + +		allocString: function(p_str) { +			const length = GodotRuntime.strlen(p_str)+1; +			const c_str = GodotRuntime.malloc(length); +			stringToUTF8(p_str, c_str, length); // eslint-disable-line no-undef +			return c_str; +		}, + +		allocStringArray: function(p_strings) { +			const size = p_strings.length; +			const c_ptr = GodotRuntime.malloc(size * 4); +			for (let i = 0; i < size; i++) { +				HEAP32[(c_ptr >> 2) + i] = GodotRuntime.allocString(p_strings[i]); +			} +			return c_ptr; +		}, + +		freeStringArray: function(p_ptr, p_len) { +			for (let i = 0; i < p_len; i++) { +				GodotRuntime.free(HEAP32[(p_ptr >> 2) + i]); +			} +			GodotRuntime.free(p_ptr); +		}, + +		stringToHeap: function (p_str, p_ptr, p_len) { +			return stringToUTF8Array(p_str, HEAP8, p_ptr, p_len); // eslint-disable-line no-undef +		}, +	}, +}; +autoAddDeps(GodotRuntime, "$GodotRuntime"); +mergeInto(LibraryManager.library, GodotRuntime);  |