The Meticulous Geek

Aki

29 November 2013

It took a good while but I think I might have stumbled onto something good this time. I’m not sure what it will look like when the dust finally settles but for now I’m happy to say that my LambdaMOO clone is finally coming alive.

It used to be able to do some fine additions and substractions but that was it. However, now I’m pleased to announce that it can finally do the really cool stuff: fork and suspend.

As always: the solution is quite simple once you find it.

The Program

The API is still very unpolished and even the stuff that is public I’m not sure about but you can now setup a simple (forked and suspended) program like this:

main := []byte {
    IMM,
    0, 0, 0, 0,     // push slot 0 from literals
    IMM,
    0, 0, 0, 1,     // push slot 1 from literals
    ADD,            // pop lhs and rhs from stack and push lhs + rhs
    FORK,           
    0, 0, 0, 0,     // fork index 0
    0, 0, 0, 2,     // delay for 2 seconds
    FORK,
    0, 0, 0, 1,     // fork index 1
    0, 0, 0, 2,     
    FORK,
    0, 0, 0, 2,     // fork index 2
    0, 0, 0, 3,     // 1 second later as the other forks (3s)
    IMM,            // Add the same slots again
    0, 0, 0, 0,     
    IMM,
    0, 0, 0, 1,     
    ADD,
    SUSPEND,        
    0, 0, 0, 5,     // suspend 5 seconds
    ADD,            
    RET,            // returns 2 * (slot 0 + slot 1)
}
// This fork adds two Int vars from the IMM slots.
fork1 := []byte { 
    IMM,
    0, 0, 0, 3,    
    IMM,
    0, 0, 0, 4,    
    ADD,            
    RET,
}
// This fork adds two Float vars from the IMM slots.
fork2 := []byte {
    IMM,
    0, 0, 0, 5,
    IMM,
    0, 0, 0, 6,
    ADD,
    RET,
}
// This fork adds two numbers in optinum range.
fork3 := []byte {
    OptinumToOpcode(2),
    OptinumToOpcode(3),
    ADD,
    RET,
}
// Load all the stuff that is used by the IMM instructions
literals := []Var {
    NewInt(123),
    NewInt(123),
    NewInt(5),
    NewFloat(2.0),
    NewFloat(3.0),
    NewStr("bar"),
    NewStr("foo"),
}
prog := &Program {
    Main: main,
    Forks: [][]byte { fork1, fork2, fork3 },
    Literals: literals,
}

This program has a main body that fires 3 delayed forks. After firing the forks it will suspend a few seconds until all the forks are completed and then will complete its final computation. We only have the ADD operation for now but we support a nice array of features with the VM nevertheless.

The Execution

After setting up the Program we can then execute it:

Execute(prog)

And that’s it. Yes. A program is (and should be) mostly a black box. It can (and will) support some features for reflection but once you Execute it, it’s bascially gone.

If we setup rudimentary logging (LOG = 1) then we can see what’s going on:

  1. First we return from the suspend (after we queued our forks, t–0)

    2013/11/29 00:56:47 => {0 0 [] 0 0 0}

  2. Next, our first two forks finish (after about 2 seconds from t–0)

    2013/11/29 00:56:49 => {0 5 [] 0 0 1}
    2013/11/29 00:56:49 => {0 0 foobar [] 0 0

  3. Our third fork finishes (after about 3 seconds from t–0)

    2013/11/29 00:56:50 => {5 0 [] 0 0 0}

  4. Our main vector finishes (after about 5 seconds from t–0)

    2013/11/29 00:56:52 => {492 0 [] 0 0 0}

A *Program is not normally something you create by hand but I always like it when I’m able to. It doesn’t even take that much setup. The outside is kinda boring but with the things we can do now it will not be much harder to put some fluff on it.

The implementation turned out reasonably nice and clean but that is probably better for another post.

Previous: Next: