From f41ece37c3bc04d77b2d0ef791d190c5c0d11f36 Mon Sep 17 00:00:00 2001 From: deltanedas <39013340+deltanedas@users.noreply.github.com> Date: Mon, 12 Feb 2024 06:45:51 +0000 Subject: [PATCH] make linking logic gates 1000% better (#25041) * make door status use SendSignal * LastSignals and logic, add ClearSignal api too * make everything outputting a logic signal default to false * refactor ops * :trollface: * :trollface: * protoid for LastSignals * oop --------- Co-authored-by: deltanedas <@deltanedas:kde.org> --- .../NetworkConfiguratorLinkMenu.xaml.cs | 3 +- .../DeviceLinking/Systems/DeviceLinkSystem.cs | 134 ++++++++++++------ .../Systems/DoorSignalControlSystem.cs | 13 +- .../DeviceLinkSourceComponent.cs | 25 ++-- .../DeviceLinking/SharedDeviceLinkSystem.cs | 6 +- .../NetworkConfiguratorUserInterfaceState.cs | 13 +- .../Doors/Airlocks/base_structureairlocks.yml | 2 + .../Structures/Doors/Shutter/shutters.yml | 2 + .../Doors/Windoors/base_structurewindoors.yml | 2 + .../Structures/Wallmounts/air_alarm.yml | 4 + .../Entities/Structures/Wallmounts/switch.yml | 2 + .../Prototypes/Entities/Structures/gates.yml | 8 ++ 12 files changed, 146 insertions(+), 68 deletions(-) diff --git a/Content.Client/NetworkConfigurator/NetworkConfiguratorLinkMenu.xaml.cs b/Content.Client/NetworkConfigurator/NetworkConfiguratorLinkMenu.xaml.cs index 20f1fb8d7c..c04b42f249 100644 --- a/Content.Client/NetworkConfigurator/NetworkConfiguratorLinkMenu.xaml.cs +++ b/Content.Client/NetworkConfigurator/NetworkConfiguratorLinkMenu.xaml.cs @@ -7,6 +7,7 @@ using Robust.Client.Graphics; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.XAML; +using Robust.Shared.Prototypes; namespace Content.Client.NetworkConfigurator; @@ -160,7 +161,7 @@ public sealed partial class NetworkConfiguratorLinkMenu : FancyWindow /// private sealed class LinksRender : Control { - public readonly List<(string, string)> Links = new(); + public readonly List<(ProtoId, ProtoId)> Links = new(); public readonly Dictionary SourceButtons = new(); public readonly Dictionary SinkButtons = new(); private readonly BoxContainer _leftButtonContainer; diff --git a/Content.Server/DeviceLinking/Systems/DeviceLinkSystem.cs b/Content.Server/DeviceLinking/Systems/DeviceLinkSystem.cs index e54e21316f..f708237480 100644 --- a/Content.Server/DeviceLinking/Systems/DeviceLinkSystem.cs +++ b/Content.Server/DeviceLinking/Systems/DeviceLinkSystem.cs @@ -1,9 +1,10 @@ -using Content.Server.DeviceLinking.Components; -using Content.Server.DeviceLinking.Events; +using Content.Server.DeviceLinking.Components; +using Content.Server.DeviceLinking.Events; using Content.Server.DeviceNetwork; using Content.Server.DeviceNetwork.Components; using Content.Server.DeviceNetwork.Systems; using Content.Shared.DeviceLinking; +using Content.Shared.DeviceLinking.Events; using Content.Shared.DeviceNetwork; namespace Content.Server.DeviceLinking.Systems; @@ -15,7 +16,9 @@ public sealed class DeviceLinkSystem : SharedDeviceLinkSystem public override void Initialize() { base.Initialize(); + SubscribeLocalEvent(OnPacketReceived); + SubscribeLocalEvent(OnNewLink); } public override void Update(float frameTime) @@ -51,61 +54,87 @@ public sealed class DeviceLinkSystem : SharedDeviceLinkSystem foreach (var (source, sink) in links) { - if (source != port) - continue; - - if (sinkComponent.InvokeCounter > sinkComponent.InvokeLimit) - { - sinkComponent.InvokeCounter = 0; - var args = new DeviceLinkOverloadedEvent(); - RaiseLocalEvent(sinkUid, ref args); - RemoveAllFromSink(sinkUid, sinkComponent); - continue; - } - - sinkComponent.InvokeCounter++; - - //Just skip using device networking if the source or the sink doesn't support it - if (!HasComp(uid) || !TryComp(sinkUid, out var sinkNetworkComponent)) - { - var eventArgs = new SignalReceivedEvent(sink, uid); - - RaiseLocalEvent(sinkUid, ref eventArgs); - continue; - } - - var payload = new NetworkPayload() - { - [InvokedPort] = sink - }; - - if (data != null) - { - //Prevent overriding the invoked port - data.Remove(InvokedPort); - foreach (var (key, value) in data) - { - payload.Add(key, value); - } - } - - // force using wireless network so things like atmos devices are able to send signals - var network = (int) DeviceNetworkComponent.DeviceNetIdDefaults.Wireless; - _deviceNetworkSystem.QueuePacket(uid, sinkNetworkComponent.Address, payload, sinkNetworkComponent.ReceiveFrequency, network); + if (source == port) + InvokeDirect((uid, sourceComponent), (sinkUid, sinkComponent), source, sink, data); } } } + /// + /// Raises an event on or sends a network packet directly to a sink from a source. + /// + private void InvokeDirect(Entity source, Entity sink, string sourcePort, string sinkPort, NetworkPayload? data) + { + if (!Resolve(sink, ref sink.Comp)) + return; + + if (sink.Comp.InvokeCounter > sink.Comp.InvokeLimit) + { + sink.Comp.InvokeCounter = 0; + var args = new DeviceLinkOverloadedEvent(); + RaiseLocalEvent(sink, ref args); + RemoveAllFromSink(sink, sink.Comp); + return; + } + + sink.Comp.InvokeCounter++; + + //Just skip using device networking if the source or the sink doesn't support it + if (!HasComp(source) || !TryComp(sink, out var sinkNetwork)) + { + var eventArgs = new SignalReceivedEvent(sinkPort, source); + RaiseLocalEvent(sink, ref eventArgs); + return; + } + + var payload = new NetworkPayload() + { + [InvokedPort] = sinkPort + }; + + if (data != null) + { + //Prevent overriding the invoked port + data.Remove(InvokedPort); + foreach (var (key, value) in data) + { + payload.Add(key, value); + } + } + + // force using wireless network so things like atmos devices are able to send signals + var network = (int) DeviceNetworkComponent.DeviceNetIdDefaults.Wireless; + _deviceNetworkSystem.QueuePacket(source, sinkNetwork.Address, payload, sinkNetwork.ReceiveFrequency, network); + } + /// /// Helper function that invokes a port with a high/low binary logic signal. /// public void SendSignal(EntityUid uid, string port, bool signal, DeviceLinkSourceComponent? comp = null) { + if (!Resolve(uid, ref comp)) + return; + var data = new NetworkPayload { [DeviceNetworkConstants.LogicState] = signal ? SignalState.High : SignalState.Low }; InvokePort(uid, port, data, comp); + + comp.LastSignals[port] = signal; + } + + /// + /// Clears the last signals state for linking. + /// This is not to be confused with sending a low signal, this is the complete absence of anything. + /// Use if the device is in an invalid state and has no reasonable output signal. + /// + public void ClearSignal(Entity ent, string port) + { + if (!Resolve(ent, ref ent.Comp)) + return; + + ent.Comp.LastSignals.Remove(port); } /// @@ -120,5 +149,24 @@ public sealed class DeviceLinkSystem : SharedDeviceLinkSystem var eventArgs = new SignalReceivedEvent(port, args.Sender, args.Data); RaiseLocalEvent(uid, ref eventArgs); } + + /// + /// When linking from a port that currently has a signal being sent, invoke the new link with that signal. + /// + private void OnNewLink(Entity ent, ref NewLinkEvent args) + { + if (args.Source != ent.Owner) + return; + + // only do anything if a signal is being sent from a port + if (!ent.Comp.LastSignals.TryGetValue(args.SourcePort, out var signal)) + return; + + var payload = new NetworkPayload() + { + [DeviceNetworkConstants.LogicState] = signal ? SignalState.High : SignalState.Low + }; + InvokeDirect(ent, args.Sink, args.SourcePort, args.SinkPort, payload); + } #endregion } diff --git a/Content.Server/DeviceLinking/Systems/DoorSignalControlSystem.cs b/Content.Server/DeviceLinking/Systems/DoorSignalControlSystem.cs index 40feda32f2..1c0c9713cf 100644 --- a/Content.Server/DeviceLinking/Systems/DoorSignalControlSystem.cs +++ b/Content.Server/DeviceLinking/Systems/DoorSignalControlSystem.cs @@ -85,23 +85,18 @@ namespace Content.Server.DeviceLinking.Systems private void OnStateChanged(EntityUid uid, DoorSignalControlComponent door, DoorStateChangedEvent args) { - var data = new NetworkPayload() - { - { DeviceNetworkConstants.LogicState, SignalState.Momentary } - }; - if (args.State == DoorState.Closed) { - data[DeviceNetworkConstants.LogicState] = SignalState.Low; - _signalSystem.InvokePort(uid, door.OutOpen, data); + // only ever say the door is closed when it is completely airtight + _signalSystem.SendSignal(uid, door.OutOpen, false); } else if (args.State == DoorState.Open || args.State == DoorState.Opening || args.State == DoorState.Closing || args.State == DoorState.Emagging) { - data[DeviceNetworkConstants.LogicState] = SignalState.High; - _signalSystem.InvokePort(uid, door.OutOpen, data); + // say the door is open whenever it would be letting air pass + _signalSystem.SendSignal(uid, door.OutOpen, true); } } } diff --git a/Content.Shared/DeviceLinking/DeviceLinkSourceComponent.cs b/Content.Shared/DeviceLinking/DeviceLinkSourceComponent.cs index 3a5d4d9f6d..332eff23cb 100644 --- a/Content.Shared/DeviceLinking/DeviceLinkSourceComponent.cs +++ b/Content.Shared/DeviceLinking/DeviceLinkSourceComponent.cs @@ -1,5 +1,5 @@ -using Robust.Shared.GameStates; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Set; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; namespace Content.Shared.DeviceLinking; @@ -11,25 +11,32 @@ public sealed partial class DeviceLinkSourceComponent : Component /// /// The ports the device link source sends signals from /// - [DataField("ports", customTypeSerializer: typeof(PrototypeIdHashSetSerializer))] - public HashSet? Ports; + [DataField] + public HashSet>? Ports; /// /// A list of sink uids that got linked for each port /// [ViewVariables] - public Dictionary> Outputs = new(); + public Dictionary, HashSet> Outputs = new(); + + /// + /// If set to High or Low, the last signal state for a given port. + /// Used when linking ports of devices that are currently outputting a signal. + /// Only set by DeviceLinkSystem.SendSignal. + /// + [DataField] + public Dictionary, bool> LastSignals = new(); /// /// The list of source to sink ports for each linked sink entity for easier managing of links /// - [DataField("linkedPorts")] - public Dictionary> LinkedPorts = new(); + [DataField] + public Dictionary source, ProtoId sink)>> LinkedPorts = new(); /// /// Limits the range devices can be linked across. /// - [DataField("range")] - [ViewVariables(VVAccess.ReadWrite)] + [DataField] public float Range = 30f; } diff --git a/Content.Shared/DeviceLinking/SharedDeviceLinkSystem.cs b/Content.Shared/DeviceLinking/SharedDeviceLinkSystem.cs index 9610e2e606..2ac525d154 100644 --- a/Content.Shared/DeviceLinking/SharedDeviceLinkSystem.cs +++ b/Content.Shared/DeviceLinking/SharedDeviceLinkSystem.cs @@ -153,7 +153,7 @@ public abstract class SharedDeviceLinkSystem : EntitySystem return; var comp = EnsureComp(uid); - comp.Ports ??= new HashSet(); + comp.Ports ??= new HashSet>(); foreach (var port in ports) { @@ -233,10 +233,10 @@ public abstract class SharedDeviceLinkSystem : EntitySystem /// Returns the links of a source /// /// A list of sink and source port ids that are linked together - public HashSet<(string source, string sink)> GetLinks(EntityUid sourceUid, EntityUid sinkUid, DeviceLinkSourceComponent? sourceComponent = null) + public HashSet<(ProtoId source, ProtoId sink)> GetLinks(EntityUid sourceUid, EntityUid sinkUid, DeviceLinkSourceComponent? sourceComponent = null) { if (!Resolve(sourceUid, ref sourceComponent) || !sourceComponent.LinkedPorts.TryGetValue(sinkUid, out var links)) - return new HashSet<(string source, string sink)>(); + return new HashSet<(ProtoId, ProtoId)>(); return links; } diff --git a/Content.Shared/DeviceNetwork/NetworkConfiguratorUserInterfaceState.cs b/Content.Shared/DeviceNetwork/NetworkConfiguratorUserInterfaceState.cs index d12456dc52..13000d00c9 100644 --- a/Content.Shared/DeviceNetwork/NetworkConfiguratorUserInterfaceState.cs +++ b/Content.Shared/DeviceNetwork/NetworkConfiguratorUserInterfaceState.cs @@ -1,4 +1,5 @@ -using Content.Shared.DeviceLinking; +using Content.Shared.DeviceLinking; +using Robust.Shared.Prototypes; using Robust.Shared.Serialization; namespace Content.Shared.DeviceNetwork; @@ -30,12 +31,18 @@ public sealed class DeviceLinkUserInterfaceState : BoundUserInterfaceState { public readonly List Sources; public readonly List Sinks; - public readonly HashSet<(string source, string sink)> Links; + public readonly HashSet<(ProtoId source, ProtoId sink)> Links; public readonly List<(string source, string sink)>? Defaults; public readonly string SourceAddress; public readonly string SinkAddress; - public DeviceLinkUserInterfaceState(List sources, List sinks, HashSet<(string source, string sink)> links, string sourceAddress, string sinkAddress, List<(string source, string sink)>? defaults = default) + public DeviceLinkUserInterfaceState( + List sources, + List sinks, + HashSet<(ProtoId source, ProtoId sink)> links, + string sourceAddress, + string sinkAddress, + List<(string source, string sink)>? defaults = default) { Links = links; SourceAddress = sourceAddress; diff --git a/Resources/Prototypes/Entities/Structures/Doors/Airlocks/base_structureairlocks.yml b/Resources/Prototypes/Entities/Structures/Doors/Airlocks/base_structureairlocks.yml index 9930e6631d..a185d45353 100644 --- a/Resources/Prototypes/Entities/Structures/Doors/Airlocks/base_structureairlocks.yml +++ b/Resources/Prototypes/Entities/Structures/Doors/Airlocks/base_structureairlocks.yml @@ -95,6 +95,8 @@ - type: DeviceLinkSource ports: - DoorStatus + lastSignals: + DoorStatus: false - type: SoundOnOverload - type: SpawnOnOverload - type: UserInterface diff --git a/Resources/Prototypes/Entities/Structures/Doors/Shutter/shutters.yml b/Resources/Prototypes/Entities/Structures/Doors/Shutter/shutters.yml index f4a5debeac..87fd8ef010 100644 --- a/Resources/Prototypes/Entities/Structures/Doors/Shutter/shutters.yml +++ b/Resources/Prototypes/Entities/Structures/Doors/Shutter/shutters.yml @@ -90,6 +90,8 @@ - type: DeviceLinkSource ports: - DoorStatus + lastSignals: + DoorStatus: false - type: InteractionPopup interactSuccessString: comp-window-knock messagePerceivedByOthers: comp-window-knock diff --git a/Resources/Prototypes/Entities/Structures/Doors/Windoors/base_structurewindoors.yml b/Resources/Prototypes/Entities/Structures/Doors/Windoors/base_structurewindoors.yml index 4281177b4b..e5eebab861 100644 --- a/Resources/Prototypes/Entities/Structures/Doors/Windoors/base_structurewindoors.yml +++ b/Resources/Prototypes/Entities/Structures/Doors/Windoors/base_structurewindoors.yml @@ -59,6 +59,8 @@ - type: DeviceLinkSource ports: - DoorStatus + lastSignals: + DoorStatus: false - type: Damageable damageContainer: Inorganic damageModifierSet: Glass diff --git a/Resources/Prototypes/Entities/Structures/Wallmounts/air_alarm.yml b/Resources/Prototypes/Entities/Structures/Wallmounts/air_alarm.yml index 80a8e8ea20..62f2000593 100644 --- a/Resources/Prototypes/Entities/Structures/Wallmounts/air_alarm.yml +++ b/Resources/Prototypes/Entities/Structures/Wallmounts/air_alarm.yml @@ -30,6 +30,10 @@ - AirDanger - AirWarning - AirNormal + lastSignals: + AirDanger: false + AirWarning: false + AirNormal: false - type: AtmosAlarmable syncWith: - AirAlarm diff --git a/Resources/Prototypes/Entities/Structures/Wallmounts/switch.yml b/Resources/Prototypes/Entities/Structures/Wallmounts/switch.yml index 1b285348d5..3f77c24860 100644 --- a/Resources/Prototypes/Entities/Structures/Wallmounts/switch.yml +++ b/Resources/Prototypes/Entities/Structures/Wallmounts/switch.yml @@ -34,6 +34,8 @@ - On - Off - Status + lastSignals: + Status: false - type: entity id: SignalButton diff --git a/Resources/Prototypes/Entities/Structures/gates.yml b/Resources/Prototypes/Entities/Structures/gates.yml index 8fccb39b71..22ab745a1e 100644 --- a/Resources/Prototypes/Entities/Structures/gates.yml +++ b/Resources/Prototypes/Entities/Structures/gates.yml @@ -34,6 +34,8 @@ - type: DeviceLinkSource ports: - Output + lastSignals: + Output: false - type: Construction graph: LogicGate node: logic_gate @@ -65,6 +67,9 @@ ports: - OutputHigh - OutputLow + lastSignals: + OutputHigh: false + OutputLow: false - type: Construction graph: LogicGate node: edge_detector @@ -108,6 +113,9 @@ ports: - PowerCharging - PowerDischarging + lastSignals: + PowerCharging: false + PowerDischarging: false - type: Construction graph: LogicGate node: power_sensor