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>
This commit is contained in:
deltanedas
2024-02-12 06:45:51 +00:00
committed by GitHub
parent 16b56c7f45
commit f41ece37c3
12 changed files with 146 additions and 68 deletions

View File

@@ -7,6 +7,7 @@ using Robust.Client.Graphics;
using Robust.Client.UserInterface; using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML; using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
namespace Content.Client.NetworkConfigurator; namespace Content.Client.NetworkConfigurator;
@@ -160,7 +161,7 @@ public sealed partial class NetworkConfiguratorLinkMenu : FancyWindow
/// </summary> /// </summary>
private sealed class LinksRender : Control private sealed class LinksRender : Control
{ {
public readonly List<(string, string)> Links = new(); public readonly List<(ProtoId<SourcePortPrototype>, ProtoId<SinkPortPrototype>)> Links = new();
public readonly Dictionary<string, Button> SourceButtons = new(); public readonly Dictionary<string, Button> SourceButtons = new();
public readonly Dictionary<string, Button> SinkButtons = new(); public readonly Dictionary<string, Button> SinkButtons = new();
private readonly BoxContainer _leftButtonContainer; private readonly BoxContainer _leftButtonContainer;

View File

@@ -1,9 +1,10 @@
using Content.Server.DeviceLinking.Components; using Content.Server.DeviceLinking.Components;
using Content.Server.DeviceLinking.Events; using Content.Server.DeviceLinking.Events;
using Content.Server.DeviceNetwork; using Content.Server.DeviceNetwork;
using Content.Server.DeviceNetwork.Components; using Content.Server.DeviceNetwork.Components;
using Content.Server.DeviceNetwork.Systems; using Content.Server.DeviceNetwork.Systems;
using Content.Shared.DeviceLinking; using Content.Shared.DeviceLinking;
using Content.Shared.DeviceLinking.Events;
using Content.Shared.DeviceNetwork; using Content.Shared.DeviceNetwork;
namespace Content.Server.DeviceLinking.Systems; namespace Content.Server.DeviceLinking.Systems;
@@ -15,7 +16,9 @@ public sealed class DeviceLinkSystem : SharedDeviceLinkSystem
public override void Initialize() public override void Initialize()
{ {
base.Initialize(); base.Initialize();
SubscribeLocalEvent<DeviceLinkSinkComponent, DeviceNetworkPacketEvent>(OnPacketReceived); SubscribeLocalEvent<DeviceLinkSinkComponent, DeviceNetworkPacketEvent>(OnPacketReceived);
SubscribeLocalEvent<DeviceLinkSourceComponent, NewLinkEvent>(OnNewLink);
} }
public override void Update(float frameTime) public override void Update(float frameTime)
@@ -51,32 +54,42 @@ public sealed class DeviceLinkSystem : SharedDeviceLinkSystem
foreach (var (source, sink) in links) foreach (var (source, sink) in links)
{ {
if (source != port) if (source == port)
continue; InvokeDirect((uid, sourceComponent), (sinkUid, sinkComponent), source, sink, data);
}
if (sinkComponent.InvokeCounter > sinkComponent.InvokeLimit) }
{
sinkComponent.InvokeCounter = 0;
var args = new DeviceLinkOverloadedEvent();
RaiseLocalEvent(sinkUid, ref args);
RemoveAllFromSink(sinkUid, sinkComponent);
continue;
} }
sinkComponent.InvokeCounter++; /// <summary>
/// Raises an event on or sends a network packet directly to a sink from a source.
/// </summary>
private void InvokeDirect(Entity<DeviceLinkSourceComponent> source, Entity<DeviceLinkSinkComponent?> 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 //Just skip using device networking if the source or the sink doesn't support it
if (!HasComp<DeviceNetworkComponent>(uid) || !TryComp<DeviceNetworkComponent>(sinkUid, out var sinkNetworkComponent)) if (!HasComp<DeviceNetworkComponent>(source) || !TryComp<DeviceNetworkComponent>(sink, out var sinkNetwork))
{ {
var eventArgs = new SignalReceivedEvent(sink, uid); var eventArgs = new SignalReceivedEvent(sinkPort, source);
RaiseLocalEvent(sink, ref eventArgs);
RaiseLocalEvent(sinkUid, ref eventArgs); return;
continue;
} }
var payload = new NetworkPayload() var payload = new NetworkPayload()
{ {
[InvokedPort] = sink [InvokedPort] = sinkPort
}; };
if (data != null) if (data != null)
@@ -91,9 +104,7 @@ public sealed class DeviceLinkSystem : SharedDeviceLinkSystem
// force using wireless network so things like atmos devices are able to send signals // force using wireless network so things like atmos devices are able to send signals
var network = (int) DeviceNetworkComponent.DeviceNetIdDefaults.Wireless; var network = (int) DeviceNetworkComponent.DeviceNetIdDefaults.Wireless;
_deviceNetworkSystem.QueuePacket(uid, sinkNetworkComponent.Address, payload, sinkNetworkComponent.ReceiveFrequency, network); _deviceNetworkSystem.QueuePacket(source, sinkNetwork.Address, payload, sinkNetwork.ReceiveFrequency, network);
}
}
} }
/// <summary> /// <summary>
@@ -101,11 +112,29 @@ public sealed class DeviceLinkSystem : SharedDeviceLinkSystem
/// </summary> /// </summary>
public void SendSignal(EntityUid uid, string port, bool signal, DeviceLinkSourceComponent? comp = null) public void SendSignal(EntityUid uid, string port, bool signal, DeviceLinkSourceComponent? comp = null)
{ {
if (!Resolve(uid, ref comp))
return;
var data = new NetworkPayload var data = new NetworkPayload
{ {
[DeviceNetworkConstants.LogicState] = signal ? SignalState.High : SignalState.Low [DeviceNetworkConstants.LogicState] = signal ? SignalState.High : SignalState.Low
}; };
InvokePort(uid, port, data, comp); InvokePort(uid, port, data, comp);
comp.LastSignals[port] = signal;
}
/// <summary>
/// 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.
/// </summary>
public void ClearSignal(Entity<DeviceLinkSourceComponent?> ent, string port)
{
if (!Resolve(ent, ref ent.Comp))
return;
ent.Comp.LastSignals.Remove(port);
} }
/// <summary> /// <summary>
@@ -120,5 +149,24 @@ public sealed class DeviceLinkSystem : SharedDeviceLinkSystem
var eventArgs = new SignalReceivedEvent(port, args.Sender, args.Data); var eventArgs = new SignalReceivedEvent(port, args.Sender, args.Data);
RaiseLocalEvent(uid, ref eventArgs); RaiseLocalEvent(uid, ref eventArgs);
} }
/// <summary>
/// When linking from a port that currently has a signal being sent, invoke the new link with that signal.
/// </summary>
private void OnNewLink(Entity<DeviceLinkSourceComponent> 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 #endregion
} }

