Skip to content

About

An experimental data structure for describing how complex logic executes, packed with advanced features.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

7 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 

Repository files navigation

CommandTree

English · 简体中文

Commands are intent, events are fact.

An experimental C# command tree framework, inspired by the Chain mechanic in OCG card games like Yu-Gi-Oh!. It is a slice extracted from a larger personal framework, kept to its core design points.

Inspiration: the OCG Chain

In Yu-Gi-Oh!, when a card activates an effect, another card can chain onto it, and a third onto that — forming a chain. When it resolves, the chain unwinds in reverse (last-in, first-out): the final link resolves first, back to the first link.

This framework bets on one idea:

Any complex logic can be described as the growth of a command tree and the chaining of its effects. And that process can itself be observed and described.

A "transfer" is simply moving money from A to B, yet it chains a series of effects — charge a fee, send a notification, write an audit log. None of these effects live inside the transfer command; they are chained onto it. On undo, they roll back in reverse — last-in, first-out — exactly mirroring chain resolution.

Yu-Gi-Oh!'s chain is a line; here it grows into a tree — one effect can chain multiple branches, and each branch can chain further.

Commands are not a list, they are a tree

Conventional command patterns are flat queues: "execute one, discard one", with undo maintained as a separate stack. Not here:

Root (Cmd_Void)                      ← an empty root that never undoes
├── Cmd_Transfer                     ← transfer money
│   ├── Cmd_DeductFee                ←     chains: charge a fee
│   ├── Cmd_SendNotification         ←     chains: send a notification
│   └── Cmd_RecordAuditLog           ←     chains: write an audit log
└── Cmd_Transfer                     ← an independent transfer
  • Every command node can have children — composite logic is expressed as tree hierarchy, not "one big command that does everything".
  • Only commands declared IBranchable may hold children; the rest are leaves. Branching is an explicitly granted capability.
  • Undo = rolling back the whole subtree in reverse (last-in, first-out): undoing the transfer first rolls back its chained subcommands, and only then the transfer itself.
  • Serialize = a pre-order walk of the whole tree: the recursive [typeId][payload][childCount] layout makes the "process of logic" itself the save format.

This is what "the process can be observed and described" means — you don't get a black-box result, you get a tree that is readable, replayable, traceable:

  • Readable: DebugAll() prints every link of the tree;
  • Replayable: deserialization rebuilds the tree and replays the logic verbatim;
  • Traceable: undo is not "jump back to a state", but walking every link of the tree in reverse.

Triggers: a command doesn't know who chains onto it

This is the most distinctive part. Cmd_Transfer does one thing — move the money. It has no idea that "charge a fee" exists:

// Transfer command: focused on one thing, oblivious to what chains onto it
public struct Cmd_Transfer : ICommand<Account>, IBranchable
{
    public int Amount;
    public bool CanExecute(Account a) => Amount > 0 && a.Balance >= Amount;
    public void Execute(Account a) => a.Balance -= Amount;   // transfer out
    public void Undo(Account a)    => a.Balance += Amount;   // transfer back
    public string Log() => $"Transfer({Amount})";
}

// Trigger: an independently registered chainer, declaring "I chain onto Cmd_Transfer"
public sealed class Tri_ChargeFee : ICommandTrigger<Account, Cmd_Transfer>
{
    public short Priority => 1;                                // multiple chains are ordered by priority

    public bool Trigger(ICommandMachine<Account> machine, in Cmd_Transfer command)
        => command.Amount > 1000;                              // condition: fee only on large transfers

    public void Insert(ICommandMachine<Account> machine, ICommandNode<Account, Cmd_Transfer> commandNode)
        => machine.Execute(commandNode, new Cmd_DeductFee { Amount = command.Amount / 100 });  // chain it on
}

When an IBranchable command executes, the machine finds the triggers that chain onto it and asks each Trigger in priority order; for those returning true, Insert chains a new command onto the current node. Therefore:

  • Open/closed principle: adding an effect to "transfer" (fee, notification, audit…) never requires touching Cmd_Transfer — just register a new trigger.
  • Composition over hard-coding: a "place order" can chain "deduct stock", "create shipment", "charge payment" — none of them live inside Cmd_PlaceOrder; triggers weave them into the tree dynamically.
  • Undo and save for free: because chained subcommands are part of the tree, they roll back in reverse and persist together with their trunk — no extra work.

Resulting properties:

  • Zero GC: unmanaged structs + in parameters + a type-safe object pool; command execution allocates nothing on the heap.
  • Engine-agnostic: pure BCL, zero dependencies — works in Unity, Godot, and native .NET.
  • Dual mode: by default execute-and-recycle (one-shot intent); with IsSaving enabled, history is kept (undoable, serializable).

Experimental status

This is a slice of a larger personal framework, still in rapid iteration, showing only the core design points — not a stable library. The API evolves fast, may break without notice, and makes no semver promise. Feel free to play with it, take it apart, change it — and open an issue if you find problems.

License

MIT

About

An experimental data structure for describing how complex logic executes, packed with advanced features.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages