Up to Main Index Up to Journal for February, 2017 JOURNAL FOR FRIDAY 17TH FEBRUARY, 2017 ______________________________________________________________________________ SUBJECT: Doors, a new postal.Service and Go 1.8 DATE: Sat 18 Feb 00:43:17 GMT 2017 This journal entry is going to be about code that is currently a work in progress. As such it is not in the public Git repository yet and is subject to continued hackery. The code in question implements doors. I knew that implementing doors was going to be ‘fun’. In fact I even said as much in my last post: “This may seem like a lot of effort to put into a simple door. However it touches on a lot of small things that will be important for building bigger things later on. So I think the time spent will be worth it.” Well I wasn’t wrong! Adding a door attribute was simple enough. Although I did find a bug in record/decoder that I had to fix — PairList was not uppercasing keys and values as advertised. With that out of the way I then implemented the ‘OPEN’ and ‘CLOSE’ commands. It worked. I now had a one sided door I could open and close, it also blocked my path if it wasn’t open and I tried to go through it. Now, I say it was a one sided door because it only existed on one side - in one location. So going through the door to the east it didn’t exist when going back west. It’s odd to try and explain it because it doesn’t obey real physics :) To get this far required touching a lot of small things as previously mentioned. Inventory already had a Contents method which returned the items it contained, but there was no way to get the Narrative items so I added a Narratives method. The Inventory.Add method only took notice of the Locate attribute on non-narratives - the door needed to know where it was to send observers a message when it reset and closed itself. I also found I suddenly needed names of things at the start of sentences for the first time and hence needed the initial letter of the name titlecased. I think. It could be that I needed the initial letter uppercased but after a lot of reading I think it’s titlecase I wanted. Anyway what I ended up with is a small utility function: func TitleName(name string) string { if len(name) == 0 { return name } r := []rune(name) r[0] = unicode.ToTitle(r[0]) return string(r) } I’m sure I could have used uppercase instead of titlecase as composite glyphs with multiple characters are not converted to multiple runes — such as the ligature ‘fi’ to ‘F’ and ‘i’ — as unicode.ToTitle only returns a single rune. The move command needed an update as it currently doesn’t check for vetoes. This is how a door blocks your path, it generates a veto dynamically based on direction of travel and state of the door. Last of all, the zone loader needed a quick hack to make sure things with a Door attribute also had a Locate attribute. This is needed so that the door knows where it is when it resets and closes itself so that messages can be sent to the location where it is - two locations if the ‘other side’ was implemented. After all of that, door — sort of working — check √ :) Before implementing the ‘other side’ of a door I wanted to get the door to reset and automatically close after a set period. I whipped up some code with a time.Timer, added a call to cmd.Script(door, 'CLOSE DOOR') when the timer expired and was rewarded with cyclic import errors when compiling :( There was also the issue of stopping the timer if the door was closed by someone before it reset. I wasn’t sure I was using timers safely as I find the documentation a little vague with statements such as “This cannot be done concurrent to other receives from the Timer’s channel”[1]. Concurrent use was exactly what I wanted! I have now created a new postal package[2] with a postal.Service which breaks the cyclic imports and handles timers properly — I think — it still needs some more work, cleaning up and documenting: package postal import ( "code.wolfmud.org/WolfMUD.git/has" "log" "time" ) var Script has.Script type Service struct { thing has.Thing input string Cancel chan bool time.Duration } func New(t has.Thing, input string, d time.Duration) *Service { c := make(chan bool, 1) p := &Service{ t, input, c, d } go func(s *Service) { t := time.NewTimer(s.Duration) log.Printf( "Postal for %T started (%s): %s", s.thing, s.Duration, s.input, ) select { case <-s.Cancel: log.Printf( "Postal for %T cancelled (%s): %s", s.thing, s.Duration, s.input, ) if !t.Stop() { <-t.C } case <-t.C: log.Printf( "Postal for %T delivered (%s): %s", s.thing, s.Duration, s.input, ) Script(s.thing, s.input) } log.Printf( "Postal completed for %T started (%s): %s", s.thing, s.Duration, s.input, ) }(p) return p } For now it’s doing its job. Examples of using postal from the Door.Open and Door.Close methods: type Door struct { Attribute direction byte frequency time.Duration open bool *postal.Service } // Code omitted for brevity func (d *Door) Open() { d.open = true if d.frequency != 0 { d.Service = postal.New(d.Parent(), "CLOSE DOOR", d.frequency) } } func (d *Door) Close() { d.open = false if d.Service != nil { d.Service.Cancel <- true d.Service = nil } } I can see from all this hacking that a) I need to refactor the zones package and implement some kind of post-unmarshal handling b) I have a lot of cleaning up of the code to do now — a side effect of make it work first coding ;) I haven’t even looked at keys and locks for doors yet. Any comments, questions or suggestions, feel free to email them my way. Saved the best for last, Go 1.8 is out! The vet command in the new version spotted I was passing copies of a sync.Mutex around in text.dictionary. I have a fix for that already, mixed in with all of my other changes at the moment. -- Diddymus [1] See: https://golang.org/pkg/time/#Timer.Stop [2] I’ve settled on postal for the package name for now until I think of a better one. Up to Main Index Up to Journal for February, 2017