terminalCode Example

Sandbox: UndoSystem — per-player undo stack with bounded history (128 steps)

calendar_today May 4, 2026 schedule ~1 min read person patrickjr verified 50

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

Was this helpful?