Upcoming Events
Unite 2010
11/10 - 11/12 @ Montréal, Canada

GDC China
12/5 - 12/7 @ Shanghai, China

Asia Game Show 2010
12/24 - 12/27  

GDC 2011
2/28 - 3/4 @ San Francisco, CA

More events...
Quick Stats
86 people currently visiting GDNet.
2406 articles in the reference section.

Help us fight cancer!
Join SETI Team GDNet!
Link to us Events 4 Gamers
Intel sponsors gamedev.net search:

Contents
 The benefit of data
 Another form
 of data

 Manipulating the
 data


 Source code
 Printable version
 Discuss this article

The Series
 An Introduction
 Data Manipulation
 Dynamic Loading
 The Stack and
 Program Flow


Manipulating the Data

Now let's add some pretty basic instructions just to prove that we can manipulate this data predictably.

op_set, // char, char : destination index, value to set
op_inc, // char : index to increment
op_dec, // char : index to decrement
op_add, // char, char, char : dest index, srce index1, srce index2

The commenting here describes the instructional data format, followed by a description of what each value represents to the instruction. For instance, the set op will set the variable at the specified index to the specified value, while the add op will set the variable at the destination index to the result of adding the values at source indices 1 and 2.

We are ready to add proper handlers for these opcodes in the virtual machine:

. . .
case op_set:
    _curState.SetVar(_instr->Data()[0], _instr->Data()[1]);
    ++_instr;
    break;
case op_inc:
    _curState.SetVar(_instr->Data()[0], _curState.GetVar(_instr->Data()[0])+1);
    ++_instr;
    break;
case op_dec:
    _curState.SetVar(_instr->Data()[0], _curState.GetVar(_instr->Data()[0])-1);
    ++_instr;
    break;
case op_add:
    _curState.SetVar(_instr->Data()[0],
                     _curState.GetVar(_instr->Data()[1])
                     + _curState.GetVar(_instr->Data()[2]));
    ++_instr;
    break;
. . .

If you trace through each handler very carefully, you will see that, although a bit circuitous, each instruction is handled as we have described. Due to the circuitous nature of these handlers, they are certainly not optimized to their fullest extent. This is partially due to not having direct write-access to the ScriptState's data. At the moment however, individual instruction handlers are not critical, as they are merely a filler to make sure the key-components of the virtual machine system are operating. You will certainly want to rewrite these later on. Right now we are more concerned with the design of the overall system, and using efficient methods that do not deal directly with handlers.

Another Little Test

We will test this out with another little script. Lacking any creativity at the moment, you may simply put a few pseudo-random manipulation instructions into the script. We will use 4 variables, set the first 3 to a value of 7, then increment the 2nd variable (index 1), decrement the 3rd (index 2), and finally add the 1st and 3rd variables, placing the result in the 4th slot (index 3).

It should resemble the following, in opcode-with-data format:

set 0, 7
set 1, 7
set 2, 7
inc 1
dec 2
add 3, 1, 2

With this deterministic script, we are able to predict the final states of each of the 4 variables. If you follow closely, you will see that they should be as follows, in index-value format:

0: 7
1: 8
2: 6
3: 13

So let's try out our enhancements with the virtual machine. We will create a second script, load it into the machine, and then execute it using the ID returned from loading. In addition, we will use our new debugging tool to check out the variable states after execution.

To create the instructions for this script, we are going to need to simulate some external data (as was done for the previous data example) for reading into the proper instructions:

// create variable manipulation data
char setData1[] = {0, 7}; char setData2[] = {1, 7}; char setData3[] = {2, 7};
char incData = 1;
char decData = 2;
char addData[] = {3, 0, 2};// add 1st and 3rd var, and store in 4th

// proper instruction data size constants (temporary for safety)
const int SET_SIZE  = 2*sizeof(char);
const int INC_SIZE  = sizeof(char);
const int DEC_SIZE  = sizeof(char);
const int ADD_SIZE  = 3*sizeof(char);

Loading the data looks something like this. Notice that we have to use a different syntax for passing single chars than for passing char arrays:

// build the variable manipulation script
vector<Instruction> varInstrList;
varInstrList.push_back(Instruction(op_set, setData1, SET_SIZE));   // set first 3 vars to 7
varInstrList.push_back(Instruction(op_set, setData2, SET_SIZE));
varInstrList.push_back(Instruction(op_set, setData3, SET_SIZE));
varInstrList.push_back(Instruction(op_inc, &incData, INC_SIZE));   // inc 2nd var
varInstrList.push_back(Instruction(op_dec, &decData, DEC_SIZE));   // dec 3rd var
varInstrList.push_back(Instruction(op_add, addData, ADD_SIZE));
varInstrList.push_back(Instruction(op_end));                       // then end

Finish by passing the instruction list, and our variable requirement. Then we can load and execute the script:

Script varScript(varInstrList, 4);  // we need 4 variables

size_t varManipID = vm.Load(varScript);

vm.Execute(varManipID);
// check out the variable states
vm.ShowVariableState();

If all goes well, you should see the correct pre-mentioned values at appropriate indices.

Reflecting

If our testing methods are beginning to seem like glorious hacks to you, you're probably right. Things are beginning to get messy in main(). We seem to be following sloppy, if not outright dangerous, practices to properly load the necessary data into particular instructions. What we are lacking is a centralized procedure for the proper handling and loading of instructions and their data.

While all of this may be fine for our small examples right now, if we are ever to go into larger things, we certainly want the centralization described to localize all possible bugs to one section of the code. That way, if we find we screwed up somewhere, we know exactly where to look while debugging. If you've not heard this before, the idea of localizing functionality is certainly something that is applicable in most, if not all, programming practices.

A mechanism to handle loading in a localized manner is definitely needed soon.

Conclusion

Quite a bit was covered in this article, even though the underlying concept was pretty simple. As basic as it may seem, the inclusion of data increases the flexibility of our instructions a great deal. What would have required hordes of different instructions now requires only a small handful, with some additional data. The virtual machine is also now capable of retaining some kind of "state" during execution, which definitely has beneficial consequences.

At this point, there is a lot of metaphorical territory to be explored on your own. As easy as it may have seemed, we already have laid out much of our foundation. There is a lot to be discovered, and the possibilities are quickly becoming endless.

I am not yet exactly sure what I will be covering in the next article, though it will still be in accordance with my original outline. I am open to suggestions. Please make use of the forum discussion, or email me: glr9940@rit.edu