Thursday, March 28, 2013

Empty Progress

So, I got a working prototype of my solution. It uses a mix of Roodylib's grammar routine helper stuff and object classes.

First, let's look at our new grammar definition:
verb "empty", "unload"
    *                                                       DoVague
#ifset NEW_EMPTY
    * (CheckEmpty) "on"/"onto" "ground"/"floor"             DoEmptyGround
#else
    * object "on"/"onto" "ground"/"floor"                   DoEmptyGround
#endif
    * multi "from"/"off"/"on"/"in" parent                    DoGet
    * multi "offof"/"outof" parent                         DoGet
    * multi "from" "offof"/"outof"/"on"/"in" parent         DoGet
! Send >UNLOAD OBJECT to DoEmptyoOrGet, which dispatches to DoEmpty or DoGet
!    * object                                                DoEmpty
#ifset NEW_EMPTY
    * (CheckEmpty)                        DoEmpty ! DoEmptyOrGet
#else
    * object                DoEmpty
#endif

In no cases does it run DoEmptyOrGet anymore as I think it's so general that it just leads to trouble. Next, let's take a look at our object classes:
#ifset NEW_EMPTY
property empty_type

class unheld_to_player
{
    empty_type NOTHELD_T
    before
    {
        object DoEmpty
        {
            local a, b, obj, xobj
            local thisobj, nextobj

            CalculateHolding(object)

            if object is container, openable, not open
            {
                VMessage(&DoEmpty, 1)           ! "It's closed."
                return true
            }

            if object is not container, platform
            {
                ParseError(12, object)
                return false
            }

            if not children(object)
            {
                VMessage(&DoEmpty, 2)           ! "It's already empty."
                return true
            }

            thisobj = child(object)
            while thisobj
            {
                nextobj = sibling(thisobj)

                print thisobj.name; ":  ";

                if thisobj is static
                    VMessage(&DoEmpty, 3)    ! "You can't move that."
                else
                {
                    a = player.holding
                    b = thisobj
                    obj = object
                    xobj = xobject

                    if Perform(&DoGet, b)
                        player.holding = a + b.size
                    else
                        player.holding = a
                    object = obj
                    xobject = xobj

                    if b not in object
                        object.holding = object.holding - b.size
                }

                thisobj = nextobj
            }

            run object.after
            return true
        }
    }
}

class held_to_player
{
    empty_type HELD_T
    inherits unheld_to_player
}

class held_to_ground
{
    empty_type HELD_T
}

constant NO_EMPTY_T 16

class no_empty
{
    empty_type NO_EMPTY_T
}
If it's not self-explanatory, that consists of a class that represents objects that must be held and empty out to player (like unloading bullets from a gun), objects that must be not held and empty out to player (like unloading a laundry machine), objects that must be held and empty out to ground (like a bottle of liquid), and objects that can't be emptied (for containers and platforms that deserve a "You can't empty that." message).

Here is the grammar helper routine:
routine CheckEmpty(obj)
{
    if obj.empty_type
    {
        TOKEN = obj.empty_type
        if not CheckObject(obj)
        {
            return false
        }
        elseif obj.empty_type = NO_EMPTY_T
        {
            ParseError(12, obj)
            return false
        }
        return true
    }
    elseif not CheckObject(obj)
        return false
    return true
}
#endif NEW_EMPTY

Lastly, whether or not players use this new system, I think Roodylib will replace DoEmpty to switch around the platform/container check before the children check:
 replace DoEmpty
{
    local a, b, obj, xobj
    local thisobj, nextobj

    CalculateHolding(object)

    if object is container, openable, not open
    {
        VMessage(&DoEmpty, 1)           ! "It's closed."
        return true
    }
    if object is not container, platform
    {
        ParseError(12, object)
        return false
    }
    if not children(object)
    {
        VMessage(&DoEmpty, 2)           ! "It's already empty."
        return true
    }

    thisobj = child(object)
    while thisobj
    {
        nextobj = sibling(thisobj)

        print thisobj.name; ":  ";

        if thisobj is static
            VMessage(&DoEmpty, 3)    ! "You can't move that."
        else
        {
            a = player.holding
            b = thisobj
            obj = object
            xobj = xobject

            if player not in location and
                (parent(player) is platform or
                    parent(player) is container) and
                not xobject:

                Perform(&DoPutIn, b, parent(player))
            else
                Perform(&DoDrop, b)

            object = obj
            xobject = xobj
            player.holding = a
            if b not in object
                object.holding = object.holding - b.size
        }

        thisobj = nextobj
    }

    run object.after
    return true
}
 EDIT: Just throwing in that, yeah, I know my code does nothing about the "multi from/outof/etc xobject" grammar that points to DoGet, but I think that would go to DoGet in most cases anyway and I'm comfortable with people having to write their own before routines to catch the rest.

full of "empty" problems

First things first, even with the next Hugo Open House months away, I've been thinking a bit about the next one's theme. I think all involved with last year agree that it was even more of a hassle to get things done, even with the extra time. I like to devote the rest of the year to real projects, though, so I don't really want to move the Open House earlier.

My recent thought was that this year's theme should be ports. Part of the inspiration for this was that Johnny Rivera and I have had our eye on Cardinal Teulbach's Hugo 1.2 games. I know Robb Sherwin has had his eye on some BASIC games, and in general, there are just a vast number of outdated game systems out there with games that could use new life.

So, yesterday, instead of actually waiting for the Hugo Open House to come around, I thought I'd dip my toe in the porting waters to see what it feels like. I started with Cardinal Teulbach's SceptreQuest which in itself is a port. Originally, I was looking at the original BASIC source and thought I'd be writing a new version of DescribePlace (which could be conceivably be used other similar BASIC ports), but when I opened up Teulbach's version, I was reminded that they are two very different games. His game is more of a re-imagining.

Anyhow, I'm mostly done with the reimplementation of it all, but I've been tidying up a couple things before I release it (even to what degree I release it is uncertain since Teulbachs was kind of hardcore about his usage license).

Long story short, in testing some of the various containers and platforms I have in the game, I have paid more attention than usual to the DoEmpty and DoEmptyOrGet routines. It is my current conclusion that they are in dire need of being fixed.

Right now, typing >EMPTY <non-held, non-container-or-platform object> results in that object being picked up.

There's also this bit in DoEmpty:
    if not children(object)
    {
        VMessage(&DoEmpty, 2)           ! "It's already empty."
        return true
    }
       
    if object is not container, platform
    {
        ParseError(12, object)
        return false
    }
I have a feeling that in earlier versions, this code may have been switched, as that ParseError message is successfully called in games like Spur and Guilty Bastards. I imagine that DoEmptyOrGet was added and DoEmpty was mixed around in the creation of Future Boy! to solve some problem, but it unwittingly opened some other problems.

Of course, sometimes game design  relies on player's not doing nonsensical things (like trying to empty or unload average non-container items), but when possible, these kind of holes should be patched.

It'd be nice if I had a better time envisioning the scenario that brought about the code change, but whatever the case, a verb routine such as DoEmpty could possibly be even more robust. For instance, I can imagine some objects that should unload or empty out onto the ground and other objects that should empty out into the player's inventory. Also, possibly there should be a way to quickly disallow emptying of a container or platform without having to write up a before routine to catch it.

This all presents the question, how do we handle all of these scenarios? Right now, I'm leaning towards a global variable used with format masks or possibly just attributes. I am going to think on this. Anyone have any suggestions?

EDIT: Ok, I was just playing with this for a bit. I was trying to do some complicated grammar token stuff, but my routine grammar doesn't play nicely with the "multi" grammar token used by much of the "empty" definitions. I have come to the conclusion that probably the best route to take would be to create object classes with various DoEmpty behaviors from which other objects can inherit from.

Saturday, March 23, 2013

3.1

