Command¶
Intent: Turn actions into first-class objects so they can be queued, replayed, and undone.
Acron Mapping¶
Pattern role |
Acron equivalent |
|---|---|
Command object |
Python callable or class with |
Command queue |
Python |
Invoker |
|
Receiver |
|
How to Implement¶
Define a command class (or use a lambda/partial) with
execute()and optionallyundo().Create a
dequeas the command queue on the controller or input manager.On input events (
self.accept('w', ...)) push command objects onto the queue.Add a task with
taskMgr.add(self._process_commands, 'commandQueue')that callscmd.execute()each frame and pops the queue.For undo: maintain a separate
undo_stackdeque; afterexecute(), push the command there. Callundo_stack.pop().undo()on undo input.
Code Sketch (Python)¶
from collections import deque
from direct.showbase.DirectObject import DirectObject
class MoveCommand:
def __init__(self, node, delta):
self.node = node
self.delta = delta
self.prev_pos = None
def execute(self):
self.prev_pos = self.node.getPos()
self.node.setPos(self.node.getPos() + self.delta)
def undo(self):
self.node.setPos(self.prev_pos)
class InputHandler(DirectObject):
def __init__(self, player):
self.player = player
self.queue = deque()
self.undo_stack = deque(maxlen=32)
self.accept('w', self.queue.append,
[MoveCommand(player, (0, 1, 0))])
self.accept('u', self._undo)
taskMgr.add(self._process, 'commandQueue')
def _process(self, task):
while self.queue:
cmd = self.queue.popleft()
cmd.execute()
self.undo_stack.append(cmd)
return task.cont
def _undo(self):
if self.undo_stack:
self.undo_stack.pop().undo()
Pitfalls¶
Event leak: always call
self.ignoreAll()in cleanup; forgottenaccept()calls keep the handler alive and queue filling after the object is “destroyed”.Frame ordering: commands queued mid-frame execute next frame — if immediate effect is needed, call
execute()directly and skip the queue.Undo with NodePath transforms:
getPos()returns a copy, so storing it before the move is safe. Watch out for parent-relative vs world-space positions.