View File

@@ -85,23 +85,18 @@ namespace Content.Server.DeviceLinking.Systems
private void OnStateChanged(EntityUid uid, DoorSignalControlComponent door, DoorStateChangedEvent args) private void OnStateChanged(EntityUid uid, DoorSignalControlComponent door, DoorStateChangedEvent args)
{ {
var data = new NetworkPayload()
{
{ DeviceNetworkConstants.LogicState, SignalState.Momentary }
};
if (args.State == DoorState.Closed) if (args.State == DoorState.Closed)
{ {
data[DeviceNetworkConstants.LogicState] = SignalState.Low; // only ever say the door is closed when it is completely airtight
_signalSystem.InvokePort(uid, door.OutOpen, data); _signalSystem.SendSignal(uid, door.OutOpen, false);
} }
else if (args.State == DoorState.Open else if (args.State == DoorState.Open
|| args.State == DoorState.Opening || args.State == DoorState.Opening
|| args.State == DoorState.Closing || args.State == DoorState.Closing
|| args.State == DoorState.Emagging) || args.State == DoorState.Emagging)
{ {
data[DeviceNetworkConstants.LogicState] = SignalState.High; // say the door is open whenever it would be letting air pass
_signalSystem.InvokePort(uid, door.OutOpen, data); _signalSystem.SendSignal(uid, door.OutOpen, true);
} }
} }
} }

