Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rejig device link sink & source startup & shutdown #29035

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 8 additions & 7 deletions Content.Shared/DeviceLinking/DeviceLinkSinkComponent.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Set;

namespace Content.Shared.DeviceLinking;
Expand All @@ -11,28 +12,28 @@ public sealed partial class DeviceLinkSinkComponent : Component
/// <summary>
/// The ports this sink has
/// </summary>
[DataField("ports", customTypeSerializer: typeof(PrototypeIdHashSetSerializer<SinkPortPrototype>))]
public HashSet<string>? Ports;
[DataField]
public HashSet<ProtoId<SinkPortPrototype>> Ports = new();

/// <summary>
/// Used for removing a sink from all linked sources when it gets removed
/// Used for removing a sink from all linked sources when this component gets removed.
/// This is not serialized to yaml as it can be inferred from source components.
/// </summary>
[DataField("links")]
[ViewVariables]
public HashSet<EntityUid> LinkedSources = new();

/// <summary>
/// Counts the amount of times a sink has been invoked for severing the link if this counter gets to high
/// The counter is counted down by one every tick if it's higher than 0
/// This is for preventing infinite loops
/// </summary>
[DataField("invokeCounter")]
[DataField]
public int InvokeCounter;

/// <summary>
/// How high the invoke counter is allowed to get before the links to the sink are removed and the DeviceLinkOverloadedEvent gets raised
/// If the invoke limit is smaller than 1 the sink can't overload
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField("invokeLimit")]
[DataField]
public int InvokeLimit = 10;
}
8 changes: 4 additions & 4 deletions Content.Shared/DeviceLinking/DeviceLinkSourceComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ public sealed partial class DeviceLinkSourceComponent : Component
/// The ports the device link source sends signals from
/// </summary>
[DataField]
public HashSet<ProtoId<SourcePortPrototype>>? Ports;
public HashSet<ProtoId<SourcePortPrototype>> Ports = new();

/// <summary>
/// A list of sink uids that got linked for each port
/// Dictionary mapping each port to a set of linked sink entities.
/// </summary>
[ViewVariables]
[ViewVariables] // This is not serialized as it can be constructed from LinkedPorts
public Dictionary<ProtoId<SourcePortPrototype>, HashSet<EntityUid>> Outputs = new();

/// <summary>
Expand All @@ -32,7 +32,7 @@ public sealed partial class DeviceLinkSourceComponent : Component
/// The list of source to sink ports for each linked sink entity for easier managing of links
/// </summary>
[DataField]
public Dictionary<EntityUid, HashSet<(ProtoId<SourcePortPrototype> source, ProtoId<SinkPortPrototype> sink)>> LinkedPorts = new();
public Dictionary<EntityUid, HashSet<(ProtoId<SourcePortPrototype> Source, ProtoId<SinkPortPrototype> Sink)>> LinkedPorts = new();

/// <summary>
/// Limits the range devices can be linked across.
Expand Down
143 changes: 48 additions & 95 deletions Content.Shared/DeviceLinking/SharedDeviceLinkSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,162 +20,123 @@ public abstract class SharedDeviceLinkSystem : EntitySystem
/// <inheritdoc/>
public override void Initialize()
{
SubscribeLocalEvent<DeviceLinkSourceComponent, ComponentInit>(OnInit);
SubscribeLocalEvent<DeviceLinkSourceComponent, ComponentStartup>(OnSourceStartup);
SubscribeLocalEvent<DeviceLinkSinkComponent, ComponentStartup>(OnSinkStartup);
SubscribeLocalEvent<DeviceLinkSourceComponent, ComponentRemove>(OnSourceRemoved);
SubscribeLocalEvent<DeviceLinkSinkComponent, ComponentRemove>(OnSinkRemoved);
}

#region Link Validation

private void OnInit(EntityUid uid, DeviceLinkSourceComponent component, ComponentInit args)
{
// Populate the output dictionary.
foreach (var (sinkUid, links) in component.LinkedPorts)
{
foreach (var link in links)
{
component.Outputs.GetOrNew(link.source).Add(sinkUid);
}
}
}

/// <summary>
/// Removes invalid links where the saved sink doesn't exist/have a sink component for example
/// </summary>
private void OnSourceStartup(EntityUid sourceUid, DeviceLinkSourceComponent sourceComponent, ComponentStartup args)
private void OnSourceStartup(Entity<DeviceLinkSourceComponent> source, ref ComponentStartup args)
{
List<EntityUid> invalidSinks = new();
foreach (var sinkUid in sourceComponent.LinkedPorts.Keys)
List<(string, string)> invalidLinks = new();
foreach (var (sink, links) in source.Comp.LinkedPorts)
{
if (!TryComp<DeviceLinkSinkComponent>(sinkUid, out var sinkComponent))
if (!TryComp(sink, out DeviceLinkSinkComponent? sinkComponent))
{
invalidSinks.Add(sinkUid);
foreach (var savedSinks in sourceComponent.Outputs.Values)
{
savedSinks.Remove(sinkUid);
}

invalidSinks.Add(sink);
continue;
}

sinkComponent.LinkedSources.Add(sourceUid);
}

foreach (var invalidSink in invalidSinks)
{
sourceComponent.LinkedPorts.Remove(invalidSink);
}
}

