Device network as ecs (#4205)

* Work on DeviceNetworkSystem

* Implement device networking as ecs
Remove mailing unit code for now
Remove device network metadata

* Implement integration tests for device networking

* Remove manual updating DeviceNetworkSystem and use WaitRunTicks

* Fix wrong component name in ignored components

* Apply suggestions from code review

Co-authored-by: mirrorcult <notzombiedude@gmail.com>

* Rename NetworkUtils to DeviceNetworkConstants
Change connection type constants to enum
Remove create function from network payload class

* Change broken nodegroup check in wirenet to grid check

* Change ComponentManager to entity manager in DeviceNetworkSystem

* Fix smaller mistakes

* Wtf random test fail pls run them again smh

* Fix DataField in DeviceNetworkComponent

* Fix yaml in DeviceNetworkTest

* Fix DeviceNetworkComponent DeviceNetId property

Co-authored-by: Julian Giebel <j.giebel@netrocks.info>
Co-authored-by: mirrorcult <notzombiedude@gmail.com>
This commit is contained in:
Julian Giebel
2021-10-11 23:41:18 +02:00
committed by GitHub
parent 4c80fb9586
commit a6f2c21919
22 changed files with 772 additions and 641 deletions

View File

@@ -281,7 +281,10 @@ namespace Content.Client.Entry
"TabletopGame", "TabletopGame",
"LitOnPowered", "LitOnPowered",
"TriggerOnSignalReceived", "TriggerOnSignalReceived",
"ToggleDoorOnTrigger" "ToggleDoorOnTrigger",
"DeviceNetworkConnection",
"WiredNetworkConnection",
"WirelessNetworkConnection"
}; };
} }
} }

View File

@@ -0,0 +1,272 @@
using Content.Server.DeviceNetwork;
using Content.Server.DeviceNetwork.Components;
using Content.Server.DeviceNetwork.Systems;
using NUnit.Framework;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Map;
using System.Threading.Tasks;
namespace Content.IntegrationTests.Tests.DeviceNetwork
{
[TestFixture]
[TestOf(typeof(DeviceNetworkComponent))]
[TestOf(typeof(WiredNetworkComponent))]
[TestOf(typeof(WirelessNetworkComponent))]
public class DeviceNetworkTest : ContentIntegrationTest
{
private const string Prototypes = @"
- type: entity
name: DummyNetworkDevice
id: DummyNetworkDevice
components:
- type: DeviceNetworkComponent
frequency: 100
- type: entity
name: DummyWiredNetworkDevice
id: DummyWiredNetworkDevice
components:
- type: DeviceNetworkComponent
deviceNetId: 1
- type: WiredNetworkConnection
- type: ApcPowerReceiver
- type: entity
name: DummyWirelessNetworkDevice
id: DummyWirelessNetworkDevice
components:
- type: DeviceNetworkComponent
frequency: 100
deviceNetId: 1
- type: WirelessNetworkConnection
range: 100
";
[Test]
public async Task NetworkDeviceSendAndReceive()
{
var options = new ServerContentIntegrationOption
{
ExtraPrototypes = Prototypes,
ContentBeforeIoC = () => {
IoCManager.Resolve<IEntitySystemManager>().LoadExtraSystemType<DeviceNetworkTestSystem>();
}
};
var server = StartServerDummyTicker(options);
await server.WaitIdleAsync();
var mapManager = server.ResolveDependency<IMapManager>();
var entityManager = server.ResolveDependency<IEntityManager>();
var deviceNetSystem = entityManager.EntitySysManager.GetEntitySystem<DeviceNetworkSystem>();
var deviceNetTestSystem = entityManager.EntitySysManager.GetEntitySystem<DeviceNetworkTestSystem>();
IEntity device1 = null;
IEntity device2 = null;
DeviceNetworkComponent networkComponent1 = null;
DeviceNetworkComponent networkComponent2 = null;
var testValue = "test";
var payload = new NetworkPayload
{
["Test"] = testValue,
["testnumber"] = 1,
["testbool"] = true
};
server.Assert(() => {
mapManager.CreateNewMapEntity(MapId.Nullspace);
device1 = entityManager.SpawnEntity("DummyNetworkDevice", MapCoordinates.Nullspace);
Assert.That(device1.TryGetComponent(out networkComponent1), Is.True);
Assert.That(networkComponent1.Open, Is.True);
Assert.That(networkComponent1.Address, Is.Not.EqualTo(string.Empty));
device2 = entityManager.SpawnEntity("DummyNetworkDevice", MapCoordinates.Nullspace);
Assert.That(device2.TryGetComponent(out networkComponent2), Is.True);
Assert.That(networkComponent2.Open, Is.True);
Assert.That(networkComponent2.Address, Is.Not.EqualTo(string.Empty));
Assert.That(networkComponent1.Address, Is.Not.EqualTo(networkComponent2.Address));
deviceNetSystem.QueuePacket(device1.Uid, networkComponent2.Address, networkComponent2.Frequency, payload);
});
await server.WaitRunTicks(1);
await server.WaitIdleAsync();
server.Assert(() => {
CollectionAssert.AreEquivalent(deviceNetTestSystem.LastPayload, payload);
});
}
[Test]
public async Task WirelessNetworkDeviceSendAndReceive()
{
var options = new ServerContentIntegrationOption
{
ExtraPrototypes = Prototypes,
ContentBeforeIoC = () => {
IoCManager.Resolve<IEntitySystemManager>().LoadExtraSystemType<DeviceNetworkTestSystem>();
}
};
var server = StartServerDummyTicker(options);
await server.WaitIdleAsync();
var mapManager = server.ResolveDependency<IMapManager>();
var entityManager = server.ResolveDependency<IEntityManager>();
var deviceNetSystem = entityManager.EntitySysManager.GetEntitySystem<DeviceNetworkSystem>();
var deviceNetTestSystem = entityManager.EntitySysManager.GetEntitySystem<DeviceNetworkTestSystem>();
IEntity device1 = null;
IEntity device2 = null;
DeviceNetworkComponent networkComponent1 = null;
DeviceNetworkComponent networkComponent2 = null;
WirelessNetworkComponent wirelessNetworkComponent = null;
var testValue = "test";
var payload = new NetworkPayload
{
["Test"] = testValue,
["testnumber"] = 1,
["testbool"] = true
};
server.Assert(() => {
mapManager.CreateNewMapEntity(MapId.Nullspace);
device1 = entityManager.SpawnEntity("DummyWirelessNetworkDevice", MapCoordinates.Nullspace);
Assert.That(device1.TryGetComponent(out networkComponent1), Is.True);
Assert.That(device1.TryGetComponent(out wirelessNetworkComponent), Is.True);
Assert.That(networkComponent1.Open, Is.True);
Assert.That(networkComponent1.Address, Is.Not.EqualTo(string.Empty));
device2 = entityManager.SpawnEntity("DummyWirelessNetworkDevice", new MapCoordinates(new Robust.Shared.Maths.Vector2(0,50), MapId.Nullspace));
Assert.That(device2.TryGetComponent(out networkComponent2), Is.True);
Assert.That(networkComponent2.Open, Is.True);
Assert.That(networkComponent2.Address, Is.Not.EqualTo(string.Empty));
Assert.That(networkComponent1.Address, Is.Not.EqualTo(networkComponent2.Address));
deviceNetSystem.QueuePacket(device1.Uid, networkComponent2.Address, networkComponent2.Frequency, payload);
});
await server.WaitRunTicks(1);
await server.WaitIdleAsync();
server.Assert(() => {
CollectionAssert.AreEqual(deviceNetTestSystem.LastPayload, payload);
payload = new NetworkPayload
{
["Wirelesstest"] = 5
};
wirelessNetworkComponent.Range = 0;
deviceNetSystem.QueuePacket(device1.Uid, networkComponent2.Address, networkComponent2.Frequency, payload);
});
await server.WaitRunTicks(1);
await server.WaitIdleAsync();
server.Assert(() => {
CollectionAssert.AreNotEqual(deviceNetTestSystem.LastPayload, payload);
});
await server.WaitIdleAsync();
}
[Test]
public async Task WiredNetworkDeviceSendAndReceive()
{
var options = new ServerContentIntegrationOption
{
ExtraPrototypes = Prototypes,
ContentBeforeIoC = () => {
IoCManager.Resolve<IEntitySystemManager>().LoadExtraSystemType<DeviceNetworkTestSystem>();
}
};
var server = StartServerDummyTicker(options);
await server.WaitIdleAsync();
var mapManager = server.ResolveDependency<IMapManager>();
var entityManager = server.ResolveDependency<IEntityManager>();
var deviceNetSystem = entityManager.EntitySysManager.GetEntitySystem<DeviceNetworkSystem>();
var deviceNetTestSystem = entityManager.EntitySysManager.GetEntitySystem<DeviceNetworkTestSystem>();
IEntity device1 = null;
IEntity device2 = null;
DeviceNetworkComponent networkComponent1 = null;
DeviceNetworkComponent networkComponent2 = null;
WiredNetworkComponent wiredNetworkComponent = null;
IMapGrid grid = null;
var testValue = "test";
var payload = new NetworkPayload
{
["Test"] = testValue,
["testnumber"] = 1,
["testbool"] = true
};
await server.WaitRunTicks(1);
await server.WaitIdleAsync();
server.Assert(() => {
var map = mapManager.CreateNewMapEntity(MapId.Nullspace);
grid = mapManager.CreateGrid(MapId.Nullspace);
device1 = entityManager.SpawnEntity("DummyWiredNetworkDevice", MapCoordinates.Nullspace);
Assert.That(device1.TryGetComponent(out networkComponent1), Is.True);
Assert.That(device1.TryGetComponent(out wiredNetworkComponent), Is.True);
Assert.That(networkComponent1.Open, Is.True);
Assert.That(networkComponent1.Address, Is.Not.EqualTo(string.Empty));
device2 = entityManager.SpawnEntity("DummyWiredNetworkDevice", new MapCoordinates(new Robust.Shared.Maths.Vector2(0, 2), MapId.Nullspace));
Assert.That(device2.TryGetComponent(out networkComponent2), Is.True);
Assert.That(networkComponent2.Open, Is.True);
Assert.That(networkComponent2.Address, Is.Not.EqualTo(string.Empty));
Assert.That(networkComponent1.Address, Is.Not.EqualTo(networkComponent2.Address));
deviceNetSystem.QueuePacket(device1.Uid, networkComponent2.Address, networkComponent2.Frequency, payload);
});
await server.WaitRunTicks(1);
await server.WaitIdleAsync();
server.Assert(() => {
//CollectionAssert.AreNotEqual(deviceNetTestSystem.LastPayload, payload);
entityManager.SpawnEntity("CableApcExtension", grid.MapToGrid(new MapCoordinates(new Robust.Shared.Maths.Vector2(0, 1), MapId.Nullspace)));
deviceNetSystem.QueuePacket(device1.Uid, networkComponent2.Address, networkComponent2.Frequency, payload);
});
await server.WaitRunTicks(1);
await server.WaitIdleAsync();
server.Assert(() => {
CollectionAssert.AreEqual(deviceNetTestSystem.LastPayload, payload);
});
await server.WaitIdleAsync();
}
}
}

