Game Engine Idea! (multiple function arguments with different types)
This has probably been done before...
While writing my console game engine, I had come across a problem: I wanted users of the engine to be able to create their own objects instead of pre-creating them. These objects would need to have their own functions that the user writes. My idea was to create a virtual class that then the user could inherit with their own classes. This would make it so that the objects could be kept in an array in each map. However, if the engine needed to call a function that the user wrote, how would it do so without the name? If the user wrote an object for an enemy, with a function
panic(panictype P, int duration) and the engine needed to call it, how would it do so? A solution to this would be an array of function pointers. But wait!!! The array could only hold function pointers of the same return value and arguments. Which brings me to the big question: How would I create a function with a varying return type and arguments? void pointers, that's how.
Solving the problem
void* is one of my favorite things about C++. Being able to store a value or object without knowing its type. Yeah yeah, other languages have that too, I know. But
void* has a special ability: it points to a block of memory rather than a value. If variables were pointing to a piece of paper with a number written on it,
void* is like pointing to a file cabinet with different folders and pieces of paper in it. The one problem with
void* is that you have to cast it to a variable before you read or write to the memory block. Which as you will read, can work to our advantage.
With the power of
void* and function pointers, my problem can be solved with the function pointer
void* (*func)(void*, int*). Lets walk through this. (If you're not familiar with function pointers, just look up function pointers on google).
void* is the return type, and because it points to a memory block, we can convert it to the proper type. The arguments are a
void* and an
void* points to a block of memory that holds the arguments, and the
int* is an array that tells how many arguments there are, and the types of each argument. Because performing operations with a
void* without casting it is a big no-no, we need an array to describe the types of each argument and how many arguments there are. For example, if the block of memory has an int and a char, the first element in the array will be 2, with two elements after it with the id's of int and char. Why are they int and char instead of int and char? Because pointers can not only point to values, but also arrays, which is useful.
Now we get to the nitty gritty. How will we create the block of memory the function will use? We do that by using
malloc a C function that allocates a block of memory of the specified size. For example,
malloc(4) will allocate 4 bytes of memory. It's safer however, to use
sizeof instead of memorizing the sizes of the variables.
malloc(sizeof(int)) will allocate a block of memory big enough for one integer. So allocating a block of memory that holds a pointer to an integer and a pointer to a char is:
void* void_ptr = malloc(sizeof(int*) + sizeof(char*)); void* temp = void_ptr;
temp is there because we will be modifying void_ptr, and need to preserve the address of the memory. In what way you ask? Remember that void_ptr is not the actual block of memory, but a pointer to it. Right now it points to the beginning of the block. So we cast it to a
int*. So a chunk of memory becomes an integer pointer. You assign the block of memory to a value, but what next? How do you allocate the next block of memory as a char? You simply add one to void_ptr. That's it. Why does this work? Because C++ treats numbers differently with pointers, adding or subtracting a number from a pointer is the same thing as saying: move this much along the block of memory. (Note: don’t add numbers to memory blocks that you don’t know the size of, or if you only allocated enough for one type). Adding one to the pointer moves it to the block that is not occupied, all you have to do is cast it to a `char`. Then you fill up an array with how many arguments there are first, and then the id’s of the char and int. The function also uses the same method as filling it up, Just check out the code below if you want to see the full thing.
One of the big cons to this is that if you want to be able to handle a lot of data types, the code can get pretty lengthy.