View File

@@ -1,5 +1,5 @@
using Robust.Shared.GameStates; using Robust.Shared.GameStates;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Set; using Robust.Shared.Prototypes;
namespace Content.Shared.DeviceLinking; namespace Content.Shared.DeviceLinking;
@@ -11,25 +11,32 @@ public sealed partial class DeviceLinkSourceComponent : Component
/// <summary> /// <summary>
/// The ports the device link source sends signals from /// The ports the device link source sends signals from
/// </summary> /// </summary>
[DataField("ports", customTypeSerializer: typeof(PrototypeIdHashSetSerializer<SourcePortPrototype>))] [DataField]
public HashSet<string>? Ports; public HashSet<ProtoId<SourcePortPrototype>>? Ports;
/// <summary> /// <summary>
/// A list of sink uids that got linked for each port /// A list of sink uids that got linked for each port
/// </summary> /// </summary>
[ViewVariables] [ViewVariables]
public Dictionary<string, HashSet<EntityUid>> Outputs = new(); public Dictionary<ProtoId<SourcePortPrototype>, HashSet<EntityUid>> Outputs = new();
/// <summary>
/// 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 <c>DeviceLinkSystem.SendSignal</c>.
/// </summary>
[DataField]
public Dictionary<ProtoId<SourcePortPrototype>, bool> LastSignals = new();
/// <summary> /// <summary>
/// The list of source to sink ports for each linked sink entity for easier managing of links /// The list of source to sink ports for each linked sink entity for easier managing of links
/// </summary> /// </summary>
[DataField("linkedPorts")] [DataField]
public Dictionary<EntityUid, HashSet<(string source, string sink)>> LinkedPorts = new(); public Dictionary<EntityUid, HashSet<(ProtoId<SourcePortPrototype> source, ProtoId<SinkPortPrototype> sink)>> LinkedPorts = new();
/// <summary> /// <summary>
/// Limits the range devices can be linked across. /// Limits the range devices can be linked across.
/// </summary> /// </summary>
[DataField("range")] [DataField]
[ViewVariables(VVAccess.ReadWrite)]
public float Range = 30f; public float Range = 30f;
} }

View File

