summaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
Diffstat (limited to 'modules')
-rw-r--r--modules/gdnative/net/webrtc_gdnative.cpp2
-rw-r--r--modules/webrtc/SCsub4
-rw-r--r--modules/webrtc/library_godot_webrtc.js407
-rw-r--r--modules/webrtc/webrtc_data_channel_js.cpp245
-rw-r--r--modules/webrtc/webrtc_data_channel_js.h10
-rw-r--r--modules/webrtc/webrtc_peer_connection_js.cpp244
-rw-r--r--modules/webrtc/webrtc_peer_connection_js.h23
-rw-r--r--modules/websocket/SCsub8
-rw-r--r--modules/websocket/emws_client.cpp144
-rw-r--r--modules/websocket/emws_client.h10
-rw-r--r--modules/websocket/emws_peer.cpp38
-rw-r--r--modules/websocket/emws_peer.h14
-rw-r--r--modules/websocket/library_godot_websocket.js187
13 files changed, 763 insertions, 573 deletions
diff --git a/modules/gdnative/net/webrtc_gdnative.cpp b/modules/gdnative/net/webrtc_gdnative.cpp
index a7355e4d12..d8c3ddc5f8 100644
--- a/modules/gdnative/net/webrtc_gdnative.cpp
+++ b/modules/gdnative/net/webrtc_gdnative.cpp
@@ -54,7 +54,7 @@ godot_error GDAPI godot_net_set_webrtc_library(const godot_net_webrtc_library *p
#ifdef WEBRTC_GDNATIVE_ENABLED
return (godot_error)WebRTCPeerConnectionGDNative::set_default_library(p_lib);
#else
- return ERR_UNAVAILABLE;
+ return (godot_error)ERR_UNAVAILABLE;
#endif
}
}
diff --git a/modules/webrtc/SCsub b/modules/webrtc/SCsub
index 20b4c8f8d2..4f870ddb2f 100644
--- a/modules/webrtc/SCsub
+++ b/modules/webrtc/SCsub
@@ -12,4 +12,8 @@ if use_gdnative: # GDNative is retained in Javascript for export compatibility
env_webrtc.Append(CPPDEFINES=["WEBRTC_GDNATIVE_ENABLED"])
env_webrtc.Prepend(CPPPATH=["#modules/gdnative/include/"])
+if env["platform"] == "javascript":
+ # Our JavaScript/C++ interface.
+ env.AddJSLibraries(["library_godot_webrtc.js"])
+
env_webrtc.add_source_files(env.modules_sources, "*.cpp")
diff --git a/modules/webrtc/library_godot_webrtc.js b/modules/webrtc/library_godot_webrtc.js
new file mode 100644
index 0000000000..b75996b1f3
--- /dev/null
+++ b/modules/webrtc/library_godot_webrtc.js
@@ -0,0 +1,407 @@
+/*************************************************************************/
+/* library_godot_webrtc.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. */
+/*************************************************************************/
+
+var GodotRTCDataChannel = {
+ // Our socket implementation that forwards events to C++.
+ $GodotRTCDataChannel__deps: ['$IDHandler', '$GodotOS'],
+ $GodotRTCDataChannel: {
+
+ connect: function(p_id, p_on_open, p_on_message, p_on_error, p_on_close) {
+ const ref = IDHandler.get(p_id);
+ if (!ref) {
+ return;
+ }
+
+ ref.binaryType = 'arraybuffer';
+ ref.onopen = function (event) {
+ p_on_open();
+ };
+ ref.onclose = function (event) {
+ p_on_close();
+ };
+ ref.onerror = function (event) {
+ p_on_error();
+ };
+ ref.onmessage = function(event) {
+ var buffer;
+ var is_string = 0;
+ if (event.data instanceof ArrayBuffer) {
+ buffer = new Uint8Array(event.data);
+ } else if (event.data instanceof Blob) {
+ console.error("Blob type not supported");
+ return;
+ } else if (typeof event.data === "string") {
+ is_string = 1;
+ var enc = new TextEncoder("utf-8");
+ buffer = new Uint8Array(enc.encode(event.data));
+ } else {
+ console.error("Unknown message type");
+ return;
+ }
+ var len = buffer.length*buffer.BYTES_PER_ELEMENT;
+ var out = _malloc(len);
+ HEAPU8.set(buffer, out);
+ p_on_message(out, len, is_string);
+ _free(out);
+ }
+ },
+
+ close: function(p_id) {
+ const ref = IDHandler.get(p_id);
+ if (!ref) {
+ return;
+ }
+ ref.onopen = null;
+ ref.onmessage = null;
+ ref.onerror = null;
+ ref.onclose = null;
+ ref.close();
+ },
+
+ get_prop: function(p_id, p_prop, p_def) {
+ const ref = IDHandler.get(p_id);
+ return (ref && ref[p_prop] !== undefined) ? ref[p_prop] : p_def;
+ },
+ },
+
+ godot_js_rtc_datachannel_ready_state_get: function(p_id) {
+ const ref = IDHandler.get(p_id);
+ if (!ref) {
+ return 3; // CLOSED
+ }
+
+ switch(ref.readyState) {
+ case "connecting":
+ return 0;
+ case "open":
+ return 1;
+ case "closing":
+ return 2;
+ case "closed":
+ return 3;
+ }
+ return 3; // CLOSED
+ },
+
+ godot_js_rtc_datachannel_send: function(p_id, p_buffer, p_length, p_raw) {
+ const ref = IDHandler.get(p_id);
+ if (!ref) {
+ return 1;
+ }
+
+ const bytes_array = new Uint8Array(p_length);
+ for (var i = 0; i < p_length; i++) {
+ bytes_array[i] = getValue(p_buffer + i, 'i8');
+ }
+
+ if (p_raw) {
+ ref.send(bytes_array.buffer);
+ } else {
+ const string = new TextDecoder('utf-8').decode(bytes_array);
+ ref.send(string);
+ }
+ },
+
+ godot_js_rtc_datachannel_is_ordered: function(p_id) {
+ return IDHandler.get_prop(p_id, 'ordered', true);
+ },
+
+ godot_js_rtc_datachannel_id_get: function(p_id) {
+ return IDHandler.get_prop(p_id, 'id', 65535);
+ },
+
+ godot_js_rtc_datachannel_max_packet_lifetime_get: function(p_id) {
+ const ref = IDHandler.get(p_id);
+ if (!ref) {
+ return 65535;
+ }
+ if (ref['maxPacketLifeTime'] !== undefined) {
+ return ref['maxPacketLifeTime'];
+ } else if (ref['maxRetransmitTime'] !== undefined) {
+ // Guess someone didn't appreciate the standardization process.
+ return ref['maxRetransmitTime'];
+ }
+ return 65535;
+ },
+
+ godot_js_rtc_datachannel_max_retransmits_get: function(p_id) {
+ return IDHandler.get_prop(p_id, 'maxRetransmits', 65535);
+ },
+
+ godot_js_rtc_datachannel_is_negotiated: function(p_id, p_def) {
+ return IDHandler.get_prop(p_id, 'negotiated', 65535);
+ },
+
+ godot_js_rtc_datachannel_label_get: function(p_id) {
+ const ref = IDHandler.get(p_id);
+ if (!ref || !ref.label) {
+ return 0;
+ }
+ return GodotOS.allocString(ref.label);
+ },
+
+ godot_js_rtc_datachannel_protocol_get: function(p_id) {
+ const ref = IDHandler.get(p_id);
+ if (!ref || !ref.protocol) {
+ return 0;
+ }
+ return GodotOS.allocString(ref.protocol);
+ },
+
+ godot_js_rtc_datachannel_destroy: function(p_id) {
+ GodotRTCDataChannel.close(p_id);
+ IDHandler.remove(p_id);
+ },
+
+ godot_js_rtc_datachannel_connect: function(p_id, p_ref, p_on_open, p_on_message, p_on_error, p_on_close) {
+ const onopen = GodotOS.get_func(p_on_open).bind(null, p_ref);
+ const onmessage = GodotOS.get_func(p_on_message).bind(null, p_ref);
+ const onerror = GodotOS.get_func(p_on_error).bind(null, p_ref);
+ const onclose = GodotOS.get_func(p_on_close).bind(null, p_ref);
+ GodotRTCDataChannel.connect(p_id, onopen, onmessage, onerror, onclose);
+ },
+
+ godot_js_rtc_datachannel_close: function(p_id) {
+ const ref = IDHandler.get(p_id);
+ if (!ref) {
+ return;
+ }
+ GodotRTCDataChannel.close(p_id);
+ },
+};
+
+autoAddDeps(GodotRTCDataChannel, '$GodotRTCDataChannel');
+mergeInto(LibraryManager.library, GodotRTCDataChannel);
+
+var GodotRTCPeerConnection = {
+
+ $GodotRTCPeerConnection__deps: ['$IDHandler', '$GodotOS', '$GodotRTCDataChannel'],
+ $GodotRTCPeerConnection: {
+ onstatechange: function(p_id, p_conn, callback, event) {
+ const ref = IDHandler.get(p_id);
+ if (!ref) {
+ return;
+ }
+ var state = 5; // CLOSED
+ switch(p_conn.iceConnectionState) {
+ case "new":
+ state = 0;
+ case "checking":
+ state = 1;
+ case "connected":
+ case "completed":
+ state = 2;
+ case "disconnected":
+ state = 3;
+ case "failed":
+ state = 4;
+ case "closed":
+ state = 5;
+ }
+ callback(state);
+ },
+
+ onicecandidate: function(p_id, callback, event) {
+ const ref = IDHandler.get(p_id);
+ if (!ref || !event.candidate) {
+ return;
+ }
+
+ let c = event.candidate;
+ let candidate_str = GodotOS.allocString(c.candidate);
+ let mid_str = GodotOS.allocString(c.sdpMid);
+ callback(mid_str, c.sdpMLineIndex, candidate_str);
+ _free(candidate_str);
+ _free(mid_str);
+ },
+
+ ondatachannel: function(p_id, callback, event) {
+ const ref = IDHandler.get(p_id);
+ if (!ref) {
+ return;
+ }
+
+ const cid = IDHandler.add(event.channel);
+ callback(cid);
+ },
+
+ onsession: function(p_id, callback, session) {
+ const ref = IDHandler.get(p_id);
+ if (!ref) {
+ return;
+ }
+ let type_str = GodotOS.allocString(session.type);
+ let sdp_str = GodotOS.allocString(session.sdp);
+ callback(type_str, sdp_str);
+ _free(type_str);
+ _free(sdp_str);
+ },
+
+ onerror: function(p_id, callback, error) {
+ const ref = IDHandler.get(p_id);
+ if (!ref) {
+ return;
+ }
+ console.error(error);
+ callback();
+ },
+ },
+
+ godot_js_rtc_pc_create: function(p_config, p_ref, p_on_state_change, p_on_candidate, p_on_datachannel) {
+ const onstatechange = GodotOS.get_func(p_on_state_change).bind(null, p_ref);
+ const oncandidate = GodotOS.get_func(p_on_candidate).bind(null, p_ref);
+ const ondatachannel = GodotOS.get_func(p_on_datachannel).bind(null, p_ref);
+
+ var config = JSON.parse(UTF8ToString(p_config));
+ var conn = null;
+ try {
+ conn = new RTCPeerConnection(config);
+ } catch (e) {
+ console.error(e);
+ return 0;
+ }
+
+ const base = GodotRTCPeerConnection;
+ const id = IDHandler.add(conn);
+ conn.oniceconnectionstatechange = base.onstatechange.bind(null, id, conn, onstatechange);
+ conn.onicecandidate = base.onicecandidate.bind(null, id, oncandidate);
+ conn.ondatachannel = base.ondatachannel.bind(null, id, ondatachannel);
+ return id;
+ },
+
+ godot_js_rtc_pc_close: function(p_id) {
+ const ref = IDHandler.get(p_id);
+ if (!ref) {
+ return;
+ }
+ ref.close();
+ },
+
+ godot_js_rtc_pc_destroy: function(p_id) {
+ const ref = IDHandler.get(p_id);
+ if (!ref) {
+ return;
+ }
+ ref.oniceconnectionstatechange = null;
+ ref.onicecandidate = null;
+ ref.ondatachannel = null;
+ IDHandler.remove(p_id);
+ },
+
+ godot_js_rtc_pc_offer_create: function(p_id, p_obj, p_on_session, p_on_error) {
+ const ref = IDHandler.get(p_id);
+ if (!ref) {
+ return;
+ }
+ const onsession = GodotOS.get_func(p_on_session).bind(null, p_obj);
+ const onerror = GodotOS.get_func(p_on_error).bind(null, p_obj);
+ ref.createOffer().then(function(session) {
+ GodotRTCPeerConnection.onsession(p_id, onsession, session);
+ }).catch(function(error) {
+ GodotRTCPeerConnection.onerror(p_id, onerror, error);
+ });
+ },
+
+ godot_js_rtc_pc_local_description_set: function(p_id, p_type, p_sdp, p_obj, p_on_error) {
+ const ref = IDHandler.get(p_id);
+ if (!ref) {
+ return;
+ }
+ const type = UTF8ToString(p_type);
+ const sdp = UTF8ToString(p_sdp);
+ const onerror = GodotOS.get_func(p_on_error).bind(null, p_obj);
+ ref.setLocalDescription({
+ 'sdp': sdp,
+ 'type': type
+ }).catch(function(error) {
+ GodotRTCPeerConnection.onerror(p_id, onerror, error);
+ });
+ },
+
+ godot_js_rtc_pc_remote_description_set: function(p_id, p_type, p_sdp, p_obj, p_session_created, p_on_error) {
+ const ref = IDHandler.get(p_id);
+ if (!ref) {
+ return;
+ }
+ const type = UTF8ToString(p_type);
+ const sdp = UTF8ToString(p_sdp);
+ const onerror = GodotOS.get_func(p_on_error).bind(null, p_obj);
+ const onsession = GodotOS.get_func(p_session_created).bind(null, p_obj);
+ ref.setRemoteDescription({
+ 'sdp': sdp,
+ 'type': type
+ }).then(function() {
+ if (type != 'offer') {
+ return;
+ }
+ return ref.createAnswer().then(function(session) {
+ GodotRTCPeerConnection.onsession(p_id, onsession, session);
+ });
+ }).catch(function(error) {
+ GodotRTCPeerConnection.onerror(p_id, onerror, error);
+ });
+ },
+
+ godot_js_rtc_pc_ice_candidate_add: function(p_id, p_mid_name, p_mline_idx, p_sdp) {
+ const ref = IDHandler.get(p_id);
+ if (!ref) {
+ return;
+ }
+ var sdpMidName = UTF8ToString(p_mid_name);
+ var sdpName = UTF8ToString(p_sdp);
+ ref.addIceCandidate(new RTCIceCandidate({
+ "candidate": sdpName,
+ "sdpMid": sdpMidName,
+ "sdpMlineIndex": p_mline_idx,
+ }));
+ },
+
+ godot_js_rtc_pc_datachannel_create__deps: ['$GodotRTCDataChannel'],
+ godot_js_rtc_pc_datachannel_create: function(p_id, p_label, p_config) {
+ try {
+ const ref = IDHandler.get(p_id);
+ if (!ref) {
+ return 0;
+ }
+
+ const label = UTF8ToString(p_label);
+ const config = JSON.parse(UTF8ToString(p_config));
+
+ const channel = ref.createDataChannel(label, config);
+ return IDHandler.add(channel);
+ } catch (e) {
+ console.error(e);
+ return 0;
+ }
+ },
+};
+
+autoAddDeps(GodotRTCPeerConnection, '$GodotRTCPeerConnection')
+mergeInto(LibraryManager.library, GodotRTCPeerConnection);
diff --git a/modules/webrtc/webrtc_data_channel_js.cpp b/modules/webrtc/webrtc_data_channel_js.cpp
index 2c648ba9f9..3a63001a56 100644
--- a/modules/webrtc/webrtc_data_channel_js.cpp
+++ b/modules/webrtc/webrtc_data_channel_js.cpp
@@ -34,65 +34,58 @@
#include "emscripten.h"
extern "C" {
-EMSCRIPTEN_KEEPALIVE void _emrtc_on_ch_error(void *obj) {
- WebRTCDataChannelJS *peer = static_cast<WebRTCDataChannelJS *>(obj);
- peer->_on_error();
-}
+typedef void (*RTCChOnOpen)(void *p_obj);
+typedef void (*RTCChOnMessage)(void *p_obj, const uint8_t *p_buffer, int p_size, int p_is_string);
+typedef void (*RTCChOnClose)(void *p_obj);
+typedef void (*RTCChOnError)(void *p_obj);
-EMSCRIPTEN_KEEPALIVE void _emrtc_on_ch_open(void *obj) {
- WebRTCDataChannelJS *peer = static_cast<WebRTCDataChannelJS *>(obj);
- peer->_on_open();
+extern int godot_js_rtc_datachannel_ready_state_get(int p_id);
+extern int godot_js_rtc_datachannel_send(int p_id, const uint8_t *p_buffer, int p_length, int p_raw);
+extern int godot_js_rtc_datachannel_is_ordered(int p_id);
+extern int godot_js_rtc_datachannel_id_get(int p_id);
+extern int godot_js_rtc_datachannel_max_packet_lifetime_get(int p_id);
+extern int godot_js_rtc_datachannel_max_retransmits_get(int p_id);
+extern int godot_js_rtc_datachannel_is_negotiated(int p_id);
+extern char *godot_js_rtc_datachannel_label_get(int p_id); // Must free the returned string.
+extern char *godot_js_rtc_datachannel_protocol_get(int p_id); // Must free the returned string.
+extern void godot_js_rtc_datachannel_destroy(int p_id);
+extern void godot_js_rtc_datachannel_connect(int p_id, void *p_obj, RTCChOnOpen p_on_open, RTCChOnMessage p_on_message, RTCChOnError p_on_error, RTCChOnClose p_on_close);
+extern void godot_js_rtc_datachannel_close(int p_id);
}
-EMSCRIPTEN_KEEPALIVE void _emrtc_on_ch_close(void *obj) {
- WebRTCDataChannelJS *peer = static_cast<WebRTCDataChannelJS *>(obj);
- peer->_on_close();
+void WebRTCDataChannelJS::_on_open(void *p_obj) {
+ WebRTCDataChannelJS *peer = static_cast<WebRTCDataChannelJS *>(p_obj);
+ peer->in_buffer.resize(peer->_in_buffer_shift);
}
-EMSCRIPTEN_KEEPALIVE void _emrtc_on_ch_message(void *obj, uint8_t *p_data, uint32_t p_size, bool p_is_string) {
- WebRTCDataChannelJS *peer = static_cast<WebRTCDataChannelJS *>(obj);
- peer->_on_message(p_data, p_size, p_is_string);
-}
+void WebRTCDataChannelJS::_on_close(void *p_obj) {
+ WebRTCDataChannelJS *peer = static_cast<WebRTCDataChannelJS *>(p_obj);
+ peer->close();
}
-void WebRTCDataChannelJS::_on_open() {
- in_buffer.resize(_in_buffer_shift);
+void WebRTCDataChannelJS::_on_error(void *p_obj) {
+ WebRTCDataChannelJS *peer = static_cast<WebRTCDataChannelJS *>(p_obj);
+ peer->close();
}
-void WebRTCDataChannelJS::_on_close() {
- close();
-}
+void WebRTCDataChannelJS::_on_message(void *p_obj, const uint8_t *p_data, int p_size, int p_is_string) {
+ WebRTCDataChannelJS *peer = static_cast<WebRTCDataChannelJS *>(p_obj);
+ RingBuffer<uint8_t> &in_buffer = peer->in_buffer;
-void WebRTCDataChannelJS::_on_error() {
- close();
-}
-
-void WebRTCDataChannelJS::_on_message(uint8_t *p_data, uint32_t p_size, bool p_is_string) {
ERR_FAIL_COND_MSG(in_buffer.space_left() < (int)(p_size + 5), "Buffer full! Dropping data.");
uint8_t is_string = p_is_string ? 1 : 0;
in_buffer.write((uint8_t *)&p_size, 4);
in_buffer.write((uint8_t *)&is_string, 1);
in_buffer.write(p_data, p_size);
- queue_count++;
+ peer->queue_count++;
}
void WebRTCDataChannelJS::close() {
in_buffer.resize(0);
queue_count = 0;
_was_string = false;
- /* clang-format off */
- EM_ASM({
- var dict = Module.IDHandler.get($0);
- if (!dict) return;
- var channel = dict["channel"];
- channel.onopen = null;
- channel.onclose = null;
- channel.onerror = null;
- channel.onmessage = null;
- channel.close();
- }, _js_id);
- /* clang-format on */
+ godot_js_rtc_datachannel_close(_js_id);
}
Error WebRTCDataChannelJS::poll() {
@@ -100,24 +93,7 @@ Error WebRTCDataChannelJS::poll() {
}
WebRTCDataChannelJS::ChannelState WebRTCDataChannelJS::get_ready_state() const {
- /* clang-format off */
- return (ChannelState) EM_ASM_INT({
- var dict = Module.IDHandler.get($0);
- if (!dict) return 3; // CLOSED
- var channel = dict["channel"];
- switch(channel.readyState) {
- case "connecting":
- return 0;
- case "open":
- return 1;
- case "closing":
- return 2;
- case "closed":
- return 3;
- }
- return 3; // CLOSED
- }, _js_id);
- /* clang-format on */
+ return (ChannelState)godot_js_rtc_datachannel_ready_state_get(_js_id);
}
int WebRTCDataChannelJS::get_available_packet_count() const {
@@ -157,27 +133,7 @@ Error WebRTCDataChannelJS::put_packet(const uint8_t *p_buffer, int p_buffer_size
ERR_FAIL_COND_V(get_ready_state() != STATE_OPEN, ERR_UNCONFIGURED);
int is_bin = _write_mode == WebRTCDataChannel::WRITE_MODE_BINARY ? 1 : 0;
-
- /* clang-format off */
- EM_ASM({
- var dict = Module.IDHandler.get($0);
- var channel = dict["channel"];
- var bytes_array = new Uint8Array($2);
- var i = 0;
-
- for(i=0; i<$2; i++) {
- bytes_array[i] = getValue($1+i, 'i8');
- }
-
- if ($3) {
- channel.send(bytes_array.buffer);
- } else {
- var string = new TextDecoder("utf-8").decode(bytes_array);
- channel.send(string);
- }
- }, _js_id, p_buffer, p_buffer_size, is_bin);
- /* clang-format on */
-
+ godot_js_rtc_datachannel_send(_js_id, p_buffer, p_buffer_size, is_bin);
return OK;
}
@@ -201,46 +157,20 @@ String WebRTCDataChannelJS::get_label() const {
return _label;
}
-/* clang-format off */
-#define _JS_GET(PROP, DEF) \
-EM_ASM_INT({ \
- var dict = Module.IDHandler.get($0); \
- if (!dict || !dict["channel"]) { \
- return DEF; \
- } \
- var out = dict["channel"].PROP; \
- return out === null ? DEF : out; \
-}, _js_id)
-/* clang-format on */
-
bool WebRTCDataChannelJS::is_ordered() const {
- return _JS_GET(ordered, true);
+ return godot_js_rtc_datachannel_is_ordered(_js_id);
}
int WebRTCDataChannelJS::get_id() const {
- return _JS_GET(id, 65535);
+ return godot_js_rtc_datachannel_id_get(_js_id);
}
int WebRTCDataChannelJS::get_max_packet_life_time() const {
- // Can't use macro, webkit workaround.
- /* clang-format off */
- return EM_ASM_INT({
- var dict = Module.IDHandler.get($0);
- if (!dict || !dict["channel"]) {
- return 65535;
- }
- if (dict["channel"].maxRetransmitTime !== undefined) {
- // Guess someone didn't appreciate the standardization process.
- return dict["channel"].maxRetransmitTime;
- }
- var out = dict["channel"].maxPacketLifeTime;
- return out === null ? 65535 : out;
- }, _js_id);
- /* clang-format on */
+ return godot_js_rtc_datachannel_max_packet_lifetime_get(_js_id);
}
int WebRTCDataChannelJS::get_max_retransmits() const {
- return _JS_GET(maxRetransmits, 65535);
+ return godot_js_rtc_datachannel_max_retransmits_get(_js_id);
}
String WebRTCDataChannelJS::get_protocol() const {
@@ -248,7 +178,7 @@ String WebRTCDataChannelJS::get_protocol() const {
}
bool WebRTCDataChannelJS::is_negotiated() const {
- return _JS_GET(negotiated, false);
+ return godot_js_rtc_datachannel_is_negotiated(_js_id);
}
WebRTCDataChannelJS::WebRTCDataChannelJS() {
@@ -264,101 +194,22 @@ WebRTCDataChannelJS::WebRTCDataChannelJS(int js_id) {
_write_mode = WRITE_MODE_BINARY;
_js_id = js_id;
- /* clang-format off */
- EM_ASM({
- var c_ptr = $0;
- var dict = Module.IDHandler.get($1);
- if (!dict) return;
- var channel = dict["channel"];
- dict["ptr"] = c_ptr;
-
- channel.binaryType = "arraybuffer";
- channel.onopen = function (evt) {
- ccall("_emrtc_on_ch_open",
- "void",
- ["number"],
- [c_ptr]
- );
- };
- channel.onclose = function (evt) {
- ccall("_emrtc_on_ch_close",
- "void",
- ["number"],
- [c_ptr]
- );
- };
- channel.onerror = function (evt) {
- ccall("_emrtc_on_ch_error",
- "void",
- ["number"],
- [c_ptr]
- );
- };
- channel.onmessage = function(event) {
- var buffer;
- var is_string = 0;
- if (event.data instanceof ArrayBuffer) {
- buffer = new Uint8Array(event.data);
- } else if (event.data instanceof Blob) {
- console.error("Blob type not supported");
- return;
- } else if (typeof event.data === "string") {
- is_string = 1;
- var enc = new TextEncoder("utf-8");
- buffer = new Uint8Array(enc.encode(event.data));
- } else {
- console.error("Unknown message type");
- return;
- }
- var len = buffer.length*buffer.BYTES_PER_ELEMENT;
- var out = _malloc(len);
- HEAPU8.set(buffer, out);
- ccall("_emrtc_on_ch_message",
- "void",
- ["number", "number", "number", "number"],
- [c_ptr, out, len, is_string]
- );
- _free(out);
- }
-
- }, this, js_id);
+ godot_js_rtc_datachannel_connect(js_id, this, &_on_open, &_on_message, &_on_error, &_on_close);
// Parse label
- char *str;
- str = (char *)EM_ASM_INT({
- var dict = Module.IDHandler.get($0);
- if (!dict || !dict["channel"]) return 0;
- var str = dict["channel"].label;
- var len = lengthBytesUTF8(str)+1;
- var ptr = _malloc(str);
- stringToUTF8(str, ptr, len+1);
- return ptr;
- }, js_id);
- if(str != nullptr) {
- _label.parse_utf8(str);
- EM_ASM({ _free($0) }, str);
+ char *label = godot_js_rtc_datachannel_label_get(js_id);
+ if (label) {
+ _label.parse_utf8(label);
+ free(label);
}
- str = (char *)EM_ASM_INT({
- var dict = Module.IDHandler.get($0);
- if (!dict || !dict["channel"]) return 0;
- var str = dict["channel"].protocol;
- var len = lengthBytesUTF8(str)+1;
- var ptr = _malloc(str);
- stringToUTF8(str, ptr, len+1);
- return ptr;
- }, js_id);
- if(str != nullptr) {
- _protocol.parse_utf8(str);
- EM_ASM({ _free($0) }, str);
+ char *protocol = godot_js_rtc_datachannel_protocol_get(js_id);
+ if (protocol) {
+ _protocol.parse_utf8(protocol);
+ free(protocol);
}
- /* clang-format on */
}
WebRTCDataChannelJS::~WebRTCDataChannelJS() {
close();
- /* clang-format off */
- EM_ASM({
- Module.IDHandler.remove($0);
- }, _js_id);
- /* clang-format on */
-};
+ godot_js_rtc_datachannel_destroy(_js_id);
+}
#endif
diff --git a/modules/webrtc/webrtc_data_channel_js.h b/modules/webrtc/webrtc_data_channel_js.h
index 7545910e66..e251760019 100644
--- a/modules/webrtc/webrtc_data_channel_js.h
+++ b/modules/webrtc/webrtc_data_channel_js.h
@@ -54,12 +54,12 @@ private:
int queue_count;
uint8_t packet_buffer[PACKET_BUFFER_SIZE];
-public:
- void _on_open();
- void _on_close();
- void _on_error();
- void _on_message(uint8_t *p_data, uint32_t p_size, bool p_is_string);
+ static void _on_open(void *p_obj);
+ static void _on_close(void *p_obj);
+ static void _on_error(void *p_obj);
+ static void _on_message(void *p_obj, const uint8_t *p_data, int p_size, int p_is_string);
+public:
virtual void set_write_mode(WriteMode mode) override;
virtual WriteMode get_write_mode() const override;
virtual bool was_string_packet() const override;
diff --git a/modules/webrtc/webrtc_peer_connection_js.cpp b/modules/webrtc/webrtc_peer_connection_js.cpp
index 593c3a5162..ad9b46a8af 100644
--- a/modules/webrtc/webrtc_peer_connection_js.cpp
+++ b/modules/webrtc/webrtc_peer_connection_js.cpp
@@ -37,116 +37,32 @@
#include "core/io/json.h"
#include "emscripten.h"
-extern "C" {
-EMSCRIPTEN_KEEPALIVE void _emrtc_on_ice_candidate(void *obj, char *p_MidName, int p_MlineIndexName, char *p_sdpName) {
- WebRTCPeerConnectionJS *peer = static_cast<WebRTCPeerConnectionJS *>(obj);
- peer->emit_signal("ice_candidate_created", String(p_MidName), p_MlineIndexName, String(p_sdpName));
+void WebRTCPeerConnectionJS::_on_ice_candidate(void *p_obj, const char *p_mid_name, int p_mline_idx, const char *p_candidate) {
+ WebRTCPeerConnectionJS *peer = static_cast<WebRTCPeerConnectionJS *>(p_obj);
+ peer->emit_signal("ice_candidate_created", String(p_mid_name), p_mline_idx, String(p_candidate));
}
-EMSCRIPTEN_KEEPALIVE void _emrtc_session_description_created(void *obj, char *p_type, char *p_offer) {
- WebRTCPeerConnectionJS *peer = static_cast<WebRTCPeerConnectionJS *>(obj);
- peer->emit_signal("session_description_created", String(p_type), String(p_offer));
+void WebRTCPeerConnectionJS::_on_session_created(void *p_obj, const char *p_type, const char *p_session) {
+ WebRTCPeerConnectionJS *peer = static_cast<WebRTCPeerConnectionJS *>(p_obj);
+ peer->emit_signal("session_description_created", String(p_type), String(p_session));
}
-EMSCRIPTEN_KEEPALIVE void _emrtc_on_connection_state_changed(void *obj) {
- WebRTCPeerConnectionJS *peer = static_cast<WebRTCPeerConnectionJS *>(obj);
- peer->_on_connection_state_changed();
+void WebRTCPeerConnectionJS::_on_connection_state_changed(void *p_obj, int p_state) {
+ WebRTCPeerConnectionJS *peer = static_cast<WebRTCPeerConnectionJS *>(p_obj);
+ peer->_conn_state = (ConnectionState)p_state;
}
-EMSCRIPTEN_KEEPALIVE void _emrtc_on_error() {
+void WebRTCPeerConnectionJS::_on_error(void *p_obj) {
ERR_PRINT("RTCPeerConnection error!");
}
-EMSCRIPTEN_KEEPALIVE void _emrtc_emit_channel(void *obj, int p_id) {
- WebRTCPeerConnectionJS *peer = static_cast<WebRTCPeerConnectionJS *>(obj);
+void WebRTCPeerConnectionJS::_on_data_channel(void *p_obj, int p_id) {
+ WebRTCPeerConnectionJS *peer = static_cast<WebRTCPeerConnectionJS *>(p_obj);
peer->emit_signal("data_channel_received", Ref<WebRTCDataChannelJS>(new WebRTCDataChannelJS(p_id)));
}
-}
-
-void _emrtc_create_pc(int p_id, const Dictionary &p_config) {
- String config = JSON::print(p_config);
- /* clang-format off */
- EM_ASM({
- var dict = Module.IDHandler.get($0);
- var c_ptr = dict["ptr"];
- var config = JSON.parse(UTF8ToString($1));
- // Setup local connaction
- var conn = null;
- try {
- conn = new RTCPeerConnection(config);
- } catch (e) {
- console.log(e);
- return;
- }
- conn.oniceconnectionstatechange = function(event) {
- if (!Module.IDHandler.get($0)) return;
- ccall("_emrtc_on_connection_state_changed", "void", ["number"], [c_ptr]);
- };
- conn.onicecandidate = function(event) {
- if (!Module.IDHandler.get($0)) return;
- if (!event.candidate) return;
-
- var c = event.candidate;
- // should emit on ice candidate
- ccall("_emrtc_on_ice_candidate",
- "void",
- ["number", "string", "number", "string"],
- [c_ptr, c.sdpMid, c.sdpMLineIndex, c.candidate]
- );
- };
- conn.ondatachannel = function (evt) {
- var dict = Module.IDHandler.get($0);
- if (!dict) {
- return;
- }
- var id = Module.IDHandler.add({"channel": evt.channel, "ptr": null});
- ccall("_emrtc_emit_channel",
- "void",
- ["number", "number"],
- [c_ptr, id]
- );
- };
- dict["conn"] = conn;
- }, p_id, config.utf8().get_data());
- /* clang-format on */
-}
-
-void WebRTCPeerConnectionJS::_on_connection_state_changed() {
- /* clang-format off */
- _conn_state = (ConnectionState)EM_ASM_INT({
- var dict = Module.IDHandler.get($0);
- if (!dict) return 5; // CLOSED
- var conn = dict["conn"];
- switch(conn.iceConnectionState) {
- case "new":
- return 0;
- case "checking":
- return 1;
- case "connected":
- case "completed":
- return 2;
- case "disconnected":
- return 3;
- case "failed":
- return 4;
- case "closed":
- return 5;
- }
- return 5; // CLOSED
- }, _js_id);
- /* clang-format on */
-}
void WebRTCPeerConnectionJS::close() {
- /* clang-format off */
- EM_ASM({
- var dict = Module.IDHandler.get($0);
- if (!dict) return;
- if (dict["conn"]) {
- dict["conn"].close();
- }
- }, _js_id);
- /* clang-format on */
+ godot_js_rtc_pc_close(_js_id);
_conn_state = STATE_CLOSED;
}
@@ -154,46 +70,12 @@ Error WebRTCPeerConnectionJS::create_offer() {
ERR_FAIL_COND_V(_conn_state != STATE_NEW, FAILED);
_conn_state = STATE_CONNECTING;
- /* clang-format off */
- EM_ASM({
- var dict = Module.IDHandler.get($0);
- var conn = dict["conn"];
- var c_ptr = dict["ptr"];
- var onError = function(error) {
- console.error(error);
- ccall("_emrtc_on_error", "void", [], []);
- };
- var onCreated = function(offer) {
- ccall("_emrtc_session_description_created",
- "void",
- ["number", "string", "string"],
- [c_ptr, offer.type, offer.sdp]
- );
- };
- conn.createOffer().then(onCreated).catch(onError);
- }, _js_id);
- /* clang-format on */
+ godot_js_rtc_pc_offer_create(_js_id, this, &_on_session_created, &_on_error);
return OK;
}
Error WebRTCPeerConnectionJS::set_local_description(String type, String sdp) {
- /* clang-format off */
- EM_ASM({
- var dict = Module.IDHandler.get($0);
- var conn = dict["conn"];
- var c_ptr = dict["ptr"];
- var type = UTF8ToString($1);
- var sdp = UTF8ToString($2);
- var onError = function(error) {
- console.error(error);
- ccall("_emrtc_on_error", "void", [], []);
- };
- conn.setLocalDescription({
- "sdp": sdp,
- "type": type
- }).catch(onError);
- }, _js_id, type.utf8().get_data(), sdp.utf8().get_data());
- /* clang-format on */
+ godot_js_rtc_pc_local_description_set(_js_id, type.utf8().get_data(), sdp.utf8().get_data(), this, &_on_error);
return OK;
}
@@ -202,83 +84,32 @@ Error WebRTCPeerConnectionJS::set_remote_description(String type, String sdp) {
ERR_FAIL_COND_V(_conn_state != STATE_NEW, FAILED);
_conn_state = STATE_CONNECTING;
}
- /* clang-format off */
- EM_ASM({
- var dict = Module.IDHandler.get($0);
- var conn = dict["conn"];
- var c_ptr = dict["ptr"];
- var type = UTF8ToString($1);
- var sdp = UTF8ToString($2);
-
- var onError = function(error) {
- console.error(error);
- ccall("_emrtc_on_error", "void", [], []);
- };
- var onCreated = function(offer) {
- ccall("_emrtc_session_description_created",
- "void",
- ["number", "string", "string"],
- [c_ptr, offer.type, offer.sdp]
- );
- };
- var onSet = function() {
- if (type != "offer") {
- return;
- }
- conn.createAnswer().then(onCreated);
- };
- conn.setRemoteDescription({
- "sdp": sdp,
- "type": type
- }).then(onSet).catch(onError);
- }, _js_id, type.utf8().get_data(), sdp.utf8().get_data());
- /* clang-format on */
+ godot_js_rtc_pc_remote_description_set(_js_id, type.utf8().get_data(), sdp.utf8().get_data(), this, &_on_session_created, &_on_error);
return OK;
}
Error WebRTCPeerConnectionJS::add_ice_candidate(String sdpMidName, int sdpMlineIndexName, String sdpName) {
- /* clang-format off */
- EM_ASM({
- var dict = Module.IDHandler.get($0);
- var conn = dict["conn"];
- var c_ptr = dict["ptr"];
- var sdpMidName = UTF8ToString($1);
- var sdpMlineIndexName = UTF8ToString($2);
- var sdpName = UTF8ToString($3);
- conn.addIceCandidate(new RTCIceCandidate({
- "candidate": sdpName,
- "sdpMid": sdpMidName,
- "sdpMlineIndex": sdpMlineIndexName
- }));
- }, _js_id, sdpMidName.utf8().get_data(), sdpMlineIndexName, sdpName.utf8().get_data());
- /* clang-format on */
+ godot_js_rtc_pc_ice_candidate_add(_js_id, sdpMidName.utf8().get_data(), sdpMlineIndexName, sdpName.utf8().get_data());
return OK;
}
Error WebRTCPeerConnectionJS::initialize(Dictionary p_config) {
- _emrtc_create_pc(_js_id, p_config);
- return OK;
+ if (_js_id) {
+ godot_js_rtc_pc_destroy(_js_id);
+ _js_id = 0;
+ }
+ _conn_state = STATE_NEW;
+
+ String config = JSON::print(p_config);
+ _js_id = godot_js_rtc_pc_create(config.utf8().get_data(), this, &_on_connection_state_changed, &_on_ice_candidate, &_on_data_channel);
+ return _js_id ? OK : FAILED;
}
Ref<WebRTCDataChannel> WebRTCPeerConnectionJS::create_data_channel(String p_channel, Dictionary p_channel_config) {
+ ERR_FAIL_COND_V(_conn_state != STATE_NEW, nullptr);
+
String config = JSON::print(p_channel_config);
- /* clang-format off */
- int id = EM_ASM_INT({
- try {
- var dict = Module.IDHandler.get($0);
- if (!dict) return 0;
- var label = UTF8ToString($1);
- var config = JSON.parse(UTF8ToString($2));
- var conn = dict["conn"];
- return Module.IDHandler.add({
- "channel": conn.createDataChannel(label, config),
- "ptr": null
- })
- } catch (e) {
- return 0;
- }
- }, _js_id, p_channel.utf8().get_data(), config.utf8().get_data());
- /* clang-format on */
+ int id = godot_js_rtc_pc_datachannel_create(_js_id, p_channel.utf8().get_data(), config.utf8().get_data());
ERR_FAIL_COND_V(id == 0, nullptr);
return memnew(WebRTCDataChannelJS(id));
}
@@ -293,22 +124,17 @@ WebRTCPeerConnection::ConnectionState WebRTCPeerConnectionJS::get_connection_sta
WebRTCPeerConnectionJS::WebRTCPeerConnectionJS() {
_conn_state = STATE_NEW;
+ _js_id = 0;
- /* clang-format off */
- _js_id = EM_ASM_INT({
- return Module.IDHandler.add({"conn": null, "ptr": $0});
- }, this);
- /* clang-format on */
Dictionary config;
- _emrtc_create_pc(_js_id, config);
+ initialize(config);
}
WebRTCPeerConnectionJS::~WebRTCPeerConnectionJS() {
close();
- /* clang-format off */
- EM_ASM({
- Module.IDHandler.remove($0);
- }, _js_id);
- /* clang-format on */
+ if (_js_id) {
+ godot_js_rtc_pc_destroy(_js_id);
+ _js_id = 0;
+ }
};
#endif
diff --git a/modules/webrtc/webrtc_peer_connection_js.h b/modules/webrtc/webrtc_peer_connection_js.h
index cdaf3068e3..e33dd5f259 100644
--- a/modules/webrtc/webrtc_peer_connection_js.h
+++ b/modules/webrtc/webrtc_peer_connection_js.h
@@ -35,16 +35,37 @@
#include "webrtc_peer_connection.h"
+extern "C" {
+typedef void (*RTCOnIceConnectionStateChange)(void *p_obj, int p_state);
+typedef void (*RTCOnIceCandidate)(void *p_obj, const char *p_mid, int p_mline_idx, const char *p_candidate);
+typedef void (*RTCOnDataChannel)(void *p_obj, int p_id);
+typedef void (*RTCOnSession)(void *p_obj, const char *p_type, const char *p_sdp);
+typedef void (*RTCOnError)(void *p_obj);
+extern int godot_js_rtc_pc_create(const char *p_config, void *p_obj, RTCOnIceConnectionStateChange p_on_state_change, RTCOnIceCandidate p_on_candidate, RTCOnDataChannel p_on_datachannel);
+extern void godot_js_rtc_pc_close(int p_id);
+extern void godot_js_rtc_pc_destroy(int p_id);
+extern void godot_js_rtc_pc_offer_create(int p_id, void *p_obj, RTCOnSession p_on_session, RTCOnError p_on_error);
+extern void godot_js_rtc_pc_local_description_set(int p_id, const char *p_type, const char *p_sdp, void *p_obj, RTCOnError p_on_error);
+extern void godot_js_rtc_pc_remote_description_set(int p_id, const char *p_type, const char *p_sdp, void *p_obj, RTCOnSession p_on_session, RTCOnError p_on_error);
+extern void godot_js_rtc_pc_ice_candidate_add(int p_id, const char *p_mid_name, int p_mline_idx, const char *p_sdo);
+extern int godot_js_rtc_pc_datachannel_create(int p_id, const char *p_label, const char *p_config);
+}
+
class WebRTCPeerConnectionJS : public WebRTCPeerConnection {
private:
int _js_id;
ConnectionState _conn_state;
+ static void _on_connection_state_changed(void *p_obj, int p_state);
+ static void _on_ice_candidate(void *p_obj, const char *p_mid_name, int p_mline_idx, const char *p_candidate);
+ static void _on_data_channel(void *p_obj, int p_channel);
+ static void _on_session_created(void *p_obj, const char *p_type, const char *p_session);
+ static void _on_error(void *p_obj);
+
public:
static WebRTCPeerConnection *_create() { return memnew(WebRTCPeerConnectionJS); }
static void make_default() { WebRTCPeerConnection::_create = WebRTCPeerConnectionJS::_create; }
- void _on_connection_state_changed();
virtual ConnectionState get_connection_state() const;
virtual Error initialize(Dictionary configuration = Dictionary());
diff --git a/modules/websocket/SCsub b/modules/websocket/SCsub
index af60055855..13e51a39c0 100644
--- a/modules/websocket/SCsub
+++ b/modules/websocket/SCsub
@@ -3,11 +3,13 @@
Import("env")
Import("env_modules")
-# Thirdparty source files
-
env_ws = env_modules.Clone()
-if env["builtin_wslay"] and not env["platform"] == "javascript": # already builtin for javascript
+if env["platform"] == "javascript":
+ # Our JavaScript/C++ interface.
+ env.AddJSLibraries(["library_godot_websocket.js"])
+elif env["builtin_wslay"]:
+ # Thirdparty source files
wslay_dir = "#thirdparty/wslay/"
wslay_sources = [
"wslay_net.c",
diff --git a/modules/websocket/emws_client.cpp b/modules/websocket/emws_client.cpp
index 93d60dca08..d6e00a26af 100644
--- a/modules/websocket/emws_client.cpp
+++ b/modules/websocket/emws_client.cpp
@@ -35,14 +35,13 @@
#include "core/io/ip.h"
#include "emscripten.h"
-extern "C" {
-EMSCRIPTEN_KEEPALIVE void _esws_on_connect(void *obj, char *proto) {
+void EMWSClient::_esws_on_connect(void *obj, char *proto) {
EMWSClient *client = static_cast<EMWSClient *>(obj);
client->_is_connecting = false;
client->_on_connect(String(proto));
}
-EMSCRIPTEN_KEEPALIVE void _esws_on_message(void *obj, uint8_t *p_data, int p_data_size, int p_is_string) {
+void EMWSClient::_esws_on_message(void *obj, const uint8_t *p_data, int p_data_size, int p_is_string) {
EMWSClient *client = static_cast<EMWSClient *>(obj);
Error err = static_cast<EMWSPeer *>(*client->get_peer(1))->read_msg(p_data, p_data_size, p_is_string == 1);
@@ -50,21 +49,26 @@ EMSCRIPTEN_KEEPALIVE void _esws_on_message(void *obj, uint8_t *p_data, int p_dat
client->_on_peer_packet();
}
-EMSCRIPTEN_KEEPALIVE void _esws_on_error(void *obj) {
+void EMWSClient::_esws_on_error(void *obj) {
EMWSClient *client = static_cast<EMWSClient *>(obj);
client->_is_connecting = false;
client->_on_error();
}
-EMSCRIPTEN_KEEPALIVE void _esws_on_close(void *obj, int code, char *reason, int was_clean) {
+void EMWSClient::_esws_on_close(void *obj, int code, const char *reason, int was_clean) {
EMWSClient *client = static_cast<EMWSClient *>(obj);
client->_on_close_request(code, String(reason));
client->_is_connecting = false;
+ client->disconnect_from_host();
client->_on_disconnect(was_clean != 0);
}
-}
Error EMWSClient::connect_to_host(String p_host, String p_path, uint16_t p_port, bool p_ssl, const Vector<String> p_protocols, const Vector<String> p_custom_headers) {
+ if (_js_id) {
+ godot_js_websocket_destroy(_js_id);
+ _js_id = 0;
+ }
+
String proto_string;
for (int i = 0; i < p_protocols.size(); i++) {
if (i != 0)
@@ -84,106 +88,17 @@ Error EMWSClient::connect_to_host(String p_host, String p_path, uint16_t p_port,
}
}
str += p_host + ":" + itos(p_port) + p_path;
-
_is_connecting = true;
- /* clang-format off */
- int peer_sock = EM_ASM_INT({
- var proto_str = UTF8ToString($2);
- var socket = null;
- try {
- if (proto_str) {
- socket = new WebSocket(UTF8ToString($1), proto_str.split(","));
- } else {
- socket = new WebSocket(UTF8ToString($1));
- }
- } catch (e) {
- return -1;
- }
- var c_ptr = Module.IDHandler.get($0);
- socket.binaryType = "arraybuffer";
-
- // Connection opened
- socket.addEventListener("open", function (event) {
- if (!Module.IDHandler.has($0))
- return; // Godot Object is gone!
- ccall("_esws_on_connect",
- "void",
- ["number", "string"],
- [c_ptr, socket.protocol]
- );
- });
-
- // Listen for messages
- socket.addEventListener("message", function (event) {
- if (!Module.IDHandler.has($0))
- return; // Godot Object is gone!
- var buffer;
- var is_string = 0;
- if (event.data instanceof ArrayBuffer) {
-
- buffer = new Uint8Array(event.data);
-
- } else if (event.data instanceof Blob) {
-
- alert("Blob type not supported");
- return;
-
- } else if (typeof event.data === "string") {
-
- is_string = 1;
- var enc = new TextEncoder("utf-8");
- buffer = new Uint8Array(enc.encode(event.data));
-
- } else {
-
- alert("Unknown message type");
- return;
-
- }
- var len = buffer.length*buffer.BYTES_PER_ELEMENT;
- var out = _malloc(len);
- HEAPU8.set(buffer, out);
- ccall("_esws_on_message",
- "void",
- ["number", "number", "number", "number"],
- [c_ptr, out, len, is_string]
- );
- _free(out);
- });
-
- socket.addEventListener("error", function (event) {
- if (!Module.IDHandler.has($0))
- return; // Godot Object is gone!
- ccall("_esws_on_error",
- "void",
- ["number"],
- [c_ptr]
- );
- });
-
- socket.addEventListener("close", function (event) {
- if (!Module.IDHandler.has($0))
- return; // Godot Object is gone!
- var was_clean = 0;
- if (event.wasClean)
- was_clean = 1;
- ccall("_esws_on_close",
- "void",
- ["number", "number", "string", "number"],
- [c_ptr, event.code, event.reason, was_clean]
- );
- });
-
- return Module.IDHandler.add(socket);
- }, _js_id, str.utf8().get_data(), proto_string.utf8().get_data());
- /* clang-format on */
- if (peer_sock == -1)
+
+ _js_id = godot_js_websocket_create(this, str.utf8().get_data(), proto_string.utf8().get_data(), &_esws_on_connect, &_esws_on_message, &_esws_on_error, &_esws_on_close);
+ if (!_js_id) {
return FAILED;
+ }
- static_cast<Ref<EMWSPeer>>(_peer)->set_sock(peer_sock, _in_buf_size, _in_pkt_size);
+ static_cast<Ref<EMWSPeer>>(_peer)->set_sock(_js_id, _in_buf_size, _in_pkt_size);
return OK;
-};
+}
void EMWSClient::poll() {
}
@@ -200,19 +115,19 @@ NetworkedMultiplayerPeer::ConnectionStatus EMWSClient::get_connection_status() c
}
return CONNECTION_DISCONNECTED;
-};
+}
void EMWSClient::disconnect_from_host(int p_code, String p_reason) {
_peer->close(p_code, p_reason);
-};
+}
IP_Address EMWSClient::get_connected_host() const {
ERR_FAIL_V_MSG(IP_Address(), "Not supported in HTML5 export.");
-};
+}
uint16_t EMWSClient::get_connected_port() const {
ERR_FAIL_V_MSG(0, "Not supported in HTML5 export.");
-};
+}
int EMWSClient::get_max_packet_size() const {
return (1 << _in_buf_size) - PROTO_SIZE;
@@ -227,24 +142,17 @@ Error EMWSClient::set_buffers(int p_in_buffer, int p_in_packets, int p_out_buffe
EMWSClient::EMWSClient() {
_in_buf_size = DEF_BUF_SHIFT;
_in_pkt_size = DEF_PKT_SHIFT;
-
_is_connecting = false;
_peer = Ref<EMWSPeer>(memnew(EMWSPeer));
- /* clang-format off */
- _js_id = EM_ASM_INT({
- return Module.IDHandler.add($0);
- }, this);
- /* clang-format on */
-};
+ _js_id = 0;
+}
EMWSClient::~EMWSClient() {
disconnect_from_host();
_peer = Ref<EMWSPeer>();
- /* clang-format off */
- EM_ASM({
- Module.IDHandler.remove($0);
- }, _js_id);
- /* clang-format on */
-};
+ if (_js_id) {
+ godot_js_websocket_destroy(_js_id);
+ }
+}
#endif // JAVASCRIPT_ENABLED
diff --git a/modules/websocket/emws_client.h b/modules/websocket/emws_client.h
index 58973c52fa..0123c37457 100644
--- a/modules/websocket/emws_client.h
+++ b/modules/websocket/emws_client.h
@@ -41,13 +41,17 @@ class EMWSClient : public WebSocketClient {
GDCIIMPL(EMWSClient, WebSocketClient);
private:
+ int _js_id;
+ bool _is_connecting;
int _in_buf_size;
int _in_pkt_size;
- int _js_id;
-public:
- bool _is_connecting;
+ static void _esws_on_connect(void *obj, char *proto);
+ static void _esws_on_message(void *obj, const uint8_t *p_data, int p_data_size, int p_is_string);
+ static void _esws_on_error(void *obj);
+ static void _esws_on_close(void *obj, int code, const char *reason, int was_clean);
+public:
Error set_buffers(int p_in_buffer, int p_in_packets, int p_out_buffer, int p_out_packets);
Error connect_to_host(String p_host, String p_path, uint16_t p_port, bool p_ssl, const Vector<String> p_protocol = Vector<String>(), const Vector<String> p_custom_headers = Vector<String>());
Ref<WebSocketPeer> get_peer(int p_peer_id) const;
diff --git a/modules/websocket/emws_peer.cpp b/modules/websocket/emws_peer.cpp
index 749f45451a..5dcfba5567 100644
--- a/modules/websocket/emws_peer.cpp
+++ b/modules/websocket/emws_peer.cpp
@@ -47,38 +47,14 @@ EMWSPeer::WriteMode EMWSPeer::get_write_mode() const {
return write_mode;
}
-Error EMWSPeer::read_msg(uint8_t *p_data, uint32_t p_size, bool p_is_string) {
+Error EMWSPeer::read_msg(const uint8_t *p_data, uint32_t p_size, bool p_is_string) {
uint8_t is_string = p_is_string ? 1 : 0;
return _in_buffer.write_packet(p_data, p_size, &is_string);
}
Error EMWSPeer::put_packet(const uint8_t *p_buffer, int p_buffer_size) {
int is_bin = write_mode == WebSocketPeer::WRITE_MODE_BINARY ? 1 : 0;
-
- /* clang-format off */
- EM_ASM({
- var sock = Module.IDHandler.get($0);
- var bytes_array = new Uint8Array($2);
- var i = 0;
-
- for(i=0; i<$2; i++) {
- bytes_array[i] = getValue($1+i, 'i8');
- }
-
- try {
- if ($3) {
- sock.send(bytes_array.buffer);
- } else {
- var string = new TextDecoder("utf-8").decode(bytes_array);
- sock.send(string);
- }
- } catch (e) {
- return 1;
- }
- return 0;
- }, peer_sock, p_buffer, p_buffer_size, is_bin);
- /* clang-format on */
-
+ godot_js_websocket_send(peer_sock, p_buffer, p_buffer_size, is_bin);
return OK;
};
@@ -110,15 +86,7 @@ bool EMWSPeer::is_connected_to_host() const {
void EMWSPeer::close(int p_code, String p_reason) {
if (peer_sock != -1) {
- /* clang-format off */
- EM_ASM({
- var sock = Module.IDHandler.get($0);
- var code = $1;
- var reason = UTF8ToString($2);
- sock.close(code, reason);
- Module.IDHandler.remove($0);
- }, peer_sock, p_code, p_reason.utf8().get_data());
- /* clang-format on */
+ godot_js_websocket_close(peer_sock, p_code, p_reason.utf8().get_data());
}
_is_string = 0;
_in_buffer.clear();
diff --git a/modules/websocket/emws_peer.h b/modules/websocket/emws_peer.h
index c94d7e9148..2291a32bbc 100644
--- a/modules/websocket/emws_peer.h
+++ b/modules/websocket/emws_peer.h
@@ -40,6 +40,18 @@
#include "packet_buffer.h"
#include "websocket_peer.h"
+extern "C" {
+typedef void (*WSOnOpen)(void *p_ref, char *p_protocol);
+typedef void (*WSOnMessage)(void *p_ref, const uint8_t *p_buf, int p_buf_len, int p_is_string);
+typedef void (*WSOnClose)(void *p_ref, int p_code, const char *p_reason, int p_is_clean);
+typedef void (*WSOnError)(void *p_ref);
+
+extern int godot_js_websocket_create(void *p_ref, const char *p_url, const char *p_proto, WSOnOpen p_on_open, WSOnMessage p_on_message, WSOnError p_on_error, WSOnClose p_on_close);
+extern int godot_js_websocket_send(int p_id, const uint8_t *p_buf, int p_buf_len, int p_raw);
+extern void godot_js_websocket_close(int p_id, int p_code, const char *p_reason);
+extern void godot_js_websocket_destroy(int p_id);
+}
+
class EMWSPeer : public WebSocketPeer {
GDCIIMPL(EMWSPeer, WebSocketPeer);
@@ -52,7 +64,7 @@ private:
uint8_t _is_string;
public:
- Error read_msg(uint8_t *p_data, uint32_t p_size, bool p_is_string);
+ Error read_msg(const uint8_t *p_data, uint32_t p_size, bool p_is_string);
void set_sock(int p_sock, unsigned int p_in_buf_size, unsigned int p_in_pkt_size);
virtual int get_available_packet_count() const;
virtual Error get_packet(const uint8_t **r_buffer, int &r_buffer_size);
diff --git a/modules/websocket/library_godot_websocket.js b/modules/websocket/library_godot_websocket.js
new file mode 100644
index 0000000000..f7d3195807
--- /dev/null
+++ b/modules/websocket/library_godot_websocket.js
@@ -0,0 +1,187 @@
+/*************************************************************************/
+/* library_godot_websocket.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. */
+/*************************************************************************/
+
+var GodotWebSocket = {
+
+ // Our socket implementation that forwards events to C++.
+ $GodotWebSocket__deps: ['$IDHandler'],
+ $GodotWebSocket: {
+ // Connection opened, report selected protocol
+ _onopen: function(p_id, callback, event) {
+ const ref = IDHandler.get(p_id);
+ if (!ref) {
+ return; // Godot object is gone.
+ }
+ let c_str = GodotOS.allocString(ref.protocol);
+ callback(c_str);
+ _free(c_str);
+ },
+
+ // Message received, report content and type (UTF8 vs binary)
+ _onmessage: function(p_id, callback, event) {
+ const ref = IDHandler.get(p_id);
+ if (!ref) {
+ return; // Godot object is gone.
+ }
+ var buffer;
+ var is_string = 0;
+ if (event.data instanceof ArrayBuffer) {
+ buffer = new Uint8Array(event.data);
+ } else if (event.data instanceof Blob) {
+ alert("Blob type not supported");
+ return;
+ } else if (typeof event.data === "string") {
+ is_string = 1;
+ var enc = new TextEncoder("utf-8");
+ buffer = new Uint8Array(enc.encode(event.data));
+ } else {
+ alert("Unknown message type");
+ return;
+ }
+ var len = buffer.length*buffer.BYTES_PER_ELEMENT;
+ var out = _malloc(len);
+ HEAPU8.set(buffer, out);
+ callback(out, len, is_string);
+ _free(out);
+ },
+
+ // An error happened, 'onclose' will be called after this.
+ _onerror: function(p_id, callback, event) {
+ const ref = IDHandler.get(p_id);
+ if (!ref) {
+ return; // Godot object is gone.
+ }
+ callback();
+ },
+
+ // Connection is closed, this is always fired. Report close code, reason, and clean status.
+ _onclose: function(p_id, callback, event) {
+ const ref = IDHandler.get(p_id);
+ if (!ref) {
+ return; // Godot object is gone.
+ }
+ let c_str = GodotOS.allocString(event.reason);
+ callback(event.code, c_str, event.wasClean ? 1 : 0);
+ _free(c_str);
+ },
+
+ // Send a message
+ send: function(p_id, p_data) {
+ const ref = IDHandler.get(p_id);
+ if (!ref || ref.readyState != ref.OPEN) {
+ return 1; // Godot object is gone or socket is not in a ready state.
+ }
+ ref.send(p_data);
+ return 0;
+ },
+
+ create: function(socket, p_on_open, p_on_message, p_on_error, p_on_close) {
+ const id = IDHandler.add(socket);
+ socket.onopen = GodotWebSocket._onopen.bind(null, id, p_on_open);
+ socket.onmessage = GodotWebSocket._onmessage.bind(null, id, p_on_message);
+ socket.onerror = GodotWebSocket._onerror.bind(null, id, p_on_error);
+ socket.onclose = GodotWebSocket._onclose.bind(null, id, p_on_close);
+ return id;
+ },
+
+ // Closes the JavaScript WebSocket (if not already closing) associated to a given C++ object.
+ close: function(p_id, p_code, p_reason) {
+ const ref = IDHandler.get(p_id);
+ if (ref && ref.readyState < ref.CLOSING) {
+ const code = p_code;
+ const reason = UTF8ToString(p_reason);
+ ref.close(code, reason);
+ }
+ },
+
+ // Deletes the reference to a C++ object (closing any connected socket if necessary).
+ destroy: function(p_id) {
+ const ref = IDHandler.get(p_id);
+ if (!ref) {
+ return;
+ }
+ GodotWebSocket.close(p_id, 1001, '');
+ IDHandler.remove(p_id);
+ ref.onopen = null;
+ ref.onmessage = null;
+ ref.onerror = null;
+ ref.onclose = null;
+ },
+ },
+
+ godot_js_websocket_create: function(p_ref, p_url, p_proto, p_on_open, p_on_message, p_on_error, p_on_close) {
+ const on_open = GodotOS.get_func(p_on_open).bind(null, p_ref);
+ const on_message = GodotOS.get_func(p_on_message).bind(null, p_ref);
+ const on_error = GodotOS.get_func(p_on_error).bind(null, p_ref);
+ const on_close = GodotOS.get_func(p_on_close).bind(null, p_ref);
+ const url = UTF8ToString(p_url);
+ const protos = UTF8ToString(p_proto);
+ var socket = null;
+ try {
+ if (protos) {
+ socket = new WebSocket(url, protos.split(","));
+ } else {
+ socket = new WebSocket(url);
+ }
+ } catch (e) {
+ return 0;
+ }
+ socket.binaryType = "arraybuffer";
+ return GodotWebSocket.create(socket, on_open, on_message, on_error, on_close);
+ },
+
+ godot_js_websocket_send: function(p_id, p_buf, p_buf_len, p_raw) {
+ var bytes_array = new Uint8Array(p_buf_len);
+ var i = 0;
+ for(i = 0; i < p_buf_len; i++) {
+ bytes_array[i] = getValue(p_buf + i, 'i8');
+ }
+ var out = bytes_array;
+ if (p_raw) {
+ out = bytes_array.buffer;
+ } else {
+ out = new TextDecoder("utf-8").decode(bytes_array);
+ }
+ return GodotWebSocket.send(p_id, out);
+ },
+
+ godot_js_websocket_close: function(p_id, p_code, p_reason) {
+ const code = p_code;
+ const reason = UTF8ToString(p_reason);
+ GodotWebSocket.close(p_id, code, reason);
+ },
+
+ godot_js_websocket_destroy: function(p_id) {
+ GodotWebSocket.destroy(p_id);
+ },
+};
+
+autoAddDeps(GodotWebSocket, '$GodotWebSocket')
+mergeInto(LibraryManager.library, GodotWebSocket);