Return Styles: Pseud0ch, Terminal, Valhalla, NES, Geocities, Blue Moon.

Pages: 1-

Switch Statements and Branch Tables

Name: Anonymous 2012-04-10 13:55

So let's say I'm making a game, and each monster in it follows a separate movement pattern. For each step of gameplay, each monster must have its movement routine called. Instead of evaluating a conditional to determine which movement pattern a monster should use, I could solve this problem easily by enumerating the movement patterns, and using this value as an index into a jump table.

My first instinct would be to write this (in C) literally as an array of function pointers. The end result would look something like:
for all monsters
    *(movfunctions + monster.movementpattern)(&monster) // call a movement pattern function


Alternatively, I could write a single movement function with a massive switch statement instead:
case MONSTER_KNIGHT: // MONSTER_KNIGHT being #define'd to a constant
    // chase the player
    break;
case MONSTER_BUG:
    // hug the walls
    break;


Now, I get that the entire purpose of a switch statement is to facilitate the creation of jump tables. Could I then assume that the compiled results of both methods would be similar?

Name: Anonymous 2012-04-10 14:06

i love niggers

Name: Anonymous 2012-04-10 14:34

case MONSTER_KNIGGER:
Also you can use enums instead of #defines.

Name: Anonymous 2012-04-10 14:38

>>3 Yes, I know what an enum is.

Name: Anonymous 2012-04-10 15:29

Why not just store a function pointer in the structure itself? It would be more extensible and probably easier to read, too.
monster.movementpattern(&monster);

Name: Anonymous 2012-04-10 15:59

>>5
>Why not just store a function pointer in the structure itself?

At this point I usually start thinking using C is not the best choice, then you're looking at C++/C# or C/C++ and Lua.

Name: Anonymous 2012-04-10 16:19

Could I then assume that the compiled results of both methods would be similar?
No, you can't make any judgments about the compiled code beyond that it upon termination should output the same as the C abstract machine given the same input. At least not until you specify a compiler.

(Besides you can't do arithmetic with function pointer types in C, so you're probably mistaking your favorite compiler for C anyway.)

I would rather store a function pointer in the base structure itself and then create a regular selector function, the end result would look something like.

move_entity(&monster);
and equivalently
move_entity(&hero);

If you really insist on being retarded you can use an enum, using a #define for this is way beyond retarded.

Name: Anonymous 2012-04-10 16:50

So let's say I'm making a game
You will fail, stop wasting your time

Name: Anonymous 2012-04-10 17:54

>>8
Someday I will finish a project ;-;

Name: Anonymous 2012-04-10 18:13

>>6
Function pointers and tables of function pointers is bread and butter.

Name: Anonymous 2012-04-10 18:40

>>5,7
just store a function pointer 
The architecture I'm working with (ARM) uses a 32-bit address space. I'm assuming that a function pointer is stored like any other memory address, therefore taking up 4 bytes.
The system I'm working on (Nintendo DS) has only 4 MiB of RAM (even less of which is available, due to the C standard library and libnds taking up more than 0 bytes). Honestly, every byte counts here. When 1 byte is more than enough to denote every possible movement pattern, I can't in good conscience use 3 whole bytes for the same effect.

Of course, using jump tables just moves those pointers from the monster type entries into a table. We're only really saving code size if numerous monsters reuse a few movement patterns, and even then the savings would be negligible.

The real reason not to store function pointers in the monster type: You aren't considering that I may need to do something with a monster's movement pattern type than just use it to call a movement function. Perhaps a certain item or action only affects monsters with a certain movement pattern. Whatever the reason, it's a whole lot easier to compare enumerated movement patterns than it is to compare function pointers.
For example, if there are 4 closely related movement patterns that an action will affect in the same way (for example, 4 non-aggressive monsters that move in different ways, but become aggressive if attack), you could easily compare the enumerators like so:
if (monster.movpattern > MOV_PASSIVE_STATIONARY && monster.movpattern < MOV_PASSIVE_CHASE)
With a bunch of function pointers, you'd have no guarantee of what order the movement functions would be arranged in, so you'd have to manually check each possible case.
Not to mention that it'd be much easier to make a mistake when comparing function pointers than with comparing enumerated values.

blah blah blah implementation defined can't assume
Ok, if you want to be pedantic, I know that the C standard leaves the implementation of these details up to the compiler vendor, and we're talking about what an up-to-date GCC ARM cross compiler does.

can't do pointer arithmetic on function pointers in standards-compliant C
Didn't know that, but what I wrote in >>1 was just pseudocode. Everyone writes function pointer tables as, well, arrays of function pointers, and would use the movement type as an index to the array. I'm pretty sure this is standards compliant.

define is retarded
1) Enumerated types don't actually have any real type safety in C.
2) You can't specify the size of an enumerated type in C.
3) You can just as easily write (raises no errors with -Wall -Werror -pedantic):
enum fruits {APPLES, ORANGES, POSTER_NO_EIGHT};
uint8_t = APPLES;

as you could with #define

If C had type-safe enumerated types whose size I could specify (enum uint8_t fruits {...}; enum uint8_t dinner = APPLES;, for example), I'd love to use them. Because I'd have to write my code as I did in 3) to get byte-wide enumerated values, however, I'm not really seeing a meaningful difference versus using a big block of #defines.

>>8
Stop wasting my time and suck my cock, dude.

Anyways, I'll get to the point. I'm going to assume that GCC's optimizations for switch statements are relatively straightforward, and guess that:
1) The array of function pointers works as expected, creating a jump table to a bunch of separate subroutines. The important part being that there are multiple subroutines created, and (here it is!) I have no idea what the binary overhead is for a function entry (I'm guessing that, at the bare minimum, at the start of every function are a few instructions, depending on architecture, for pushing the registers onto the stack, and that, the more functions you have, the more times these instructions have to be duplicated).

2) The single move function with a switch statement will create a giant function with a jump table to the relevant action for each movement type.

What I was really trying to ask, then, is if:
1) Are my assumptions on GCC's optimizations for switch statements and function pointer arrays well founded?
2) If so, what is the potential impact of each method on performance (don't forget about cache misses, the ARM9 has both instruction and data caches) and code size (perhaps more important, given the limitations that 4 MiB of RAM impose)?

Name: Anonymous 2012-04-10 19:08

>>11
gcc see_for_yourself_``faggot''.c -O3 -S

Name: Anonymous 2012-04-11 1:17

4 MB
constrained

YOU KIDS GET OFF MY LAWN

A lot of what you're describing is undeniably premature optimisation, which as we all know is the root of all evil.

Name: Anonymous 2012-04-11 1:41

>>13
Yes, we all know a bit about that don't we?

Name: Anonymous 2012-04-11 2:48

hmm, what about this?


MonsterMovementTable[monster->monster_type](monster);

Name: Anonymous 2012-04-11 4:10

>>15
It's perfect. You just have to keep the arrays of function pointers correctly sorted. One thing that is actually kind of bitchen about that too is that you can have multiple index fields in the monster instance.

Name: Anonymous 2012-04-11 5:44

>>13 it's just exploring one of the many ways of implementing polymorphism in C. There isn't any optimization here, just a careful design of something that is normally a language feature.

Name: Anonymous 2012-04-17 14:09

bampu pantsu

Name: Anonymous 2012-04-17 14:32

OP: you could drop the overhead of the functions by putting all your movements in one scope and labeling them. then store the label value[1] (&&MOVEMENT_KNIGHT) of the entities and use a GOTO. Of course, at point your pretty much writing assembly so perhaps you should learn how to work with that?

[1] http://gcc.gnu.org/onlinedocs/gcc/Labels-as-Values.html

Name: Anonymous 2012-04-17 16:03

>>19
drop the overhead of the functions
Or go with portable C and use a switch statement.

Name: Anonymous 2012-04-17 17:32

>>20
Or drop C entirely due to dangerous and crippling undefined behaviour and use x86 assembly.

Name: Anonymous 2012-04-17 19:32

>>21
x86
Or you could stop writing for a dead platform and embrace ARM.

Name: Anonymous 2012-04-17 19:34

>>22
Or you could stop writing for a dead platform and embrace MIPS.

Name: Anonymous 2012-04-17 20:56

>>11
The real reason not to store function pointers in the monster type: You aren't considering that I may need to do something with a monster's movement pattern type than just use it to call a movement function. Perhaps a certain item or action only affects monsters with a certain movement pattern. Whatever the reason, it's a whole lot easier to compare enumerated movement patterns than it is to compare function pointers.
You would store the functions in a vtable not in every instance of the struct in existence, you would typically store a pointer to the vtable in a movable entity structure and then let every structure inherit from it (put it at the top and then treat it as a movable entity structure when appropriate).

1) Enumerated types don't actually have any real type safety in C.
What is your point, why does a #define do better in that regard?

2) You can't specify the size of an enumerated type in C.
Again what is your point?

3) You can just as easily write (raises no errors with -Wall -Werror -pedantic):
I'm sure but what is your point?

If you use defines you can't add new entries later in an alphabetic manner without either changing every old value that has an identifier that's larger in an alphabetical sense or search the entire list for which value is the greatest, an enum ensures that every member has a different value (unless you explicitly make them equal of course) and you can just add them in an alphabetic manner without ever worrying about collision of values. Of course this argument applies to any other sorting scheme, like sorting identifiers and values by how much damage they do or how potent a potion is.

Besides it seems that you've already made up your mind about doing something stupid and you keep defending it, if that's really the case then why are you asking us?

Name: Anonymous 2012-04-17 21:07

>>7
>abstract machine
Fuck off back to Israel, Anal Touring-san.

Name: Anonymous 2012-04-18 4:04

Looks like you haven't made it into the present yet, >>1-san. I suggest an enterprise-ready turnkey-scaleable OOP solution for your problem.

Name: Anonymous 2012-04-18 16:36

you can use

patterns[monster.mov_pattern](*monster);

where

void func1(monster * m);
void (*patterns[])(monster *) = {func1, func2, ...};

looks healthy I think

Name: Anonymous 2012-04-18 17:10

>>23
ARM is locked bootloader shit.
MIPS is dead shit.
x86 SUPREMACY

Name: Anonymous 2012-04-18 20:08

>>28
Back to Shizrael, Mooly.

Name: Anonymous 2012-04-18 21:19

>>29
fuck you, goyshit

Name: Anonymous 2012-04-18 22:51

>>27
It might become terrible to maintain the arrays of function pointers, but using array indeces instead of pointers make serialization easier, and there is the advantage of being able to use the index for multiple arrays.

Name: Anonymous 2012-04-19 7:16

I don't see why you would have multiple arrays, is the pattern able to change that much?
if so you could have a int mov_group and many different monsters use the same mov_group, and this symbol identifying a static index that now is of type patterns[], so

pattern_groups[monster.mov_group].patterns[monster.mov_pattern](*monster);

and each monster 'class' would default to a certain pattern group but able to change between movements that belong to it's group, maybe by stances? defensive, offensive, ..
now if you wanted a particular monster to suddenly use a unique movement pattern you could make a rule that element at 0 on index 0 (pattern_group[0].patterns[0]) is reserved to call a function referenced by the callee instead, something like

if(mov_special == TRUE){
this.special = my_func;
pattern_group[0].patterns[0](*this);
}

where 'this' is
struct Monster{
... // typical monster stuff
void (*special[])(monster *);
}

and the default function in [0,0] is
void default_special(monster * m){
m->special();
}


I don't know if this helps, it is getting dangerously close to OOP

Name: Anonymous 2012-04-19 7:19

>>32
pattern_groups[monster.mov_group].patterns[monster.mov_pattern](&monster);


also it doesn't make sense to allocate the whole line of index[0][x] just for one element ([0][0])

Name: Anonymous 2012-04-19 7:21

>>32

if(mov_special == TRUE){
this.special = my_func;
pattern_group[0].patterns[0](*this);
}

you'd set that only once

Name: bampu pantsu 2012-05-29 4:18

bampu pantsu

Don't change these.
Name: Email:
Entire Thread Thread List