Prevent infinite loops in device linking (#16856)

This commit is contained in:
Julian Giebel
2023-05-28 18:14:06 +02:00
committed by GitHub
parent 9c1fe530c1
commit 49cb9d0e1e
11 changed files with 185 additions and 14 deletions

View File

@@ -0,0 +1,26 @@
using Content.Server.DeviceLinking.Systems;
using Robust.Shared.Audio;
namespace Content.Server.DeviceLinking.Components.Overload;
/// <summary>
/// Plays a sound when a device link overloads.
/// An overload happens when a device link sink is invoked to many times per tick
/// and it raises a <see cref="Content.Server.DeviceLinking.Events.DeviceLinkOverloadedEvent"/>
/// </summary>
[RegisterComponent]
[Access(typeof(DeviceLinkOverloadSystem))]
public sealed class SoundOnOverloadComponent : Component
{
/// <summary>
/// Sound to play when the device overloads
/// </summary>
[DataField("sound")]
public SoundSpecifier? OverloadSound = new SoundPathSpecifier("/Audio/Items/Defib/defib_zap.ogg");
/// <summary>
/// Modifies the volume the sound is played at
/// </summary>
[DataField("volumeModifier")]
public float VolumeModifier;
}

View File

@@ -0,0 +1,21 @@
using Content.Server.DeviceLinking.Systems;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Server.DeviceLinking.Components.Overload;
/// <summary>
/// Spawns an entity when a device link overloads.
/// An overload happens when a device link sink is invoked to many times per tick
/// and it raises a <see cref="Content.Server.DeviceLinking.Events.DeviceLinkOverloadedEvent"/>
/// </summary>
[RegisterComponent]
[Access(typeof(DeviceLinkOverloadSystem))]
public sealed class SpawnOnOverloadComponent : Component
{
/// <summary>
/// The entity prototype to spawn when the device overloads
/// </summary>
[DataField("spawnedPrototype", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>))]
public string Prototype = "PuddleSparkle";
}

View File

@@ -0,0 +1,4 @@
namespace Content.Server.DeviceLinking.Events;
[ByRefEvent]
public readonly record struct DeviceLinkOverloadedEvent;

View File

@@ -0,0 +1,29 @@
using Content.Server.DeviceLinking.Components;
using Content.Server.DeviceLinking.Components.Overload;
using Content.Server.DeviceLinking.Events;
using Robust.Server.GameObjects;
using Robust.Shared.Audio;
namespace Content.Server.DeviceLinking.Systems;
public sealed class DeviceLinkOverloadSystem : EntitySystem
{
[Dependency] private readonly AudioSystem _audioSystem = default!;
public override void Initialize()
{
SubscribeLocalEvent<SoundOnOverloadComponent, DeviceLinkOverloadedEvent>(OnOverloadSound);
SubscribeLocalEvent<SpawnOnOverloadComponent, DeviceLinkOverloadedEvent>(OnOverloadSpawn);
}
private void OnOverloadSound(EntityUid uid, SoundOnOverloadComponent component, ref DeviceLinkOverloadedEvent args)
{
_audioSystem.PlayPvs(component.OverloadSound, uid, AudioParams.Default.WithVolume(component.VolumeModifier));
}
private void OnOverloadSpawn(EntityUid uid, SpawnOnOverloadComponent component, ref DeviceLinkOverloadedEvent args)
{
Spawn(component.Prototype, Transform(uid).Coordinates);
}
}

View File

@@ -19,6 +19,23 @@ public sealed class DeviceLinkSystem : SharedDeviceLinkSystem
SubscribeLocalEvent<DeviceLinkSinkComponent, DeviceNetworkPacketEvent>(OnPacketReceived);
}
public override void Update(float frameTime)
{
var query = EntityQueryEnumerator<DeviceLinkSinkComponent>();
while (query.MoveNext(out var component))
{
if (component.InvokeLimit < 1)
{
component.InvokeCounter = 0;
continue;
}
if(component.InvokeCounter > 0)
component.InvokeCounter--;
}
}
/// <summary>
/// Moves existing links from machine linking to device linking to ensure linked things still work even when the map wasn't updated yet
/// </summary>
@@ -62,11 +79,25 @@ public sealed class DeviceLinkSystem : SharedDeviceLinkSystem
if (!sourceComponent.LinkedPorts.TryGetValue(sinkUid, out var links))
continue;
if (!TryComp<DeviceLinkSinkComponent>(sinkUid, out var sinkComponent))
continue;
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<DeviceNetworkComponent>(uid) || !TryComp<DeviceNetworkComponent?>(sinkUid, out var sinkNetworkComponent))
{
@@ -109,6 +140,4 @@ public sealed class DeviceLinkSystem : SharedDeviceLinkSystem
RaiseLocalEvent(uid, ref eventArgs);
}
#endregion
}

