Skip to content

Commit

Permalink
feat: Allowing multiple instances of javascript client (#14)
Browse files Browse the repository at this point in the history
* refactoring js client to allow for multiple instances

* tests for opening multiple instances of the client
  • Loading branch information
James-Frowen authored Oct 10, 2020
1 parent 0154303 commit cc6e513
Show file tree
Hide file tree
Showing 8 changed files with 160 additions and 105 deletions.
67 changes: 16 additions & 51 deletions source/Client/SimpleWebClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,34 +4,34 @@

namespace Mirror.SimpleWeb
{
public interface IWebSocketClient
{
event Action onConnect;
event Action onDisconnect;
event Action<ArraySegment<byte>> onData;
event Action<Exception> onError;

ClientState ConnectionState { get; }
void Connect(string address);
void Disconnect();
void Send(ArraySegment<byte> segment);
void ProcessMessageQueue(MonoBehaviour behaviour);
}

public enum ClientState
{
NotConnected = 0,
Connecting = 1,
Connected = 2,
Disconnecting = 3,
}
public abstract class WebSocketClientBase : IWebSocketClient
/// <summary>
/// Client used to control websockets
/// <para>Base class used by WebSocketClientWebGl and WebSocketClientStandAlone</para>
/// </summary>
public abstract class SimpleWebClient
{
public static SimpleWebClient Create(int maxMessageSize, int maxMessagesPerTick)
{
#if UNITY_WEBGL && !UNITY_EDITOR
return new WebSocketClientWebGl(maxMessageSize, maxMessagesPerTick);
#else
return new WebSocketClientStandAlone(maxMessageSize, maxMessagesPerTick);
#endif
}


readonly int maxMessagesPerTick;
protected readonly ConcurrentQueue<Message> receiveQueue = new ConcurrentQueue<Message>();
protected ClientState state;

protected WebSocketClientBase(int maxMessagesPerTick)
protected SimpleWebClient(int maxMessagesPerTick)
{
this.maxMessagesPerTick = maxMessagesPerTick;
}
Expand Down Expand Up @@ -77,39 +77,4 @@ public void ProcessMessageQueue(MonoBehaviour behaviour)
public abstract void Disconnect();
public abstract void Send(ArraySegment<byte> segment);
}

public static class SimpleWebClient
{
static IWebSocketClient instance;

public static IWebSocketClient Create(int maxMessageSize, int clientMaxMessagesPerTick)
{
if (instance != null)
{
Debug.LogError("Cant create SimpleWebClient while one already exists");
return null;
}

#if UNITY_WEBGL && !UNITY_EDITOR
instance = new WebSocketClientWebGl(maxMessageSize, clientMaxMessagesPerTick);
#else
instance = new WebSocketClientStandAlone(maxMessageSize, clientMaxMessagesPerTick);
#endif
return instance;
}

public static void CloseExisting()
{
instance?.Disconnect();
instance = null;
}

/// <summary>
/// Called by IWebSocketClient on disconnect
/// </summary>
internal static void RemoveInstance()
{
instance = null;
}
}
}
2 changes: 1 addition & 1 deletion source/Client/StandAlone/WebSocketClientStandAlone.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

namespace Mirror.SimpleWeb
{
internal class WebSocketClientStandAlone : WebSocketClientBase, IWebSocketClient
internal class WebSocketClientStandAlone : SimpleWebClient
{
object lockObject = new object();
bool hasClosed;
Expand Down
18 changes: 10 additions & 8 deletions source/Client/Webgl/SimpleWebJSLib.cs
Original file line number Diff line number Diff line change
@@ -1,32 +1,34 @@
using System;
#if UNITY_WEBGL
using System.Runtime.InteropServices;
#endif

namespace Mirror.SimpleWeb
{
internal static class SimpleWebJSLib
{
#if UNITY_WEBGL
[DllImport("__Internal")]
internal static extern bool IsConnected();
internal static extern bool IsConnected(int index);

#pragma warning disable CA2101 // Specify marshaling for P/Invoke string arguments
[DllImport("__Internal")]
#pragma warning restore CA2101 // Specify marshaling for P/Invoke string arguments
internal static extern void Connect(string address, Action openCallback, Action closeCallBack, Action<IntPtr, int> messageCallback, Action errorCallback);
internal static extern int Connect(string address, Action<int> openCallback, Action<int> closeCallBack, Action<int, IntPtr, int> messageCallback, Action<int> errorCallback);

[DllImport("__Internal")]
internal static extern void Disconnect();
internal static extern void Disconnect(int index);

[DllImport("__Internal")]
internal static extern bool Send(byte[] array, int offset, int length);
internal static extern bool Send(int index, byte[] array, int offset, int length);
#else
internal static bool IsConnected() => throw new NotSupportedException();
internal static bool IsConnected(int index) => throw new NotSupportedException();

internal static void Connect(string address, Action openCallback, Action closeCallBack, Action<IntPtr, int> messageCallback, Action errorCallback) => throw new NotSupportedException();
internal static int Connect(string address, Action<int> openCallback, Action<int> closeCallBack, Action<int, IntPtr, int> messageCallback, Action<int> errorCallback) => throw new NotSupportedException();

internal static void Disconnect() => throw new NotSupportedException();
internal static void Disconnect(int index) => throw new NotSupportedException();

internal static bool Send(byte[] array, int offset, int length) => throw new NotSupportedException();
internal static bool Send(int index, byte[] array, int offset, int length) => throw new NotSupportedException();
#endif
}
}
67 changes: 38 additions & 29 deletions source/Client/Webgl/WebSocketClientWebGl.cs
Original file line number Diff line number Diff line change
@@ -1,39 +1,40 @@
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using AOT;
using UnityEngine;

namespace Mirror.SimpleWeb
{
internal class WebSocketClientWebGl : WebSocketClientBase, IWebSocketClient
internal class WebSocketClientWebGl : SimpleWebClient
{
static WebSocketClientWebGl instance;
static readonly Dictionary<int, WebSocketClientWebGl> instances = new Dictionary<int, WebSocketClientWebGl>();

readonly int maxMessageSize;
public int index;

internal WebSocketClientWebGl(int maxMessageSize, int maxMessagesPerTick) : base(maxMessagesPerTick)
{
#if UNITY_WEBGL && !UNITY_EDITOR
instance = this;
this.maxMessageSize = maxMessageSize;
#else
#if !UNITY_WEBGL || UNITY_EDITOR
throw new NotSupportedException();
#endif
}

public bool CheckJsConnected() => SimpleWebJSLib.IsConnected();
public bool CheckJsConnected() => SimpleWebJSLib.IsConnected(index);

public override void Connect(string address)
{
SimpleWebJSLib.Connect(address, OpenCallback, CloseCallBack, MessageCallback, ErrorCallback);
index = SimpleWebJSLib.Connect(address, OpenCallback, CloseCallBack, MessageCallback, ErrorCallback);
instances.Add(index, this);
state = ClientState.Connecting;
}

public override void Disconnect()
{
instance.state = ClientState.Disconnecting;
state = ClientState.Disconnecting;
// disconnect should cause closeCallback and OnDisconnect to be called
SimpleWebJSLib.Disconnect();
SimpleWebJSLib.Disconnect(index);
}

public override void Send(ArraySegment<byte> segment)
Expand All @@ -44,49 +45,57 @@ public override void Send(ArraySegment<byte> segment)
return;
}

SimpleWebJSLib.Send(segment.Array, 0, segment.Count);
SimpleWebJSLib.Send(index, segment.Array, 0, segment.Count);
}


[MonoPInvokeCallback(typeof(Action))]
static void OpenCallback()
void onOpen()
{
instance.receiveQueue.Enqueue(new Message(EventType.Connected));
instance.state = ClientState.Connected;
receiveQueue.Enqueue(new Message(EventType.Connected));
state = ClientState.Connected;
}

[MonoPInvokeCallback(typeof(Action))]
static void CloseCallBack()
void onClose()
{
instance.receiveQueue.Enqueue(new Message(EventType.Disconnected));
instance.state = ClientState.NotConnected;
SimpleWebClient.RemoveInstance();
// this code should be last in this class

receiveQueue.Enqueue(new Message(EventType.Disconnected));
state = ClientState.NotConnected;
instances.Remove(index);
}

[MonoPInvokeCallback(typeof(Action<IntPtr, int>))]
static void MessageCallback(IntPtr bufferPtr, int count)
void onMessage(IntPtr bufferPtr, int count)
{
try
{
byte[] buffer = new byte[count];
Marshal.Copy(bufferPtr, buffer, 0, count);

ArraySegment<byte> segment = new ArraySegment<byte>(buffer, 0, count);
instance.receiveQueue.Enqueue(new Message(segment));
receiveQueue.Enqueue(new Message(segment));
}
catch (Exception e)
{
Log.Error($"onData {e.GetType()}: {e.Message}\n{e.StackTrace}");
instance.receiveQueue.Enqueue(new Message(e));
receiveQueue.Enqueue(new Message(e));
}
}

[MonoPInvokeCallback(typeof(Action))]
static void ErrorCallback()
void onErr()
{
instance.receiveQueue.Enqueue(new Message(new Exception("Javascript Websocket error")));
SimpleWebJSLib.Disconnect();
instance.state = ClientState.NotConnected;
receiveQueue.Enqueue(new Message(new Exception("Javascript Websocket error")));
Disconnect();
}

[MonoPInvokeCallback(typeof(Action<int>))]
static void OpenCallback(int index) => instances[index].onOpen();

[MonoPInvokeCallback(typeof(Action<int>))]
static void CloseCallBack(int index) => instances[index].onClose();

[MonoPInvokeCallback(typeof(Action<int, IntPtr, int>))]
static void MessageCallback(int index, IntPtr bufferPtr, int count) => instances[index].onMessage(bufferPtr, count);

[MonoPInvokeCallback(typeof(Action<int>))]
static void ErrorCallback(int index) => instances[index].onErr();
}
}
50 changes: 39 additions & 11 deletions source/Client/Webgl/plugin/SimpleWeb.jslib
Original file line number Diff line number Diff line change
@@ -1,6 +1,23 @@
let webSocket = undefined;
// this will create a global object
const SimpleWeb = {
webSockets: [],
next: 1,
GetWebSocket: function (index) {
return SimpleWeb.webSockets[index]
},
AddNextSocket: function (webSocket) {
var index = SimpleWeb.next;
SimpleWeb.next++;
SimpleWeb.webSockets[index] = webSocket;
return index;
},
RemoveSocket: function (index) {
SimpleWeb.webSockets[index] = undefined;
},
};

function IsConnected() {
function IsConnected(index) {
var webSocket = SimpleWeb.GetWebSocket(index);
if (webSocket) {
return webSocket.readyState === webSocket.OPEN;
}
Expand All @@ -15,15 +32,16 @@ function Connect(addressPtr, openCallbackPtr, closeCallBackPtr, messageCallbackP
// Create webSocket connection.
webSocket = new WebSocket(address);
webSocket.binaryType = 'arraybuffer';
const index = SimpleWeb.AddNextSocket(webSocket);

// Connection opened
webSocket.addEventListener('open', function (event) {
console.log("Connected to " + address);
Runtime.dynCall('v', openCallbackPtr, 0);
Runtime.dynCall('vi', openCallbackPtr, [index]);
});
webSocket.addEventListener('close', function (event) {
console.log("Disconnected from " + address);
Runtime.dynCall('v', closeCallBackPtr, 0);
Runtime.dynCall('vi', closeCallBackPtr, [index]);
});

// Listen for messages
Expand All @@ -37,7 +55,7 @@ function Connect(addressPtr, openCallbackPtr, closeCallBackPtr, messageCallbackP
var dataBuffer = new Uint8Array(HEAPU8.buffer, bufferPtr, arrayLength);
dataBuffer.set(array);

Runtime.dynCall('vii', messageCallbackPtr, [bufferPtr, arrayLength]);
Runtime.dynCall('viii', messageCallbackPtr, [index, bufferPtr, arrayLength]);
_free(bufferPtr);
}
else {
Expand All @@ -48,30 +66,40 @@ function Connect(addressPtr, openCallbackPtr, closeCallBackPtr, messageCallbackP
webSocket.addEventListener('error', function (event) {
console.error('Socket Error', event);

Runtime.dynCall('v', errorCallbackPtr, 0);
Runtime.dynCall('vi', errorCallbackPtr, [index]);
});

return index;
}

function Disconnect() {
function Disconnect(index) {
var webSocket = SimpleWeb.GetWebSocket(index);
if (webSocket) {
webSocket.close(1000, "Disconnect Called by Mirror");
}

webSocket = undefined;
SimpleWeb.RemoveSocket(index);
}

function Send(arrayPtr, offset, length) {
function Send(index, arrayPtr, offset, length) {
var webSocket = SimpleWeb.GetWebSocket(index);
if (webSocket) {
const start = arrayPtr + offset;
const end = start + length;
const data = HEAPU8.buffer.slice(start, end);
webSocket.send(data);
return true;
}
return false;
}

mergeInto(LibraryManager.library, {

const SimpleWebLib = {
$SimpleWeb: SimpleWeb,
IsConnected,
Connect,
Disconnect,
Send
});
};
autoAddDeps(SimpleWebLib, '$SimpleWeb');
mergeInto(LibraryManager.library, SimpleWebLib);
Loading

0 comments on commit cc6e513

Please sign in to comment.