View File

@@ -0,0 +1,27 @@
using Content.Server.DeviceNetwork;
using Content.Server.DeviceNetwork.Components;
using Content.Server.DeviceNetwork.Systems;
using Robust.Shared.GameObjects;
using Robust.Shared.Reflection;
namespace Content.IntegrationTests.Tests.DeviceNetwork
{
[Reflect(false)]
public class DeviceNetworkTestSystem : EntitySystem
{
public NetworkPayload LastPayload = default;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<DeviceNetworkComponent, PacketSentEvent>(OnPacketReceived);
}
private void OnPacketReceived(EntityUid uid, DeviceNetworkComponent component, PacketSentEvent args)
{
LastPayload = args.Data;
}
}
}

View File

@@ -0,0 +1,35 @@
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes;
using Robust.Shared.ViewVariables;
namespace Content.Server.DeviceNetwork.Components
{
[RegisterComponent]
public class DeviceNetworkComponent : Component
{
public override string Name => "DeviceNetworkComponent";
/// <summary>
/// The device networks netID this DeviceNetworkComponent connects to.
/// The netID is used to seperate device networks that shouldn't interact with each other e.g. wireless and wired.
/// The default netID's are_
/// 0 -> Private
/// 1 -> Wired
/// 2 -> Wireless
/// </summary>
[DataField("deviceNetId")]
public int DeviceNetId { get; set; } = (int)DeviceNetworkConstants.ConnectionType.Private;
[DataField("frequency")]
public int Frequency { get; set; } = 0;
[ViewVariables]
public bool Open;
[ViewVariables]
public string Address = string.Empty;
[DataField("receiveAll")]
public bool ReceiveAll;
}
}

View File

@@ -0,0 +1,10 @@
using Robust.Shared.GameObjects;
namespace Content.Server.DeviceNetwork.Components
{
[RegisterComponent]
public class WiredNetworkComponent : Component
{
public override string Name => "WiredNetworkConnection";
}
}

View File

