CatStation/Content.Server/DeviceNetwork/Systems/DeviceNetworkSystem.cs
Rouden cc2c14f8e4
Device Network refactor part 1: API improvements (#44346)
* Refactor DeviceNetwork

* Singleton

* NetworkPayloads refactor

* Reorganize DeviceNetworkSystem

* Surveillance cameras

* Fix code

* Fix build

* Fix test fail by rewriting entire singleton logic

* Properly predict examine

* Update DeviceNetworkSystem.API.cs

* Update EntityTest.cs

* Update DeviceNetworkSystem.cs

* Alright I'll do this expensive check fine

* Fix disposals debug assert

* Implement device network static events support

* Use frozen dictionaries for performance

* Update SharedDeviceNetworkSystem.Payloads.cs

* Parallel payload handling support

* Fix some spagetti-code

* Optimize NetworkPayload cancellation

* Parallel payloads are now cancellable

* Fix screens

* Fix robotics console

* Allow new methods for shared

* Mail fix

* Turret fix

* Fix network system method usage + signal and borg fix

* finally fix atmos devices OH MY GOOOD

* Remove stupid parallelism from fax

* Fix surveiollance cameras

* Working on a single thing for 2 days straight award

* Fix the test?

* Update units.yml

* Add IDevice interface because why not

* Add more device network suffixes

* Fix camera router failing test

* yaml linter peak

* Improve docs for DeviceNetworkHandlers

* fix build

* Update CartridgeLoaderSystem.Relay.cs

* Remove parallel system and EntitySystem post init

* Add more docs and also events

* Remove unnecessary caching

* Update DevicePayloadComponentSystem.cs

* Back to entity events

* Implement DeviceData

* Remove serializable from payloads

* Update DeviceData.cs

* Add a prototype for a singleton

* Fix tests

* Fix air alarms

* Update DeviceNetworkSystem.cs

* Fix cameras

* Make camera router event-less

* Fix atmos sync payloads, add more docs

* Remove singleton + instant packets

* oops remove public access

* Fix tests?

* Why the hell overload effect makes puddle sparks

* Apparently it was redundant

* Update gates.yml

* Finish documentation

* Review

* Mark relay properties as obsolete

* Struct payloads support (#blazinglyfastmemorysafe)

* Update DeviceNetworkTest.cs

* Make device structs stuff better

* Move address prefix to DeviceNetworkComponent

* Locale fixes

* Fix tests

* Doesn't know boolean logic award

* Update AtmosAlarmableSystem.cs

* Fix address predicted examine

* Partial fix to air alarmables

* Fix deployable turrets UI

* Fix firelocks, maybe

* Apparently Activatable UI was enough lol

* Fix surveillance cameras

* Rename `QueuePacket` into `SendPacket`

* Simplify DeviceData as much as possible

* Update DeviceData.cs

* Update FaxMachineComponent.cs

* Remove DeviceData because its stupid

* Update ScreenSystem.cs

* Fix build (camera map broken still)

* Fix map

* fix usings

* Review changes

* Build fix
2026-08-31 19:42:56 +00:00

227 lines
7.5 KiB
C#

using System.Buffers;
using System.Diagnostics.CodeAnalysis;
using Content.Shared.DeviceNetwork;
using Content.Shared.DeviceNetwork.Components;
using Content.Shared.DeviceNetwork.Events;
using Content.Shared.DeviceNetwork.Systems;
using Robust.Shared.Random;
namespace Content.Server.DeviceNetwork.Systems;
/// <inheritdoc/>
public sealed partial class DeviceNetworkSystem : SharedDeviceNetworkSystem
{
[Dependency] private IRobustRandom _random = default!;
[Dependency] private SharedTransformSystem _transformSystem = default!;
[Dependency] private DeviceListSystem _deviceLists = default!;
[Dependency] private NetworkConfiguratorSystem _configurator = default!;
[Dependency] private EntityQuery<DeviceNetworkComponent> _deviceQuery = default!;
/// <summary>
/// Basically a cache of devices to connect them together faster.
/// </summary>
private readonly Dictionary<int, DeviceNet> _networks = new(4);
/// <summary>
/// Automatically attempt to connect some devices when a map starts.
/// </summary>
[SubscribeLocalEvent]
private void OnMapInit(Entity<DeviceNetworkComponent> ent, ref MapInitEvent args)
{
var device = ent.Comp;
if (device.ReceiveFrequency == null
&& device.ReceiveFrequencyId != null
&& ProtoMan.TryIndex(device.ReceiveFrequencyId, out var receive))
{
device.ReceiveFrequency = receive.Frequency;
}
if (device.TransmitFrequency == null
&& device.TransmitFrequencyId != null
&& ProtoMan.TryIndex(device.TransmitFrequencyId, out var xmit))
{
device.TransmitFrequency = xmit.Frequency;
}
if (device.AutoConnect)
ConnectDevice(ent.AsNullable());
}
/// <summary>
/// Automatically disconnect when an entity with a DeviceNetworkComponent shuts down.
/// </summary>
[SubscribeLocalEvent]
private void OnNetworkShutdown(Entity<DeviceNetworkComponent> ent, ref ComponentShutdown args)
{
var component = ent.Comp;
foreach (var list in component.DeviceLists)
{
if (Deleted(list))
return;
_deviceLists.OnDeviceShutdown(list, ent);
}
foreach (var list in component.Configurators)
{
if (Deleted(list))
return;
_configurator.OnDeviceShutdown(list, ent);
}
if (TryGetNetwork(component.DeviceNetId, out var network))
network.Remove(ent);
}
/// <summary>
/// Try to find a device on a network using its address.
/// </summary>
private bool TryGetDevice(int netId, string address, [NotNullWhen(true)] out Device? device)
{
device = null;
if (!TryGetNetwork(netId, out var network)
|| !network.Devices.TryGetValue(address, out var foundDevice))
return false;
device = foundDevice;
return true;
}
/// <summary>
/// Tries to get an already existing device network, and creates a new network if it doesn't exist.
/// </summary>
/// <returns>False if the manager is not initialized.</returns>
/// <returns></returns>
private bool TryEnsureNetwork(int netId, [NotNullWhen(true)] out DeviceNet? network)
{
network = null;
if (_networks.TryGetValue(netId, out var deviceNet))
{
network = deviceNet;
return true;
}
var newDeviceNet = new DeviceNet(netId, _random);
_networks[netId] = newDeviceNet;
network = newDeviceNet;
return true;
}
/// <summary>
/// Tries to get an already existing network.
/// </summary>
/// <returns>False if the manager is not initialized, or the network wasn't found.</returns>
private bool TryGetNetwork(int netId, [NotNullWhen(true)] out DeviceNet? network)
{
network = null;
if (!_networks.TryGetValue(netId, out var deviceNet))
return false;
network = deviceNet;
return true;
}
private void SendPacket<T>(ref DeviceNetworkPacketEvent<T> packet) where T : INetworkPayload
{
if (!TryEnsureNetwork(packet.NetId, out var network))
return;
if (packet.Address == null)
{
// Broadcast to all listening devices
if (network.ListeningDevices.TryGetValue(packet.Frequency, out var devices) && CheckRecipientsList(packet, ref devices))
{
var deviceCopy = ArrayPool<Device>.Shared.Rent(devices.Count);
devices.CopyTo(deviceCopy);
SendToConnections(deviceCopy.AsSpan(0, devices.Count), packet);
ArrayPool<Device>.Shared.Return(deviceCopy);
}
}
else
{
var totalDevices = 0;
var hasTargetedDevice = false;
if (network.ReceiveAllDevices.TryGetValue(packet.Frequency, out var devices))
{
totalDevices += devices.Count;
}
if (!TryGetDevice(packet.NetId, packet.Address, out var device))
return;
if (!device.Value.ReceiveAll &&
device.Value.ReceiveFrequency == packet.Frequency)
{
totalDevices += 1;
hasTargetedDevice = true;
}
var deviceCopy = ArrayPool<Device>.Shared.Rent(totalDevices);
if (devices != null)
{
devices.CopyTo(deviceCopy);
}
if (hasTargetedDevice)
{
deviceCopy[totalDevices - 1] = device.Value;
}
SendToConnections(deviceCopy.AsSpan(0, totalDevices), packet);
ArrayPool<Device>.Shared.Return(deviceCopy);
}
}
/// <summary>
/// Sends the <see cref="BeforeBroadcastAttemptEvent"/> to the sending entity if the packets SendBeforeBroadcastAttemptEvent field is set to true.
/// The recipients is set to the modified recipient list.
/// </summary>
/// <returns>false if the broadcast was canceled</returns>
private bool CheckRecipientsList<T>(DeviceNetworkPacketEvent<T> packet, ref HashSet<Device> recipients) where T : INetworkPayload
{
if (!packet.Sender.Comp.SendBroadcastAttemptEvent)
return true;
var beforeBroadcastAttemptEvent = new BeforeBroadcastAttemptEvent(recipients);
RaiseLocalEvent(packet.Sender, ref beforeBroadcastAttemptEvent, true);
if (beforeBroadcastAttemptEvent.Cancelled || beforeBroadcastAttemptEvent.ModifiedRecipients == null)
return false;
recipients = beforeBroadcastAttemptEvent.ModifiedRecipients;
return true;
}
private void SendToConnections<T>(ReadOnlySpan<Device> connections, DeviceNetworkPacketEvent<T> packet) where T : INetworkPayload
{
if (Deleted(packet.Sender))
{
return;
}
var xform = Transform(packet.Sender);
var senderPos = _transformSystem.GetWorldPosition(xform);
foreach (var connection in connections)
{
if (connection.Owner == packet.Sender.Owner)
continue;
var beforeEv = new BeforePacketSentEvent(packet.NetId,
packet.Address,
packet.Frequency,
packet.SenderAddress,
packet.Sender,
xform,
senderPos);
RaiseLocalEvent(connection.Owner, ref beforeEv);
if (beforeEv.Cancelled)
continue;
RaiseLocalEvent(connection.Owner, ref packet);
}
}
}