package westworldwithwoman // StateMachine is the Go version of the C++ class template: // // template // class StateMachine { ... }; // // Same trick as State[T]: [T any] is a blank slot filled in with // whichever entity type owns this machine, e.g. StateMachine[Miner]. // Instead of an agent managing its own currentState field and // ChangeState method by hand (like Miner did before), the agent now // just owns one of these and hands it all the work. type StateMachine[T any] struct { // owner is the agent this state machine belongs to and acts on. // Go version of "entity_type* m_pOwner". owner *T // currentState is whatever mode the agent is in right now. currentState State[T] // previousState is a record of the last state the agent was in, // so RevertToPreviousState can jump back to it. previousState State[T] // globalState's Execute runs on every single Update, no matter // what currentState is - for behavior that should always be // checked (the book will show a real use for this later). globalState State[T] } // NewStateMachine is the Go stand-in for the C++ constructor // "StateMachine(entity_type* owner)". Go doesn't run constructor // initializer lists automatically, so this function does that job // by hand: remember the owner, leave every state slot empty. An // empty interface value in Go is nil - that's the direct equivalent // of the book's NULL. func NewStateMachine[T any](owner *T) *StateMachine[T] { return &StateMachine[T]{owner: owner} } // SetCurrentState, SetGlobalState and SetPreviousState are for // initializing the FSM directly - for example, giving an agent its // very first state without running it through the Enter/Exit dance // that ChangeState does. func (sm *StateMachine[T]) SetCurrentState(s State[T]) { sm.currentState = s } func (sm *StateMachine[T]) SetGlobalState(s State[T]) { sm.globalState = s } func (sm *StateMachine[T]) SetPreviousState(s State[T]) { sm.previousState = s } // Update runs every tick. The C++ version marked this method const // (a promise that it won't change the StateMachine's own fields) - // Go has no equivalent keyword, and doesn't need one; nothing here // requires special marking. If a global state is set, its Execute // always runs; then, separately, whatever the current state is also // runs. Both can fire in the same tick - that's the point of having // two slots instead of one. func (sm *StateMachine[T]) Update() { // if a global state exists, call its execute method if sm.globalState != nil { sm.globalState.Execute(sm.owner) } // same for the current state if sm.currentState != nil { sm.currentState.Execute(sm.owner) } } // ChangeState switches the agent into a new state properly: it // remembers where it came from, tells the old state it's being // left, swaps over, then tells the new state it's being entered. func (sm *StateMachine[T]) ChangeState(newState State[T]) { // The C++ code used assert() to make sure the new state isn't a // null pointer. Go's equivalent is panic() - nil is Go's version // of a null pointer/null value. if newState == nil { panic("StateMachine.ChangeState: trying to change to a nil state") } // keep a record of the previous state sm.previousState = sm.currentState // call the exit method of the existing state sm.currentState.Exit(sm.owner) // change state to the new state sm.currentState = newState // call the entry method of the new state sm.currentState.Enter(sm.owner) } // RevertToPreviousState switches back to whatever state the agent // was in right before its current one. func (sm *StateMachine[T]) RevertToPreviousState() { sm.ChangeState(sm.previousState) } // CurrentState, GlobalState and PreviousState are plain read-only // accessors for the three state slots. func (sm *StateMachine[T]) CurrentState() State[T] { return sm.currentState } func (sm *StateMachine[T]) GlobalState() State[T] { return sm.globalState } func (sm *StateMachine[T]) PreviousState() State[T] { return sm.previousState } // IsInState reports whether the agent's current state is the same // state as the one passed in. The book describes this as comparing // state *types* (it takes "const State&", not a // pointer, and presumably checks typeid under the hood) - but every // concrete state in this codebase is a singleton (there's only ever // one *doHousework, one *visitBathroom, and so on), so comparing the // two interface values directly for equality gets the same answer: // they're equal exactly when they're both that one shared instance // of the same state type. func (sm *StateMachine[T]) IsInState(st State[T]) bool { return sm.currentState == st }