Just letting you all know that Roodylib v3.1 is now up on Hugo by Example and the bitbucket repository. I also uploaded it to the IF Archive, but it'll probably be some days before that one is accepted.

straight from the Hugo source

So I asked Kent Tessman about exactly what was going wrong when something like my ClearArray routine uses up all of the UNDO memory. He answered:
The way Hugo's undo currently works, it stores a stack of individual commands, and basically sticks a bookmark in where a player command would be. Sorry, but the first 'individual commands' I mean 'program statements' or 'program commands'. So when you assign a variable or -- as you discovered, a single array element -- it uses an undo slot. Now, there are lots of them, but the default (and essentially at this point standard) engine has an arbitrary limit to keep memory usage down.
So that'll be an interesting thing to keep in mind in the future. I then asked him if, memory-wise, array elements, variables, and property elements- and the setting of- are interchangeable. He answered:
For the most part.  I mean, basically what Hugo does is allocate a big chunk of bytes, and divvies it up.
 So I think the lesson to be learned from that is to not put all of your state-tracking changes in one basket but to split them up between arrays, property arrays, and variables. Whatever the case, most importantly, don't write code that sets many of these values needlessly.

Anyhow, I'll try to have a new version of Roodylib up soon.

Friday, March 22, 2013

"new fuse" progress

I really was liking this new fuse system, but the fact that using it seemed to limit even a small game to one UNDO was killing me. As far as I could tell, the problem was my call to "runevents" after an UNDO and other xverb commands. It just was taking up too much memory.

I decided just now that I'd code my own version of "runevents" that I'd run in such cases. I call it... fake_runevents!

The new "NEW_FUSE" code:
#ifclear NO_FUSES

#ifset NEW_FUSE
property fuse_length alias e_to

replace fuse
{
    type fuse
    size 0
    timer
    {
        if self.fuse_length
            return (self.fuse_length - counter)
        else
            return 0
    }
    fuse_length 0
    in_scope 0
    tick
    {
        local a
        a = self.timer
        if a <= 0
            self.fuse_length = 0
#ifset DEBUG
        if debug_flags & D_FUSES
        {
            print "[Running fuse "; number self; ":  timer = ";
            print number a; "]"
        }
#endif

        if a = 0
            Deactivate(self)
        return a
    }
}

property timer_start alias fuse_length
property fake_event alias s_to

replace daemon
{
    type daemon
    size 0
    in_scope 0
    timer
    {
        if self.timer_start
        {
            return (counter - self.timer_start)
        }
        else
            return 0
    }
}

object fuse_bucket
{}

routine fake_runevents
{
    local i
    for i in fuse_bucket
    {
!        if i.type = fuse
!        {
            run i.fake_event
!        }
    }
}
#endif  ! NEW_FUSE

!\
Activate - added a warning for when it is called before the player global has been set.
\!
replace Activate(a, set)                ! <set> is for fuses only
{
    local err
    if not player
        {
        Font(BOLD_ON)
        print "[WARNING:  The player global must be set before
        daemon (object "; number a;") can be activated.]"
        err = true
        }
#ifset NEW_FUSE
    move a to fuse_bucket
#endif
    a.in_scope = player
    a is active
    if a.type = fuse and not err
    {
        if set
#ifclear NEW_FUSE
            a.timer = set
#else
            a.fuse_length = (counter + set)
#endif

        run a.activate_event
    }
    elseif a.type = daemon and not err
    {
#ifclear NEW_FUSE
        if set and not a.#timer
#else
        if set and not a.#timer_start
#endif
        {
            Font(BOLD_ON)
            print "[WARNING:  Attempt to set nonexistent timer
                property on daemon (object "; number a; ")]"
            err = true
        }
        else
#ifclear NEW_FUSE
            a.timer = set
#else
            a.timer_start = (counter - set)
#endif
        run a.activate_event
    }
    elseif not err
    {
        Font(BOLD_ON)
        print "[WARNING:  Attempt to activate non-fuse/\
        daemon (object "; number a; ")]"
        err = true
    }