/// <summary>
/// Same with <see cref="OnSourceStartup"/> but also checks that the saved ports are present on the sink
/// </summary>
private void OnSinkStartup(EntityUid sinkUid, DeviceLinkSinkComponent sinkComponent, ComponentStartup args)
{
List<EntityUid> invalidSources = new();
foreach (var sourceUid in sinkComponent.LinkedSources)
{
if (!TryComp<DeviceLinkSourceComponent>(sourceUid, out var sourceComponent))
foreach (var link in links)
{
invalidSources.Add(sourceUid);
continue;
if (sinkComponent.Ports.Contains(link.Sink) && source.Comp.Ports.Contains(link.Source))
source.Comp.Outputs.GetOrNew(link.Source).Add(sink);
else
invalidLinks.Add(link);
}

if (!sourceComponent.LinkedPorts.TryGetValue(sinkUid, out var linkedPorts))
foreach (var link in invalidLinks)
{
foreach (var savedSinks in sourceComponent.Outputs.Values)
{
savedSinks.Remove(sinkUid);
}
continue;
Log.Warning($"Device source {ToPrettyString(source)} contains invalid links to entity {ToPrettyString(sink)}: {link.Item1}->{link.Item2}");
links.Remove(link);
}

if (sinkComponent.Ports == null)
continue;

List<(string, string)> invalidLinks = new();
foreach (var link in linkedPorts)
if (links.Count == 0)
{
if (!sinkComponent.Ports.Contains(link.sink))
invalidLinks.Add(link);
invalidSinks.Add(sink);
continue;
}

foreach (var invalidLink in invalidLinks)
{
linkedPorts.Remove(invalidLink);
sourceComponent.Outputs.GetValueOrDefault(invalidLink.Item1)?.Remove(sinkUid);
}
invalidLinks.Clear();
sinkComponent.LinkedSources.Add(source.Owner);
}

foreach (var invalidSource in invalidSources)
foreach (var sink in invalidSinks)
{
sinkComponent.LinkedSources.Remove(invalidSource);
source.Comp.LinkedPorts.Remove(sink);
Log.Warning($"Device source {ToPrettyString(source)} contains invalid sink: {ToPrettyString(sink)}");
}
}
#endregion

/// <summary>
/// Ensures that its links get deleted when a source gets removed
/// </summary>
private void OnSourceRemoved(EntityUid uid, DeviceLinkSourceComponent component, ComponentRemove args)
private void OnSourceRemoved(Entity<DeviceLinkSourceComponent> source, ref ComponentRemove args)
{
var query = GetEntityQuery<DeviceLinkSinkComponent>();
foreach (var sinkUid in component.LinkedPorts.Keys)
foreach (var sinkUid in source.Comp.LinkedPorts.Keys)
{
if (query.TryGetComponent(sinkUid, out var sink))
RemoveSinkFromSourceInternal(uid, sinkUid, component, sink);
RemoveSinkFromSourceInternal(source, sinkUid, source, sink);
else
Log.Error($"Device source {ToPrettyString(source)} links to invalid entity: {ToPrettyString(sinkUid)}");
}
}

/// <summary>
/// Ensures that its links get deleted when a sink gets removed
/// </summary>
private void OnSinkRemoved(EntityUid sinkUid, DeviceLinkSinkComponent sinkComponent, ComponentRemove args)
private void OnSinkRemoved(Entity<DeviceLinkSinkComponent> sink, ref ComponentRemove args)
{
var query = GetEntityQuery<DeviceLinkSourceComponent>();
foreach (var linkedSource in sinkComponent.LinkedSources)
foreach (var sourceUid in sink.Comp.LinkedSources)
{
if (query.TryGetComponent(sinkUid, out var source))
RemoveSinkFromSourceInternal(linkedSource, sinkUid, source, sinkComponent);
if (TryComp(sourceUid, out DeviceLinkSourceComponent? source))
RemoveSinkFromSourceInternal(sourceUid, sink, source, sink);
else
Log.Error($"Device sink {ToPrettyString(sink)} source list contains invalid entity: {ToPrettyString(sourceUid)}");
}
}

#region Ports
/// <summary>
/// Convenience function to add several ports to an entity
/// </summary>
public void EnsureSourcePorts(EntityUid uid, params string[] ports)
public void EnsureSourcePorts(EntityUid uid, params ProtoId<SourcePortPrototype>[] ports)
{
if (ports.Length == 0)
return;

var comp = EnsureComp<DeviceLinkSourceComponent>(uid);
comp.Ports ??= new HashSet<ProtoId<SourcePortPrototype>>();

foreach (var port in ports)
{
DebugTools.Assert(_prototypeManager.HasIndex<SourcePortPrototype>(port));
comp.Ports?.Add(port);
if (!_prototypeManager.HasIndex(port))
Log.Error($"Attempted to add invalid port {port} to {ToPrettyString(uid)}");
else
comp.Ports.Add(port);
}
}