@@ -0,0 +1,17 @@
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Content.Server.DeviceNetwork.Components
{
/// <summary>
/// Sends and receives device network messages wirelessly. Devices sending and receiving need to be in range and on the same frequency.
/// </summary>
[RegisterComponent]
public class WirelessNetworkComponent : Component
{
public override string Name => "WirelessNetworkConnection";
[DataField("range")]
public int Range { get; set; }
}
}

View File

@@ -1,69 +0,0 @@
using System.Collections.Generic;
using Robust.Shared.IoC;
using Robust.Shared.ViewVariables;
namespace Content.Server.DeviceNetwork.Connections
{
public abstract class BaseNetworkConnection : IDeviceNetworkConnection
{
protected readonly DeviceNetworkConnection Connection;
protected OnReceiveNetMessage MessageHandler;
[ViewVariables]
public bool Open => Connection.Open;
[ViewVariables]
public string Address => Connection.Address;
[ViewVariables]
public int Frequency => Connection.Frequency;
protected BaseNetworkConnection(int netId, int frequency, OnReceiveNetMessage onReceive, bool receiveAll)
{
var network = IoCManager.Resolve<IDeviceNetwork>();
Connection = network.Register(netId, frequency, OnReceiveNetMessage, receiveAll);
MessageHandler = onReceive;
}
public bool Send(int frequency, string address, Dictionary<string, string> payload)
{
var data = ManipulatePayload(payload);
var metadata = GetMetadata();
return Connection.Send(frequency, address, data, metadata);
}
public bool Send(string address, Dictionary<string, string> payload)
{
return Send(0, address, payload);
}
public bool Broadcast(int frequency, Dictionary<string, string> payload)
{
var data = ManipulatePayload(payload);
var metadata = GetMetadata();
return Connection.Broadcast(frequency, data, metadata);
}
public bool Broadcast(Dictionary<string, string> payload)
{
return Broadcast(0, payload);
}
public void Close()
{
Connection.Close();
}
private void OnReceiveNetMessage(int frequency, string sender, IReadOnlyDictionary<string, string> payload, Metadata metadata, bool broadcast)
{
if (CanReceive(frequency, sender, payload, metadata, broadcast))
{
MessageHandler(frequency, sender, payload, metadata, broadcast);
}
}
protected abstract bool CanReceive(int frequency, string sender, IReadOnlyDictionary<string, string> payload, Metadata metadata, bool broadcast);
protected abstract Dictionary<string, string> ManipulatePayload(Dictionary<string, string> payload);
protected abstract Metadata GetMetadata();
}
}

View File

@@ -1,71 +0,0 @@
using System.Collections.Generic;
using Robust.Shared.ViewVariables;
namespace Content.Server.DeviceNetwork.Connections
{
public class DeviceNetworkConnection : IDeviceNetworkConnection
{
private readonly DeviceNetwork _network;
[ViewVariables]
private readonly int _netId;
[ViewVariables]
public bool Open { get; private set; }
[ViewVariables]
public string Address { get; private set; }
[ViewVariables]
public int Frequency { get; private set; }
[ViewVariables]
public bool ReceiveAll
{
get => _network.GetDeviceReceiveAll(_netId, Frequency, Address);
set => _network.SetDeviceReceiveAll(_netId, Frequency, Address, value);
}
public DeviceNetworkConnection(DeviceNetwork network, int netId, string address, int frequency)
{
_network = network;
_netId = netId;
Open = true;
Address = address;
Frequency = frequency;
}
public bool Send(int frequency, string address, IReadOnlyDictionary<string, string> payload, Metadata metadata)
{
return Open && _network.EnqueuePackage(_netId, frequency, address, payload, Address, metadata);
}
public bool Send(int frequency, string address, Dictionary<string, string> payload)
{
return Send(frequency, address, payload, new Metadata());
}
public bool Send(string address, Dictionary<string, string> payload)
{
return Send(0, address, payload);
}
public bool Broadcast(int frequency, IReadOnlyDictionary<string, string> payload, Metadata metadata)
{
return Open && _network.EnqueuePackage(_netId, frequency, "", payload, Address, metadata, true);
}
public bool Broadcast(int frequency, Dictionary<string, string> payload)
{
return Broadcast(frequency, payload, new Metadata());
}
public bool Broadcast(Dictionary<string, string> payload)
{
return Broadcast(0, payload);
}
public void Close()
{
_network.RemoveDevice(_netId, Frequency, Address);
Open = false;
}
}
}

View File

