2021-10-11 23:41:18 +02:00
using Content.Server.DeviceNetwork.Components ;
2022-04-09 00:27:10 +12:00
using Content.Shared.DeviceNetwork ;
2021-10-11 23:41:18 +02:00
using JetBrains.Annotations ;
2022-04-09 00:27:10 +12:00
using Robust.Shared.Prototypes ;
2021-10-11 23:41:18 +02:00
using Robust.Shared.Random ;
2022-05-16 16:52:37 -07:00
using System.Buffers ;
2021-10-11 23:41:18 +02:00
using System.Diagnostics.CodeAnalysis ;
2022-09-22 12:00:54 -07:00
using Content.Shared.Examine ;
2021-10-11 23:41:18 +02:00
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]
2022-02-16 00:23:23 -07:00
public sealed class DeviceNetworkSystem : EntitySystem
2021-10-11 23:41:18 +02:00
{
[Dependency] private readonly IRobustRandom _random = default ! ;
2022-04-09 00:27:10 +12:00
[Dependency] private readonly IPrototypeManager _protoMan = default ! ;
[Dependency] private readonly SharedTransformSystem _transformSystem = default ! ;
2021-10-11 23:41:18 +02:00
2022-05-22 07:36:21 -07:00
private readonly Dictionary < int , DeviceNet > _networks = new ( 4 ) ;
2023-05-28 18:14:06 +02:00
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 ! ;
2021-10-11 23:41:18 +02:00
public override void Initialize ( )
{
2022-04-09 00:27:10 +12:00
SubscribeLocalEvent < DeviceNetworkComponent , MapInitEvent > ( OnMapInit ) ;
2021-10-11 23:41:18 +02:00
SubscribeLocalEvent < DeviceNetworkComponent , ComponentShutdown > ( OnNetworkShutdown ) ;
2022-09-22 12:00:54 -07:00
SubscribeLocalEvent < DeviceNetworkComponent , ExaminedEvent > ( OnExamine ) ;
2023-05-28 18:14:06 +02:00
_activeQueue = _queueA ;
_nextQueue = _queueB ;
2021-10-11 23:41:18 +02:00
}
public override void Update ( float frameTime )
{
2023-05-28 18:14:06 +02:00
while ( _activeQueue . TryDequeue ( out var packet ) )
2021-10-11 23:41:18 +02:00
{
SendPacket ( packet ) ;
}
2023-05-28 18:14:06 +02:00
SwapQueues ( ) ;
2021-10-11 23:41:18 +02:00
}
/// <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>
2022-04-09 00:27:10 +12:00
/// <param name="address">The address of the entity that the packet gets sent to. If null, the message is broadcast to all devices on that frequency (except the sender)</param>
2021-10-11 23:41:18 +02:00
/// <param name="frequency">The frequency to send on</param>
/// <param name="data">The data to be sent</param>
2023-01-23 02:07:57 +01:00
/// <returns>Returns true when the packet was successfully enqueued.</returns>
public bool QueuePacket ( EntityUid uid , string? address , NetworkPayload data , uint? frequency = null , DeviceNetworkComponent ? device = null )
2021-10-11 23:41:18 +02:00
{
2022-04-09 00:27:10 +12:00
if ( ! Resolve ( uid , ref device , false ) )
2023-01-23 02:07:57 +01:00
return false ;
2022-04-09 00:27:10 +12:00
2022-06-10 03:28:24 +02:00
if ( device . Address = = string . Empty )
2023-01-23 02:07:57 +01:00
return false ;
2022-04-09 00:27:10 +12:00
frequency ? ? = device . TransmitFrequency ;
2023-01-23 02:07:57 +01:00
if ( frequency = = null )
return false ;
2023-05-28 18:14:06 +02:00
_nextQueue . Enqueue ( new DeviceNetworkPacketEvent ( device . DeviceNetId , address , frequency . Value , device . Address , uid , data ) ) ;
2023-01-23 02:07:57 +01:00
return true ;
2021-10-11 23:41:18 +02:00
}
2022-09-22 12:00:54 -07:00
2023-05-28 18:14:06 +02:00
/// <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 ;
}
2022-09-22 12:00:54 -07:00
private void OnExamine ( EntityUid uid , DeviceNetworkComponent device , ExaminedEvent args )
{
if ( device . ExaminableAddress )
{
args . PushText ( Loc . GetString ( "device-address-examine-message" , ( "address" , device . Address ) ) ) ;
}
}
2021-10-11 23:41:18 +02:00
/// <summary>
2022-04-09 00:27:10 +12:00
/// Automatically attempt to connect some devices when a map starts.
2021-10-11 23:41:18 +02:00
/// </summary>
2022-04-09 00:27:10 +12:00
private void OnMapInit ( EntityUid uid , DeviceNetworkComponent device , MapInitEvent args )
2021-10-11 23:41:18 +02:00
{
2022-04-09 00:27:10 +12:00
if ( device . ReceiveFrequency = = null
& & device . ReceiveFrequencyId ! = null
& & _protoMan . TryIndex < DeviceFrequencyPrototype > ( device . ReceiveFrequencyId , out var receive ) )
{
device . ReceiveFrequency = receive . Frequency ;
}
if ( device . TransmitFrequency = = null
& & device . TransmitFrequencyId ! = null
& & _protoMan . TryIndex < DeviceFrequencyPrototype > ( device . TransmitFrequencyId , out var xmit ) )
2021-10-11 23:41:18 +02:00
{
2022-04-09 00:27:10 +12:00
device . TransmitFrequency = xmit . Frequency ;
2021-10-11 23:41:18 +02:00
}
2022-04-09 00:27:10 +12:00
if ( device . AutoConnect )
ConnectDevice ( uid , device ) ;
2021-10-11 23:41:18 +02:00
}
2022-05-22 07:36:21 -07:00
private DeviceNet GetNetwork ( int netId )
{
if ( _networks . TryGetValue ( netId , out var deviceNet ) )
return deviceNet ;
var newDeviceNet = new DeviceNet ( netId , _random ) ;
_networks [ netId ] = newDeviceNet ;
return newDeviceNet ;
}
2022-05-16 16:52:37 -07:00
2021-10-11 23:41:18 +02:00
/// <summary>
2022-04-09 00:27:10 +12:00
/// Automatically disconnect when an entity with a DeviceNetworkComponent shuts down.
2021-10-11 23:41:18 +02:00
/// </summary>
2022-04-09 00:27:10 +12:00
private void OnNetworkShutdown ( EntityUid uid , DeviceNetworkComponent component , ComponentShutdown args )
2021-10-11 23:41:18 +02:00
{
2023-05-26 00:08:22 +02:00
var eventArgs = new DeviceShutDownEvent ( uid ) ;
foreach ( var shutdownSubscriberId in component . ShutdownSubscribers )
{
RaiseLocalEvent ( shutdownSubscriberId , ref eventArgs ) ;
DeviceNetworkComponent ? device = null ! ;
if ( Resolve ( shutdownSubscriberId , ref device ) )
device . ShutdownSubscribers . Remove ( uid ) ;
}
2022-05-16 16:52:37 -07:00
GetNetwork ( component . DeviceNetId ) . Remove ( component ) ;
2021-10-11 23:41:18 +02:00
}
/// <summary>
2022-04-09 00:27:10 +12:00
/// Connect an entity with a DeviceNetworkComponent. Note that this will re-use an existing address if the
/// device already had one configured. If there is a clash, the device cannot join the network.
2021-10-11 23:41:18 +02:00
/// </summary>
2022-04-09 00:27:10 +12:00
public bool ConnectDevice ( EntityUid uid , DeviceNetworkComponent ? device = null )
2021-10-11 23:41:18 +02:00
{
2022-04-09 00:27:10 +12:00
if ( ! Resolve ( uid , ref device , false ) )
return false ;
2022-05-16 16:52:37 -07:00
return GetNetwork ( device . DeviceNetId ) . Add ( device ) ;
2021-10-11 23:41:18 +02:00
}
2022-04-09 00:27:10 +12:00
/// <summary>
/// Disconnect an entity with a DeviceNetworkComponent.
/// </summary>
public bool DisconnectDevice ( EntityUid uid , DeviceNetworkComponent ? device , bool preventAutoConnect = true )
2021-10-11 23:41:18 +02:00
{
2022-04-09 00:27:10 +12:00
if ( ! Resolve ( uid , ref device , false ) )
return false ;
2021-10-11 23:41:18 +02:00
2022-04-09 00:27:10 +12:00
// If manually disconnected, don't auto reconnect when a game state is loaded.
if ( preventAutoConnect )
device . AutoConnect = false ;
2022-05-16 16:52:37 -07:00
return GetNetwork ( device . DeviceNetId ) . Remove ( device ) ;
}
2021-10-11 23:41:18 +02:00
2023-01-23 02:07:57 +01:00
/// <summary>
/// Checks if a device is already connected to its network
/// </summary>
/// <returns>True if the device was found in the network with its corresponding network id</returns>
public bool IsDeviceConnected ( EntityUid uid , DeviceNetworkComponent ? device )
{
if ( ! Resolve ( uid , ref device , false ) )
return false ;
if ( ! _networks . TryGetValue ( device . DeviceNetId , out var deviceNet ) )
return false ;
return deviceNet . Devices . ContainsValue ( device ) ;
}
/// <summary>
/// Checks if an address exists in the network with the given netId
/// </summary>
public bool IsAddressPresent ( int netId , string? address )
{
if ( address = = null | | ! _networks . TryGetValue ( netId , out var network ) )
return false ;
return network . Devices . ContainsKey ( address ) ;
}
2022-05-16 16:52:37 -07:00
public void SetReceiveFrequency ( EntityUid uid , uint? frequency , DeviceNetworkComponent ? device = null )
{
if ( ! Resolve ( uid , ref device , false ) )
return ;
2022-04-09 00:27:10 +12:00
2022-05-16 16:52:37 -07:00
if ( device . ReceiveFrequency = = frequency ) return ;
2022-04-09 00:27:10 +12:00
2022-05-16 16:52:37 -07:00
var deviceNet = GetNetwork ( device . DeviceNetId ) ;
deviceNet . Remove ( device ) ;
device . ReceiveFrequency = frequency ;
deviceNet . Add ( device ) ;
2021-10-11 23:41:18 +02:00
}
2022-05-16 16:52:37 -07:00
public void SetTransmitFrequency ( EntityUid uid , uint? frequency , DeviceNetworkComponent ? device = null )
2021-10-11 23:41:18 +02:00
{
2022-05-16 16:52:37 -07:00
if ( Resolve ( uid , ref device , false ) )
device . TransmitFrequency = frequency ;
}
2022-04-09 00:27:10 +12:00
2022-05-16 16:52:37 -07:00
public void SetReceiveAll ( EntityUid uid , bool receiveAll , DeviceNetworkComponent ? device = null )
{
if ( ! Resolve ( uid , ref device , false ) )
return ;
if ( device . ReceiveAll = = receiveAll ) return ;
var deviceNet = GetNetwork ( device . DeviceNetId ) ;
deviceNet . Remove ( device ) ;
device . ReceiveAll = receiveAll ;
deviceNet . Add ( device ) ;
2021-10-11 23:41:18 +02:00
}
2022-05-16 16:52:37 -07:00
public void SetAddress ( EntityUid uid , string address , DeviceNetworkComponent ? device = null )
2021-10-11 23:41:18 +02:00
{
2022-05-16 16:52:37 -07:00
if ( ! Resolve ( uid , ref device , false ) )
return ;
2023-01-19 03:56:45 +01:00
if ( device . Address = = address & & device . CustomAddress ) return ;
2022-04-09 00:27:10 +12:00
2022-05-16 16:52:37 -07:00
var deviceNet = GetNetwork ( device . DeviceNetId ) ;
deviceNet . Remove ( device ) ;
device . CustomAddress = true ;
device . Address = address ;
deviceNet . Add ( device ) ;
}
public void RandomizeAddress ( EntityUid uid , DeviceNetworkComponent ? device = null )
{
if ( ! Resolve ( uid , ref device , false ) )
return ;
var deviceNet = GetNetwork ( device . DeviceNetId ) ;
deviceNet . Remove ( device ) ;
device . CustomAddress = false ;
device . Address = "" ;
deviceNet . Add ( device ) ;
2022-04-09 00:27:10 +12:00
}
2021-10-11 23:41:18 +02:00
2023-05-26 00:08:22 +02:00
public void SubscribeToDeviceShutdown (
EntityUid subscriberId , EntityUid targetId ,
DeviceNetworkComponent ? subscribingDevice = null ,
DeviceNetworkComponent ? targetDevice = null )
{
if ( subscriberId = = targetId )
return ;
if ( ! Resolve ( subscriberId , ref subscribingDevice ) | | ! Resolve ( targetId , ref targetDevice ) )
return ;
targetDevice . ShutdownSubscribers . Add ( subscriberId ) ;
subscribingDevice . ShutdownSubscribers . Add ( targetId ) ;
}
public void UnsubscribeFromDeviceShutdown (
EntityUid subscriberId , EntityUid targetId ,
DeviceNetworkComponent ? subscribingDevice = null ,
DeviceNetworkComponent ? targetDevice = null )
{
if ( subscriberId = = targetId )
return ;
if ( ! Resolve ( subscriberId , ref subscribingDevice ) | | ! Resolve ( targetId , ref targetDevice ) )
return ;
targetDevice . ShutdownSubscribers . Remove ( subscriberId ) ;
subscribingDevice . ShutdownSubscribers . Remove ( targetId ) ;
}
2022-04-09 00:27:10 +12:00
/// <summary>
/// Try to find a device on a network using its address.
/// </summary>
2022-05-22 07:36:21 -07:00
private bool TryGetDevice ( int netId , string address , [ NotNullWhen ( true ) ] out DeviceNetworkComponent ? device ) = >
2022-05-16 16:52:37 -07:00
GetNetwork ( netId ) . Devices . TryGetValue ( address , out device ) ;
2021-10-11 23:41:18 +02:00
2022-04-09 00:27:10 +12:00
private void SendPacket ( DeviceNetworkPacketEvent packet )
2021-10-11 23:41:18 +02:00
{
2022-05-16 16:52:37 -07:00
var network = GetNetwork ( packet . NetId ) ;
2022-04-09 00:27:10 +12:00
if ( packet . Address = = null )
{
2022-06-10 03:28:24 +02:00
// Broadcast to all listening devices
if ( network . ListeningDevices . TryGetValue ( packet . Frequency , out var devices ) & & CheckRecipientsList ( packet , ref devices ) )
2022-05-16 16:52:37 -07:00
{
var deviceCopy = ArrayPool < DeviceNetworkComponent > . Shared . Rent ( devices . Count ) ;
devices . CopyTo ( deviceCopy ) ;
SendToConnections ( deviceCopy . AsSpan ( 0 , devices . Count ) , packet ) ;
ArrayPool < DeviceNetworkComponent > . Shared . Return ( deviceCopy ) ;
}
2022-04-09 00:27:10 +12:00
}
else
{
2022-05-16 16:52:37 -07:00
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 ) & &
! device . ReceiveAll & &
device . ReceiveFrequency = = packet . Frequency )
{
totalDevices + = 1 ;
hasTargetedDevice = true ;
}
var deviceCopy = ArrayPool < DeviceNetworkComponent > . Shared . Rent ( totalDevices ) ;
if ( devices ! = null )
{
devices . CopyTo ( deviceCopy ) ;
}
if ( hasTargetedDevice )
{
deviceCopy [ totalDevices - 1 ] = device ! ;
}
SendToConnections ( deviceCopy . AsSpan ( 0 , totalDevices ) , packet ) ;
ArrayPool < DeviceNetworkComponent > . Shared . Return ( deviceCopy ) ;
2022-04-09 00:27:10 +12:00
}
2021-10-11 23:41:18 +02:00
}
2022-06-10 03:28:24 +02:00
/// <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 ( DeviceNetworkPacketEvent packet , ref HashSet < DeviceNetworkComponent > recipients )
{
if ( ! _networks . ContainsKey ( packet . NetId ) | | ! _networks [ packet . NetId ] . Devices . ContainsKey ( packet . SenderAddress ) )
return false ;
var sender = _networks [ packet . NetId ] . Devices [ packet . SenderAddress ] ;
if ( ! sender . SendBroadcastAttemptEvent )
return true ;
var beforeBroadcastAttemptEvent = new BeforeBroadcastAttemptEvent ( recipients ) ;
2022-06-22 09:53:41 +10:00
RaiseLocalEvent ( packet . Sender , beforeBroadcastAttemptEvent , true ) ;
2022-06-10 03:28:24 +02:00
if ( beforeBroadcastAttemptEvent . Cancelled | | beforeBroadcastAttemptEvent . ModifiedRecipients = = null )
return false ;
recipients = beforeBroadcastAttemptEvent . ModifiedRecipients ;
return true ;
}
2022-05-16 16:52:37 -07:00
private void SendToConnections ( ReadOnlySpan < DeviceNetworkComponent > connections , DeviceNetworkPacketEvent packet )
2021-10-11 23:41:18 +02:00
{
2022-09-04 02:04:15 -07:00
if ( Deleted ( packet . Sender ) )
{
return ;
}
2022-04-09 00:27:10 +12:00
var xform = Transform ( packet . Sender ) ;
2021-10-11 23:41:18 +02:00
2022-04-09 00:27:10 +12:00
BeforePacketSentEvent beforeEv = new ( packet . Sender , xform , _transformSystem . GetWorldPosition ( xform ) ) ;
2021-10-11 23:41:18 +02:00
2022-04-09 00:27:10 +12:00
foreach ( var connection in connections )
2021-10-11 23:41:18 +02:00
{
2022-04-09 00:27:10 +12:00
if ( connection . Owner = = packet . Sender )
continue ;
RaiseLocalEvent ( connection . Owner , beforeEv , false ) ;
2021-10-11 23:41:18 +02:00
2022-04-09 00:27:10 +12:00
if ( ! beforeEv . Cancelled )
RaiseLocalEvent ( connection . Owner , packet , false ) ;
else
beforeEv . Uncancel ( ) ;
}
2021-10-11 23:41:18 +02:00
}
}
/// <summary>
/// Event raised before a device network packet is send.
/// Subscribed to by other systems to prevent the packet from being sent.
/// </summary>
2022-02-16 00:23:23 -07:00
public sealed class BeforePacketSentEvent : CancellableEntityEventArgs
2021-10-11 23:41:18 +02:00
{
/// <summary>
/// The EntityUid of the entity the packet was sent from.
/// </summary>
2022-04-09 00:27:10 +12:00
public readonly EntityUid Sender ;
public readonly TransformComponent SenderTransform ;
/// <summary>
/// The senders current position in world coordinates.
/// </summary>
public readonly Vector2 SenderPosition ;
2021-10-11 23:41:18 +02:00
2022-04-09 00:27:10 +12:00
public BeforePacketSentEvent ( EntityUid sender , TransformComponent xform , Vector2 senderPosition )
2021-10-11 23:41:18 +02:00
{
Sender = sender ;
2022-04-09 00:27:10 +12:00
SenderTransform = xform ;
SenderPosition = senderPosition ;
2021-10-11 23:41:18 +02:00
}
}
2022-06-10 03:28:24 +02:00
/// <summary>
/// Sent to the sending entity before broadcasting network packets to recipients
/// </summary>
public sealed class BeforeBroadcastAttemptEvent : CancellableEntityEventArgs
{
public readonly IReadOnlySet < DeviceNetworkComponent > Recipients ;
public HashSet < DeviceNetworkComponent > ? ModifiedRecipients ;
public BeforeBroadcastAttemptEvent ( IReadOnlySet < DeviceNetworkComponent > recipients )
{
Recipients = recipients ;
}
}
2021-10-11 23:41:18 +02:00
/// <summary>
/// Event raised when a device network packet gets sent.
/// </summary>
2022-04-09 00:27:10 +12:00
public sealed class DeviceNetworkPacketEvent : EntityEventArgs
2021-10-11 23:41:18 +02:00
{
2022-04-09 00:27:10 +12:00
/// <summary>
2022-05-22 07:36:21 -07:00
/// The id of the network that this packet is being sent on.
2022-04-09 00:27:10 +12:00
/// </summary>
2022-05-22 07:36:21 -07:00
public int NetId ;
2022-04-09 00:27:10 +12:00
2021-10-11 23:41:18 +02:00
/// <summary>
/// The frequency the packet is sent on.
/// </summary>
2022-04-09 00:27:10 +12:00
public readonly uint Frequency ;
/// <summary>
/// Address of the intended recipient. Null if the message was broadcast.
/// </summary>
public string? Address ;
2021-10-11 23:41:18 +02:00
/// <summary>
/// The device network address of the sending entity.
/// </summary>
2022-04-09 00:27:10 +12:00
public readonly string SenderAddress ;
2021-10-11 23:41:18 +02:00
/// <summary>
2022-04-09 00:27:10 +12:00
/// The entity that sent the packet.
2021-10-11 23:41:18 +02:00
/// </summary>
2022-04-09 00:27:10 +12:00
public EntityUid Sender ;
2021-10-11 23:41:18 +02:00
/// <summary>
2022-04-09 00:27:10 +12:00
/// The data that is being sent.
2021-10-11 23:41:18 +02:00
/// </summary>
2022-04-09 00:27:10 +12:00
public readonly NetworkPayload Data ;
2021-10-11 23:41:18 +02:00
2022-05-22 07:36:21 -07:00
public DeviceNetworkPacketEvent ( int netId , string? address , uint frequency , string senderAddress , EntityUid sender , NetworkPayload data )
2021-10-11 23:41:18 +02:00
{
2022-04-09 00:27:10 +12:00
NetId = netId ;
Address = address ;
2021-10-11 23:41:18 +02:00
Frequency = frequency ;
SenderAddress = senderAddress ;
2022-04-09 00:27:10 +12:00
Sender = sender ;
2021-10-11 23:41:18 +02:00
Data = data ;
}
}
2023-05-26 00:08:22 +02:00
/// <summary>
/// Gets raised on entities that subscribed to shutdown event of the shut down entity
/// </summary>
/// <param name="ShutDownEntityUid">The entity that was shut down</param>
[ByRefEvent]
public readonly record struct DeviceShutDownEvent ( EntityUid ShutDownEntityUid ) ;
2021-10-11 23:41:18 +02:00
}