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

Restore GCQue #244

Merged
merged 1 commit into from
Aug 21, 2023
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
1 change: 1 addition & 0 deletions Content.Client/Entry/EntryPoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ public override void Init()
_prototypeManager.RegisterIgnore("noiseChannel");
_prototypeManager.RegisterIgnore("spaceBiome");
_prototypeManager.RegisterIgnore("worldgenConfig");
_prototypeManager.RegisterIgnore("gcQueue");
_prototypeManager.RegisterIgnore("gameRule");
_prototypeManager.RegisterIgnore("worldSpell");
_prototypeManager.RegisterIgnore("entitySpell");
Expand Down
2 changes: 1 addition & 1 deletion Content.Server/Atmos/EntitySystems/AirtightSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ private void OnAirtightShutdown(EntityUid uid, AirtightComponent airtight, Compo

private void OnAirtightPositionChanged(EntityUid uid, AirtightComponent airtight, ref AnchorStateChangedEvent args)
{
var xform = args.Transform;
var xform = Transform(uid);

if (!TryComp(xform.GridUid, out MapGridComponent? grid))
return;
Expand Down
21 changes: 21 additions & 0 deletions Content.Server/Worldgen/Components/GC/GCAbleObjectComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using Content.Server.Worldgen.Prototypes;
using Content.Server.Worldgen.Systems.GC;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;

namespace Content.Server.Worldgen.Components.GC;

/// <summary>
/// This is used for whether or not a GCable object is "dirty". Firing GCDirtyEvent on the object is the correct way to
/// set this up.
/// </summary>
[RegisterComponent]
[Access(typeof(GCQueueSystem))]
public sealed class GCAbleObjectComponent : Component
{
/// <summary>
/// Which queue to insert this object into when GCing
/// </summary>
[DataField("queue", required: true, customTypeSerializer: typeof(PrototypeIdSerializer<GCQueuePrototype>))]
public string Queue = default!;
}

41 changes: 41 additions & 0 deletions Content.Server/Worldgen/Prototypes/GCQueuePrototype.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using Robust.Shared.Prototypes;

namespace Content.Server.Worldgen.Prototypes;

/// <summary>
/// This is a prototype for a GC queue.
/// </summary>
[Prototype("gcQueue")]
public sealed class GCQueuePrototype : IPrototype
{
/// <inheritdoc />
[IdDataField]
public string ID { get; } = default!;

/// <summary>
/// How deep the GC queue is at most. If this value is ever exceeded entities get processed automatically regardless of
/// tick-time cap.
/// </summary>
[DataField("depth", required: true)]
public int Depth { get; }

/// <summary>
/// The maximum amount of time that can be spent processing this queue.
/// </summary>
[DataField("maximumTickTime")]
public TimeSpan MaximumTickTime { get; } = TimeSpan.FromMilliseconds(1);

/// <summary>
/// The minimum depth before entities in the queue actually get processed for deletion.
/// </summary>
[DataField("minDepthToProcess", required: true)]
public int MinDepthToProcess { get; }

/// <summary>
/// Whether or not the GC should fire an event on the entity to see if it's eligible to skip the queue.
/// Useful for making it so only objects a player has actually interacted with get put in the collection queue.
/// </summary>
[DataField("trySkipQueue")]
public bool TrySkipQueue { get; }
}

Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Numerics;
using Content.Server.Worldgen.Components;
using Content.Server.Worldgen.Components.Debris;
using Content.Server.Worldgen.Systems.GC;
using Content.Server.Worldgen.Tools;
using JetBrains.Annotations;
using Robust.Server.GameObjects;
Expand All @@ -16,6 +17,7 @@ namespace Content.Server.Worldgen.Systems.Debris;
/// </summary>
public sealed class DebrisFeaturePlacerSystem : BaseWorldSystem
{
[Dependency] private readonly GCQueueSystem _gc = default!;
[Dependency] private readonly NoiseIndexSystem _noiseIndex = default!;
[Dependency] private readonly PoissonDiskSampler _sampler = default!;
[Dependency] private readonly TransformSystem _xformSys = default!;
Expand All @@ -33,10 +35,19 @@ public override void Initialize()
SubscribeLocalEvent<DebrisFeaturePlacerControllerComponent, WorldChunkUnloadedEvent>(OnChunkUnloaded);
SubscribeLocalEvent<OwnedDebrisComponent, ComponentShutdown>(OnDebrisShutdown);
SubscribeLocalEvent<OwnedDebrisComponent, MoveEvent>(OnDebrisMove);
SubscribeLocalEvent<OwnedDebrisComponent, TryCancelGC>(OnTryCancelGC);
SubscribeLocalEvent<SimpleDebrisSelectorComponent, TryGetPlaceableDebrisFeatureEvent>(
OnTryGetPlacableDebrisEvent);
}

/// <summary>
/// Handles GC cancellation in case the chunk is still loaded.
/// </summary>
private void OnTryCancelGC(EntityUid uid, OwnedDebrisComponent component, ref TryCancelGC args)
{
args.Cancelled |= HasComp<LoadedChunkComponent>(component.OwningController);
}

/// <summary>
/// Handles debris moving, and making sure it stays parented to a chunk for loading purposes.
/// </summary>
Expand Down Expand Up @@ -91,6 +102,12 @@ private void OnDebrisShutdown(EntityUid uid, OwnedDebrisComponent component, Com
private void OnChunkUnloaded(EntityUid uid, DebrisFeaturePlacerControllerComponent component,
ref WorldChunkUnloadedEvent args)
{
foreach (var (_, debris) in component.OwnedDebris)
{
if (debris is not null)
_gc.TryGCEntity(debris.Value); // gonb.
}

component.DoSpawns = true;
}

Expand Down
124 changes: 124 additions & 0 deletions Content.Server/Worldgen/Systems/GC/GCQueueSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
using System.Linq;
using Content.Server.Worldgen.Components.GC;
using Content.Server.Worldgen.Prototypes;
using Content.Shared.CCVar;
using JetBrains.Annotations;
using Robust.Shared.Configuration;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Timing;

namespace Content.Server.Worldgen.Systems.GC;

/// <summary>
/// This handles delayed garbage collection of entities, to avoid overloading the tick in particularly expensive cases.
/// </summary>
public sealed class GCQueueSystem : EntitySystem
{
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly IRobustRandom _random = default!;

[ViewVariables] private TimeSpan _maximumProcessTime = TimeSpan.Zero;

[ViewVariables] private readonly Dictionary<string, Queue<EntityUid>> _queues = new();

/// <inheritdoc />
public override void Initialize()
{
_cfg.OnValueChanged(CCVars.GCMaximumTimeMs, s => _maximumProcessTime = TimeSpan.FromMilliseconds(s),
true);
}

/// <inheritdoc />CCVars
public override void Update(float frameTime)
{
var overallWatch = new Stopwatch();
var queueWatch = new Stopwatch();
var queues = _queues.ToList();
_random.Shuffle(queues); // Avert resource starvation by always processing in random order.
overallWatch.Start();
foreach (var (pId, queue) in queues)
{
if (overallWatch.Elapsed > _maximumProcessTime)
return;

var proto = _proto.Index<GCQueuePrototype>(pId);
if (queue.Count < proto.MinDepthToProcess)
continue;

queueWatch.Restart();
while (queueWatch.Elapsed < proto.MaximumTickTime && queue.Count >= proto.MinDepthToProcess &&
overallWatch.Elapsed < _maximumProcessTime)
{
var e = queue.Dequeue();
if (!Deleted(e))
{
var ev = new TryCancelGC();
RaiseLocalEvent(e, ref ev);

if (!ev.Cancelled)
Del(e);
}
}
}
}

/// <summary>
/// Attempts to GC an entity. This functions as QueueDel if it can't.
/// </summary>
/// <param name="e">Entity to GC.</param>
public void TryGCEntity(EntityUid e)
{
if (!TryComp<GCAbleObjectComponent>(e, out var comp))
{
QueueDel(e); // not our problem :)
return;
}

if (!_queues.TryGetValue(comp.Queue, out var queue))
{
queue = new Queue<EntityUid>();
_queues[comp.Queue] = queue;
}

var proto = _proto.Index<GCQueuePrototype>(comp.Queue);
if (queue.Count > proto.Depth)
{
QueueDel(e); // whelp, too full.
return;
}

if (proto.TrySkipQueue)
{
var ev = new TryGCImmediately();
RaiseLocalEvent(e, ref ev);
if (!ev.Cancelled)
{
QueueDel(e);
return;
}
}

queue.Enqueue(e);
}
}

/// <summary>
/// Fired by GCQueueSystem to check if it can simply immediately GC an entity, for example if it was never fully
/// loaded.
/// </summary>
/// <param name="Cancelled">Whether or not the immediate deletion attempt was cancelled.</param>
[ByRefEvent]
[PublicAPI]
public record struct TryGCImmediately(bool Cancelled = false);

/// <summary>
/// Fired by GCQueueSystem to check if the collection of the given entity should be cancelled, for example it's chunk
/// being loaded again.
/// </summary>
/// <param name="Cancelled">Whether or not the deletion attempt was cancelled.</param>
[ByRefEvent]
[PublicAPI]
public record struct TryCancelGC(bool Cancelled = false);

2 changes: 2 additions & 0 deletions Resources/Prototypes/Entities/World/Debris/asteroids.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@
- id: WallRockArtifactFragment
prob: 0.01
orGroup: rock
- type: GCAbleObject
queue: SpaceDebris
- type: IFF
flags: HideLabel
color: "#d67e27"
Expand Down
2 changes: 2 additions & 0 deletions Resources/Prototypes/Entities/World/Debris/wrecks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@
prob: 0.03
- id: SpawnMobPurpleSnake
prob: 0.02
- type: GCAbleObject
queue: SpaceDebris
- type: IFF
flags: HideLabel
color: "#88b0d1"
Expand Down
4 changes: 4 additions & 0 deletions Resources/Prototypes/GC/world.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
- type: gcQueue
id: SpaceDebris
depth: 512 # So there's a decent bit of time before roids unload.
minDepthToProcess: 256
Loading