@@ -153,7 +153,7 @@ public abstract class SharedDeviceLinkSystem : EntitySystem
return; return;
var comp = EnsureComp<DeviceLinkSourceComponent>(uid); var comp = EnsureComp<DeviceLinkSourceComponent>(uid);
comp.Ports ??= new HashSet<string>(); comp.Ports ??= new HashSet<ProtoId<SourcePortPrototype>>();
foreach (var port in ports) foreach (var port in ports)
{ {
@@ -233,10 +233,10 @@ public abstract class SharedDeviceLinkSystem : EntitySystem
/// Returns the links of a source /// Returns the links of a source
/// </summary> /// </summary>
/// <returns>A list of sink and source port ids that are linked together</returns> /// <returns>A list of sink and source port ids that are linked together</returns>
public HashSet<(string source, string sink)> GetLinks(EntityUid sourceUid, EntityUid sinkUid, DeviceLinkSourceComponent? sourceComponent = null) public HashSet<(ProtoId<SourcePortPrototype> source, ProtoId<SinkPortPrototype> sink)> GetLinks(EntityUid sourceUid, EntityUid sinkUid, DeviceLinkSourceComponent? sourceComponent = null)
{ {
if (!Resolve(sourceUid, ref sourceComponent) || !sourceComponent.LinkedPorts.TryGetValue(sinkUid, out var links)) if (!Resolve(sourceUid, ref sourceComponent) || !sourceComponent.LinkedPorts.TryGetValue(sinkUid, out var links))
return new HashSet<(string source, string sink)>(); return new HashSet<(ProtoId<SourcePortPrototype>, ProtoId<SinkPortPrototype>)>();
return links; return links;
} }

View File

@@ -1,4 +1,5 @@
using Content.Shared.DeviceLinking; using Content.Shared.DeviceLinking;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization; using Robust.Shared.Serialization;
namespace Content.Shared.DeviceNetwork; namespace Content.Shared.DeviceNetwork;
@@ -30,12 +31,18 @@ public sealed class DeviceLinkUserInterfaceState : BoundUserInterfaceState
{ {
public readonly List<SourcePortPrototype> Sources; public readonly List<SourcePortPrototype> Sources;
public readonly List<SinkPortPrototype> Sinks; public readonly List<SinkPortPrototype> Sinks;
public readonly HashSet<(string source, string sink)> Links; public readonly HashSet<(ProtoId<SourcePortPrototype> source, ProtoId<SinkPortPrototype> sink)> Links;
public readonly List<(string source, string sink)>? Defaults; public readonly List<(string source, string sink)>? Defaults;
public readonly string SourceAddress; public readonly string SourceAddress;
public readonly string SinkAddress; public readonly string SinkAddress;
public DeviceLinkUserInterfaceState(List<SourcePortPrototype> sources, List<SinkPortPrototype> sinks, HashSet<(string source, string sink)> links, string sourceAddress, string sinkAddress, List<(string source, string sink)>? defaults = default) public DeviceLinkUserInterfaceState(
List<SourcePortPrototype> sources,
List<SinkPortPrototype> sinks,
HashSet<(ProtoId<SourcePortPrototype> source, ProtoId<SinkPortPrototype> sink)> links,
string sourceAddress,
string sinkAddress,
List<(string source, string sink)>? defaults = default)
{ {
Links = links; Links = links;
SourceAddress = sourceAddress; SourceAddress = sourceAddress;

View File

@@ -95,6 +95,8 @@
- type: DeviceLinkSource - type: DeviceLinkSource
ports: ports:
- DoorStatus - DoorStatus
lastSignals:
DoorStatus: false
- type: SoundOnOverload - type: SoundOnOverload
- type: SpawnOnOverload - type: SpawnOnOverload
- type: UserInterface - type: UserInterface

View File

@@ -90,6 +90,8 @@
- type: DeviceLinkSource - type: DeviceLinkSource
ports: ports:
- DoorStatus - DoorStatus
lastSignals:
DoorStatus: false
- type: InteractionPopup - type: InteractionPopup
interactSuccessString: comp-window-knock interactSuccessString: comp-window-knock
messagePerceivedByOthers: comp-window-knock messagePerceivedByOthers: comp-window-knock

View File

@@ -59,6 +59,8 @@
- type: DeviceLinkSource - type: DeviceLinkSource
ports: ports:
- DoorStatus - DoorStatus
lastSignals:
DoorStatus: false
- type: Damageable - type: Damageable
damageContainer: Inorganic damageContainer: Inorganic
damageModifierSet: Glass damageModifierSet: Glass

View File

@@ -30,6 +30,10 @@
- AirDanger - AirDanger
- AirWarning - AirWarning
- AirNormal - AirNormal
lastSignals:
AirDanger: false
AirWarning: false
AirNormal: false
- type: AtmosAlarmable - type: AtmosAlarmable
syncWith: syncWith:
- AirAlarm - AirAlarm

View File

@@ -34,6 +34,8 @@
- On - On
- Off - Off
- Status - Status
lastSignals:
Status: false
- type: entity - type: entity
id: SignalButton id: SignalButton

View File

@@ -34,6 +34,8 @@
- type: DeviceLinkSource - type: DeviceLinkSource
ports: ports:
- Output - Output
lastSignals:
Output: false
- type: Construction - type: Construction
graph: LogicGate graph: LogicGate
node: logic_gate node: logic_gate
@@ -65,6 +67,9 @@
ports: ports:
- OutputHigh - OutputHigh
- OutputLow - OutputLow
lastSignals:
OutputHigh: false
OutputLow: false
- type: Construction - type: Construction
graph: LogicGate graph: LogicGate
node: edge_detector node: edge_detector
@@ -108,6 +113,9 @@
ports: ports:
- PowerCharging - PowerCharging
- PowerDischarging - PowerDischarging
lastSignals:
PowerCharging: false
PowerDischarging: false
- type: Construction - type: Construction
graph: LogicGate graph: LogicGate
node: power_sensor node: power_sensor