-
Notifications
You must be signed in to change notification settings - Fork 523
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'new-frontiers-14:master' into Lockers-Update-2
- Loading branch information
Showing
11 changed files
with
233 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
21 changes: 21 additions & 0 deletions
21
Content.Server/Worldgen/Components/GC/GCAbleObjectComponent.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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!; | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; } | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |