Seems to be fully working

This commit is contained in:
2026-01-15 00:02:26 +01:00
parent 3f7122d30a
commit 0bbf7c2a30
4 changed files with 189 additions and 48 deletions

View File

@@ -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)
{ {

View File

@@ -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;
} }
} }
} }

View File

@@ -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; }
} }
} }
} }

View File

@@ -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.