Seems to be fully working
This commit is contained in:
@@ -1,13 +1,18 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Linq;
|
||||||
using Assets.Scripts;
|
using Assets.Scripts;
|
||||||
using Assets.Scripts.Objects.Electrical;
|
using Assets.Scripts.Objects.Electrical;
|
||||||
using HarmonyLib;
|
using HarmonyLib;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
|
using RemoteControl.Message;
|
||||||
|
using Swan;
|
||||||
|
using GameDevice = Assets.Scripts.Objects.Pipes.Device;
|
||||||
|
|
||||||
namespace RemoteControl.Patches
|
namespace RemoteControl.Patches
|
||||||
{
|
{
|
||||||
[HarmonyPatch(typeof(LogicStack), nameof(LogicStack.LogicStackTick))]
|
[HarmonyPatch(typeof(RoomController), nameof(RoomController.ThreadedWork))]
|
||||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||||
// ReSharper disable once InconsistentNaming
|
// ReSharper disable once InconsistentNaming
|
||||||
public class LogicStack_LogicStackTick
|
public class LogicStack_LogicStackTick
|
||||||
@@ -18,6 +23,7 @@ namespace RemoteControl.Patches
|
|||||||
internal bool Taken;
|
internal bool Taken;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[UsedImplicitly]
|
[UsedImplicitly]
|
||||||
private static void Prefix(out State __state)
|
private static void Prefix(out State __state)
|
||||||
{
|
{
|
||||||
@@ -26,12 +32,11 @@ namespace RemoteControl.Patches
|
|||||||
LockObj = SubscriptionManager.Lock,
|
LockObj = SubscriptionManager.Lock,
|
||||||
Taken = false
|
Taken = false
|
||||||
};
|
};
|
||||||
if (!GameManager.RunSimulation) return;
|
|
||||||
// System.Threading.Monitor.Enter(__state.LockObj, ref __state.Taken);
|
// System.Threading.Monitor.Enter(__state.LockObj, ref __state.Taken);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
RemoteControl.Log("logic stack tick: start prefix");
|
RemoteControl.Log("logic stack tick: start prefix");
|
||||||
SubscriptionManager.ApplyUpdates();
|
RemoteControl.Subscribers.SendUpdate();
|
||||||
RemoteControl.Log("logic stack tick: end prefix");
|
RemoteControl.Log("logic stack tick: end prefix");
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
@@ -46,7 +51,10 @@ namespace RemoteControl.Patches
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!GameManager.RunSimulation) return;
|
if (!GameManager.RunSimulation) return;
|
||||||
|
RemoteControl.Log("logic stack tick: start prefix");
|
||||||
SubscriptionManager.RescanNetworks();
|
SubscriptionManager.RescanNetworks();
|
||||||
|
RemoteControl.Log("logic stack tick: start postfix");
|
||||||
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ using EmbedIO.Routing;
|
|||||||
using EmbedIO.WebApi;
|
using EmbedIO.WebApi;
|
||||||
using RemoteControl.Message;
|
using RemoteControl.Message;
|
||||||
using RemoteControl.Utils;
|
using RemoteControl.Utils;
|
||||||
using Swan;
|
|
||||||
using GameDevice = Assets.Scripts.Objects.Pipes.Device;
|
using GameDevice = Assets.Scripts.Objects.Pipes.Device;
|
||||||
|
|
||||||
|
|
||||||
@@ -38,7 +37,8 @@ namespace RemoteControl
|
|||||||
|
|
||||||
private readonly Harmony _harmony = new Harmony(pluginGuid);
|
private readonly Harmony _harmony = new Harmony(pluginGuid);
|
||||||
|
|
||||||
public WebServer WebServer { get; private set; }
|
public static WebServer WebServer { get; private set; }
|
||||||
|
internal static SubscriptionModule Subscribers { get; private set; }
|
||||||
|
|
||||||
|
|
||||||
private CancellationTokenSource _modLifecycle = new();
|
private CancellationTokenSource _modLifecycle = new();
|
||||||
@@ -70,6 +70,7 @@ namespace RemoteControl
|
|||||||
|
|
||||||
|
|
||||||
var subscriptionModule = new SubscriptionModule("/subscribe");
|
var subscriptionModule = new SubscriptionModule("/subscribe");
|
||||||
|
Subscribers = subscriptionModule;
|
||||||
WebServer = new WebServer(o =>
|
WebServer = new WebServer(o =>
|
||||||
o.WithUrlPrefix($"http://{(ListenOnAllInterfaces.Value ? "0.0.0.0" : "localhost")}:{Port.Value}/")
|
o.WithUrlPrefix($"http://{(ListenOnAllInterfaces.Value ? "0.0.0.0" : "localhost")}:{Port.Value}/")
|
||||||
.WithEmbedIOHttpListener()
|
.WithEmbedIOHttpListener()
|
||||||
@@ -125,26 +126,81 @@ namespace RemoteControl
|
|||||||
private static readonly Dictionary<string, LogicType> LtByName = EnumCollections.LogicTypes.AsLookupDict(true);
|
private static readonly Dictionary<string, LogicType> LtByName = EnumCollections.LogicTypes.AsLookupDict(true);
|
||||||
private static readonly Dictionary<string, LogicSlotType> StByName = EnumCollections.LogicSlotTypes.AsLookupDict(true);
|
private static readonly Dictionary<string, LogicSlotType> StByName = EnumCollections.LogicSlotTypes.AsLookupDict(true);
|
||||||
|
|
||||||
private static Device GetDeviceJson(GameDevice device) =>
|
private static Device GetDeviceJson(GameDevice device) => new(device);
|
||||||
new()
|
|
||||||
{
|
|
||||||
ReferenceId = device.ReferenceId,
|
|
||||||
Name = device.DisplayName,
|
|
||||||
NameHash = device.GetNameHash(),
|
|
||||||
PrefabHash = device.PrefabHash,
|
|
||||||
PrefabName = device.PrefabName,
|
|
||||||
LogicValues = EnumCollections.LogicTypes.Values.Where(ty => device.CanLogicRead(ty))
|
|
||||||
.ToDictionary(ty => ty, ty => device.GetLogicValue(ty)),
|
|
||||||
Slots = Enumerable.Range(0, device.TotalSlots)
|
|
||||||
.Select(slot => EnumCollections.LogicSlotTypes.Values.Where(sty => device.CanLogicRead(sty, slot))
|
|
||||||
.ToDictionary(ty => ty, sty=> device.GetLogicValue(sty, slot)))
|
|
||||||
.ToList()
|
|
||||||
};
|
|
||||||
|
|
||||||
private static Device? GetDeviceJson(long referenceId)
|
private static Device? GetDeviceJson(long referenceId)
|
||||||
{
|
{
|
||||||
var dev = Referencable.Find<GameDevice>(referenceId);
|
var dev = Referencable.Find<GameDevice>(referenceId);
|
||||||
return dev == null ? null : GetDeviceJson(dev);
|
return dev == null ? null : new Device(dev);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static GameDevice? GetNetworkDevice(string probeName, long referenceId)
|
||||||
|
{
|
||||||
|
GameDevice? dev = null;
|
||||||
|
|
||||||
|
GameDevice.AllDevices.ForEach(anaDev =>
|
||||||
|
{
|
||||||
|
if (anaDev is CableAnalyser analyser)
|
||||||
|
{
|
||||||
|
if (analyser.DisplayName != probeName) return false;
|
||||||
|
dev = analyser.CableNetwork.DeviceList.Find(netDev => netDev.ReferenceId == referenceId);
|
||||||
|
return dev != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
return dev;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static List<GameDevice> GetNetworkDevice(string probeName, string deviceName)
|
||||||
|
{
|
||||||
|
List<GameDevice> result = new();
|
||||||
|
|
||||||
|
GameDevice.AllDevices.ForEach(anaDev =>
|
||||||
|
{
|
||||||
|
if (anaDev is not CableAnalyser analyser) return;
|
||||||
|
if (analyser.DisplayName != probeName) return;
|
||||||
|
var found = analyser.CableNetwork.DeviceList.Find(netDev => netDev.DisplayName == deviceName);
|
||||||
|
if (found is not null)
|
||||||
|
{
|
||||||
|
result.Add(found);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static T FindUniqueDeviceForHttp<T>(string networkName, string deviceName)
|
||||||
|
{
|
||||||
|
var result = default(T);
|
||||||
|
bool isFound = false;
|
||||||
|
|
||||||
|
GameDevice.AllDevices.ForEach(anaDev =>
|
||||||
|
{
|
||||||
|
if (anaDev is not CableAnalyser analyser) return;
|
||||||
|
if (analyser.DisplayName != networkName) return;
|
||||||
|
var found = analyser.CableNetwork.DeviceList.Find(netDev =>
|
||||||
|
netDev is T && (netDev.DisplayName == deviceName || netDev.ReferenceId.ToString() == deviceName));
|
||||||
|
if (found is T item)
|
||||||
|
{
|
||||||
|
if (isFound)
|
||||||
|
{
|
||||||
|
throw HttpException.InternalServerError("More than one object matches");
|
||||||
|
}
|
||||||
|
|
||||||
|
result = item;
|
||||||
|
isFound = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!isFound)
|
||||||
|
{
|
||||||
|
throw HttpException.NotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
return result!;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Route(HttpVerbs.Get, "/networks")]
|
[Route(HttpVerbs.Get, "/networks")]
|
||||||
@@ -159,11 +215,9 @@ namespace RemoteControl
|
|||||||
var networks = new HashSet<CableNetwork>();
|
var networks = new HashSet<CableNetwork>();
|
||||||
GameDevice.AllDevices.ForEach(dev =>
|
GameDevice.AllDevices.ForEach(dev =>
|
||||||
{
|
{
|
||||||
if (dev is CableAnalyser analyser)
|
if (dev is CableAnalyser analyser && dev.DisplayName == networkId)
|
||||||
{
|
{
|
||||||
Debug.Log($"Found CA {analyser.DisplayName}: {analyser.ReferenceId}");
|
|
||||||
networks.Add(analyser.CableNetwork);
|
networks.Add(analyser.CableNetwork);
|
||||||
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -172,7 +226,6 @@ namespace RemoteControl
|
|||||||
{
|
{
|
||||||
foreach (var device in network.DeviceList)
|
foreach (var device in network.DeviceList)
|
||||||
{
|
{
|
||||||
RemoteControl.Log($"Found {device.PrefabName}: {device.DisplayName}({device.ReferenceId})");
|
|
||||||
if (devices.ContainsKey(device.ReferenceId)) continue;
|
if (devices.ContainsKey(device.ReferenceId)) continue;
|
||||||
devices.Add(device.ReferenceId, GetDeviceJson(device));
|
devices.Add(device.ReferenceId, GetDeviceJson(device));
|
||||||
}
|
}
|
||||||
@@ -191,12 +244,19 @@ namespace RemoteControl
|
|||||||
}
|
}
|
||||||
|
|
||||||
var value = await HttpContext.GetRequestDataAsync<double>();
|
var value = await HttpContext.GetRequestDataAsync<double>();
|
||||||
|
var dev = GetNetworkDevice(networkId, deviceId) ?? throw HttpException.NotFound();
|
||||||
SubscriptionManager.SetLogic(networkId, deviceId, ltype, value);
|
if (dev.CanLogicWrite(ltype))
|
||||||
|
{
|
||||||
|
dev.SetLogicValue(ltype, value);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw HttpException.MethodNotAllowed();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Route(HttpVerbs.Post, "/networks/{networkId}/device/{deviceId}/slot/{slotId}/{varId}")]
|
[Route(HttpVerbs.Post, "/networks/{networkId}/device/{deviceId}/slot/{slotId}/{varId}")]
|
||||||
public async Task SetSlot(string networkId, long deviceId, int slotId, string varId)
|
public async Task SetSlot(string networkId, string deviceId, int slotId, string varId)
|
||||||
{
|
{
|
||||||
LogicSlotType ltype;
|
LogicSlotType ltype;
|
||||||
if (!StByName.TryGetValue(varId.ToLowerInvariant(), out ltype))
|
if (!StByName.TryGetValue(varId.ToLowerInvariant(), out ltype))
|
||||||
@@ -205,11 +265,19 @@ namespace RemoteControl
|
|||||||
}
|
}
|
||||||
|
|
||||||
var value = await HttpContext.GetRequestDataAsync<double>();
|
var value = await HttpContext.GetRequestDataAsync<double>();
|
||||||
SubscriptionManager.SetSlot(networkId, deviceId, slotId, ltype, value);
|
var dev = FindUniqueDeviceForHttp<GameDevice>(networkId, deviceId);
|
||||||
|
if (dev.CanLogicWrite(ltype, slotId))
|
||||||
|
{
|
||||||
|
dev.SetLogicValue(ltype, slotId, value);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw HttpException.MethodNotAllowed();
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
[Route(HttpVerbs.Patch, "/networks/{networkId}/device/{deviceId}")]
|
[Route(HttpVerbs.Patch, "/networks/{networkId}/device/{deviceId}")]
|
||||||
public async Task PatchDevice(string networkId, long deviceId)
|
public async Task PatchDevice(string networkId, string deviceId)
|
||||||
{
|
{
|
||||||
var values = await HttpContext.GetRequestDataAsync<Dictionary<string, double>>();
|
var values = await HttpContext.GetRequestDataAsync<Dictionary<string, double>>();
|
||||||
if (values.Keys.Any(key => !LtByName.ContainsKey(key.ToLowerInvariant())))
|
if (values.Keys.Any(key => !LtByName.ContainsKey(key.ToLowerInvariant())))
|
||||||
@@ -217,30 +285,29 @@ namespace RemoteControl
|
|||||||
throw HttpException.BadRequest();
|
throw HttpException.BadRequest();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var dev = FindUniqueDeviceForHttp<GameDevice>(networkId, deviceId);
|
||||||
foreach (var keyValuePair in values)
|
foreach (var keyValuePair in values)
|
||||||
{
|
{
|
||||||
SubscriptionManager.SetLogic(networkId, deviceId, LtByName[keyValuePair.Key.ToLowerInvariant()], keyValuePair.Value);
|
var logicType = LtByName[keyValuePair.Key.ToLowerInvariant()];
|
||||||
|
if (dev.CanLogicWrite(logicType))
|
||||||
|
{
|
||||||
|
dev.SetLogicValue(logicType, keyValuePair.Value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Route(HttpVerbs.Get, "/networks/{networkId}/device/{deviceId}/code")]
|
[Route(HttpVerbs.Get, "/networks/{networkId}/device/{deviceId}/code")]
|
||||||
public async UniTask<string> GetCode(string networkId, long deviceId)
|
public UniTask<string> GetCode(string networkId, string deviceId)
|
||||||
{
|
{
|
||||||
await UniTask.SwitchToMainThread(HttpContext.CancellationToken);
|
var dev = FindUniqueDeviceForHttp<ICircuitHolder>(networkId, deviceId);
|
||||||
|
return UniTask.FromResult(dev.GetSourceCode());
|
||||||
|
}
|
||||||
|
|
||||||
string source;
|
[Route(HttpVerbs.Post, "/networks/{networkId}/device/{deviceId}/code")]
|
||||||
// lock (SubscriptionManager.Lock)
|
public UniTask<string> SetCode(string networkId, string deviceId, string code)
|
||||||
{
|
{
|
||||||
var device = SubscriptionManager.GetDevice(networkId, deviceId) ?? throw HttpException.NotFound();
|
var dev = FindUniqueDeviceForHttp<ICircuitHolder>(networkId, deviceId);
|
||||||
if (device is not ICircuitHolder)
|
return UniTask.FromResult(dev.GetSourceCode());
|
||||||
{
|
|
||||||
throw HttpException.NotFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
source = ((ICircuitHolder)device).GetSourceCode();
|
|
||||||
}
|
|
||||||
|
|
||||||
return source;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,15 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Assets.Scripts;
|
||||||
|
using Assets.Scripts.Objects.Electrical;
|
||||||
using Assets.Scripts.Objects.Motherboards;
|
using Assets.Scripts.Objects.Motherboards;
|
||||||
using EmbedIO.WebSockets;
|
using EmbedIO.WebSockets;
|
||||||
|
using RemoteControl.Message;
|
||||||
using Swan;
|
using Swan;
|
||||||
|
using Swan.Diagnostics;
|
||||||
|
using GameDevice = Assets.Scripts.Objects.Pipes.Device;
|
||||||
|
|
||||||
namespace RemoteControl
|
namespace RemoteControl
|
||||||
{
|
{
|
||||||
@@ -24,7 +31,6 @@ namespace RemoteControl
|
|||||||
|
|
||||||
public SubscriptionModule(string urlPath, bool enableConnectionWatchdog = true) : base(urlPath, enableConnectionWatchdog)
|
public SubscriptionModule(string urlPath, bool enableConnectionWatchdog = true) : base(urlPath, enableConnectionWatchdog)
|
||||||
{
|
{
|
||||||
AddProtocols("json");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Task OnMessageReceivedAsync(IWebSocketContext context, byte[] buffer, IWebSocketReceiveResult result)
|
protected override Task OnMessageReceivedAsync(IWebSocketContext context, byte[] buffer, IWebSocketReceiveResult result)
|
||||||
@@ -43,12 +49,52 @@ namespace RemoteControl
|
|||||||
_jsonSubscribers.Remove(context);
|
_jsonSubscribers.Remove(context);
|
||||||
return base.OnClientDisconnectedAsync(context);
|
return base.OnClientDisconnectedAsync(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static Update GetUpdate()
|
||||||
|
{
|
||||||
|
Dictionary<long, Device> cache = new();
|
||||||
|
Dictionary<long, Message.Network> networks = new();
|
||||||
|
|
||||||
|
|
||||||
|
// lock (SubscriptionManager.Lock)
|
||||||
|
var timer = new HighResolutionTimer();
|
||||||
|
timer.Start();
|
||||||
|
GameDevice.AllDevices.ForEach(dev =>
|
||||||
|
{
|
||||||
|
if (dev is CableAnalyser analyser)
|
||||||
|
{
|
||||||
|
var network = networks.GetOrAdd(analyser.CableNetwork.ReferenceId, (long _) => new Message.Network
|
||||||
|
{
|
||||||
|
ReferenceId = analyser.CableNetwork.ReferenceId,
|
||||||
|
Devices = analyser.CableNetwork.DeviceList
|
||||||
|
.Select(subdev => cache.GetOrAdd(subdev.ReferenceId, _ => new Device(subdev)))
|
||||||
|
.ToList()
|
||||||
|
});
|
||||||
|
network.Probes.Add(analyser.DisplayName);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Update update = new()
|
||||||
|
{
|
||||||
|
Networks = networks.Values.ToList(),
|
||||||
|
CalculationTime = timer.ElapsedMicroseconds,
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
return update;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void SendUpdate()
|
||||||
|
{
|
||||||
|
SendUpdate(GetUpdate());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace Message
|
namespace Message
|
||||||
{
|
{
|
||||||
public class Update
|
public class Update
|
||||||
{
|
{
|
||||||
|
public long CalculationTime { get; set; }
|
||||||
public List<Network> Networks { get; set; } = new();
|
public List<Network> Networks { get; set; } = new();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,6 +108,24 @@ namespace RemoteControl
|
|||||||
|
|
||||||
public class Device
|
public class Device
|
||||||
{
|
{
|
||||||
|
public Device() {}
|
||||||
|
|
||||||
|
public Device(GameDevice device)
|
||||||
|
{
|
||||||
|
ReferenceId = device.ReferenceId;
|
||||||
|
Name = device.DisplayName;
|
||||||
|
NameHash = device.GetNameHash();
|
||||||
|
PrefabHash = device.PrefabHash;
|
||||||
|
PrefabName = device.PrefabName;
|
||||||
|
LogicValues = EnumCollections.LogicTypes.Values.Where(device.CanLogicRead)
|
||||||
|
.ToDictionary(ty => ty, device.GetLogicValue);
|
||||||
|
Slots = Enumerable.Range(0, device.TotalSlots)
|
||||||
|
.Select(slot => EnumCollections.LogicSlotTypes.Values.Where(sty => device.CanLogicRead(sty, slot))
|
||||||
|
.ToDictionary(ty => ty, sty => device.GetLogicValue(sty, slot)))
|
||||||
|
.ToList();
|
||||||
|
IsCircuitHolder = device is ICircuitHolder;
|
||||||
|
}
|
||||||
|
|
||||||
public long ReferenceId { get; set; }
|
public long ReferenceId { get; set; }
|
||||||
public long NameHash{ get; set; }
|
public long NameHash{ get; set; }
|
||||||
public string Name{ get; set; }
|
public string Name{ get; set; }
|
||||||
@@ -69,6 +133,7 @@ namespace RemoteControl
|
|||||||
public string PrefabName{ get; set; }
|
public string PrefabName{ get; set; }
|
||||||
public Dictionary<LogicType, double> LogicValues { get; set; } = new();
|
public Dictionary<LogicType, double> LogicValues { get; set; } = new();
|
||||||
public List<Dictionary<LogicSlotType, double>> Slots { get; set; }= new();
|
public List<Dictionary<LogicSlotType, double>> Slots { get; set; }= new();
|
||||||
|
public bool IsCircuitHolder { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -49,6 +49,7 @@ namespace EmbedIO.WebSockets
|
|||||||
private TimeSpan _keepAliveInterval;
|
private TimeSpan _keepAliveInterval;
|
||||||
private Encoding _encoding;
|
private Encoding _encoding;
|
||||||
private PeriodicTask? _connectionWatchdog;
|
private PeriodicTask? _connectionWatchdog;
|
||||||
|
private bool _allowNullProtocol = false;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="WebSocketModule" /> class.
|
/// Initializes a new instance of the <see cref="WebSocketModule" /> class.
|
||||||
|
|||||||
Reference in New Issue
Block a user