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