From 6a918288e9f1cfec25b9e0f1ddc852de5ae07b04 Mon Sep 17 00:00:00 2001 From: Stephen Hodgson Date: Mon, 4 Nov 2024 21:11:13 -0500 Subject: [PATCH] com.utilities.webscokets 1.0.1 (#5) - updated [unity 6 support](https://docs.unity3d.com/6000.0/Documentation/Manual/web-interacting-browser-deprecated.html) --- Runtime/Plugins/WebSocket.jslib | 443 +++++++++++++-------------- Runtime/Plugins/WebSocket.jspre | 22 ++ Runtime/Plugins/WebSocket.jspre.meta | 32 ++ Runtime/WebSocket_WebGL.cs | 2 +- package.json | 4 +- 5 files changed, 267 insertions(+), 236 deletions(-) create mode 100644 Runtime/Plugins/WebSocket.jspre create mode 100644 Runtime/Plugins/WebSocket.jspre.meta diff --git a/Runtime/Plugins/WebSocket.jslib b/Runtime/Plugins/WebSocket.jslib index d145301..00ea2eb 100644 --- a/Runtime/Plugins/WebSocket.jslib +++ b/Runtime/Plugins/WebSocket.jslib @@ -1,254 +1,231 @@ var UnityWebSocketLibrary = { - /** - * Pointer index for WebSocket objects. - */ - $ptrIndex: 0, - /** - * Array of instanced WebSocket objects. - */ - $webSockets: [], - /** - * Create a new WebSocket instance and adds it to the $webSockets array. - * @param {string} url - The URL to which to connect. - * @param {string[]} subProtocols - An json array of strings that indicate the sub-protocols the client is willing to speak. - * @returns {number} - A pointer to the WebSocket instance. - * @param {function} onOpenCallback - The callback function. WebSocket_OnOpenDelegate(IntPtr websocketPtr) in C#. - * @param {function} onMessageCallback - The callback function. WebSocket_OnMessageDelegate(IntPtr websocketPtr, IntPtr data, int length, int type) in C#. - * @param {function} onErrorCallback - The callback function. WebSocket_OnErrorDelegate(IntPtr websocketPtr, IntPtr messagePtr) in C#. - * @param {function} onCloseCallback - The callback function. WebSocket_OnCloseDelegate(IntPtr websocketPtr, int code, IntPtr reasonPtr) in C#. - */ - WebSocket_Create: function (url, subProtocols, onOpenCallback, onMessageCallback, onErrorCallback, onCloseCallback) { - var urlStr = UTF8ToString(url); - - try { - var subProtocolsStr = UTF8ToString(subProtocols); - var subProtocolsArr = subProtocolsStr ? JSON.parse(subProtocolsStr) : undefined; - - for (var i = 0; i < webSockets.length; i++) { - var instance = webSockets[i]; - - if (instance !== undefined && instance.url !== undefined && instance.url === urlStr) { - console.error('WebSocket connection already exists for URL: ', urlStr); - return 0; - } - } - - var socketPtr = ++ptrIndex; - webSockets[socketPtr] = { - socket: null, - url: urlStr, - onOpenCallback: onOpenCallback, - onMessageCallback: onMessageCallback, - onErrorCallback: onErrorCallback, - onCloseCallback: onCloseCallback - }; - - if (subProtocolsArr && Array.isArray(subProtocolsArr)) { - webSockets[socketPtr].subProtocols = subProtocolsArr; - } else { - console.error('subProtocols is not an array'); - } - - // console.log(`Created WebSocket object with websocketPtr: ${socketPtr} for URL: ${urlStr}, sub-protocols: ${subProtocolsArr}`); - return socketPtr; - } catch (error) { - console.error('Error creating WebSocket object for URL: ', urlStr, ' Error: ', error); - return 0; - } - }, - /** - * Get the current state of the WebSocket connection. - * @param socketPtr - A pointer to the WebSocket object. IntPtr in C#. - * @returns {number} - The current state of the WebSocket connection. - */ - WebSocket_GetState: function (socketPtr) { - try { - var instance = webSockets[socketPtr]; - - if (!instance || !instance.socket) { - return 0; - } - - return instance.socket.readyState; - } catch (error) { - console.error('Error getting WebSocket state for websocketPtr: ', socketPtr, ' Error: ', error); - return 3; + /** + * Pointer index for WebSocket objects. + */ + $ptrIndex: 0, + /** + * Array of instanced WebSocket objects. + */ + $webSockets: [], + /** + * Create a new WebSocket instance and adds it to the $webSockets array. + * @param {string} urlPtr - A pointer to the URL string of connection. + * @param {string[]} subProtocolsPtr - a pointer to a json array of strings that indicate the sub-protocols the client is willing to speak. + * @param {function} onOpenCallback - The callback function. WebSocket_OnOpenDelegate(IntPtr websocketPtr) in C#. + * @param {function} onMessageCallback - The callback function. WebSocket_OnMessageDelegate(IntPtr websocketPtr, IntPtr data, int length, int type) in C#. + * @param {function} onErrorCallback - The callback function. WebSocket_OnErrorDelegate(IntPtr websocketPtr, IntPtr messagePtr) in C#. + * @param {function} onCloseCallback - The callback function. WebSocket_OnCloseDelegate(IntPtr websocketPtr, int code, IntPtr reasonPtr) in C#. + * @returns {number} - A pointer to the WebSocket instance, IntPtr in C#. + */ + WebSocket_Create: function (urlPtr, subProtocolsPtr, onOpenCallback, onMessageCallback, onErrorCallback, onCloseCallback) { + var url = UTF8ToString(urlPtr); + try { + initializeDynCalls(); + for (var i = 0; i < webSockets.length; i++) { + var instance = webSockets[i]; + if (instance !== undefined && instance.url !== undefined && instance.url === url) { + console.error(`WebSocket connection already exists for URL: ${url}`); + return 0; } - }, - /** - * Connect the WebSocket connection. - * @param socketPtr - A pointer to the WebSocket object. IntPtr in C#. - */ - WebSocket_Connect: function (socketPtr) { + } + var socketPtr = ++ptrIndex; + webSockets[socketPtr] = { + socket: null, + url: url, + onOpenCallback: onOpenCallback, + onMessageCallback: onMessageCallback, + onErrorCallback: onErrorCallback, + onCloseCallback: onCloseCallback + }; + var subprotocolsStr = UTF8ToString(subProtocolsPtr); + var subProtocols = JSON.parse(subprotocolsStr); + if (subProtocols && Array.isArray(subProtocols)) { + webSockets[socketPtr].subProtocols = subProtocols; + } else { + console.error(`subProtocols is not an array: ${subprotocolsStr}`); + } + // console.log(`Created WebSocket object with websocketPtr: ${socketPtr} for URL: ${url}, sub-protocols: ${subProtocols}`); + return socketPtr; + } catch (error) { + console.error(`Error creating WebSocket object for URL: ${url} Error: ${error}`); + return 0; + } + }, + /** + * Get the current state of the WebSocket connection. + * @param socketPtr - A pointer to the WebSocket object. IntPtr in C#. + * @returns {number} - The current state of the WebSocket connection. + */ + WebSocket_GetState: function (socketPtr) { + try { + var instance = webSockets[socketPtr]; + if (!instance || !instance.socket) { return 0; } + return instance.socket.readyState; + } catch (error) { + console.error(`Error getting WebSocket state for websocketPtr: ${socketPtr} Error: ${error}`); + return 3; + } + }, + /** + * Connect the WebSocket connection. + * @param socketPtr - A pointer to the WebSocket object. IntPtr in C#. + */ + WebSocket_Connect: function (socketPtr) { + try { + var instance = webSockets[socketPtr]; + if (!instance) { + console.error(`WebSocket instance not found for websocketPtr: ${socketPtr}`); + return; + } + if (!instance.subProtocols || instance.subProtocols.length === 0) { + instance.socket = new WebSocket(instance.url); + } else { + instance.socket = new WebSocket(instance.url, instance.subProtocols); + } + instance.socket.binaryType = 'arraybuffer'; + instance.socket.onopen = function () { try { - var instance = webSockets[socketPtr]; - - if (!instance) { - console.error('WebSocket instance not found for websocketPtr: ', socketPtr); - return; - } - - if (!instance.subProtocols || instance.subProtocols.length === 0) { - instance.socket = new WebSocket(instance.url); - } else { - instance.socket = new WebSocket(instance.url, instance.subProtocols); - } - - instance.socket.binaryType = 'arraybuffer'; - instance.socket.onopen = function () { - try { - // console.log('WebSocket connection opened for websocketPtr: ', socketPtr); - Module.dynCall_vi(instance.onOpenCallback, socketPtr); - } catch (error) { - console.error('Error calling onOpen callback for websocketPtr: ', socketPtr, ' Error: ', error); - } - }; - instance.socket.onmessage = function (event) { - try { - // console.log('Received message for websocketPtr: ', socketPtr, ' with data: ', event.data); - if (event.data instanceof ArrayBuffer) { - var array = new Uint8Array(event.data); - var buffer = Module._malloc(array.length); - writeArrayToMemory(array, buffer); - - try { - Module.dynCall_viiii(instance.onMessageCallback, socketPtr, buffer, array.length, 1); - } finally { - Module._free(buffer); - } - } else if (typeof event.data === 'string') { - var length = lengthBytesUTF8(event.data) + 1; - var buffer = Module._malloc(length); - stringToUTF8(event.data, buffer, length); - - try { - Module.dynCall_viiii(instance.onMessageCallback, socketPtr, buffer, length, 0); - } finally { - Module._free(buffer); - } - } else { - console.error('Error parsing message for websocketPtr: ', socketPtr, ' with data: ', event.data); - } - } catch (error) { - console.error('Error calling onMessage callback for websocketPtr: ', socketPtr, ' Error: ', error); - } - }; - instance.socket.onerror = function (event) { - try { - console.error('WebSocket error for websocketPtr: ', socketPtr, ' with message: ', event); - var json = JSON.stringify(event); - var length = lengthBytesUTF8(json) + 1; - var buffer = Module._malloc(length); - stringToUTF8(json, buffer, length); - - try { - Module.dynCall_vii(instance.onErrorCallback, socketPtr, buffer); - } finally { - Module._free(buffer); - } - } catch (error) { - console.error('Error calling onError callback for websocketPtr: ', socketPtr, ' Error: ', error); - } - }; - instance.socket.onclose = function (event) { - try { - // console.log('WebSocket connection closed for websocketPtr: ', socketPtr, ' with code: ', event.code, ' and reason: ', event.reason); - var length = lengthBytesUTF8(event.reason) + 1; - var buffer = Module._malloc(length); - stringToUTF8(event.reason, buffer, length); - - try { - Module.dynCall_viii(instance.onCloseCallback, socketPtr, event.code, buffer); - } finally { - Module._free(buffer); - } - } catch (error) { - console.error('Error calling onClose callback for websocketPtr: ', socketPtr, ' Error: ', error); - } - }; - // console.log('Connecting WebSocket connection for websocketPtr: ', socketPtr); + // console.log(`WebSocket connection opened for websocketPtr: ${socketPtr}`); + Module.dynCall_vi(instance.onOpenCallback, socketPtr); } catch (error) { - console.error('Error connecting WebSocket connection for websocketPtr: ', socketPtr, ' Error: ', error); + console.error(`Error calling onOpen callback for websocketPtr: ${socketPtr} Error: ${error}`); } - }, - /** - * Send data to the WebSocket connection. - * @param socketPtr - A pointer to the WebSocket object. IntPtr in C#. - * @param data - A pointer to the data to send. - * @param length - The length of the data to send. - */ - WebSocket_SendData: function (socketPtr, data, length) { + }; + instance.socket.onmessage = function (event) { try { - var instance = webSockets[socketPtr]; - - if (!instance || !instance.socket || instance.socket.readyState !== 1) { - console.error('WebSocket connection does not exist for websocketPtr: ', socketPtr); - return; + // console.log(`Received message for websocketPtr: ${socketPtr} with data: ${event.data}`); + if (event.data instanceof ArrayBuffer) { + var array = new Uint8Array(event.data); + var buffer = _malloc(array.length); + writeArrayToMemory(array, buffer); + try { + Module.dynCall_viiii(instance.onMessageCallback, socketPtr, buffer, array.length, 1); + } finally { + _free(buffer); } - - // console.log('Sending message to WebSocket connection for websocketPtr: ', socketPtr, ' with data: ', data, ' and length: ', length); - instance.socket.send(buffer.slice(data, data + length)); - } catch (error) { - console.error('Error sending message to WebSocket connection for websocketPtr: ', socketPtr, ' Error: ', error); - } - }, - /** - * Send a string to the WebSocket connection. - * @param socketPtr - A pointer to the WebSocket object. IntPtr in C#. - * @param data - The string to send. - */ - WebSocket_SendString: function (socketPtr, data) { - try { - var instance = webSockets[socketPtr]; - - if (!instance || !instance.socket || instance.socket.readyState !== 1) { - console.error('WebSocket connection does not exist for websocketPtr: ', socketPtr); - return; + } else if (typeof event.data === 'string') { + var length = lengthBytesUTF8(event.data) + 1; + var buffer = _malloc(length); + stringToUTF8(event.data, buffer, length); + try { + Module.dynCall_viiii(instance.onMessageCallback, socketPtr, buffer, length, 0); + } finally { + _free(buffer); } - - var dataStr = UTF8ToString(data); - // console.log('Sending message to WebSocket connection for websocketPtr: ', socketPtr, ' with data: ', dataStr); - instance.socket.send(dataStr); + } else { + console.error(`Error parsing message for websocketPtr: ${socketPtr} with data: ${event.data}`); + } } catch (error) { - console.error('Error sending message to WebSocket connection for websocketPtr: ', socketPtr, ' Error: ', error); + console.error(`Error calling onMessage callback for websocketPtr: ${socketPtr} Error: ${error}`); } - }, - /** - * Close the WebSocket connection. - * @param socketPtr - A pointer to the WebSocket object. IntPtr in C#. - * @param code - The status code for the close. - * @param reason - The reason for the close. - */ - WebSocket_Close: function (socketPtr, code, reason) { + }; + instance.socket.onerror = function (event) { try { - var instance = webSockets[socketPtr]; - - if (!instance || !instance.socket || instance.socket.readyState >= 2) { - console.error('WebSocket connection already closed for websocketPtr: ', socketPtr); - return; - } - - var reasonStr = UTF8ToString(reason); - // console.log('Closing WebSocket connection for websocketPtr: ', socketPtr, ' with code: ', code, ' and reason: ', reasonStr); - instance.socket.close(code, reasonStr); + console.error(`WebSocket error for websocketPtr: ${socketPtr} with message: ${event}`); + var json = JSON.stringify(event); + var length = lengthBytesUTF8(json) + 1; + var buffer = _malloc(length); + stringToUTF8(json, buffer, length); + try { + Module.dynCall_vii(instance.onErrorCallback, socketPtr, buffer); + } finally { + _free(buffer); + } } catch (error) { - console.error('Error closing WebSocket connection for websocketPtr: ', socketPtr, ' Error: ', error); + console.error(`Error calling onError callback for websocketPtr: ${socketPtr} Error: ${error}`); } - }, - /** - * Destroy a WebSocket object. - * @param socketPtr - A pointer to the WebSocket object. IntPtr in C#. - */ - WebSocket_Dispose: function (socketPtr) { + }; + instance.socket.onclose = function (event) { try { - // console.log('Disposing WebSocket object with websocketPtr: ', socketPtr); - delete webSockets[socketPtr]; + // console.log(`WebSocket connection closed for websocketPtr: ${socketPtr} with code: ${event.code} and reason: ${event.reason}`); + var length = lengthBytesUTF8(event.reason) + 1; + var buffer = _malloc(length); + stringToUTF8(event.reason, buffer, length); + try { + Module.dynCall_viii(instance.onCloseCallback, socketPtr, event.code, buffer); + } finally { + _free(buffer); + } } catch (error) { - console.error('Error disposing WebSocket object with websocketPtr: ', socketPtr, ' Error: ', error); + console.error(`Error calling onClose callback for websocketPtr: ${socketPtr} Error: ${error}`); } + }; + // console.log(`Connecting WebSocket connection for websocketPtr: ${socketPtr}`); + } catch (error) { + console.error(`Error connecting WebSocket connection for websocketPtr: ${socketPtr} Error: ${error}`); + } + }, + /** + * Send data to the WebSocket connection. + * @param socketPtr - A pointer to the WebSocket object. IntPtr in C#. + * @param data - A pointer to the data to send. + * @param length - The length of the data to send. + */ + WebSocket_SendData: function (socketPtr, data, length) { + try { + var instance = webSockets[socketPtr]; + if (!instance || !instance.socket || instance.socket.readyState !== 1) { + console.error(`WebSocket connection does not exist for websocketPtr: ${socketPtr}`); + return; + } + // console.log(`Sending message to WebSocket connection for websocketPtr: ${socketPtr} with data: ${data} and length: ${length}`); + instance.socket.send(new Uint8Array(Module.HEAPU8.subarray(data, data + length))); + } catch (error) { + console.error(`Error sending message to WebSocket connection for websocketPtr: ${socketPtr} Error: ${error}`); + } + }, + /** + * Send a string to the WebSocket connection. + * @param socketPtr - A pointer to the WebSocket object. IntPtr in C#. + * @param data - The string to send. + */ + WebSocket_SendString: function (socketPtr, data) { + try { + var instance = webSockets[socketPtr]; + if (!instance || !instance.socket || instance.socket.readyState !== 1) { + console.error(`WebSocket connection does not exist for websocketPtr: ${socketPtr}`); + return; + } + var dataStr = UTF8ToString(data); + // console.log(`Sending message to WebSocket connection for websocketPtr: ${socketPtr} with data: ${dataStr}`); + instance.socket.send(dataStr); + } catch (error) { + console.error(`Error sending message to WebSocket connection for websocketPtr: ${socketPtr} Error: ${error}`); + } + }, + /** + * Close the WebSocket connection. + * @param socketPtr - A pointer to the WebSocket object. IntPtr in C#. + * @param code - The status code for the close. + * @param reason - The reason for the close. + */ + WebSocket_Close: function (socketPtr, code, reason) { + try { + var instance = webSockets[socketPtr]; + if (!instance || !instance.socket || instance.socket.readyState >= 2) { + console.error(`WebSocket connection already closed for websocketPtr: ${socketPtr}`); + return; + } + var reasonStr = UTF8ToString(reason); + // console.log(`Closing WebSocket connection for websocketPtr: ${socketPtr} with code: ${code} and reason: ${reasonStr}`); + instance.socket.close(code, reasonStr); + } catch (error) { + console.error(`Error closing WebSocket connection for websocketPtr: ${socketPtr} Error: ${error}`); + } + }, + /** + * Destroy a WebSocket object. + * @param socketPtr - A pointer to the WebSocket object. IntPtr in C#. + */ + WebSocket_Dispose: function (socketPtr) { + try { + // console.log(`Disposing WebSocket object with websocketPtr: ${socketPtr}`); + delete webSockets[socketPtr]; + } catch (error) { + console.error(`Error disposing WebSocket object with websocketPtr: ${socketPtr} Error: ${error}`); } + } }; - autoAddDeps(UnityWebSocketLibrary, '$ptrIndex'); autoAddDeps(UnityWebSocketLibrary, '$webSockets'); mergeInto(LibraryManager.library, UnityWebSocketLibrary); diff --git a/Runtime/Plugins/WebSocket.jspre b/Runtime/Plugins/WebSocket.jspre new file mode 100644 index 0000000..16b68be --- /dev/null +++ b/Runtime/Plugins/WebSocket.jspre @@ -0,0 +1,22 @@ +/** + * Initializes the dynCall_* function table lookups. + * Thanks to @De-Panther for the following code snippet. + * Checks if specific dynCall functions exist, + * if not, it will create them using the getWasmTableEntry function. + * @see https://discussions.unity.com/t/makedyncall-replacing-dyncall-in-unity-6/1543088 + * @returns {void} +*/ +function initializeDynCalls() { + Module.dynCall_vi = Module.dynCall_vi || function (cb, arg1) { + return getWasmTableEntry(cb)(arg1); + }; + Module.dynCall_vii = Module.dynCall_vii || function (cb, arg1, arg2) { + return getWasmTableEntry(cb)(arg1, arg2); + } + Module.dynCall_viii = Module.dynCall_viii || function (cb, arg1, arg2, arg3) { + return getWasmTableEntry(cb)(arg1, arg2, arg3); + } + Module.dynCall_viiii = Module.dynCall_viiii || function (cb, arg1, arg2, arg3, arg4) { + return getWasmTableEntry(cb)(arg1, arg2, arg3, arg4); + } +} \ No newline at end of file diff --git a/Runtime/Plugins/WebSocket.jspre.meta b/Runtime/Plugins/WebSocket.jspre.meta new file mode 100644 index 0000000..f421192 --- /dev/null +++ b/Runtime/Plugins/WebSocket.jspre.meta @@ -0,0 +1,32 @@ +fileFormatVersion: 2 +guid: 3a10f3add6f3d90428c7f10defa7f23d +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 1 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + - first: + WebGL: WebGL + second: + enabled: 1 + settings: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/WebSocket_WebGL.cs b/Runtime/WebSocket_WebGL.cs index c6b7edb..b9a53b3 100644 --- a/Runtime/WebSocket_WebGL.cs +++ b/Runtime/WebSocket_WebGL.cs @@ -41,7 +41,7 @@ public WebSocket(Uri uri, IReadOnlyDictionary requestHeaders = n RequestHeaders = requestHeaders ?? new Dictionary(); _socket = WebSocket_Create( uri.ToString(), - JsonConvert.SerializeObject(subProtocols), + JsonConvert.SerializeObject(SubProtocols), WebSocket_OnOpen, WebSocket_OnMessage, WebSocket_OnError, diff --git a/package.json b/package.json index 5f22bd6..7fb4226 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "displayName": "Utilities.WebSockets", "description": "A simple websocket package for Unity (UPM)", "keywords": [], - "version": "1.0.0", + "version": "1.0.1", "unity": "2021.3", "documentationUrl": "https://github.com/RageAgainstThePixel/com.utilities.websockets#documentation", "changelogUrl": "https://github.com/RageAgainstThePixel/com.utilities.websockets/releases", @@ -17,7 +17,7 @@ "url": "https://github.com/StephenHodgson" }, "dependencies": { - "com.utilities.async": "2.1.7", + "com.utilities.async": "2.2.1", "com.unity.nuget.newtonsoft-json": "3.2.1" }, "samples": [