@@ -1,28 +0,0 @@
using System.Collections.Generic;
namespace Content.Server.DeviceNetwork.Connections
{
public interface IDeviceNetworkConnection
{
public int Frequency { get; }
/// <summary>
/// Sends a package to a specific device
/// </summary>
/// <param name="frequency">The frequency the package should be send on</param>
/// <param name="address">The target devices address</param>
/// <param name="payload"></param>
/// <returns></returns>
public bool Send(int frequency, string address, Dictionary<string, string> payload);
/// <see cref="Send(int, string, Dictionary{string, string})"/>
public bool Send(string address, Dictionary<string, string> payload);
/// <summary>
/// Sends a package to all devices
/// </summary>
/// <param name="frequency">The frequency the package should be send on</param>
/// <param name="payload"></param>
/// <returns></returns>
public bool Broadcast(int frequency, Dictionary<string, string> payload);
/// <see cref="Broadcast(int, Dictionary{string, string})"/>
public bool Broadcast(Dictionary<string, string> payload);
}
}

View File

@@ -1,87 +0,0 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Content.Server.NodeContainer;
using Content.Server.NodeContainer.NodeGroups;
using Content.Server.Power.Components;
using Robust.Shared.GameObjects;
namespace Content.Server.DeviceNetwork.Connections
{
public class WiredNetworkConnection : BaseNetworkConnection
{
public const string WIRENET = "powernet";
private readonly IEntity _owner;
public WiredNetworkConnection(OnReceiveNetMessage onReceive, bool receiveAll, IEntity owner) : base(NetworkUtils.WIRED, 0, onReceive, receiveAll)
{
_owner = owner;
}
protected override bool CanReceive(int frequency, string sender, IReadOnlyDictionary<string, string> payload, Metadata metadata, bool broadcast)
{
if (_owner.Deleted)
{
Connection.Close();
return false;
}
if (_owner.TryGetComponent<ApcPowerReceiverComponent>(out var powerReceiver)
&& TryGetWireNet(powerReceiver, out var ownNet)
&& metadata.TryParseMetadata<INodeGroup>(WIRENET, out var senderNet))
{
return ownNet.Equals(senderNet);
}
return false;
}
protected override Metadata GetMetadata()
{
if (_owner.Deleted)
{
Connection.Close();
return new Metadata();
}
if (_owner.TryGetComponent<ApcPowerReceiverComponent>(out var powerReceiver)
&& TryGetWireNet(powerReceiver, out var net))
{
var metadata = new Metadata
{
{WIRENET, net }
};
return metadata;
}
return new Metadata();
}
protected override Dictionary<string, string> ManipulatePayload(Dictionary<string, string> payload)
{
return payload;
}
private bool TryGetWireNet(ApcPowerReceiverComponent apcPowerReceiver, [NotNullWhen(true)] out INodeGroup? net)
{
var provider = apcPowerReceiver.Provider;
if (provider != null && provider.ProviderOwner.TryGetComponent<NodeContainerComponent>(out var nodeContainer))
{
var nodes = nodeContainer.Nodes;
foreach (var node in nodes.Values)
{
if (node.NodeGroupID == NodeGroupID.WireNet && node.NodeGroup != null)
{
net = node.NodeGroup;
return true;
}
}
}
net = default;
return false;
}
}
}

View File

@@ -1,63 +0,0 @@
using System;
using System.Collections.Generic;
using Robust.Shared.GameObjects;
using Robust.Shared.Maths;
namespace Content.Server.DeviceNetwork.Connections
{
public class WirelessNetworkConnection : BaseNetworkConnection
{
public const string WIRELESS_POSITION = "position";
private readonly IEntity _owner;
private float _range;
public float Range { get => _range; set => _range = Math.Abs(value); }
public WirelessNetworkConnection(int frequency, OnReceiveNetMessage onReceive, bool receiveAll, IEntity owner, float range) : base(NetworkUtils.WIRELESS, frequency, onReceive, receiveAll)
{
_owner = owner;
Range = range;
}
protected override bool CanReceive(int frequency, string sender, IReadOnlyDictionary<string, string> payload, Metadata metadata, bool broadcast)
{
if (_owner.Deleted)
{
Connection.Close();
return false;
}
if (metadata.TryParseMetadata<Vector2>(WIRELESS_POSITION, out var position))
{
var ownPosition = _owner.Transform.WorldPosition;
var distance = (ownPosition - position).Length;
return distance <= Range;
}
//Only receive packages with the same frequency
return frequency == Frequency;
}
protected override Metadata GetMetadata()
{
if (_owner.Deleted)
{
Connection.Close();
return new Metadata();
}
var position = _owner.Transform.WorldPosition;
var metadata = new Metadata
{
{WIRELESS_POSITION, position}
};
return metadata;
}
protected override Dictionary<string, string> ManipulatePayload(Dictionary<string, string> payload)
{
return payload;
}
}
}

