#if UNITY_WEBGL && !UNITY_EDITOR using System; using System.Collections.Generic; using System.Runtime.InteropServices; using Best.HTTP.Shared; using Best.HTTP.Shared.PlatformSupport.Memory; namespace Best.WebSockets.Implementations { delegate void OnWebGLWebSocketOpenDelegate(uint id); delegate void OnWebGLWebSocketTextDelegate(uint id, [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.U1, SizeParamIndex = 2)] byte[] textBuffer, int allocatedLength, int length); delegate void OnWebGLWebSocketBinaryDelegate(uint id, [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.U1, SizeParamIndex = 2)] byte[] buffer, int allocatedLength, int length); delegate void OnWebGLWebSocketErrorDelegate(uint id, string error); delegate void OnWebGLWebSocketCloseDelegate(uint id, int code, string reason); delegate IntPtr OnWebGLAllocArray(int nativeId, int size); internal sealed class WebGLBrowser : WebSocketBaseImplementation { public override WebSocketStates State => ImplementationId != 0 ? WS_GetState(ImplementationId) : WebSocketStates.Unknown; public override bool IsOpen => ImplementationId != 0 && WS_GetState(ImplementationId) == WebSocketStates.Open; public override int BufferedAmount => WS_GetBufferedAmount(ImplementationId); internal static Dictionary WebSockets = new Dictionary(); private uint ImplementationId; public WebGLBrowser(WebSocket parent, Uri uri, string origin, string protocol) : base(parent, uri, origin, protocol) { } public override void StartOpen() { try { ImplementationId = WS_Create(this.Uri.OriginalString, this.Protocol, OnOpenCallback, OnTextCallback, OnBinaryCallback, OnErrorCallback, OnCloseCallback, Allocator); WebSockets.Add(ImplementationId, this.Parent); } catch(Exception ex) { HTTPManager.Logger.Exception("WebSocket", "Open", ex, this.Parent.Context); } } public override void StartClose(WebSocketStatusCodes code, string message) { WS_Close(this.ImplementationId, (ushort)code, message); } public override void Send(string message) { var count = System.Text.Encoding.UTF8.GetByteCount(message); var buffer = BufferPool.Get(count, true); System.Text.Encoding.UTF8.GetBytes(message, 0, message.Length, buffer, 0); WS_Send_String(this.ImplementationId, buffer, 0, count); BufferPool.Release(buffer); } public override void Send(byte[] buffer) { WS_Send_Binary(this.ImplementationId, buffer, 0, buffer.Length); } public override void Send(byte[] buffer, ulong offset, ulong count) { WS_Send_Binary(this.ImplementationId, buffer, (int)offset, (int)count); } public override void SendAsBinary(BufferSegment data) { WS_Send_Binary(this.ImplementationId, data.Data, data.Offset, data.Count); BufferPool.Release(data); } public override void SendAsText(BufferSegment data) { WS_Send_String(this.ImplementationId, data.Data, data.Offset, data.Count); BufferPool.Release(data); } [DllImport("__Internal")] static extern uint WS_Create(string url, string protocol, OnWebGLWebSocketOpenDelegate onOpen, OnWebGLWebSocketTextDelegate onText, OnWebGLWebSocketBinaryDelegate onBinary, OnWebGLWebSocketErrorDelegate onError, OnWebGLWebSocketCloseDelegate onClose, OnWebGLAllocArray allocator); [DllImport("__Internal")] static extern WebSocketStates WS_GetState(uint id); [DllImport("__Internal")] static extern int WS_GetBufferedAmount(uint id); [DllImport("__Internal")] static extern int WS_Send_String(uint id, byte[] strData, int pos, int length); [DllImport("__Internal")] static extern int WS_Send_Binary(uint id, byte[] buffer, int pos, int length); [DllImport("__Internal")] static extern void WS_Close(uint id, ushort code, string reason); [DllImport("__Internal")] static extern void WS_Release(uint id); [AOT.MonoPInvokeCallback(typeof(OnWebGLAllocArray))] static unsafe IntPtr Allocator(int nativeId, int length) { byte[] buffer = BufferPool.Get(length, true); if (HTTPManager.Logger.IsDiagnostic) HTTPManager.Logger.Verbose(nameof(WebGLBrowser), $"Allocator - allocated: {buffer.Length}"); buffer[0] = (byte)(buffer.Length >> 24); buffer[1] = (byte)(buffer.Length >> 16); buffer[2] = (byte)(buffer.Length >> 8); buffer[3] = (byte)(buffer.Length); fixed (byte* ptr = buffer) { var p = (IntPtr)ptr; if (HTTPManager.Logger.IsDiagnostic) HTTPManager.Logger.Verbose(nameof(WebGLBrowser), $"({p}) <= Allocator({nativeId}, {length})"); return p; } } [AOT.MonoPInvokeCallback(typeof(OnWebGLWebSocketOpenDelegate))] static void OnOpenCallback(uint id) { WebSocket ws; if (WebSockets.TryGetValue(id, out ws)) { if (ws.OnOpen != null) { try { ws.OnOpen(ws); } catch(Exception ex) { HTTPManager.Logger.Exception("WebSocket", "OnOpen", ex, ws.Context); } } } else HTTPManager.Logger.Warning("WebSocket", "OnOpenCallback - No WebSocket found for id: " + id.ToString(), ws.Context); } [AOT.MonoPInvokeCallback(typeof(OnWebGLWebSocketTextDelegate))] static void OnTextCallback(uint id, [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.U1, SizeParamIndex = 2)] byte[] textBuffer, int allocatedLength, int length) { try { WebSocket ws; if (WebSockets.TryGetValue(id, out ws)) { if (ws.OnMessage != null) { try { var text = System.Text.Encoding.UTF8.GetString(textBuffer, 0, length); if (HTTPManager.Logger.IsDiagnostic) HTTPManager.Logger.Verbose(nameof(WebGLBrowser), $"{id}, {textBuffer}, {length} => {text}", ws.Context); ws.OnMessage(ws, text); } catch (Exception ex) { HTTPManager.Logger.Exception("WebSocket", "OnMessage", ex, ws.Context); } } } else HTTPManager.Logger.Warning("WebSocket", "OnTextCallback - No WebSocket found for id: " + id.ToString()); } finally { BufferPool.Release(textBuffer); } } [AOT.MonoPInvokeCallback(typeof(OnWebGLWebSocketBinaryDelegate))] static void OnBinaryCallback(uint id, [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.U1, SizeParamIndex = 2)] byte[] buffer, int allocatedLength, int length) { WebSocket ws; if (WebSockets.TryGetValue(id, out ws)) { if (ws.OnBinary != null) { try { ws.OnBinary(ws, new BufferSegment(buffer, 0, length)); BufferPool.Release(buffer); } catch (Exception ex) { HTTPManager.Logger.Exception("WebSocket", "OnBinary", ex, ws.Context); } } } else HTTPManager.Logger.Warning("WebSocket", "OnBinaryCallback - No WebSocket found for id: " + id.ToString()); } [AOT.MonoPInvokeCallback(typeof(OnWebGLWebSocketErrorDelegate))] static void OnErrorCallback(uint id, string error) { WebSocket ws; if (WebSockets.TryGetValue(id, out ws)) { WebSockets.Remove(id); if (ws.OnClosed != null) { try { ws.OnClosed(ws, WebSocketStatusCodes.ClosedAbnormally, error); } catch (Exception ex) { HTTPManager.Logger.Exception("WebSocket", "OnError", ex, ws.Context); } } } else HTTPManager.Logger.Warning("WebSocket", "OnErrorCallback - No WebSocket found for id: " + id.ToString()); try { WS_Release(id); } catch(Exception ex) { HTTPManager.Logger.Exception("WebSocket", "WS_Release", ex); } } [AOT.MonoPInvokeCallback(typeof(OnWebGLWebSocketCloseDelegate))] static void OnCloseCallback(uint id, int code, string reason) { WebSocket ws; if (WebSockets.TryGetValue(id, out ws)) { WebSockets.Remove(id); if (ws.OnClosed != null) { try { ws.OnClosed(ws, (WebSocketStatusCodes)code, reason); } catch (Exception ex) { HTTPManager.Logger.Exception("WebSocket", "OnClosed", ex, ws.Context); } } } else HTTPManager.Logger.Warning("WebSocket", "OnCloseCallback - No WebSocket found for id: " + id.ToString()); try { WS_Release(id); } catch(Exception ex) { HTTPManager.Logger.Exception("WebSocket", "WS_Release", ex); } } } } #endif