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


Continuing On

I last left off with a very simple example of a machine capable of outputting some text. That was all it could do, and it was always the same text. If you remember, last time I spoke about the difference between programs built in this static manner, and programs that are able to handle more dynamic situations. If it were really necessary to create a different type of instruction for every type of message you wanted to output, it could end up being a nightmare.

The Benefit of Data

The simplest remedy to this situation is to create a new style of instruction that makes use of optional data to dictate the message you would like printed. With this type of instruction, all that would be necessary to print a custom message would be to assign it the proper data. No need for hordes of specialized instruction types.

So now we will add support in our Instruction class for using additional data:

// the basic instruction
class Instruction
{
public:
    Instruction(opcode code) : _code(code), _data(0) {}
    Instruction(opcode code, const char* data, size_t dataSize)
        : _code(code), _data(new char[dataSize])
    { memcpy(_data, data, dataSize); }
    ~Instruction()  { delete[] _data; }

    opcode Code() const         { return _code; }
    const char* Data() const    { return _data; }   // read the data
private:
    opcode  _code;
    char*   _data;  // additional data
};

While creating an instruction, additional data can be paired with an opcode by using the second form of constructor. This constructor allocates memory of the correct length to store this data and then copies the source data into its own private storage. This data can be read, but will never be changed again, according to the current interface. A destructor has been added to handle deletion of the data.

If you're asking why the constructor creates a copy of the data provided when it seems simple enough just to assign the internal pointer to the address of the data provided, consider this: What would happen if the source data were to leave scope? You would be left with a dangling pointer. This is why the class owns its data buffer.

Now, we would like to add a new opcode to designate the new functionality we require:

enum opcode
{
    op_talk,
    op_print,    // our new printing code
    op_end
};

The last new inclusion to make is in the virtual machine's processing loop. In the case of our new opcode, it must print the message described by the data, and then go to the next instruction:

void VirtualMachine::Execute(size_t scriptId)
{
    SelectScript(scriptId);   // select our _instrPtr by script ID
    _instr = _instrPtr;       // set our iterator to the beginning
    while (_instr)
    {
        switch(_instr->Code())
        {
        case op_talk:
            std::cout << "I am talking." << std::endl;
            ++_instr;         // iterate
            break;
        case op_print:
            std::cout << _instr->Data() << std::endl;    // print data
            ++_instr;         // iterate
            break;
        case op_end:
            _instr = 0;       // discontinue the loop
            break;
        }
    }
}

It would be a good idea to make sure things work correctly. In our main source, we will test the new instruction. All we need is some data to print, which we then pass to the printing instruction's constructor, along with its proper length (the string length + 1 for the terminating null character):

VirtualMachine vm;

// simulate some external data
char* buffer = "this is printed data";

// build the script
vector<Instruction> InstrList;
InstrList.push_back(Instruction(op_talk));  // talk still works the same way
InstrList.push_back(Instruction(op_print, buffer, strlen(buffer)+1));  // print
InstrList.push_back(Instruction(op_end));   // then end
Script script(InstrList);

// load the script and save the id
size_t scriptID = vm.Load(script);

// execute the script by its id
vm.Execute(scriptID);

If all is in working order, this code should talk, and then print the message provided by the data.



Next : Another form of data