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 Probes = new (); internal CableNetwork Network { get; private set; } private readonly HashSet _knownIds = new(); internal readonly Dictionary 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(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(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()); } 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 { 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 { 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 DataNetworks = new(); // private Dictionary> _probesByName = new(); // private Dictionary _probesById = new(); private static readonly Dictionary PendingUpdates = new(); private static readonly Dictionary 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 GetDataNetwork(string probeName) { // lock (Lock) { List 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(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(referenceID); if (GetDataNetwork(probeName).Any(network => network.DeviceCache.ContainsKey(referenceID))) { return device; } return null; } /// /// Called from Unity thread pool before the logic tick /// /// 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(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 scannedAnalyzers = new(); HashSet scannedNetworks = new(); scannedNetworks.Clear(); GameDevice.AllDevices.ForEach(dev => { if (dev is CableAnalyser analyser) { scannedAnalyzers.Add(analyser); scannedNetworks.Add(analyser.CableNetwork); } }); var removed = new List(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 GetProbes() { List 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); } } }