Got at least one data fetching method working; turns out, we can't use a patched LogicStack to get the data
This commit is contained in:
246
Scripts/Remotecontrol.cs
Normal file
246
Scripts/Remotecontrol.cs
Normal file
@@ -0,0 +1,246 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Assets.Scripts;
|
||||
using Assets.Scripts.Networks;
|
||||
using Assets.Scripts.Objects.Electrical;
|
||||
using Assets.Scripts.Objects.Motherboards;
|
||||
using BepInEx;
|
||||
using BepInEx.Configuration;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using HarmonyLib;
|
||||
using EmbedIO;
|
||||
using EmbedIO.Routing;
|
||||
using EmbedIO.WebApi;
|
||||
using RemoteControl.Message;
|
||||
using RemoteControl.Utils;
|
||||
using Swan;
|
||||
using GameDevice = Assets.Scripts.Objects.Pipes.Device;
|
||||
|
||||
|
||||
namespace RemoteControl
|
||||
{
|
||||
[BepInPlugin(pluginGuid, pluginName, pluginVersion)]
|
||||
[SuppressMessage("ReSharper", "MemberCanBePrivate.Global")]
|
||||
public class RemoteControl : BepInEx.BaseUnityPlugin
|
||||
{
|
||||
// ReSharper disable once MemberCanBePrivate.Global
|
||||
|
||||
// ReSharper disable once MemberCanBePrivate.Global
|
||||
public static ConfigEntry<int> Port;
|
||||
|
||||
// ReSharper disable once MemberCanBePrivate.Global
|
||||
public static ConfigEntry<bool> ListenOnAllInterfaces;
|
||||
|
||||
private readonly Harmony _harmony = new Harmony(pluginGuid);
|
||||
|
||||
public WebServer WebServer { get; private set; }
|
||||
|
||||
|
||||
private CancellationTokenSource _modLifecycle = new();
|
||||
|
||||
public const string pluginGuid = "com.thequux.stationeers.RemoteControl";
|
||||
public const string pluginName = "RemoteControl";
|
||||
public const string pluginVersion = "1.0";
|
||||
|
||||
public static void Log(string line)
|
||||
{
|
||||
Debug.Log("[" + pluginName + "]: " + line);
|
||||
}
|
||||
|
||||
private void StartRC()
|
||||
{
|
||||
if (!_modLifecycle.IsCancellationRequested)
|
||||
{
|
||||
_modLifecycle.Cancel();
|
||||
}
|
||||
|
||||
_modLifecycle = new CancellationTokenSource();
|
||||
|
||||
Port = Config.Bind(new ConfigDefinition("Server", "Port"), 39125,
|
||||
new ConfigDescription("The port to listen on"));
|
||||
ListenOnAllInterfaces = Config.Bind(
|
||||
new ConfigDefinition("Server", "ListenOnAllInterfaces"),
|
||||
false,
|
||||
new ConfigDescription("If set, listen on all interfaces. Otherwise, listen only on localhost"));
|
||||
|
||||
|
||||
var subscriptionModule = new SubscriptionModule("/subscribe");
|
||||
WebServer = new WebServer(o =>
|
||||
o.WithUrlPrefix($"http://{(ListenOnAllInterfaces.Value ? "0.0.0.0" : "localhost")}:{Port.Value}/")
|
||||
.WithEmbedIOHttpListener()
|
||||
)
|
||||
.WithWebApi("/api/v1", m => m.WithController<ApiController>())
|
||||
.WithModule(subscriptionModule);
|
||||
WebServer.Start(_modLifecycle.Token);
|
||||
|
||||
_harmony.PatchAll();
|
||||
foreach (var patchedMethod in _harmony.GetPatchedMethods())
|
||||
{
|
||||
Log($"Patched {patchedMethod.FullDescription()}");
|
||||
}
|
||||
Log($"Patched {_harmony.GetPatchedMethods().Count()} methods total");
|
||||
|
||||
|
||||
}
|
||||
|
||||
public void OnLoaded(List<GameObject> prefabs)
|
||||
{
|
||||
// Start();
|
||||
#if DEVELOPMENT_BUILD
|
||||
Debug.Log($"Loaded {prefabs.Count} prefabs");
|
||||
#endif
|
||||
}
|
||||
|
||||
public void OnUnloaded(List<GameObject> prefabs)
|
||||
{
|
||||
// Stop();
|
||||
}
|
||||
|
||||
public void StopRC()
|
||||
{
|
||||
_modLifecycle.Cancel();
|
||||
_harmony.UnpatchSelf();
|
||||
}
|
||||
|
||||
public void Awake()
|
||||
{
|
||||
Log("Starting RemoteControl");
|
||||
StartRC();
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
Log("Tearing down RemoteControl");
|
||||
StopRC();
|
||||
}
|
||||
}
|
||||
|
||||
internal class ApiController : WebApiController
|
||||
{
|
||||
private static readonly Dictionary<string, LogicType> LtByName = EnumCollections.LogicTypes.AsLookupDict(true);
|
||||
private static readonly Dictionary<string, LogicSlotType> StByName = EnumCollections.LogicSlotTypes.AsLookupDict(true);
|
||||
|
||||
private static Device GetDeviceJson(GameDevice 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)
|
||||
{
|
||||
var dev = Referencable.Find<GameDevice>(referenceId);
|
||||
return dev == null ? null : GetDeviceJson(dev);
|
||||
}
|
||||
|
||||
[Route(HttpVerbs.Get, "/networks")]
|
||||
public Task<IList<string>> ListNetworks() => Task.FromResult(SubscriptionManager.GetProbes());
|
||||
|
||||
[Route(HttpVerbs.Get, "/networks/{networkId}")]
|
||||
public Task<IDictionary<long, Device>> ListDevices(string networkId)
|
||||
{
|
||||
// lock (SubscriptionManager.Lock)
|
||||
using (new LogTimer("ListDevices"))
|
||||
{
|
||||
var networks = new HashSet<CableNetwork>();
|
||||
GameDevice.AllDevices.ForEach(dev =>
|
||||
{
|
||||
if (dev is CableAnalyser analyser)
|
||||
{
|
||||
Debug.Log($"Found CA {analyser.DisplayName}: {analyser.ReferenceId}");
|
||||
networks.Add(analyser.CableNetwork);
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
var devices = new Dictionary<long, Device>();
|
||||
foreach (var network in networks)
|
||||
{
|
||||
foreach (var device in network.DeviceList)
|
||||
{
|
||||
RemoteControl.Log($"Found {device.PrefabName}: {device.DisplayName}({device.ReferenceId})");
|
||||
if (devices.ContainsKey(device.ReferenceId)) continue;
|
||||
devices.Add(device.ReferenceId, GetDeviceJson(device));
|
||||
}
|
||||
}
|
||||
return Task.FromResult<IDictionary<long, Device>>(devices);
|
||||
}
|
||||
}
|
||||
|
||||
[Route(HttpVerbs.Post, "/networks/{networkId}/device/{deviceId}/logic/{varId}")]
|
||||
public async Task SetLogic(string networkId, long deviceId, string varId)
|
||||
{
|
||||
LogicType ltype;
|
||||
if (!LtByName.TryGetValue(varId.ToLowerInvariant(), out ltype))
|
||||
{
|
||||
throw HttpException.NotFound();
|
||||
}
|
||||
|
||||
var value = await HttpContext.GetRequestDataAsync<double>();
|
||||
|
||||
SubscriptionManager.SetLogic(networkId, deviceId, ltype, value);
|
||||
}
|
||||
|
||||
[Route(HttpVerbs.Post, "/networks/{networkId}/device/{deviceId}/slot/{slotId}/{varId}")]
|
||||
public async Task SetSlot(string networkId, long deviceId, int slotId, string varId)
|
||||
{
|
||||
LogicSlotType ltype;
|
||||
if (!StByName.TryGetValue(varId.ToLowerInvariant(), out ltype))
|
||||
{
|
||||
throw HttpException.NotFound();
|
||||
}
|
||||
|
||||
var value = await HttpContext.GetRequestDataAsync<double>();
|
||||
SubscriptionManager.SetSlot(networkId, deviceId, slotId, ltype, value);
|
||||
}
|
||||
|
||||
[Route(HttpVerbs.Patch, "/networks/{networkId}/device/{deviceId}")]
|
||||
public async Task PatchDevice(string networkId, long deviceId)
|
||||
{
|
||||
var values = await HttpContext.GetRequestDataAsync<Dictionary<string, double>>();
|
||||
if (values.Keys.Any(key => !LtByName.ContainsKey(key.ToLowerInvariant())))
|
||||
{
|
||||
throw HttpException.BadRequest();
|
||||
}
|
||||
|
||||
foreach (var keyValuePair in values)
|
||||
{
|
||||
SubscriptionManager.SetLogic(networkId, deviceId, LtByName[keyValuePair.Key.ToLowerInvariant()], keyValuePair.Value);
|
||||
}
|
||||
}
|
||||
|
||||
[Route(HttpVerbs.Get, "/networks/{networkId}/device/{deviceId}/code")]
|
||||
public async UniTask<string> GetCode(string networkId, long deviceId)
|
||||
{
|
||||
await UniTask.SwitchToMainThread(HttpContext.CancellationToken);
|
||||
|
||||
string source;
|
||||
// lock (SubscriptionManager.Lock)
|
||||
{
|
||||
var device = SubscriptionManager.GetDevice(networkId, deviceId) ?? throw HttpException.NotFound();
|
||||
if (device is not ICircuitHolder)
|
||||
{
|
||||
throw HttpException.NotFound();
|
||||
}
|
||||
|
||||
source = ((ICircuitHolder)device).GetSourceCode();
|
||||
}
|
||||
|
||||
return source;
|
||||
}
|
||||
}
|
||||
}
|
||||
390
Scripts/SubscriptionManager.cs
Normal file
390
Scripts/SubscriptionManager.cs
Normal file
@@ -0,0 +1,390 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Assets.Scripts;
|
||||
using Assets.Scripts.Networks;
|
||||
using Assets.Scripts.Objects.Electrical;
|
||||
using Assets.Scripts.Objects.Motherboards;
|
||||
using Assets.Scripts.Util;
|
||||
using JetBrains.Annotations;
|
||||
using RemoteControl.Message;
|
||||
using Swan;
|
||||
using UnityEngine;
|
||||
using GameDevice = Assets.Scripts.Objects.Pipes.Device;
|
||||
// ReSharper disable ClassNeverInstantiated.Global
|
||||
// ReSharper disable MemberCanBePrivate.Global
|
||||
namespace RemoteControl
|
||||
{
|
||||
public class DataNetwork
|
||||
{
|
||||
public long ReferenceId { get; private set; }
|
||||
internal readonly List<CableAnalyser> Probes = new ();
|
||||
internal CableNetwork Network { get; private set; }
|
||||
|
||||
private readonly HashSet<long> _knownIds = new();
|
||||
|
||||
|
||||
internal readonly Dictionary<long, Device> DeviceCache = new();
|
||||
|
||||
public DataNetwork(CableNetwork network)
|
||||
{
|
||||
|
||||
ReferenceId = network.ReferenceId;
|
||||
Network = network;
|
||||
}
|
||||
|
||||
internal void RescanNetworkComposition()
|
||||
{
|
||||
_knownIds.Clear();
|
||||
_knownIds.UnionWith(Network.DataDeviceList.Select(device => device.ReferenceId));
|
||||
DeviceCache.Keys.Except(_knownIds).ToList().ForEach(device => DeviceCache.Remove(device));
|
||||
|
||||
_knownIds.Except(DeviceCache.Keys).ToList().ForEach(device =>
|
||||
{
|
||||
var newDev = new Device();
|
||||
var dev = Referencable.Find<GameDevice>(device);
|
||||
if (dev == null) return;
|
||||
newDev.ReferenceId = device;
|
||||
newDev.PrefabHash = dev.PrefabHash;
|
||||
newDev.PrefabName = dev.PrefabName;
|
||||
DeviceCache.Add(device, newDev);
|
||||
});
|
||||
UpdateAllDevices();
|
||||
}
|
||||
|
||||
private void UpdateAllDevices()
|
||||
{
|
||||
foreach (var kvp in DeviceCache)
|
||||
{
|
||||
var device = kvp.Value;
|
||||
var gameObj = Referencable.Find<GameDevice>(kvp.Key);
|
||||
if (gameObj == null) continue;
|
||||
device.LogicValues.Clear();
|
||||
foreach (var type in EnumCollections.LogicTypes.Values)
|
||||
{
|
||||
if (gameObj.CanLogicRead(type))
|
||||
{
|
||||
device.LogicValues[type] = gameObj.GetLogicValue(type);
|
||||
}
|
||||
}
|
||||
|
||||
var slotCount = gameObj.TotalSlots;
|
||||
while (device.Slots.Count > slotCount)
|
||||
{
|
||||
device.Slots.RemoveAt(device.Slots.Count - 1);
|
||||
}
|
||||
for (int slotIndex = 0; slotIndex < slotCount; slotIndex++)
|
||||
{
|
||||
if (device.Slots.Count <= slotIndex)
|
||||
{
|
||||
device.Slots.Add(new Dictionary<LogicSlotType, double>());
|
||||
}
|
||||
|
||||
foreach (var type in EnumCollections.LogicSlotTypes.Values)
|
||||
{
|
||||
if (gameObj.CanLogicRead(type, slotIndex))
|
||||
{
|
||||
device.Slots[slotIndex][type] = gameObj.GetLogicValue(type, slotIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public DataNetwork Reset(CableNetwork cnet)
|
||||
{
|
||||
ReferenceId = cnet.ReferenceId;
|
||||
Network = cnet;
|
||||
Probes.Clear();
|
||||
DeviceCache.Clear();
|
||||
_knownIds.Clear();
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly struct LogicUpdate : IEquatable<LogicUpdate>
|
||||
{
|
||||
public readonly long TargetReferenceId;
|
||||
public readonly LogicType LogicType;
|
||||
|
||||
public LogicUpdate(long targetReferenceId, LogicType logicType)
|
||||
{
|
||||
TargetReferenceId = targetReferenceId;
|
||||
LogicType = logicType;
|
||||
}
|
||||
|
||||
public bool Equals(LogicUpdate other)
|
||||
{
|
||||
return TargetReferenceId == other.TargetReferenceId && LogicType == other.LogicType;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is LogicUpdate other && Equals(other);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return CompositeHashCode.Using(TargetReferenceId, LogicType);
|
||||
}
|
||||
}
|
||||
|
||||
public readonly struct SlotUpdate : IEquatable<SlotUpdate>
|
||||
{
|
||||
public readonly long ReferenceId;
|
||||
public readonly int Slot;
|
||||
public readonly LogicSlotType Type;
|
||||
|
||||
public SlotUpdate(long referenceID, int slot, LogicSlotType type)
|
||||
{
|
||||
ReferenceId = referenceID;
|
||||
Slot = slot;
|
||||
Type = type;
|
||||
}
|
||||
|
||||
public bool Equals(SlotUpdate other)
|
||||
{
|
||||
return ReferenceId == other.ReferenceId && Slot == other.Slot && Type == other.Type;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is SlotUpdate other && Equals(other);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return CompositeHashCode.Using(ReferenceId, Slot, Type);
|
||||
}
|
||||
}
|
||||
|
||||
public static class SubscriptionManager
|
||||
{
|
||||
public static readonly object Lock = new();
|
||||
|
||||
private static readonly Dictionary<long, DataNetwork> DataNetworks = new();
|
||||
// private Dictionary<string, List<RemoteControlLimpet>> _probesByName = new();
|
||||
// private Dictionary<long, RemoteControlLimpet> _probesById = new();
|
||||
private static readonly Dictionary<LogicUpdate, double> PendingUpdates = new();
|
||||
private static readonly Dictionary<SlotUpdate, double> PendingSlotUpdates = new();
|
||||
|
||||
[CanBeNull]
|
||||
public static Device FindCachedDevice(string probeName, long referenceID)
|
||||
{
|
||||
foreach (var analyzer in GetDataNetwork(probeName))
|
||||
{
|
||||
if (analyzer.DeviceCache.TryGetValue(referenceID, out var device))
|
||||
{
|
||||
return device;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Can be called from any thread
|
||||
public static bool SetLogic(string probeName, long referenceID, LogicType type, double value)
|
||||
{
|
||||
// lock (Lock)
|
||||
{
|
||||
var dev = FindCachedDevice(probeName, referenceID);
|
||||
if (dev == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
dev.LogicValues[type] = value;
|
||||
PendingUpdates[new LogicUpdate(referenceID, type)] = value;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Can be called from any thread
|
||||
public static bool SetSlot(string probeName, long referenceID, int slot, LogicSlotType type, double value)
|
||||
{
|
||||
// lock (Lock)
|
||||
{
|
||||
|
||||
var dev = FindCachedDevice(probeName, referenceID);
|
||||
if (dev != null && dev.Slots.Count > slot && dev.Slots[slot].ContainsKey(type))
|
||||
{
|
||||
dev.Slots[slot][type] = value;
|
||||
PendingSlotUpdates[new SlotUpdate(referenceID, slot, type)] = value;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static DataNetwork GetDataNetwork(CableNetwork network)
|
||||
{
|
||||
if (DataNetworks.TryGetValue(network.ReferenceId, out var dataNetwork))
|
||||
{
|
||||
return dataNetwork;
|
||||
}
|
||||
else
|
||||
{
|
||||
var ret = new DataNetwork(network);
|
||||
DataNetworks.Add(network.ReferenceId, ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<DataNetwork> GetDataNetwork(string probeName)
|
||||
{
|
||||
// lock (Lock)
|
||||
{
|
||||
List<DataNetwork> networks = new();
|
||||
GameDevice.AllDevices.ForEach(dev =>
|
||||
{
|
||||
if (dev is CableAnalyser analyzer)
|
||||
{
|
||||
networks.Add(GetDataNetwork(analyzer.CableNetwork));
|
||||
}
|
||||
});
|
||||
return networks;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static GameDevice GetDevice(long referenceID)
|
||||
{
|
||||
var device = Referencable.Find<GameDevice>(referenceID);
|
||||
if (!DataNetworks.Values.Any((item) => item.Network.DataDeviceList.Contains(device)))
|
||||
return null;
|
||||
return device;
|
||||
|
||||
}
|
||||
|
||||
public static GameDevice GetDevice(string probeName, long referenceID)
|
||||
{
|
||||
var device = Referencable.Find<GameDevice>(referenceID);
|
||||
if (GetDataNetwork(probeName).Any(network => network.DeviceCache.ContainsKey(referenceID)))
|
||||
{
|
||||
return device;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called from Unity thread pool before the logic tick
|
||||
/// </summary>
|
||||
/// <exception cref="NotImplementedException"></exception>
|
||||
public static void ApplyUpdates()
|
||||
{
|
||||
lock (Lock)
|
||||
{
|
||||
foreach (var update in PendingUpdates)
|
||||
{
|
||||
var device = GetDevice(update.Key.TargetReferenceId);
|
||||
|
||||
if (!device.CanLogicWrite(update.Key.LogicType))
|
||||
{
|
||||
device.SetLogicValue(update.Key.LogicType, update.Value);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var update in PendingSlotUpdates)
|
||||
{
|
||||
var device = Referencable.Find<Assets.Scripts.Objects.Pipes.Device>(update.Key.ReferenceId);
|
||||
if (!DataNetworks.Values.Any((item) => item.Network.DataDeviceList.Contains(device)))
|
||||
continue;
|
||||
|
||||
if (!device.CanLogicWrite(update.Key.Type, update.Key.Slot))
|
||||
{
|
||||
device.SetLogicValue(update.Key.Type, update.Key.Slot, update.Value);
|
||||
}
|
||||
}
|
||||
|
||||
PendingUpdates.Clear();
|
||||
PendingSlotUpdates.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public static void RescanNetworks()
|
||||
{
|
||||
using (new LogTimer("RescanNetworks"))
|
||||
{
|
||||
HashSet<CableAnalyser> scannedAnalyzers = new();
|
||||
HashSet<CableNetwork> scannedNetworks = new();
|
||||
|
||||
scannedNetworks.Clear();
|
||||
GameDevice.AllDevices.ForEach(dev =>
|
||||
{
|
||||
if (dev is CableAnalyser analyser)
|
||||
{
|
||||
scannedAnalyzers.Add(analyser);
|
||||
scannedNetworks.Add(analyser.CableNetwork);
|
||||
}
|
||||
});
|
||||
|
||||
var removed =
|
||||
new List<DataNetwork>(DataNetworks.Values.Where(dataNetwork =>
|
||||
!scannedNetworks.Contains(dataNetwork.Network)));
|
||||
foreach (var dataNet in removed)
|
||||
{
|
||||
DataNetworks.Remove(dataNet.ReferenceId);
|
||||
}
|
||||
|
||||
foreach (var dataNet in DataNetworks.Values)
|
||||
{
|
||||
dataNet.Probes.Clear();
|
||||
}
|
||||
|
||||
foreach (var analyzer in scannedAnalyzers)
|
||||
{
|
||||
if (DataNetworks.ContainsKey(analyzer.ReferenceId)) continue;
|
||||
|
||||
var dataNet = removed.Pop()?.Reset(analyzer.CableNetwork) ?? new DataNetwork(analyzer.CableNetwork);
|
||||
dataNet.Probes.Add(analyzer);
|
||||
}
|
||||
|
||||
|
||||
// TODO: when we have our own device for tagging a network, make this triggered by on the DataNetworkChange method
|
||||
foreach (var dataNet in DataNetworks.Values)
|
||||
{
|
||||
dataNet.RescanNetworkComposition();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static IList<string> GetProbes()
|
||||
{
|
||||
List<string> probes = new();
|
||||
GameDevice.AllDevices.ForEach(dev =>
|
||||
{
|
||||
if (dev is CableAnalyser)
|
||||
{
|
||||
probes.Add(dev.DisplayName);
|
||||
}
|
||||
});
|
||||
return probes;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class LogTimer : IDisposable
|
||||
{
|
||||
private readonly DateTime _startTime = DateTime.Now;
|
||||
private string _action;
|
||||
|
||||
public LogTimer(string action)
|
||||
{
|
||||
RemoteControl.Log($"Beginning {action}");
|
||||
_action = action;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
var endTime = DateTime.Now;
|
||||
var elapsed = endTime - _startTime;
|
||||
Debug.Log($"Time taken for {_action}: " + elapsed);
|
||||
}
|
||||
}
|
||||
}
|
||||
74
Scripts/SubscriptionModule.cs
Normal file
74
Scripts/SubscriptionModule.cs
Normal file
@@ -0,0 +1,74 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Assets.Scripts.Objects.Motherboards;
|
||||
using EmbedIO.WebSockets;
|
||||
using Swan;
|
||||
|
||||
namespace RemoteControl
|
||||
{
|
||||
internal class SubscriptionModule: WebSocketModule
|
||||
{
|
||||
private HashSet<IWebSocketContext> _jsonSubscribers = new();
|
||||
public void SendUpdate(Message.Update update)
|
||||
{
|
||||
if (_jsonSubscribers.Count > 0)
|
||||
{
|
||||
var encoded = System.Text.Encoding.UTF8.GetBytes(update.ToJson());
|
||||
foreach (var webSocketContext in _jsonSubscribers)
|
||||
{
|
||||
_ = SendAsync(webSocketContext, encoded);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public SubscriptionModule(string urlPath, bool enableConnectionWatchdog = true) : base(urlPath, enableConnectionWatchdog)
|
||||
{
|
||||
AddProtocols("json");
|
||||
}
|
||||
|
||||
protected override Task OnMessageReceivedAsync(IWebSocketContext context, byte[] buffer, IWebSocketReceiveResult result)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
protected override Task OnClientConnectedAsync(IWebSocketContext context)
|
||||
{
|
||||
_jsonSubscribers.Add(context);
|
||||
return base.OnClientConnectedAsync(context);
|
||||
}
|
||||
|
||||
protected override Task OnClientDisconnectedAsync(IWebSocketContext context)
|
||||
{
|
||||
_jsonSubscribers.Remove(context);
|
||||
return base.OnClientDisconnectedAsync(context);
|
||||
}
|
||||
}
|
||||
|
||||
namespace Message
|
||||
{
|
||||
public class Update
|
||||
{
|
||||
public List<Network> Networks { get; set; } = new();
|
||||
}
|
||||
|
||||
public class Network
|
||||
{
|
||||
public long ReferenceId { get; set; }
|
||||
public List<string> Probes { get; set; } = new();
|
||||
|
||||
public List<Device> Devices { get; set; } = new();
|
||||
}
|
||||
|
||||
public class Device
|
||||
{
|
||||
public long ReferenceId { get; set; }
|
||||
public long NameHash{ get; set; }
|
||||
public string Name{ get; set; }
|
||||
public long PrefabHash{ get; set; }
|
||||
public string PrefabName{ get; set; }
|
||||
public Dictionary<LogicType, double> LogicValues { get; set; } = new();
|
||||
public List<Dictionary<LogicSlotType, double>> Slots { get; set; }= new();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user