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.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
namespace Content.Client.NetworkConfigurator;
@@ -160,7 +161,7 @@ public sealed partial class NetworkConfiguratorLinkMenu : FancyWindow
/// </summary>
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> SinkButtons = new();
private readonly BoxContainer _leftButtonContainer;

View File

@@ -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<DeviceLinkSinkComponent, DeviceNetworkPacketEvent>(OnPacketReceived);
SubscribeLocalEvent<DeviceLinkSourceComponent, NewLinkEvent>(OnNewLink);
}
public override void Update(float frameTime)
@@ -51,32 +54,42 @@ 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;
if (source == port)
InvokeDirect((uid, sourceComponent), (sinkUid, sinkComponent), source, sink, data);
}
}
}
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
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);
RaiseLocalEvent(sinkUid, ref eventArgs);
continue;
var eventArgs = new SignalReceivedEvent(sinkPort, source);
RaiseLocalEvent(sink, ref eventArgs);
return;
}
var payload = new NetworkPayload()
{
[InvokedPort] = sink
[InvokedPort] = sinkPort
};
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
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>
@@ -101,11 +112,29 @@ public sealed class DeviceLinkSystem : SharedDeviceLinkSystem
/// </summary>
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;
}
/// <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>
@@ -120,5 +149,24 @@ public sealed class DeviceLinkSystem : SharedDeviceLinkSystem
var eventArgs = new SignalReceivedEvent(port, args.Sender, args.Data);
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
}

View File

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

View File

@@ -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
/// <summary>
/// The ports the device link source sends signals from
/// </summary>
[DataField("ports", customTypeSerializer: typeof(PrototypeIdHashSetSerializer<SourcePortPrototype>))]
public HashSet<string>? Ports;
[DataField]
public HashSet<ProtoId<SourcePortPrototype>>? Ports;
/// <summary>
/// A list of sink uids that got linked for each port
/// </summary>
[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>
/// The list of source to sink ports for each linked sink entity for easier managing of links
/// </summary>
[DataField("linkedPorts")]
public Dictionary<EntityUid, HashSet<(string source, string sink)>> LinkedPorts = new();
[DataField]
public Dictionary<EntityUid, HashSet<(ProtoId<SourcePortPrototype> source, ProtoId<SinkPortPrototype> sink)>> LinkedPorts = new();
/// <summary>
/// Limits the range devices can be linked across.
/// </summary>
[DataField("range")]
[ViewVariables(VVAccess.ReadWrite)]
[DataField]
public float Range = 30f;
}

View File

@@ -153,7 +153,7 @@ public abstract class SharedDeviceLinkSystem : EntitySystem
return;
var comp = EnsureComp<DeviceLinkSourceComponent>(uid);
comp.Ports ??= new HashSet<string>();
comp.Ports ??= new HashSet<ProtoId<SourcePortPrototype>>();
foreach (var port in ports)
{
@@ -233,10 +233,10 @@ public abstract class SharedDeviceLinkSystem : EntitySystem
/// Returns the links of a source
/// </summary>
/// <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))
return new HashSet<(string source, string sink)>();
return new HashSet<(ProtoId<SourcePortPrototype>, ProtoId<SinkPortPrototype>)>();
return links;
}

View File

@@ -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<SourcePortPrototype> Sources;
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 string SourceAddress;
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;
SourceAddress = sourceAddress;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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