View File

@@ -43,7 +43,7 @@ namespace Content.Server.DeviceLinking.Systems
{
if (state == SignalState.High || state == SignalState.Momentary)
{
if (door.State != DoorState.Open)
if (door.State == DoorState.Closed)
_doorSystem.TryOpen(uid, door);
}
}
@@ -51,7 +51,7 @@ namespace Content.Server.DeviceLinking.Systems
{
if (state == SignalState.High || state == SignalState.Momentary)
{
if (door.State != DoorState.Closed)
if (door.State == DoorState.Open)
_doorSystem.TryClose(uid, door);
}
}
@@ -59,7 +59,8 @@ namespace Content.Server.DeviceLinking.Systems
{
if (state == SignalState.High || state == SignalState.Momentary)
{
_doorSystem.TryToggleDoor(uid, door);
if (door.State is DoorState.Closed or DoorState.Open)
_doorSystem.TryToggleDoor(uid, door);
}
}
else if (args.Port == component.InBolt)

View File

@@ -3,11 +3,9 @@ using Content.Shared.DeviceNetwork;
using JetBrains.Annotations;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Utility;
using System.Buffers;
using System.Diagnostics.CodeAnalysis;
using Content.Shared.Examine;
using static Content.Server.DeviceNetwork.Components.DeviceNetworkComponent;
namespace Content.Server.DeviceNetwork.Systems
{
@@ -23,21 +21,39 @@ namespace Content.Server.DeviceNetwork.Systems
[Dependency] private readonly SharedTransformSystem _transformSystem = default!;
private readonly Dictionary<int, DeviceNet> _networks = new(4);
private readonly Queue<DeviceNetworkPacketEvent> _packets = new();
private readonly Queue<DeviceNetworkPacketEvent> _queueA = new();
private readonly Queue<DeviceNetworkPacketEvent> _queueB = new();
/// <summary>
/// The queue being processed in the current tick
/// </summary>
private Queue<DeviceNetworkPacketEvent> _activeQueue = null!;
/// <summary>
/// The queue that will be processed in the next tick
/// </summary>
private Queue<DeviceNetworkPacketEvent> _nextQueue = null!;
public override void Initialize()
{
SubscribeLocalEvent<DeviceNetworkComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<DeviceNetworkComponent, ComponentShutdown>(OnNetworkShutdown);
SubscribeLocalEvent<DeviceNetworkComponent, ExaminedEvent>(OnExamine);
_activeQueue = _queueA;
_nextQueue = _queueB;
}
public override void Update(float frameTime)
{
while (_packets.TryDequeue(out var packet))
while (_activeQueue.TryDequeue(out var packet))
{
SendPacket(packet);
}
SwapQueues();
}
/// <summary>
@@ -62,10 +78,23 @@ namespace Content.Server.DeviceNetwork.Systems
if (frequency == null)
return false;
_packets.Enqueue(new DeviceNetworkPacketEvent(device.DeviceNetId, address, frequency.Value, device.Address, uid, data));
_nextQueue.Enqueue(new DeviceNetworkPacketEvent(device.DeviceNetId, address, frequency.Value, device.Address, uid, data));
return true;
}
/// <summary>
/// Swaps the active queue.
/// Queues are swapped so that packets being sent in the current tick get processed in the next tick.
/// </summary>
/// <remarks>
/// This prevents infinite loops while sending packets
/// </remarks>
private void SwapQueues()
{
_nextQueue = _activeQueue;
_activeQueue = _activeQueue == _queueA ? _queueB : _queueA;
}
private void OnExamine(EntityUid uid, DeviceNetworkComponent device, ExaminedEvent args)
{
if (device.ExaminableAddress)