Files
tbd-station-14/Content.Server/DeviceNetwork/Systems/DeviceNetworkSystem.cs
Julian Giebel 45caf25ea9 Apc device network and apc based light switch (#4908)
* Remove WireNet node group from cables
Implement extension cable components and system
Remove connection over distance logic from ApcPower... components

* Add extension cable components to prototypes

* Implement ApcNetwork
Implement ApcNetSwitch

* Fix ignoredComponents.cs

* Add friend attribute to new components

* Add construction graph for a light switch

* Address reviews

* Fix broken test

* Move ConnectionType enum to DeviceNetworkComponent
Change netId data definition to use the ConnectionType enum values

Co-authored-by: Julian Giebel <j.giebel@netrocks.info>
2021-10-23 16:23:19 -07:00

287 lines
10 KiB
C#

using Content.Server.DeviceNetwork.Components;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Random;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
namespace Content.Server.DeviceNetwork.Systems
{
/// <summary>
/// Entity system that handles everything device network related.
/// Device networking allows machines and devices to communicate with each other while adhering to restrictions like range or being connected to the same powernet.
/// </summary>
[UsedImplicitly]
public class DeviceNetworkSystem : EntitySystem
{
[Dependency] private readonly IRobustRandom _random = default!;
private readonly Dictionary<DeviceNetworkComponent.ConnectionType, List<DeviceNetworkComponent>> _connections = new();
private readonly Queue<NetworkPacket> _packets = new();
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<DeviceNetworkComponent, ComponentStartup>(OnNetworkStarted);
SubscribeLocalEvent<DeviceNetworkComponent, ComponentShutdown>(OnNetworkShutdown);
}
public override void Update(float frameTime)
{
base.Update(frameTime);
while (_packets.Count > 0)
{
var packet = _packets.Dequeue();
if(packet.Broadcast)
{
BroadcastPacket(packet);
continue;
}
SendPacket(packet);
}
}
/// <summary>
/// Manually connect an entity with a DeviceNetworkComponent.
/// </summary>
/// <param name="uid">The Entity containing a DeviceNetworkComponent</param>
public void Connect(EntityUid uid)
{
if (EntityManager.GetEntity(uid).TryGetComponent<DeviceNetworkComponent>(out var component))
{
AddConnection(component);
}
}
/// <summary>
/// Sends the given payload as a device network packet to the entity with the given address and frequency.
/// Addresses are given to the DeviceNetworkComponent of an entity when connecting.
/// </summary>
/// <param name="uid">The EntityUid of the sending entity</param>
/// <param name="address">The address of the entity that the packet gets sent to when not broadcasting</param>
/// <param name="frequency">The frequency to send on</param>
/// <param name="data">The data to be sent</param>
/// <param name="broadcast">Send to all devices on the same device network on the given frequency</param>
public void QueuePacket(EntityUid uid, string address, int frequency, NetworkPayload data, bool broadcast = false)
{
if (EntityManager.TryGetComponent<DeviceNetworkComponent>(uid, out var component))
{
var packet = new NetworkPacket
{
NetId = component.DeviceNetId,
Address = address,
Frequency = frequency,
Broadcast = broadcast,
Data = data,
Sender = component
};
_packets.Enqueue(packet);
}
}
/// <summary>
/// Manually disconnect an entity with a DeviceNetworkComponent.
/// </summary>
/// <param name="uid">The Entity containing a DeviceNetworkComponent</param>
public void Disconnect(EntityUid uid)
{
if (EntityManager.GetEntity(uid).TryGetComponent<DeviceNetworkComponent>(out var component))
{
RemoveConnection(component);
}
}
/// <summary>
/// Automatically connect when an entity with a DeviceNetworkComponent starts up.
/// </summary>
private void OnNetworkStarted(EntityUid uid, DeviceNetworkComponent component, ComponentStartup args)
{
AddConnection(component);
}
/// <summary>
/// Automatically disconnect when an entity with a DeviceNetworkComponent shuts down.
/// </summary>
private void OnNetworkShutdown(EntityUid uid, DeviceNetworkComponent component, ComponentShutdown args)
{
RemoveConnection(component);
}
private bool AddConnection(DeviceNetworkComponent connection)
{
var netId = connection.DeviceNetId;
if (!_connections.ContainsKey(netId))
_connections[netId] = new List<DeviceNetworkComponent>();
if (!_connections[netId].Contains(connection))
{
connection.Address = GenerateValidAddress(netId);
_connections[netId].Add(connection);
connection.Open = true;
return true;
}
return false;
}
private bool RemoveConnection(DeviceNetworkComponent connection)
{
connection.Address = "";
connection.Open = false;
return _connections[connection.DeviceNetId].Remove(connection);
}
/// <summary>
/// Generates a valid address by randomly generating one and checking if it already exists on the device network with the given device netId.
/// </summary>
private string GenerateValidAddress(DeviceNetworkComponent.ConnectionType netId)
{
var unique = false;
var connections = _connections[netId];
var address = "";
while (!unique)
{
address = _random.Next().ToString("x");
unique = !connections.Exists(connection => connection.Address == address);
}
return address;
}
private List<DeviceNetworkComponent> ConnectionsForFrequency(DeviceNetworkComponent.ConnectionType netId, int frequency)
{
if (!_connections.ContainsKey(netId))
return new List<DeviceNetworkComponent>();
var result = _connections[netId].FindAll(connection => connection.Frequency == frequency);
return result;
}
private bool TryGetConnectionWithAddress(DeviceNetworkComponent.ConnectionType netId, int frequency, string address, [NotNullWhen(true)] out DeviceNetworkComponent connection)
{
var connections = ConnectionsForFrequency(netId, frequency);
var result = connections.Find(dvc => dvc.Address == address);
if(result != null)
{
connection = result;
return true;
}
connection = default!;
return false;
}
private List<DeviceNetworkComponent> ConnectionsWithReceiveAll(DeviceNetworkComponent.ConnectionType netId, int frequency)
{
if (!_connections.ContainsKey(netId))
return new List<DeviceNetworkComponent>();
var result = _connections[netId].FindAll(device => device.Frequency == frequency && device.ReceiveAll);
return result;
}
private void SendPacket(NetworkPacket packet)
{
if (!TryGetConnectionWithAddress(packet.NetId, packet.Frequency, packet.Address, out var connection))
return;
var receivers = ConnectionsWithReceiveAll(packet.NetId, packet.Frequency);
receivers.Add(connection);
SendToConnections(receivers, packet);
}
private void BroadcastPacket(NetworkPacket packet)
{
var receivers = ConnectionsForFrequency(packet.NetId, packet.Frequency);
SendToConnections(receivers, packet);
}
private void SendToConnections(List<DeviceNetworkComponent> connections, NetworkPacket packet)
{
foreach (var connection in connections)
{
var beforeEvent = new BeforePacketSentEvent(packet.Sender.Owner.Uid);
RaiseLocalEvent(connection.Owner.Uid, beforeEvent, false);
if (!beforeEvent.Cancelled)
{
RaiseLocalEvent(connection.Owner.Uid, new PacketSentEvent(connection.Frequency, packet.Sender.Address, packet.Data, packet.Broadcast) , false);
}
}
}
internal struct NetworkPacket
{
public DeviceNetworkComponent.ConnectionType NetId;
public int Frequency;
public string Address;
public bool Broadcast;
public NetworkPayload Data;
public DeviceNetworkComponent Sender;
}
}
/// <summary>
/// Event raised before a device network packet is send.
/// Subscribed to by other systems to prevent the packet from being sent.
/// </summary>
public class BeforePacketSentEvent : CancellableEntityEventArgs
{
/// <summary>
/// The EntityUid of the entity the packet was sent from.
/// </summary>
public EntityUid Sender;
public BeforePacketSentEvent(EntityUid sender)
{
Sender = sender;
}
}
/// <summary>
/// Event raised when a device network packet gets sent.
/// </summary>
public class PacketSentEvent : EntityEventArgs
{
/// <summary>
/// The frequency the packet is sent on.
/// </summary>
public int Frequency;
/// <summary>
/// The device network address of the sending entity.
/// </summary>
public string SenderAddress;
/// <summary>
/// The data that is beeing sent.
/// </summary>
public NetworkPayload Data;
/// <summary>
/// Whether the packet was broadcasted.
/// </summary>
public bool Broadcast;
public PacketSentEvent(int frequency, string senderAddress, NetworkPayload data, bool broadcast)
{
Frequency = frequency;
SenderAddress = senderAddress;
Data = data;
Broadcast = broadcast;
}
}
}