View File

@@ -1,223 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Content.Server.DeviceNetwork.Connections;
using Robust.Shared.IoC;
using Robust.Shared.Random;
namespace Content.Server.DeviceNetwork
{
public delegate void OnReceiveNetMessage(int frequency, string sender, IReadOnlyDictionary<string, string> payload, Metadata metadata, bool broadcast);
public class DeviceNetwork : IDeviceNetwork
{
private const int PACKAGES_PER_TICK = 30;
[Dependency] private readonly IRobustRandom _random = default!;
private readonly Dictionary<int, List<NetworkDevice>> _devices = new();
private readonly Queue<NetworkPackage> _packages = new();
/// <inheritdoc/>
public DeviceNetworkConnection Register(int netId, int frequency, OnReceiveNetMessage messageHandler, bool receiveAll = false)
{
var address = GenerateValidAddress(netId, frequency);
var device = new NetworkDevice(frequency, address, messageHandler, receiveAll);
AddDevice(netId, device);
return new DeviceNetworkConnection(this, netId, address, frequency);
}
public DeviceNetworkConnection Register(int netId, OnReceiveNetMessage messageHandler, bool receiveAll = false)
{
return Register(netId, 0, messageHandler, receiveAll);
}
public void Update()
{
var count = Math.Min(PACKAGES_PER_TICK, _packages.Count);
for (var i = 0; i < count; i++)
{
var package = _packages.Dequeue();
if (package.Broadcast)
{
BroadcastPackage(package);
continue;
}
SendPackage(package);
}
}
public bool EnqueuePackage(int netId, int frequency, string address, IReadOnlyDictionary<string, string> data, string sender, Metadata metadata, bool broadcast = false)
{
if (!_devices.ContainsKey(netId))
return false;
var package = new NetworkPackage(netId, frequency, address, broadcast, data, metadata, sender);
_packages.Enqueue(package);
return true;
}
public void RemoveDevice(int netId, int frequency, string address)
{
if (TryDeviceWithAddress(netId, frequency, address, out var device))
{
_devices[netId].Remove(device);
}
}
public void SetDeviceReceiveAll(int netId, int frequency, string address, bool receiveAll)
{
if (TryDeviceWithAddress(netId, frequency, address, out var device))
{
device.ReceiveAll = receiveAll;
}
}
public bool GetDeviceReceiveAll(int netId, int frequency, string address)
{
if (TryDeviceWithAddress(netId, frequency, address, out var device))
{
return device.ReceiveAll;
}
return false;
}
private string GenerateValidAddress(int netId, int frequency)
{
var unique = false;
var devices = DevicesForFrequency(netId, frequency);
var address = "";
while (!unique)
{
address = _random.Next().ToString("x");
unique = !devices.Exists(device => device.Address == address);
}
return address;
}
private void AddDevice(int netId, NetworkDevice networkDevice)
{
if(!_devices.ContainsKey(netId))
_devices[netId] = new List<NetworkDevice>();
_devices[netId].Add(networkDevice);
}
private List<NetworkDevice> DevicesForFrequency(int netId, int frequency)
{
if (!_devices.ContainsKey(netId))
return new List<NetworkDevice>();
var result = _devices[netId].FindAll(device => device.Frequency == frequency);
return result;
}
private NetworkDevice? DeviceWithAddress(int netId, int frequency, string address)
{
var devices = DevicesForFrequency(netId, frequency);
var device = devices.Find(dvc => dvc.Address == address);
return device;
}
private bool TryDeviceWithAddress(int netId, int frequency, string address,
[NotNullWhen(true)] out NetworkDevice? device)
{
return (device = DeviceWithAddress(netId, frequency, address)) != null;
}
private List<NetworkDevice> DevicesWithReceiveAll(int netId, int frequency)
{
if (!_devices.ContainsKey(netId))
return new List<NetworkDevice>();
var result = _devices[netId].FindAll(device => device.Frequency == frequency && device.ReceiveAll);
return result;
}
private void BroadcastPackage(NetworkPackage package)
{
var devices = DevicesForFrequency(package.NetId, package.Frequency);
SendToDevices(devices, package, true);
}
private void SendPackage(NetworkPackage package)
{
var devices = DevicesWithReceiveAll(package.NetId, package.Frequency);
if (TryDeviceWithAddress(package.NetId, package.Frequency, package.Address, out var device))
{
devices.Add(device);
}
SendToDevices(devices, package, false);
}
private void SendToDevices(List<NetworkDevice> devices, NetworkPackage package, bool broadcast)
{
foreach (var device in devices)
{
if (device.Address == package.Sender)
continue;
device.ReceiveNetMessage(package.Frequency, package.Sender, package.Data, package.Metadata, broadcast);
}
}
internal class NetworkDevice
{
internal NetworkDevice(int frequency, string address, OnReceiveNetMessage receiveNetMessage, bool receiveAll)
{
Frequency = frequency;
Address = address;
ReceiveNetMessage = receiveNetMessage;
ReceiveAll = receiveAll;
}
public int Frequency;
public string Address;
public OnReceiveNetMessage ReceiveNetMessage;
public bool ReceiveAll;
}
internal class NetworkPackage
{
internal NetworkPackage(
int netId,
int frequency,
string address,
bool broadcast,
IReadOnlyDictionary<string, string> data,
Metadata metadata,
string sender)
{
NetId = netId;
Frequency = frequency;
Address = address;
Broadcast = broadcast;
Data = data;
Metadata = metadata;
Sender = sender;
}
public int NetId;
public int Frequency;
public string Address;
public bool Broadcast;
public IReadOnlyDictionary<string, string> Data { get; set; }
public Metadata Metadata;
public string Sender;
}
}
}

