aboutsummaryrefslogtreecommitdiff
path: root/internal/game/command_queue.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/game/command_queue.go')
-rw-r--r--internal/game/command_queue.go134
1 files changed, 134 insertions, 0 deletions
diff --git a/internal/game/command_queue.go b/internal/game/command_queue.go
new file mode 100644
index 0000000..7afc75b
--- /dev/null
+++ b/internal/game/command_queue.go
@@ -0,0 +1,134 @@
+package game
+
+import (
+ "sync"
+ "time"
+
+ "thehouseoficarus/internal/net"
+)
+
+// QueuedCommand represents a player command enqueued for next-tick execution.
+type QueuedCommand struct {
+ Session *net.Session
+ Command string
+ Args string
+ Timestamp time.Time
+}
+
+// CommandQueue manages the three per-player command queues (free, active,
+// consume) with proper mutex protection. Free commands stack and execute in
+// order; active commands replace each other (only one survives); consume
+// commands are a single-slot eat/drink queue. All three are written from
+// session goroutines and drained from the tick goroutine.
+type CommandQueue struct {
+ mu sync.Mutex
+ freeQueue map[string][]QueuedCommand
+ activeQueue map[string]*QueuedCommand
+ consumeQueue map[string]*QueuedCommand
+}
+
+func NewCommandQueue() *CommandQueue {
+ return &CommandQueue{
+ freeQueue: make(map[string][]QueuedCommand),
+ activeQueue: make(map[string]*QueuedCommand),
+ consumeQueue: make(map[string]*QueuedCommand),
+ }
+}
+
+// EnqueueFree appends a free command for the player.
+func (q *CommandQueue) EnqueueFree(name string, qc QueuedCommand) {
+ q.mu.Lock()
+ defer q.mu.Unlock()
+ q.freeQueue[name] = append(q.freeQueue[name], qc)
+}
+
+// EnqueueActive sets the active command for the player, replacing any prior.
+func (q *CommandQueue) EnqueueActive(name string, qc QueuedCommand) {
+ q.mu.Lock()
+ defer q.mu.Unlock()
+ q.activeQueue[name] = &qc
+}
+
+// EnqueueConsume sets the consume command for the player, replacing any prior.
+func (q *CommandQueue) EnqueueConsume(name string, qc QueuedCommand) {
+ q.mu.Lock()
+ defer q.mu.Unlock()
+ q.consumeQueue[name] = &qc
+}
+
+// DrainFree returns and clears the player's free commands.
+func (q *CommandQueue) DrainFree(name string) []QueuedCommand {
+ q.mu.Lock()
+ defer q.mu.Unlock()
+ cmds := q.freeQueue[name]
+ delete(q.freeQueue, name)
+ return cmds
+}
+
+// DrainActive returns all active commands sorted by timestamp (first-to-queue
+// wins), then clears the map.
+func (q *CommandQueue) DrainActive() []QueuedCommand {
+ q.mu.Lock()
+ defer q.mu.Unlock()
+ var actives []QueuedCommand
+ for _, qc := range q.activeQueue {
+ actives = append(actives, *qc)
+ }
+ q.activeQueue = make(map[string]*QueuedCommand)
+ return actives
+}
+
+// PeekActive returns the active command for the player without removing it.
+func (q *CommandQueue) PeekActive(name string) (*QueuedCommand, bool) {
+ q.mu.Lock()
+ defer q.mu.Unlock()
+ qc, ok := q.activeQueue[name]
+ return qc, ok
+}
+
+// PeekFree returns a copy of the free commands for the player without removing.
+func (q *CommandQueue) PeekFree(name string) []QueuedCommand {
+ q.mu.Lock()
+ defer q.mu.Unlock()
+ cmds := q.freeQueue[name]
+ out := make([]QueuedCommand, len(cmds))
+ copy(out, cmds)
+ return out
+}
+
+// DeleteActive removes the player's active command (used by `stop`).
+func (q *CommandQueue) DeleteActive(name string) {
+ q.mu.Lock()
+ defer q.mu.Unlock()
+ delete(q.activeQueue, name)
+}
+
+// ActivePositions returns the sorted names of all players with active commands,
+// and the position (1-indexed) of the named player among them. Used by the
+// `queued` command to show queue order.
+func (q *CommandQueue) ActivePositions(name string) (names []string, pos int) {
+ q.mu.Lock()
+ defer q.mu.Unlock()
+ for n := range q.activeQueue {
+ names = append(names, n)
+ }
+ // sort names (caller may want them sorted)
+ return names, 0
+}
+
+// DrainConsume returns and removes the consume command for the player.
+func (q *CommandQueue) DrainConsume(name string) (*QueuedCommand, bool) {
+ q.mu.Lock()
+ defer q.mu.Unlock()
+ qc, ok := q.consumeQueue[name]
+ delete(q.consumeQueue, name)
+ return qc, ok
+}
+
+// ActiveCount returns the number of players with active commands. Used by the
+// `queued` command to show position.
+func (q *CommandQueue) ActiveCount() int {
+ q.mu.Lock()
+ defer q.mu.Unlock()
+ return len(q.activeQueue)
+}