#ifset DEBUG
    if debug_flags & D_FUSES and not err
    {
        print "[Activating "; a.name; " "; number a;
        if a.type = fuse
            print " (timer = "; number a.timer; ")";
        print "]"
    }
#endif
    if err
        {
        Font(BOLD_OFF)
        "\npress a key to continue..."
        HiddenPause
        }
    return (not err)
}

replace Deactivate(a)
{
    local err

    remove a
    a.in_scope = 0
    a is not active

    if a.type ~= fuse and a.type ~= daemon
    {
        print "[WARNING:  Attempt to deactivate non-fuse/\
            daemon (object "; number a; ")]"
        err = true
    }
    else
    {
        run a.deactivate_event
    }

#ifset DEBUG
    if debug_flags & D_FUSES and not err
    {
        print "[Deactivating "; a.name; " "; number a; "]"
    }
#endif

    return (not err)
}
#endif ! ifclear NO_FUSES
So, you copy your "event in <fuse or daemon>" code to the fuse's fake_event property. Of course, like the old "new fuse" stuff, whether or not your code properly prints stuff will be based on whether your code uses the new counter-based timing effectively.

Anyhow, my guess is that "runevents" uses a fair amount of memory since it's doing a lot of FindObject-y stuff behind the scenes, and this method of helping Hugo find the fuses and daemons quicker seems to help my test game out a good amount.

more about what went wrong

So, let's share an example of memory-hogging code. This is the ClearArray routine that was sucking a lot of available memory out of Roodylib-using games:
routine ClearArray(array_to_be_cleared)
{
    local n,t
    for (n=0;n< array array_to_be_cleared[] ; n++ )
        {
            array array_to_be_cleared[n] = 0
        }
}

The problem was that I was using this with _temp_string every turn, and _temp_string has 256 elements, meaning that it goes through the loop 256 times. This all seems to count against the available memory.

The reason I wrote this routine in the first place was because of an instance in The Clockwork Boy 2 was because something was overwriting the end 0 bit of the current string, so the game kept printing until it got to the end of a previously-saved string. I thought, a-ha, I'll fix this by clearing _temp_string all of the time!

Of course, now I wish I had just fixed whatever was writing over that 0 bit. I can't remember what the code was like before, but in the meantime, I changed my PrintStatusLine code to not use ClearArray.

Instead of throwing ClearArray out altogether (although I might do it eventually anyway), I changed the loop to quit out as soon as the array has two empty elements in a row:

routine ClearArray(array_to_be_cleared)
{
    local n,t
    for (n=0;n< array array_to_be_cleared[] ; n++ )
        {
            if    array array_to_be_cleared[n] = 0
                t++
            else
            {
                array array_to_be_cleared[n] = 0
                t = 0
            }
            if t = 2
            {
                break
            }
        }
}

The main thing is, let this be a good lesson to not let your code loop more than it has to.

Thursday, March 21, 2013

worst case scenario

Grrr, so annoyed. While testing out that fuses code today, I noticed that games compiled with recent versions of Roodylib don't have enough memory for even one UNDO. Truth be told, I often test new code with Gargoyle since the Gargoyle window is so unobtrusive, but Gargoyle's UNDO buffer is so small that I probably didn't blink twice when newly compiled games were unable to compile.

Testing now with Hugor, Roodylib has been short on UNDO memory since before I started uploading to bitbucket (and definitely before the 3.0 release I uploaded to the IF Archive).

The good news is, getting rid of just one for loop called by PrintStatusLine freed up space for one UNDO, and compiling without my new fuse code frees up memory for even more.

The bad news is, now I need to go through all of Roodylib with a particular eye for optimization. Having games unable to UNDO is unacceptable. I've long been worried that I'd run into this problem, but it is quite annoying to have to deal with it.

My optimization strategy will consist of cutting any loops that I can and making sure that the others are as concise as possible. Beyond that, I've long been suspicious that Perform doesn't need to call SetUpDirectionObjects every time, and I think I'll see if I can move it somewhere else.