Thursday 7 September 2017

Debugging your TTS mods with console++ : Manipulating Variables

If you haven't read it then you should check out the first installment of this series, which goes over how to get set up.

Last time we saw how to look at our program's structure.  This time we'll see how we can manipulate it in real-time by changing and adding variables.

First things first though; last time we were entering commands by typing the command symbol ('>') and then the command.  Let's shortcut past that by entering command mode.  You can type '>cmd' at the prompt to enter command mode, and when in command mode you can type 'exit' to leave it.  When you are in command mode the console will interpret all your inputs as commands, so you no longer need to prefix everything with '>'.
But we're not going to type those!  Instead, an even easier way to enter and exit command mode is to just type the command character on it's own; if you hit '>' then enter it will toggle you in and out of command mode.  Try it now:

After I entered command mode I typed 'cd cards' (just like last time), and you can see that while in command mode any commands you perform will end with a prompt which includes our current path.

OK, so now we can simply type commands without a prefix, let's try a new one out: the 'tgl' (or 'toggle') command flips a boolean.  Let's go back to the root table with 'cd /' then remind ourselves what is there with 'ls'.

At the bottom you can see the four global variables in the mod, and the bottom one is a boolean, currently set to false.  If we look at the code we can see what it does:

update_cards acts as a sentinel on the onUpdate event. Let's turn it on so the mod actually does what it's supposed to do.  Type: 'tgl update_cards' and see what happens.

Well, that isn't good.  The mod clearly doesn't work right, and worse, because the bug is in the onUpdate event it is spamming errors every time it engages.  Investigating will be impossible while the console is being filled with error messages, so turn it off again: in the console hit up-arrow to get the previous command and enter to execute it; this will toggle the update_cards boolean back off, disabling the faulty onUpdate event code.  Note this technique; it's always a good idea to wrap timer-event driven code inside a boolean like this so that if things go wrong you can easily disable the code without aborting from the faulty session.

So what's the problem?  The error is on line 1542, so go to the global script in Atom and hit ctrl-g and enter 1542 to go to the offending line number.  The Atom plugin will take into account any #includes in the code, and will take you to the correct line, which in this case is the one that reads 'cards[count].flip()'.

The error is suggesting that we're trying to index a nil value, so either cards is nil, or the individual location of cards[count] is nil.  Let's have a look: type 'ls cards' at the console.
Nothing? Oh, the cards are game objects; try 'ls -o cards'.

There they are; cards is clearly not nil.  Let's look again at the code - what is the cards table supposed to do?  You can see in the onUpdate event it is looping over the numbers 2 to 10 - the card values on the table.  The table has been set up such that to access the 2 of Spades we use cards[2], to get the 3 of Diamonds we use cards[3], etc.  In the onUpdate event we are looping over each of the cards from 2 to 10 and possibly flipping them.  However, look at the 'ls' output: the cards are not indexed from 2 to 10, they are indexed from 1 to 9.  Looks like the table has been set up wrong! There is no cards[10] so when it tries to access it on the last iteration of the loop it doesn't find anything, thus the error message.
Before we jump back into the code to try to track this down, let's confirm that that is the problem; we'll add a card at that index and see if the code works.  To add a variable we use the 'add' command.  We'll set it to the card that's not being looped over: cards[1].
Type 'add cards/10 cards/1', and then 'ls cards' again.

You can see it has a card at index 10, and it has the same GUID as the one in index 1.  Turn on the code by toggling the boolean: 'tgl update_cards'. No errors - nice!  The update code is working, but our table is still set up wrong; all the indexes are off by one (as you can tell by the card 3 being face up, but the total of the dice only being 2).  Select the dice and hit 'r' to roll them.  The cards will update, but always to the wrong value.

We now know what to fix in the code though, and on investigation you can see the part that sets up the table:

This looks pretty straight-forward; the table is created with a null value in index 1, so that when it loops over the GUIDs of the cards it will add them to slot 2, 3, 4... etc. all the way up to 10.  However, if you are more familiar with Lua than the programmer of this code you will know that nil does not behave just like null does in other languages; in Lua when you assign nil you are actually deleting that entry, so this code is no different from typing 'cards = {}'... it makes an empty table.  The fix should be simple: change the line to 'cards = {'dummy'}' and hit Save And Play.  Now we can go back into command mode with '>' and type 'ls -o cards' to see that they are correctly indexed, 2-10.  (An exercise to the reader: where is the 'dummy' card?)

Toggle the update_cards variable to turn on the event code.  No errors, and the card being displayed matches the total of the dice!  Select the dice and hit 'r' to roll them, and you can watch the cards flip to match.

It feels a bit clunky though; the cards are updating very slowly.  Type 'ls' again to see what variables we have to play with: sure enough, there's a 'check_delay' number set to 1.5 seconds.  Let's set it to a shorter delay using the 'set' command.  Type 'set check_delay 0.3' then select the dice and roll them.  Much more responsive!  If you were writing this code you could easily play with varying values for check_delay until you found the one you liked the best, and then amend the code afterwards to match.

That covers the basics of manipulating variables in the run-time environment.  You can see how we can use it to debug our code in a controlled manner, and also to help in refining control variables. We've now looked at simple variables and objects; next time we'll look at the third of the triad: functions.

1 comment:

  1. Brilliant stuff! I'm surprised these aren't built-in functions.