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,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:]
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user