feat: westworldwithmessaging. add entitymanager, telegram, messagehandlers, delayedmessages, expand baseentity
This commit is contained in:
1 parent
0e7482e04c
commit
129dd5d8b8
18 files changed
+505
-19
No files matched your search
@@ -0,0 +1,40 @@
|
||||
package westworldwithmessaging
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// clockSingleton is our own stand-in for whatever timer/clock utility
|
||||
// class the book uses elsewhere (it only shows up here as
|
||||
// "Clock->GetCurrentTime()" - the book hasn't shown us that class's
|
||||
// actual source, likely a separate timer file). All DispatchMessage
|
||||
// and DispatchDelayedMessages actually need is a steadily increasing
|
||||
// number of seconds, so this just measures time elapsed since the
|
||||
// clock was first used.
|
||||
type clockSingleton struct {
|
||||
start time.Time
|
||||
}
|
||||
|
||||
var (
|
||||
clockInstance *clockSingleton
|
||||
clockOnce sync.Once
|
||||
)
|
||||
|
||||
func newClock() *clockSingleton {
|
||||
clockOnce.Do(func() {
|
||||
clockInstance = &clockSingleton{start: time.Now()}
|
||||
})
|
||||
return clockInstance
|
||||
}
|
||||
|
||||
// Clock is a shorthand for the clock singleton, same "#define"-style
|
||||
// convenience pattern as EntityMgr and Dispatch.
|
||||
var Clock = newClock()
|
||||
|
||||
// GetCurrentTime returns the number of seconds elapsed since the
|
||||
// clock started, matching the book's
|
||||
// "double CurrentTime = Clock->GetCurrentTime();".
|
||||
func (c *clockSingleton) GetCurrentTime() float64 {
|
||||
return time.Since(c.start).Seconds()
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package westworldwithwoman
|
||||
package westworldwithmessaging
|
||||
|
||||
// Elsa is the miner's wife, introduced in this section of the book.
|
||||
// She's built the exact same way Miner is: embed *BaseGameEntity for
|
||||
@@ -28,6 +28,10 @@ func NewElsa(id int) *Elsa {
|
||||
e.stateMachine.SetCurrentState(DoHousework())
|
||||
e.stateMachine.SetGlobalState(WifesGlobalState())
|
||||
|
||||
// Register with the entity manager automatically, same reasoning
|
||||
// as NewMiner - one less manual step for every caller to remember.
|
||||
EntityMgr.RegisterEntity(e)
|
||||
|
||||
return e
|
||||
}
|
||||
|
||||
@@ -47,3 +51,9 @@ func (e *Elsa) Update() {
|
||||
func (e *Elsa) ChangeState(newState State[Elsa]) {
|
||||
e.stateMachine.ChangeState(newState)
|
||||
}
|
||||
|
||||
// HandleMessage forwards to the state machine, same as
|
||||
// Miner.HandleMessage.
|
||||
func (e *Elsa) HandleMessage(msg Telegram) bool {
|
||||
return e.stateMachine.HandleMessage(msg)
|
||||
}
|
||||
@@ -1,15 +1,21 @@
|
||||
package westworldwithwoman
|
||||
package westworldwithmessaging
|
||||
|
||||
// Entity is what every game object in the world must be able to do.
|
||||
// The C++ code used a pure virtual function (Update()=0) to force
|
||||
// every subclass to write its own Update. Go has no subclasses, so
|
||||
// instead we say: "anything with an Update() and an ID() method
|
||||
// counts as an Entity." A type only satisfies this interface once
|
||||
// it has its own Update method - just like the C++ version couldn't
|
||||
// be used until Update was implemented.
|
||||
// The C++ code used pure virtual functions (Update()=0, and now
|
||||
// HandleMessage()=0) to force every subclass to write its own
|
||||
// versions. Go has no subclasses, so instead we say: "anything with
|
||||
// Update(), ID() and HandleMessage() methods counts as an Entity." A
|
||||
// type only satisfies this interface once it has all three - just
|
||||
// like the C++ version couldn't be used until both were implemented.
|
||||
type Entity interface {
|
||||
Update()
|
||||
ID() int
|
||||
|
||||
// HandleMessage lets an entity respond to a Telegram sent to it
|
||||
// by the message dispatcher. It returns true if the message was
|
||||
// actually handled, false otherwise - callers use that to decide
|
||||
// whether to try routing the message somewhere else instead.
|
||||
HandleMessage(msg Telegram) bool
|
||||
}
|
||||
|
||||
// nextValidID plays the role of the C++ "static int m_iNextValidID".
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
package westworldwithmessaging
|
||||
|
||||
import "sync"
|
||||
|
||||
// entityManager is the Go version of the singleton class
|
||||
// EntityManager. Same singleton pattern used throughout this
|
||||
// package: an unexported type, one shared instance, reached only
|
||||
// through the EntityManager() function below.
|
||||
//
|
||||
// The C++ version stores "BaseGameEntity*" pointers in its map. In
|
||||
// C++, a BaseGameEntity* pointing at an actual Miner still calls
|
||||
// Miner's real Update() (that's what "virtual" functions do - even
|
||||
// through a base-class pointer, the real subclass's version runs).
|
||||
// Go doesn't have virtual functions or base-class pointers, but our
|
||||
// Entity interface (from entity.go: Update() and ID()) gives us the
|
||||
// exact same behavior - a variable of type Entity holding a *Miner
|
||||
// still calls *Miner's own Update(). So Entity is what we store here
|
||||
// instead of *BaseGameEntity, to get the same real-subclass-runs
|
||||
// behavior C++ gets from virtual dispatch.
|
||||
type entityManager struct {
|
||||
// entityMap cross-references entities by their ID number, same
|
||||
// as the book's std::map<int, BaseGameEntity*> m_EntityMap. (The
|
||||
// book's own comment above that field actually describes a
|
||||
// std::vector instead - a leftover mismatch in the book's text
|
||||
// itself. We're matching the real field, the map.)
|
||||
entityMap map[int]Entity
|
||||
}
|
||||
|
||||
var (
|
||||
entityManagerInstance *entityManager
|
||||
entityManagerOnce sync.Once
|
||||
)
|
||||
|
||||
// EntityManager is the Go stand-in for "EntityManager::Instance()".
|
||||
func EntityManager() *entityManager {
|
||||
entityManagerOnce.Do(func() {
|
||||
entityManagerInstance = &entityManager{
|
||||
entityMap: make(map[int]Entity),
|
||||
}
|
||||
})
|
||||
return entityManagerInstance
|
||||
}
|
||||
|
||||
// EntityMgr is a shorthand for EntityManager(), matching the book's
|
||||
// "#define EntityMgr EntityManager::Instance()" macro. Go has no
|
||||
// preprocessor and no macros, so instead this is just a plain
|
||||
// package-level variable, set up once when the package is loaded -
|
||||
// EntityMgr.RegisterEntity(...) reads the same as the book's
|
||||
// EntityMgr->RegisterEntity(...), just with a dot instead of an
|
||||
// arrow (Go doesn't distinguish "." for values from "->" for
|
||||
// pointers the way C++ does - it's always ".").
|
||||
var EntityMgr = EntityManager()
|
||||
|
||||
// RegisterEntity stores an entity so it can be looked up later by
|
||||
// its ID.
|
||||
func (m *entityManager) RegisterEntity(newEntity Entity) {
|
||||
m.entityMap[newEntity.ID()] = newEntity
|
||||
}
|
||||
|
||||
// GetEntityFromID returns the entity with the given ID, or nil if
|
||||
// none is registered under that ID (same as the C++ version
|
||||
// implicitly returning nullptr for a lookup miss).
|
||||
func (m *entityManager) GetEntityFromID(id int) Entity {
|
||||
return m.entityMap[id]
|
||||
}
|
||||
|
||||
// RemoveEntity removes an entity from the registry.
|
||||
func (m *entityManager) RemoveEntity(entity Entity) {
|
||||
delete(m.entityMap, entity.ID())
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
package westworldwithmessaging
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// messageDispatcher is the Go version of the singleton class
|
||||
// MessageDispatcher. Same singleton pattern as everything else in
|
||||
// this package: unexported type, one shared instance, reached only
|
||||
// through the MessageDispatcher() function below.
|
||||
type messageDispatcher struct {
|
||||
// priorityQueue holds telegrams that haven't gone out yet because
|
||||
// they were sent with a delay, mirroring the book's
|
||||
// "std::set<Telegram> PriorityQ". A std::set keeps its contents
|
||||
// sorted (here, by DispatchTime) and automatically drops
|
||||
// duplicates. Go has no built-in sorted-set container, so we
|
||||
// reproduce both properties by hand: insertIntoPriorityQueue
|
||||
// (below) always keeps this slice sorted by DispatchTime, and
|
||||
// skips inserting anything that looks like a near-duplicate of
|
||||
// something already queued (see telegramsMatch in telegram.go).
|
||||
// index 0 is always the next telegram due to go out.
|
||||
priorityQueue []Telegram
|
||||
}
|
||||
|
||||
var (
|
||||
messageDispatcherInstance *messageDispatcher
|
||||
messageDispatcherOnce sync.Once
|
||||
)
|
||||
|
||||
// MessageDispatcher is the Go stand-in for
|
||||
// "MessageDispatcher::Instance()".
|
||||
func MessageDispatcher() *messageDispatcher {
|
||||
messageDispatcherOnce.Do(func() {
|
||||
messageDispatcherInstance = &messageDispatcher{}
|
||||
})
|
||||
return messageDispatcherInstance
|
||||
}
|
||||
|
||||
// Dispatch is a shorthand for MessageDispatcher(), matching the
|
||||
// book's "#define Dispatch MessageDispatcher::Instance()" macro -
|
||||
// same trick as EntityMgr earlier: Go has no preprocessor, so this
|
||||
// is a plain package-level variable instead, set up once when the
|
||||
// package loads. Dispatch.DispatchMessage(...) reads the same as the
|
||||
// book's Dispatch->DispatchMessage(...).
|
||||
var Dispatch = MessageDispatcher()
|
||||
|
||||
// discharge is the Go version of the private C++ method Discharge.
|
||||
// It's what actually hands a telegram to its receiving entity - both
|
||||
// DispatchMessage (for immediate messages) and
|
||||
// DispatchDelayedMessages (once a delayed one comes due) call this
|
||||
// to do the real delivery. The book never pasted this method's body
|
||||
// directly, but we now have everything it needs: Entity.HandleMessage
|
||||
// (added this round), which is exactly "the message handling method
|
||||
// of the receiving entity" the book described back when DispatchMessage
|
||||
// was introduced.
|
||||
//
|
||||
// One thing we added that the book's description didn't mention: a
|
||||
// nil check. GetEntityFromID returns nil for an unregistered ID, and
|
||||
// calling a method on a nil Entity would panic - the book's C++
|
||||
// equivalent (calling through a null BaseGameEntity*) would crash
|
||||
// the same way, so this guard is a real improvement, not just a
|
||||
// style choice.
|
||||
func (d *messageDispatcher) discharge(receiver Entity, msg Telegram) {
|
||||
if receiver == nil {
|
||||
return
|
||||
}
|
||||
receiver.HandleMessage(msg)
|
||||
}
|
||||
|
||||
// insertIntoPriorityQueue is our Go stand-in for
|
||||
// "PriorityQ.insert(telegram)". A C++ std::set does two things on
|
||||
// insert: it keeps everything sorted, and it silently refuses to add
|
||||
// something that's already there (per whatever == means for the
|
||||
// contained type). We reproduce both by hand here: skip the insert
|
||||
// entirely if an equivalent telegram (telegramsMatch, see
|
||||
// telegram.go) is already queued, otherwise insert it at the correct
|
||||
// sorted position by DispatchTime.
|
||||
func (d *messageDispatcher) insertIntoPriorityQueue(t Telegram) {
|
||||
for _, existing := range d.priorityQueue {
|
||||
if telegramsMatch(existing, t) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
i := sort.Search(len(d.priorityQueue), func(i int) bool {
|
||||
return d.priorityQueue[i].DispatchTime >= t.DispatchTime
|
||||
})
|
||||
d.priorityQueue = append(d.priorityQueue, Telegram{})
|
||||
copy(d.priorityQueue[i+1:], d.priorityQueue[i:])
|
||||
d.priorityQueue[i] = t
|
||||
}
|
||||
|
||||
// DispatchMessage sends a message to another entity, either right
|
||||
// now (delay <= 0) or at some point in the future (delay > 0, added
|
||||
// to the current time to get the telegram's DispatchTime).
|
||||
func (d *messageDispatcher) DispatchMessage(delay float64, sender int, receiver int, msg int, extraInfo any) {
|
||||
// get a pointer to the receiver of the message
|
||||
pReceiver := EntityMgr.GetEntityFromID(receiver)
|
||||
|
||||
// create the telegram
|
||||
telegram := Telegram{
|
||||
Sender: sender,
|
||||
Receiver: receiver,
|
||||
Msg: msg,
|
||||
DispatchTime: 0,
|
||||
ExtraInfo: extraInfo,
|
||||
}
|
||||
|
||||
// if there is no delay, route the telegram immediately
|
||||
if delay <= 0.0 {
|
||||
// send the telegram to the recipient
|
||||
d.discharge(pReceiver, telegram)
|
||||
} else {
|
||||
// calculate the time when the telegram should be dispatched
|
||||
currentTime := Clock.GetCurrentTime()
|
||||
telegram.DispatchTime = currentTime + delay
|
||||
|
||||
// and put it in the queue
|
||||
d.insertIntoPriorityQueue(telegram)
|
||||
}
|
||||
}
|
||||
|
||||
// DispatchDelayedMessages is meant to be called once per pass through
|
||||
// the main game loop: it checks the front of priorityQueue and
|
||||
// discharges any telegram whose DispatchTime has arrived.
|
||||
//
|
||||
// One difference from the book's version worth flagging: the C++
|
||||
// code checks PriorityQ.begin()->DispatchTime without first checking
|
||||
// whether PriorityQ is empty - calling begin() on an empty std::set
|
||||
// and dereferencing it is undefined behavior in C++. Indexing an
|
||||
// empty Go slice would panic the same way, so we added the
|
||||
// len(d.priorityQueue) > 0 check ourselves to actually be safe,
|
||||
// rather than translate that bug faithfully.
|
||||
func (d *messageDispatcher) DispatchDelayedMessages() {
|
||||
// first get current time
|
||||
currentTime := Clock.GetCurrentTime()
|
||||
|
||||
// now peek at the queue to see if any telegrams need dispatching.
|
||||
// remove all telegrams from the front of the queue that have gone
|
||||
// past their sell-by date
|
||||
for len(d.priorityQueue) > 0 &&
|
||||
d.priorityQueue[0].DispatchTime < currentTime &&
|
||||
d.priorityQueue[0].DispatchTime > 0 {
|
||||
|
||||
// read the telegram from the front of the queue
|
||||
telegram := d.priorityQueue[0]
|
||||
|
||||
// find the recipient
|
||||
pReceiver := EntityMgr.GetEntityFromID(telegram.Receiver)
|
||||
|
||||
// send the telegram to the recipient
|
||||
d.discharge(pReceiver, telegram)
|
||||
|
||||
// and remove it from the queue
|
||||
d.priorityQueue = d.priorityQueue[1:]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package westworldwithmessaging
|
||||
|
||||
// MessageType is the Go version of the C++ enum:
|
||||
//
|
||||
// enum message_type
|
||||
// {
|
||||
// Msg_HiHoneyImHome,
|
||||
// Msg_StewReady
|
||||
// };
|
||||
//
|
||||
// from the book's "MessageTypes.h". Same trick used for locationType
|
||||
// back in the westworld package: a named int type plus a block of
|
||||
// auto-numbered constants.
|
||||
type MessageType int
|
||||
|
||||
// MsgHiHoneyImHome and MsgStewReady are the two message types the
|
||||
// book has shown us so far - more get added here as the book
|
||||
// introduces them. (Go naming convention drops underscores from
|
||||
// constant names, hence "MsgHiHoneyImHome" instead of the C++
|
||||
// "Msg_HiHoneyImHome".)
|
||||
const (
|
||||
MsgHiHoneyImHome MessageType = iota
|
||||
MsgStewReady
|
||||
)
|
||||
@@ -1,4 +1,4 @@
|
||||
package westworldwithwoman
|
||||
package westworldwithmessaging
|
||||
|
||||
// State is the Go version of the C++ class template:
|
||||
//
|
||||
@@ -39,6 +39,14 @@ type State[T any] interface {
|
||||
|
||||
// Exit runs once, the moment the entity leaves this state.
|
||||
Exit(entity *T)
|
||||
|
||||
// OnMessage runs when the entity receives a Telegram while this
|
||||
// state is active (current or global). It returns true if this
|
||||
// state actually dealt with the message, false if it doesn't
|
||||
// care about that kind of message - StateMachine.HandleMessage
|
||||
// uses that return value to decide whether to also try routing
|
||||
// the message to the global state.
|
||||
OnMessage(entity *T, msg Telegram) bool
|
||||
}
|
||||
|
||||
// locationType is a temporary stand-in for the C++ enum
|
||||
@@ -116,6 +124,13 @@ func NewMiner(id int) *Miner {
|
||||
m.stateMachine.SetCurrentState(GoHomeAndSleepTilRested())
|
||||
m.stateMachine.SetGlobalState(MinerGlobalState())
|
||||
|
||||
// The book registers an entity as a separate step at the call
|
||||
// site ("Miner* Bob = new Miner(...); EntityMgr->RegisterEntity(Bob);").
|
||||
// We're folding that into the constructor instead, so every miner
|
||||
// is automatically registered and reachable by ID the moment it
|
||||
// exists - nobody can forget the second step.
|
||||
EntityMgr.RegisterEntity(m)
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
@@ -246,3 +261,11 @@ func (m *Miner) Update() {
|
||||
func (m *Miner) ChangeState(newState State[Miner]) {
|
||||
m.stateMachine.ChangeState(newState)
|
||||
}
|
||||
|
||||
// HandleMessage is the Go version of Miner::HandleMessage - it does
|
||||
// nothing itself, just routes the telegram straight to the state
|
||||
// machine, which knows how to try the current state first and the
|
||||
// global state second.
|
||||
func (m *Miner) HandleMessage(msg Telegram) bool {
|
||||
return m.stateMachine.HandleMessage(msg)
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package westworldwithwoman
|
||||
package westworldwithmessaging
|
||||
|
||||
import "fmt"
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package westworldwithwoman
|
||||
package westworldwithmessaging
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -46,3 +46,12 @@ func (s *doHousework) Execute(e *Elsa) {
|
||||
|
||||
// Exit does nothing, for the same reason as Enter.
|
||||
func (s *doHousework) Exit(e *Elsa) {}
|
||||
|
||||
// OnMessage is required now that State[T] includes it, so that every
|
||||
// state satisfies the interface. This state doesn't do anything
|
||||
// special with messages yet - false means "I didn't handle it,"
|
||||
// which lets StateMachine.HandleMessage fall through and try the
|
||||
// global state instead.
|
||||
func (s *doHousework) OnMessage(e *Elsa, msg Telegram) bool {
|
||||
return false
|
||||
}
|
||||
+8
-1
@@ -1,4 +1,4 @@
|
||||
package westworldwithwoman
|
||||
package westworldwithmessaging
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -80,3 +80,10 @@ func (s *enterMineAndDigForNugget) Execute(m *Miner) {
|
||||
func (s *enterMineAndDigForNugget) Exit(m *Miner) {
|
||||
fmt.Printf("\n%s: Ah'm leavin' the gold mine with mah pockets full o' sweet gold", GetNameOfEntity(m.ID()))
|
||||
}
|
||||
|
||||
// OnMessage is required now that State[T] includes it. This state
|
||||
// doesn't handle any messages yet - false ("not handled") is the
|
||||
// correct default until the book shows otherwise.
|
||||
func (s *enterMineAndDigForNugget) OnMessage(m *Miner, msg Telegram) bool {
|
||||
return false
|
||||
}
|
||||
+10
-1
@@ -1,4 +1,4 @@
|
||||
package westworldwithwoman
|
||||
package westworldwithmessaging
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -57,3 +57,12 @@ func (s *goHomeAndSleepTilRested) Execute(m *Miner) {
|
||||
func (s *goHomeAndSleepTilRested) Exit(m *Miner) {
|
||||
fmt.Printf("\n%s: What a great sleep! Time to find more gold", GetNameOfEntity(m.ID()))
|
||||
}
|
||||
|
||||
// OnMessage is required now that State[T] includes it. This state
|
||||
// doesn't handle any messages yet (the book hasn't shown us that a
|
||||
// sleeping miner reacts to anything, like a "stew's ready" message,
|
||||
// though that seems like a likely candidate later), so false ("not
|
||||
// handled") is the correct default for now.
|
||||
func (s *goHomeAndSleepTilRested) OnMessage(m *Miner, msg Telegram) bool {
|
||||
return false
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package westworldwithwoman
|
||||
package westworldwithmessaging
|
||||
|
||||
// StateMachine is the Go version of the C++ class template:
|
||||
//
|
||||
@@ -129,3 +129,25 @@ func (sm *StateMachine[T]) PreviousState() State[T] {
|
||||
func (sm *StateMachine[T]) IsInState(st State[T]) bool {
|
||||
return sm.currentState == st
|
||||
}
|
||||
|
||||
// HandleMessage is the Go version of StateMachine::HandleMessage. A
|
||||
// telegram is offered to the current state first; if that state
|
||||
// doesn't have code in place to deal with this particular message
|
||||
// (OnMessage returns false), it's offered to the global state
|
||||
// instead. The C++ version was marked const - same as Update, Go has
|
||||
// no equivalent keyword and doesn't need one.
|
||||
func (sm *StateMachine[T]) HandleMessage(msg Telegram) bool {
|
||||
// first see if the current state is valid and that it can handle
|
||||
// the message
|
||||
if sm.currentState != nil && sm.currentState.OnMessage(sm.owner, msg) {
|
||||
return true
|
||||
}
|
||||
|
||||
// if not, and if a global state has been implemented, send
|
||||
// the message to the global state
|
||||
if sm.globalState != nil && sm.globalState.OnMessage(sm.owner, msg) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
+9
-1
@@ -1,4 +1,4 @@
|
||||
package westworldwithwoman
|
||||
package westworldwithmessaging
|
||||
|
||||
import "sync"
|
||||
|
||||
@@ -41,3 +41,11 @@ func (s *minerGlobalState) Execute(m *Miner) {
|
||||
func (s *minerGlobalState) Exit(m *Miner) {
|
||||
// TODO: fill in once the book shows this method's body.
|
||||
}
|
||||
|
||||
// OnMessage is required now that State[T] includes it. This state
|
||||
// doesn't handle any messages yet, so false ("not handled") is the
|
||||
// correct default until the book shows otherwise.
|
||||
func (s *minerGlobalState) OnMessage(m *Miner, msg Telegram) bool {
|
||||
// TODO: fill in once the book shows this method's body.
|
||||
return false
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package westworldwithwoman
|
||||
package westworldwithmessaging
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -51,3 +51,10 @@ func (s *quenchThirst) Execute(m *Miner) {
|
||||
func (s *quenchThirst) Exit(m *Miner) {
|
||||
fmt.Printf("\n%s: Leavin' the saloon, feelin' good", GetNameOfEntity(m.ID()))
|
||||
}
|
||||
|
||||
// OnMessage is required now that State[T] includes it. This state
|
||||
// doesn't handle any messages yet - false ("not handled") is the
|
||||
// correct default until the book shows otherwise.
|
||||
func (s *quenchThirst) OnMessage(m *Miner, msg Telegram) bool {
|
||||
return false
|
||||
}
|
||||
+8
-1
@@ -1,4 +1,4 @@
|
||||
package westworldwithwoman
|
||||
package westworldwithmessaging
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -59,3 +59,10 @@ func (s *visitBankAndDepositGold) Execute(m *Miner) {
|
||||
func (s *visitBankAndDepositGold) Exit(m *Miner) {
|
||||
fmt.Printf("\n%s: Leavin' the bank", GetNameOfEntity(m.ID()))
|
||||
}
|
||||
|
||||
// OnMessage is required now that State[T] includes it. This state
|
||||
// doesn't handle any messages yet - false ("not handled") is the
|
||||
// correct default until the book shows otherwise.
|
||||
func (s *visitBankAndDepositGold) OnMessage(m *Miner, msg Telegram) bool {
|
||||
return false
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package westworldwithwoman
|
||||
package westworldwithmessaging
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -45,3 +45,10 @@ func (s *visitBathroom) Execute(e *Elsa) {
|
||||
func (s *visitBathroom) Exit(e *Elsa) {
|
||||
fmt.Printf("\n%s: Leavin' the john", GetNameOfEntity(e.ID()))
|
||||
}
|
||||
|
||||
// OnMessage is required now that State[T] includes it. This state
|
||||
// doesn't handle any messages yet - false ("not handled") is the
|
||||
// correct default until the book shows otherwise.
|
||||
func (s *visitBathroom) OnMessage(e *Elsa, msg Telegram) bool {
|
||||
return false
|
||||
}
|
||||
+12
-1
@@ -1,4 +1,4 @@
|
||||
package westworldwithwoman
|
||||
package westworldwithmessaging
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
@@ -48,3 +48,14 @@ func (s *wifesGlobalState) Execute(e *Elsa) {
|
||||
e.ChangeState(VisitBathroom())
|
||||
}
|
||||
}
|
||||
|
||||
// OnMessage is required now that State[T] includes it. This state
|
||||
// doesn't handle any messages yet - false ("not handled") is the
|
||||
// correct default until the book shows otherwise. (Note: our Elsa
|
||||
// only has DoHousework and VisitBathroom, not a cooking state, so
|
||||
// the book's stew-related messages between Miner and Elsa may not
|
||||
// map cleanly onto this simplified version - worth revisiting if
|
||||
// that comes up.)
|
||||
func (s *wifesGlobalState) OnMessage(e *Elsa, msg Telegram) bool {
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package westworldwithmessaging
|
||||
|
||||
import "math"
|
||||
|
||||
// Telegram is the Go version of the C++ struct:
|
||||
//
|
||||
// struct Telegram
|
||||
// {
|
||||
// int Sender;
|
||||
// int Receiver;
|
||||
// int Msg;
|
||||
// double DispatchTime;
|
||||
// void* ExtraInfo;
|
||||
// };
|
||||
//
|
||||
// It's one message passed between entities through the dispatcher /
|
||||
// entity manager we'll build next: who sent it, who it's for, what
|
||||
// it says, when it should actually go out, and any extra data riding
|
||||
// along with it. The book's "/* CONSTRUCTORS OMITTED */" means we
|
||||
// haven't been shown how one gets built yet, so this is just the
|
||||
// plain data shape for now, no constructor.
|
||||
type Telegram struct {
|
||||
// Sender is the ID of the entity that sent this telegram.
|
||||
Sender int
|
||||
|
||||
// Receiver is the ID of the entity meant to receive this telegram.
|
||||
Receiver int
|
||||
|
||||
// Msg is the message itself. The book keeps this as a plain int
|
||||
// rather than the MessageType enum, presumably so the dispatcher
|
||||
// can stay generic and doesn't need to know about every possible
|
||||
// message type - we're matching that choice exactly rather than
|
||||
// tightening it to MessageType ourselves.
|
||||
Msg int
|
||||
|
||||
// DispatchTime supports delayed messages: instead of going out
|
||||
// immediately, a telegram can be stamped with the time it should
|
||||
// actually be delivered. C++'s "double" becomes Go's float64 -
|
||||
// Go has no bare "double" keyword, float64 is its 64-bit
|
||||
// floating-point type.
|
||||
DispatchTime float64
|
||||
|
||||
// ExtraInfo carries any additional data that should travel with
|
||||
// the message. C++'s "void*" (a pointer with no fixed type -
|
||||
// "could point to anything") becomes Go's "any" (an alias for
|
||||
// interface{} - "could hold a value of any type"). Whoever reads
|
||||
// ExtraInfo later type-asserts it back to something concrete.
|
||||
ExtraInfo any
|
||||
}
|
||||
|
||||
// smallestDelay is our stand-in for the book's "a quarter of a
|
||||
// second" tolerance - it doesn't give us an exact named constant for
|
||||
// this, just that written description.
|
||||
const smallestDelay = 0.25
|
||||
|
||||
// telegramsMatch is our own Go version of the behavior the book
|
||||
// describes for Telegram.h's overloaded == operator (we haven't been
|
||||
// shown that file's actual code, only this description): two
|
||||
// telegrams count as the same message if they're headed the same
|
||||
// direction with the same content, and their dispatch times are less
|
||||
// than smallestDelay apart. This is what keeps a burst of near-
|
||||
// identical messages from flooding an entity's queue.
|
||||
func telegramsMatch(a, b Telegram) bool {
|
||||
return a.Sender == b.Sender &&
|
||||
a.Receiver == b.Receiver &&
|
||||
a.Msg == b.Msg &&
|
||||
math.Abs(a.DispatchTime-b.DispatchTime) < smallestDelay
|
||||
}
|
||||
Reference in new issue
Block a user