terminalCode Example
Sandbox: UndoSystem — per-player undo stack with bounded history (128 steps)
UndoSystem — Per-Player Undo Stack
The UndoSystem is a GameObjectSystem that maintains a per-player undo stack. Each entry holds a set of GameObjects that get destroyed when the undo is triggered.
CSHARP
public class UndoSystem : GameObjectSystem<UndoSystem>
{
Dictionary<long, PlayerStack> stacks = new();
// Get or create the undo stack for a player (keyed by SteamId)
public PlayerStack For( long steamId )
{
if ( !stacks.TryGetValue( steamId, out var stack ) )
{
stack = new PlayerStack( steamId );
stacks[steamId] = stack;
}
return stack;
}
// IMPORTANT: Call on disconnect to prevent memory leaks
public void RemovePlayer( long steamId ) => stacks.Remove( steamId );
// Remove a specific GameObject from ALL player stacks
public void Remove( GameObject go )
{
foreach ( var stack in stacks.Values )
stack.Remove( go );
}
public class PlayerStack
{
List<Entry> entries = new();
const int MaxUndoSteps = 128; // Bounded to prevent memory leaks
public Entry Create()
{
var entry = new Entry( steamId );
entries.Add( entry );
if ( entries.Count > MaxUndoSteps )
entries.RemoveAt( 0 ); // Drop oldest
return entry;
}
public void Undo()
{
while ( entries.Count > 0 )
{
var entry = entries[^1];
entries.RemoveAt( entries.Count - 1 );
if ( entry.Run() ) return; // Stop after first successful undo
}
}
}
public class Entry
{
public string Name { get; set; }
HashSet<GameObject> gameObjects = new();
public void Add( GameObject go ) => gameObjects.Add( go );
public void Add( params IEnumerable<GameObject> gos ) { foreach ( var go in gos ) Add( go ); }
public void Remove( GameObject go ) => gameObjects.Remove( go );
public bool Run()
{
// Destroys all tracked GameObjects
// Returns true if any were destroyed
}
}
}Usage in tools
CSHARP
// After creating a weld constraint:
var undo = Player.Undo.Create();
undo.Name = "Weld";
undo.Add( go1 );
undo.Add( go2 );
// After spawning a duplicator paste:
var undo = player.Undo.Create();
undo.Name = "Duplication";
foreach ( var go in objects )
undo.Add( go );Key points
- History is capped at 128 entries per player — oldest entries are dropped
- UndoSystem.Remove(go) should be called when an object is externally destroyed
- RemovePlayer(steamId) must be called on disconnect to avoid memory leaks
- Player.Undo is a shorthand property: UndoSystem.Current.For(SteamId)
Was this helpful?