Getting Even with Field Pointers

This is the third post in the series on field pointers, and since it resolves the problems raised in the second field pointers post, it’s worth reading that first. Today we try to rewrite our extended SUB_UseTargets function to retain the extra functionality, but also not use as much of the stack. We’ll explore two QuakeC compiler extensions in our solutions – both of them require FTEQCC.

Using Macros

Last time we had SUB_UseTargets which called SUB_UseName which called SUB_UseSpecificTarget, which called the actual use function. We can reduce the number of functions here by using a macro, which I think is a new topic for this blog. Macros comprise a name and a template, and what the compiler does is search out that name, and replace it with the templated code. We can create a macro that mimicks SUB_UseName:

#define SUB_USENAME(matchstring) do {\
SUB_UseSpecificTarget(matchstring, targetname);\
SUB_UseSpecificTarget(matchstring, targetname2);\
SUB_UseSpecificTarget(matchstring, targetname3);\
SUB_UseSpecificTarget(matchstring, targetname4);\
}while(0)

In order to create a macro, we start with #define. Straight after that, we have the name. You’ll notice that we can add “parameters” to the macro. When we use the macro in the code, we specify a value for the parameter, and it gets substituted when the template code is inserted, so it works a bit like a function. You can see the code from last week is in there, except we end every line with a backslash – this is because macros are meant to be one-liners. The final change is we’ve wrapped the code in a do{...}while(0) block. See this FAQ for what that does.

So this macro can replace SUB_UseName, and because the macro is expanded at compile time, we don’t make a function call for it, so we’ve gone from 3 to 2 levels of calls. We’ve also managed to maintain the Don’t Repeat Yourself rule, because the code for using all the targetnames is still defined in just one place. Could we extend this idea to SUB_UseSpecificTarget? Unfortunately it’s not practical, because the function uses lots of local variables, which you can’t really do in a macro.

Using Arrays

Although we’ve made some savings on the stack (the macro was enough to avoid the stack overflow in the map which crashed last time) we’d like to make sure our new code uses exactly the same amount of the stack, so that no map could crash in our mod but not in stock Quake. The aim is to rewrite SUB_UseTarget so that the line

self.use();

falls within it.

We’re going to start with a modest rewrite of SUB_UseName (yes, the one we got rid of in the first half). Imagine that we have a function GetTargetname(float index) which we can pass the values 0...3 and get targetname...targetname4 returned. Then we replace all those calls to SUB_UseSpecificTarget with a for loop which looks like

local var .string specific_targetname;
for(i = 0; i < 4; i = i + 1)
{
  specific_targetname = GetTargetname(i);
  SUB_UseSpecificTarget(matchstring, specific_targetname);
}

There’s a little tidbit on field pointers here actually: how to define a local variable which holds a field pointer. Previously we’ve only ever had field pointers as parameters to functions. The trick is to use the var keyword, so that the compiler knows we aren’t trying to define a new field on entities.

So why did we bother rewriting a function we made redundant earlier? Well, the interesting thing about this rewrite is that we only call SUB_UseSpecificTarget in one place. That means we can substitute the code from SUB_UseSpecificTarget in place of the call to the function, without violating Don’t Repeat Yourself. That’s one fewer function call in the stack.

It’s such a good trick, why not do it again! We can replace our multiple calls to SUB_UseName with a similar loop

local var .string specific_target;
for(i = 0; i < 4; i = i + 1)
{
  specific_target = GetTarget(i);
  SUB_UseName(self.specific_target);
}

Again, GetTarget is a function we haven’t written yet which returns target...target4 when given the numbers 0...3. Again, we can replace the call to SUB_UseName with the actual code to SUB_UseName, which now contains the actual code to SUB_UseSpecificTarget. Hey presto, we’ve collapsed the three functions into one! We just need to deal with these unwritten functions.

You can probably imagine how to write that function with a series of if...else... functions but the neat thing is that we don’t have to. We can instead use FTEQCC’s arrays. We can define a pair of arrays in defs.qc:

.string target_fields[4];
.string targetname_fields[4];

Then we need to set the array values. Even though we want to use a constant array, there isn’t a way to do that with the compiler at this time. So instead, we just add some lines to worldspawn to initialise the arrays

target_fields[0] = target;
target_fields[1] = target2;
target_fields[2] = target3;
target_fields[3] = target4;
targetname_fields[0] = targetname;
targetname_fields[1] = targetname2;
targetname_fields[2] = targetname3;
targetname_fields[3] = targetname4;

Finally, we replace GetTarget(i) with target_fields[i] and GetTargetname(i) with targetname_fields[i]. It’s worth mentioning that behind the scenes the compiler is doing all this arrays stuff with functions just like the GetTarget function we imagined – QuakeC doesn’t support “real” arrays. Still, it’s very nice to have the compiler doing all the heavy lifting for us. Here’s all the code put together:

  local float i, j;
  local var .string specific_target;
  local var .string specific_targetname;
  local entity stemp, otemp, act;
  local string search_name;
  //loop through each target field
  for(i = 0; i < 4; i = i + 1)
  {
    specific_target = target_fields[i];
    search_name = self.specific_target;
    if(!search_name)
      continue;

    stemp = self;
    otemp = other;
    act = activator;
    //loop through each targetname field
    for(j = 0; j < 4; j = j + 1)
    {
      specific_targetname = targetname_fields[j];
      //loop through each matching entity
      self = world;
      while(self = find(self, specific_targetname,
                        search_name))
      {
        if (!self.use)
          continue;
        if (self.use == SUB_Null)
          continue;
        self.use ();
        activator = act;
        other = otemp;
      }
    }
    self = stemp;
  }

You’ll probably notice I’ve made a few other changes to the normal appearance of this code. The major change is something I’ve blogged about before, avoiding layers of nesting in loops, particularly through using the continue keyword. This is an excellent example to showcase it, because we have 3 levels of nested loops. Without using continue all those if statements would lead to 6 or 7 levels of indentation, which is best avoided.

There’s also some other little tweaks in there. For example, we don’t bother restoring the original value of self until the whole inner loop has completed, since the next pass of the loop will immediately change self anyway. The way that the inner loop simultaneously assigns to self and tests if self is false is very compact, but it does raise a compiler warning, so you may prefer to rewrite it as a for loop instead. That’s all for today, and it wraps up our series on field pointers. I hope the ideas will bear fruit, and if nothing else the practical example here is useful to you!

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s