View File

@@ -0,0 +1,21 @@
namespace Content.Server.DeviceNetwork
{
/// <summary>
/// A collection of utilities to help with using device networks
/// </summary>
public static class DeviceNetworkConstants
{
public enum ConnectionType
{
Private,
Wired,
Wireless
}
/// <summary>
/// The key for command names
/// E.g. [DeviceNetworkConstants.Command] = "ping"
/// </summary>
public const string Command = "command";
}
}

View File

@@ -1,18 +0,0 @@
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
namespace Content.Server.DeviceNetwork
{
[UsedImplicitly]
internal sealed class DeviceNetworkSystem : EntitySystem
{
[Dependency] private readonly IDeviceNetwork _network = default!;
public override void Update(float frameTime)
{
base.Update(frameTime);
_network.Update();
}
}
}

View File

@@ -1,24 +0,0 @@
using Content.Server.DeviceNetwork.Connections;
namespace Content.Server.DeviceNetwork
{
/// <summary>
/// Package based device network allowing devices to communicate with eachother
/// </summary>
public interface IDeviceNetwork
{
/// <summary>
/// Registers a device with the device network
/// </summary>
/// <param name="netId"><see cref="NetworkUtils"/>The id of the network to register with</param>
/// <param name="frequency">The frequency the device receives packages on. Wired networks use frequency 0</param>
/// <param name="messageHandler">The delegate that gets called when the device receives a message</param>
/// <param name="receiveAll">If the device should receive all packages on its frequency or only ones addressed to itself</param>
/// <returns></returns>
public DeviceNetworkConnection Register(int netId, int frequency, OnReceiveNetMessage messageHandler, bool receiveAll = false);
/// <see cref="Register(int, int, OnReceiveNetMessage, bool)"/>
public DeviceNetworkConnection Register(int netId, OnReceiveNetMessage messageHandler, bool receiveAll = false);
public void Update();
}
}

View File

@@ -1,20 +0,0 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
namespace Content.Server.DeviceNetwork
{
public class Metadata : Dictionary<string, object>
{
public bool TryParseMetadata<T>(string key, [NotNullWhen(true)] out T? data)
{
if (TryGetValue(key, out var value) && value is T typedValue)
{
data = typedValue;
return true;
}
data = default;
return false;
}
}
}

View File

@@ -0,0 +1,28 @@
using Robust.Shared.Log;
using System.Collections.Generic;
using Robust.Shared.Utility;
using System.Diagnostics.CodeAnalysis;
namespace Content.Server.DeviceNetwork
{
public class NetworkPayload : Dictionary<string, object>
{
/// <summary>
/// Tries to get a value from the payload and checks if that value is of type T.
/// </summary>
/// <typeparam name="T">The type that should be casted to</typeparam>
/// <returns>Whether the value was present in the payload and of the required type</returns>
public bool TryGetValue<T>(string key, [NotNullWhen(true)] out T? value)
{
if (this.TryCastValue(key, out T? result))
{
value = result;
return true;
}
value = default;
return false;
}
}
}

View File

