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
49 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:

Agenda

During the course of this article I will illustrate a way to implement the following:

  • primitives to support the abstraction of a high-level function
  • calls to hard-coded "external" functions

Scripted functions will simply allow your scripts to become more modular, while externally called functions will allow your scripting system to interact with an existing code-base. The second method is how we will create a system that can grab an interface of function pointers from something like a game engine, and then be able to operate this system by calling scripts.

Improving the Stack

As I said at the end of the last article, the tools for expressing the higher-level concept of functions in scripts have already been provided. Although this is true, the ScopeStack can be enhanced to make things easier for us when trying to pass parameters to a new scope without having to hack around the system. One way to do this is to introduce a second parameter to its PushScope() member which will allow the new scope pushed to include variables pushed onto the stack during the previous scope.

In ScopeStack "would" be:

void PushScope(size_t scopeSize, size_t numParams)
{
    assert(_stack.size()>=numParams);       // make sure our stack is as large as we think
    _indices.push_back(_stackBase);         // store the current index
    _stackBase = _stack.size()-numParams;   // get new scope offset
    _stack.resize(_stackBase+scopeSize);    // accommodate new scope data
}

This is good, except that it could cause problems later on if we are not careful. The danger is hinted at by the assertion. The problem here is that if the proper number of variables intended to be parameters aren't pushed onto the stack before calling PushScope(), the new scope will actually leak into data that is strictly intended for use by the old scope. Furthermore, when this scope is popped off the stack, the amount of data the scope had leaked into will be lost. So let's fix the inherent problem in the ScopeStack to deal with this issue.

We will make use of an additional data member in the ScopeStack to store the next base offset for the stack in the event of a scope being pushed:

size_t	_nextBase;

A few changes to the scope interface's member functions will now determine the starting offset for the next scope to be pushed onto the stack initially, and every time a new scope is pushed. We have succeeded in squashing this possible bug preemptively.

Actual ScopeStack code:

// scope interface
void SetInitialScope(size_t scopeSize)
{
    assert(_stack.empty() && _indices.empty()); // initial scope should only be set once
    _nextBase = _stackBase+scopeSize;           // preemptively set the next offset
    _stack.resize(_nextBase);                   // accommodate initial scope data
    _indices.push_back(_stackBase);             // register the base offset
}        
void PushScope(size_t scopeSize)
{
    _stackBase = _nextBase;                 // get new scope offset
    _indices.push_back(_stackBase);         // register this offset
    _nextBase = _stackBase+scopeSize;       // store the next index
    _stack.resize(_nextBase);               // accommodate new scope data
}
void PopScope()
{
    assert(!_indices.empty());      // don't try to pop a non-existent scope
    _stack.resize(_stackBase);      // shrink back to the end of the former scope
    _nextBase = _stackBase;         // store what the next index would be
    _indices.pop_back();            // take that index off the index stack
    _stackBase = _indices.back();   // grab the base index for the former scope
}

With the changes to our stack, we will need to modify the execution's function to expose its stack state. The difference is that the end of the current scope is now found using the next depth, rather than the current one. If there is no next depth, the end of the stack is used. There was also a bug where the value output was not being taken relative to the first depth (index 0), as it should have been. Well, no harm done. Here is the modified function:

void VirtualMachine::Execution::ExposeStackState(ostream& out) const
{
    size_t numScopes = _stack.ScopeDepth();
    size_t stackSize = _stack.Size();
    size_t index, label, curDepth = 0, curEnd = 0;
    for (index = 0, label = 0; index < stackSize; ++index, ++label) // for the entire stack
    {
        if (index == curEnd)    // for every new scope in the stack
        {
            label = 0;
            out << "Scope Depth: " << curDepth << endl; // output the scope depth
            if (curDepth < numScopes)
            {
                if (curDepth+1 < numScopes)
                    curEnd = _stack.GetScopeIndex(curDepth+1);  // get end of this scope
                else
                    curEnd = _stack.Size(); // end of stack
                ++curDepth; // advance to next scope depth
            }
            else
                curEnd = 0;
        }
        // output each value
        out << "    " << label << ": " << static_cast<int>(_stack.GetAtDepth(index, 0)) << endl;
    }
}

To help during debugging, I have also introduced an instruction that will expose the stack state at our whim. It simply calls ExposeStackState() with the std::cout object as the output stream:

// expose state
case op_expose_state:
    ExposeStackState(cout);
    ++_instr;
    break;




Page 2

Contents
  Introduction
  Page 2
  Page 3
  Page 4

  Source code
  Printable version
  Discuss this article

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