/// <summary>
/// Convenience function to add several ports to an entity.
/// </summary>
public void EnsureSinkPorts(EntityUid uid, params string[] ports)
public void EnsureSinkPorts(EntityUid uid, params ProtoId<SinkPortPrototype>[] ports)
{
if (ports.Length == 0)
return;

var comp = EnsureComp<DeviceLinkSinkComponent>(uid);
comp.Ports ??= new HashSet<string>();

foreach (var port in ports)
{
DebugTools.Assert(_prototypeManager.HasIndex<SinkPortPrototype>(port));
comp.Ports?.Add(port);
if (!_prototypeManager.HasIndex(port))
Log.Error($"Attempted to add invalid port {port} to {ToPrettyString(uid)}");
else
comp.Ports.Add(port);
}
}

Expand All @@ -185,13 +146,13 @@ public void EnsureSinkPorts(EntityUid uid, params string[] ports)
/// <returns>A list of source port prototypes</returns>
public List<SourcePortPrototype> GetSourcePorts(EntityUid sourceUid, DeviceLinkSourceComponent? sourceComponent = null)
{
if (!Resolve(sourceUid, ref sourceComponent) || sourceComponent.Ports == null)
if (!Resolve(sourceUid, ref sourceComponent))
return new List<SourcePortPrototype>();

var sourcePorts = new List<SourcePortPrototype>();
foreach (var port in sourceComponent.Ports)
{
sourcePorts.Add(_prototypeManager.Index<SourcePortPrototype>(port));
sourcePorts.Add(_prototypeManager.Index(port));
}

return sourcePorts;
Expand All @@ -203,13 +164,13 @@ public List<SourcePortPrototype> GetSourcePorts(EntityUid sourceUid, DeviceLinkS
/// <returns>A list of sink port prototypes</returns>
public List<SinkPortPrototype> GetSinkPorts(EntityUid sinkUid, DeviceLinkSinkComponent? sinkComponent = null)
{
if (!Resolve(sinkUid, ref sinkComponent) || sinkComponent.Ports == null)
if (!Resolve(sinkUid, ref sinkComponent))
return new List<SinkPortPrototype>();

var sinkPorts = new List<SinkPortPrototype>();
foreach (var port in sinkComponent.Ports)
{
sinkPorts.Add(_prototypeManager.Index<SinkPortPrototype>(port));
sinkPorts.Add(_prototypeManager.Index(port));
}

return sinkPorts;
Expand Down Expand Up @@ -315,9 +276,6 @@ public void SaveLinks(
if (!Resolve(sourceUid, ref sourceComponent) || !Resolve(sinkUid, ref sinkComponent))
return;

if (sourceComponent.Ports == null || sinkComponent.Ports == null)
return;

if (!InRange(sourceUid, sinkUid, sourceComponent.Range))
{
if (userId != null)
Expand Down Expand Up @@ -391,7 +349,7 @@ public void RemoveSinkFromSource(
else
{
Log.Error($"Attempted to remove link between {ToPrettyString(sourceUid)} and {ToPrettyString(sinkUid)}, but the sink component was missing.");
sourceComponent.LinkedPorts.Remove(sourceUid);
sourceComponent.LinkedPorts.Remove(sinkUid);
}
}

Expand All @@ -414,12 +372,10 @@ private void RemoveSinkFromSourceInternal(

sinkComponent.LinkedSources.Remove(sourceUid);
sourceComponent.LinkedPorts.Remove(sinkUid);
var outputLists = sourceComponent.Outputs.Values;
foreach (var outputList in outputLists)
foreach (var outputList in sourceComponent.Outputs.Values)
{
outputList.Remove(sinkUid);
}

}

/// <summary>
Expand All @@ -438,9 +394,6 @@ public bool ToggleLink(
if (!Resolve(sourceUid, ref sourceComponent) || !Resolve(sinkUid, ref sinkComponent))
return false;

if (sourceComponent.Ports == null || sinkComponent.Ports == null)
return false;

var outputs = sourceComponent.Outputs.GetOrNew(source);
var linkedPorts = sourceComponent.LinkedPorts.GetOrNew(sinkUid);

Expand Down
3 changes: 0 additions & 3 deletions Resources/Maps/core.yml
Original file line number Diff line number Diff line change
Expand Up @@ -115412,9 +115412,6 @@ entities:
rot: 3.141592653589793 rad
pos: 50.5,-2.5
parent: 2
- type: DeviceLinkSource
linkedPorts:
6516: []
- proto: SignalButtonDirectional
entities:
- uid: 2100
Expand Down
Loading