@@ -1,36 +0,0 @@
using System.Collections.Generic;
using Content.Server.DeviceNetwork.Connections;
namespace Content.Server.DeviceNetwork
{
/// <summary>
/// A collection of utilities to help with using device networks
/// </summary>
public static class NetworkUtils
{
public const int PRIVATE = 0;
public const int WIRED = 1;
public const int WIRELESS = 2;
public const string COMMAND = "command";
public const string MESSAGE = "message";
public const string PING = "ping";
/// <summary>
/// Handles responding to pings.
/// </summary>
public static void PingResponse<T>(T connection, string sender, IReadOnlyDictionary<string, string> payload, string message = "") where T : IDeviceNetworkConnection
{
if (payload.TryGetValue(COMMAND, out var command) && command == PING)
{
var response = new Dictionary<string, string>
{
{COMMAND, "ping_response"},
{MESSAGE, message}
};
connection.Send(connection.Frequency, sender, response);
}
}
}
}

View File

@@ -0,0 +1,286 @@
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<int, 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(int 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(int 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(int 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(int 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.Frequency, packet.NetId);
receivers.Add(connection);
SendToConnections(receivers, packet);
}
private void BroadcastPacket(NetworkPacket packet)
{
var receivers = ConnectionsForFrequency(packet.Frequency, packet.NetId);
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 int 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;
}
}
}

View File

@@ -0,0 +1,39 @@
using Content.Server.DeviceNetwork.Components;
using Content.Server.NodeContainer;
using Content.Server.NodeContainer.NodeGroups;
using Content.Server.Power.Components;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
using System.Diagnostics.CodeAnalysis;
namespace Content.Server.DeviceNetwork.Systems
{
[UsedImplicitly]
public class WiredNetworkSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<WiredNetworkComponent, BeforePacketSentEvent>(OnBeforePacketSent);
}
/// <summary>
/// Checks if both devices are on the same grid
/// </summary>
private void OnBeforePacketSent(EntityUid uid, WiredNetworkComponent component, BeforePacketSentEvent args)
{
IEntity sender = EntityManager.GetEntity(args.Sender);
IEntity receiver = EntityManager.GetEntity(uid);
if (receiver.Transform.GridID != sender.Transform.GridID)
{
args.Cancel();
}
}
//Things to do in a future PR:
//Abstract out the connection between the apcExtensionCable and the apcPowerReceiver
//Traverse the power cables using path traversal
//Cache an optimized representation of the traversed path (Probably just cache Devices)
}
}

View File

@@ -0,0 +1,33 @@
using Content.Server.DeviceNetwork.Components;
using JetBrains.Annotations;
using Robust.Shared.GameObjects;
namespace Content.Server.DeviceNetwork.Systems
{
[UsedImplicitly]
public class WirelessNetworkSystem : EntitySystem
{
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<WirelessNetworkComponent, BeforePacketSentEvent>(OnBeforePacketSent);
}
/// <summary>
/// Gets the position of both the sending and receiving entity and checks if the receiver is in range of the sender.
/// </summary>
private void OnBeforePacketSent(EntityUid uid, WirelessNetworkComponent component, BeforePacketSentEvent args)
{
var sender = EntityManager.GetEntity(args.Sender);
var ownPosition = component.Owner.Transform.WorldPosition;
var position = sender.Transform.WorldPosition;
var distance = (ownPosition - position).Length;
if(sender.TryGetComponent<WirelessNetworkComponent>(out var sendingComponent) && distance > sendingComponent.Range)
{
args.Cancel();
}
}
}
}

View File

@@ -47,7 +47,6 @@ namespace Content.Server.IoC
IoCManager.Register<IConnectionManager, ConnectionManager>(); IoCManager.Register<IConnectionManager, ConnectionManager>();
IoCManager.Register<IObjectivesManager, ObjectivesManager>(); IoCManager.Register<IObjectivesManager, ObjectivesManager>();
IoCManager.Register<IAdminManager, AdminManager>(); IoCManager.Register<IAdminManager, AdminManager>();
IoCManager.Register<IDeviceNetwork, DeviceNetwork.DeviceNetwork>();
IoCManager.Register<EuiManager, EuiManager>(); IoCManager.Register<EuiManager, EuiManager>();
IoCManager.Register<IVoteManager, VoteManager>(); IoCManager.Register<IVoteManager, VoteManager>();
IoCManager.Register<INpcBehaviorManager, NpcBehaviorManager>(); IoCManager.Register<INpcBehaviorManager, NpcBehaviorManager>();