Up to Main Index                              Up to Journal for June, 2020

                      JOURNAL FOR FRIDAY 26TH JUNE, 2020
______________________________________________________________________________

SUBJECT: All I wanted to do was draw a tree (and fix a nasty bug)
   DATE: Fri 26 Jun 20:35:57 BST 2020


  TLDR; There is currently a nasty bug where if you pick up a spawnable
        container and then take a spawnable item from that container it
        causes a nil pointer panic in the reset code.


Have you ever had a really great idea for solving a programming problem? You
write some beautifully simple and elegant code. You maybe show it to friends
and family — despite the blank stares they return. However, lurking in the
shadows is an evil, insidious corner case. Now your beautiful code is littered
with if and if/else all over the place — just so that you can deal with that
single corner case…

Last weekend I was working on WolfMUD, writing code, debugging, all the usual
stuff. Then a user made an observation that resets of items in containers was
problematic. Sometimes a container could reset but its content hadn’t yet. I
sat and thought for a while and decided that it would be better if a container
waited and didn’t reset if its content hadn’t reset. It was while working on
this that I found a nasty bug. If you pick up a spawnable container and then
take a spawnable item from that container it causes a nil pointer panic in the
reset code.

Dammit! All hands on deck, drop everything, we have a problem here! I was sure
all of the reset stability bugs were nailed ages ago :(

My main debugging tools are Printf and WolfMUD’s #DEBUG[1] command.

As an example there is a small red ball. The ball is kept safe in a leather
pouch. For extra security the pouch is kept in an iron bound wooden chest. If
I want to see the status of these three items in-game I can do ‘#DUMP CHEST’:


  >#dump chest
    0xc00011dc40 *attr.Thing #UID-5F, collectable: false, 7 attributes:
      0xc000200a20 *attr.Inventory Lock ID: 90, 1 items (players: 0,
  contents: 1, narratives: 0, disabled: 0):
        0xc00011dac0 *attr.Thing #UID-5E, collectable: false, 7 attributes:
          0xc00011d840 *attr.Name: "a small leather pouch"
          0xc00011d880 *attr.Alias 2 aliases: "POUCH", "#UID-5E", 0
  qualifiers:
          0xc00010d180 *attr.Reset After: 1m0s Jitter: 1m0s Spawn: false
            0x0 event.Cancel
          0xc00011d8c0 *attr.Cleanup After: 10m0s Jitter: 10m0s
            0x0 event.Cancel
          0xc000200a80 *attr.Inventory Lock ID: 91, 1 items (players: 0,
  contents: 1, narratives: 0, disabled: 0):
            0xc00011da00 *attr.Thing #UID-5D, collectable: false, 6
  attributes:
              0xc00011d900 *attr.Name: "a small red ball"
              0xc00011d940 *attr.Alias 2 aliases: "#UID-5D", "BALL", 0
  qualifiers:
              0xc00010d1d0 *attr.Reset After: 0s Jitter: 2m0s Spawn: true
                0x0 event.Cancel
              0xc00011d980 *attr.Cleanup After: 1s Jitter: 0s
                0x0 event.Cancel
              0xc00011d9c0 *attr.Description: "This is a small, red, rubber
  ball."
              0xc00010d220 *attr.Locate -> Origin: 0xc000200a80 a small
  leather pouch, Where: 0xc000200a80 a small leather pouch
          0xc00011da80 *attr.Description: "This is a small pouch made of soft
  dark leather."
          0xc00010d270 *attr.Locate -> Origin: 0xc000200a20 an iron bound
  chest, Where: 0xc000200a20 an iron bound chest
      0xc00011db40 *attr.Description: "This is a very stout wooden chest about
  2 feet wide and 1 foot deep. Thick metal bands bind it."
      0xc00011db80 *attr.Name: "an iron bound chest"
      0xc00011dbc0 *attr.Alias 2 aliases: "CHEST", "#UID-5F", 0 qualifiers:
      0xc00010d2c0 *attr.Reset After: 1m0s Jitter: 1m0s Spawn: false
        0x0 event.Cancel
      0xc00011dc00 *attr.Cleanup After: 10m0s Jitter: 5m0s
        0x0 event.Cancel
      0xc00010d310 *attr.Locate -> Origin: 0xc0000c2180 Trading post, Where:
  0xc0000c2180 Trading post


Bleugh! Not a pretty sight. For someone as intimate with WolfMUD as I am it
does make sense. Sort of. Some of it can be hard to follow, especially if the
#DUMP covers a lot more items. For debugging containers and resets #DUMP is an
essential tool for peeking inside WolfMUD.

So I thought to myself “This would look a lot nicer and make more sense if I
drew a nice tree down the side showing the hierarchy clearly and added line
wrapping”. What I had in mind was something like:


  >#dump chest
    `- 0xc000145e80 *attr.Thing - uid: #UID-5G (an iron bound chest),
       |            collectable: false, attributes: 7
       |- 0xc00005f800 *attr.Inventory - Lock ID: 90, items: 1[players: 0,
       |  |            contents: 1, narratives: 0, disabled: 0]
       |  `- 0xc000145d00 *attr.Thing - uid: #UID-5F (a small leather pouch),
       |     |            collectable: false, attributes: 7
       |     |- 0xc0004e1b80 *attr.Cleanup: After: 10m0s Jitter: 10m0s
       |     |  `- 0x0 event.Cancel:
       |     |- 0xc00005f860 *attr.Inventory - Lock ID: 91, items: 1[players:
       |     |  |            0, contents: 1, narratives: 0, disabled: 0]
       |     |  `- 0xc0004e1cc0 *attr.Thing - uid: #UID-5E (a small red ball),
       |     |     |            collectable: false, attributes: 6
       |     |     |- 0xc0004e1bc0 *attr.Description: "This is a small, red,
       |     |     |               rubber ball."
       |     |     |- 0xc0004e1c00 *attr.Name: "a small red ball"
       |     |     |- 0xc0004e1c40 *attr.Alias aliases: 2["#UID-5E", "BALL"]
       |     |     |               qualifiers: 0[]
       |     |     |- 0xc0004d9090 *attr.Reset: After: 0s Jitter: 2m0s Spawn:
       |     |     |  |            true
       |     |     |  `- 0x0 event.Cancel:
       |     |     |- 0xc0004e1c80 *attr.Cleanup: After: 1s Jitter: 0s
       |     |     |  `- 0x0 event.Cancel:
       |     |     `- 0xc0004d90e0 *attr.Locate: Origin: 0xc00005f860 a small
       |     |                     leather pouch, Where: 0xc00005f860 a small
       |     |                     leather pouch
       |     |- 0xc0004e1d40 *attr.Description: "This is a small pouch made of
       |     |               soft dark leather."
       |     |- 0xc0004e1d80 *attr.Name: "a small leather pouch"
       |     |- 0xc0004e1dc0 *attr.Alias aliases: 2["POUCH", "#UID-5F"]
       |     |               qualifiers: 0[]
       |     |- 0xc0004d9130 *attr.Reset: After: 1m0s Jitter: 1m0s Spawn: true
       |     |  `- 0x0 event.Cancel:
       |     `- 0xc0004d9180 *attr.Locate: Origin: 0xc00005f800 an iron bound
       |                     chest, Where: 0xc00005f800 an iron bound chest
       |- 0xc000145d80 *attr.Description: "This is a very stout wooden chest
       |               about 2 feet wide and 1 foot deep. Thick metal bands
       |               bind it."
       |- 0xc000145dc0 *attr.Name: "an iron bound chest"
       |- 0xc000145e00 *attr.Alias aliases: 2["#UID-5G", "CHEST"] qualifiers:
       |               0[]
       |- 0xc000101630 *attr.Reset: After: 1m0s Jitter: 1m0s Spawn: true
       |  `- 0x0 event.Cancel:
       |- 0xc000145e40 *attr.Cleanup: After: 10m0s Jitter: 5m0s
       |  `- 0x0 event.Cancel:
       `- 0xc000101680 *attr.Locate: Origin: 0xc00005e540 Trading post, Where:
                       0xc00005e540 Trading post


My first naïve attempt was a failure. I took the original output and using the
indentation I tried to fudge the tree into it. Lots of string manipulation and
multiple passes over the text resulted in something that very nearly worked.
The code was horrible but it was just a debug command, right? It wasn’t
critical code that was going to be called over and over again. Still, did I
really want to make such a mess public in my Git repository? Not really.

I started over with a clean slate, outside of the WolfMUD sources, and wrote a
stand-alone tree drawing package. It was clean and elegant. I plugged it into
WolfMUD and had all sorts of issues due to how the API worked. The API let you
append text items or you could add a branch with a text item. However this
would mean having to write something like:


  for x, a := range attributes {
    if x == 0 {
      tree.Branch(a)
      continue
    }
    tree.Append(a)
  }


Ugh! What I wanted was something clean. Something like:


  tree.Branch()
  for _, a := range attributes {
    tree.Append(a)
  }


Clean slate number two. This time I had a better understanding of my problem.
I wrote some simple programs to test the package and try out different tree
structures. Everything looked good. I showed my daughter and she seemed quite
impressed. Then we started playing with one of the trees and trying things on
the fly. It was then that the insidious corner case crept out of its dark
corner and pounced :( My daughter was a little dismayed “Oh! I’ve broken your
software…” and I explained that good tests were ones that find bugs — so this
was a good test and would make the software better.

After a few more iterations I had working code — it produced the above ‘nice’
graph, had an easy to use API and it covered my daughter’s test case :) The
source is currently just under 200 lines of code not including tests, examples
and documentation. For now it’s in my text/tree package.

It’s not on the public test branch yet as I now have to sort out all of the
attribute Dump methods. So far I’ve rewritten enough for debugging the reset
problem, which I’m still working on. I also need to standardise the formatting
of the output for each attribute.

Current plan is: fix reset bug, finish debugger, look at sequence of resets
and delay containers to wait for content to reset, continue working on combat…

--
Diddymus

  [1] The #DUMP command is disabled in the default data/config.wrj file. To
      enable it Debug.AllowDump needs to be set to true.


  Up to Main Index                              Up to Journal for June, 2020