Introduction to funcdef (Function Definition)
Angelscript Documentation Wrote:A function pointer is a data type that can be dynamically set to point to a global function that has a matching function signature as that defined by the variable declaration.
In other words, you're making a "type" (something like
int or
string), but instead of this type's variables containing text (string) or numbers (int), they contain functions - or more specifically, a
pointer to a function! Let us consider the following example:
funcdef void fdSimpleFunction();
This little snippet defines a "type" which can contain any function which takes no arguments, and returns no parameters. The following is an example of such a function:
void sfHelloWorld() {
//Output hello world
AddDebugMessage("Hello World!",false);
}
All that matters when wanting to store that function in a function pointer defined with "fdSimpleFunction" is simply that it's return type is void, and that it takes no arguments. We have a funcdef, and a function which matches the funcdef signature. Time to actually use this funcdef to make a variable to point to our function.
fdSimpleFunction@ functionVar;
This is similar to just normal variable creation, except the "@" sign - The "@" symbol in angelscript literally means "Handle of" / "Address of", and is just stating that our variable is a pointer. A pointer, as the name suggests points to something - it contains the location of what you are looking for as opposed to what you are actually looking for; consider opening a box, but instead of what you want, the box contains a note telling you to look in a specific different box for what you want. In this case, what we are looking for is a function.
Now we have a variable, which is a function pointer. However, currently, it doesn't point anywhere (it literally "is null"). The aim is to get it to point to the function "sfHelloWorld". Again, this is very similar to what is done with normal variables, except that "@" symbol appears again:
//We know this line declares our functionVar
fdSimpleFunction@ functionVar;
//We now will make this point to sfHelloWorld()!
@functionVar = @sfHelloWorld;
This new line is making "functionVar" point to the address of sfHelloWorld (in other words, we just put a note in our box saying look into the specific other box for sfHelloWorld). You are actually saying "set the handle of function var to the handle of sfHelloWorld" - this means "functionVar" can be called just like the function!
//If all has gone well:
functionVar();
//Does exactly the same as:
sfHelloWorld();
The full code for part 1:
//Make the type "fdSimpleFunction"
funcdef void fdSimpleFunction();
//Output hello world
void sfHelloWorld() {
AddDebugMessage("Hello World!",false);
}
void OnStart() {
//Call the function normally!
AddDebugMessage("Calling the function normally!",false);
sfHelloWorld();
//Call the function using our variable!
AddDebugMessage("Calling the function using the pointer!",false);
fdSimpleFunction@ functionVar;
@functionVar = @sfHelloWorld;
functionVar();
}
So why bother? Why go through all this hassle, to just call a function we could have called anyway?
Solving a problem with funcdef
This following example is a little contrived and could be solved a better way, but obviously the function pointer approach is chosen for the purposes of this tutorial
. The idea is that if a function pointer can vary - we can make one function do different things by changing what other functions it calls. Let's say we have two functions, as follows:
void bigFunction()
{
subFunction();
}
void subFunction()
{
}
At the end of bigFunction we want to call a function called "output1". However, subFunction should have a random chance of making bigFunction call "output2" instead:
void output1() {
AddDebugMessage("Yo!",false);
}
void output2() {
AddDebugMessage("Dawg!",false);
}
We can do that using a function pointer. This is our code so far:
//Create a function definition (which is actually the same as fdSimpleFunction)
//Which will matches the signature of both our output functions
funcdef void fdOutput();
//Our output functions
void output1() {
AddDebugMessage("Yo!",false);
}
void output2() {
AddDebugMessage("Dawg!",false);
}
//The actual stuff that does the descision making
void bigFunction()
{
subFunction();
}
void subFunction()
{
}
We next add our variable, in this case we shall imaginatively call it "outputChoice". We shall initially make it point to output1 inside bigFunction, and call the function "outputChoice" points to at the end.
fdOutput @outputChoice;
void bigFunction() {
@outputChoice = @output1;
subFunction();
outputChoice();
}
Meanwhile, our subfunction will have a 1 in 4 chance of replacing which function it points to with output2 instead!
void subFunction()
{
if(RandInt(0,3)==1)
{
//A 1 in 4 chance of this code being reached
@outputChoice = @output2;
}
}
Giving us the following final code:
//Create a function definition (which is actually the same as fdSimpleFunction)
//Which will matches the signature of both our output functions
funcdef void fdOutput();
//Our output functions
void output1() {
AddDebugMessage("Yo!",false);
}
void output2() {
AddDebugMessage("Dawg!",false);
}
//The actual stuff that does the descision making
fdOutput @outputChoice;
void bigFunction() {
//Initially point to output 1
@outputChoice = @output1;
//1 in 4 of output 2...
subFunction();
//Call whichever output has been chosen
outputChoice();
}
void subFunction()
{
if(RandInt(0,3)==1)
{
//A 1 in 4 chance of this code being reached
@outputChoice = @output2;
}
}
void OnStart() {
//Call bigFunction 20 times - 1/4 chance of output2?
for(int i=0; i<20; i++) bigFunction();
}
That's all well and good; but that isn't useful except in certain not-so-common circumstances when it comes to amnesia scripting. So how can it help out in more "normal" scripts?
Arrays, funcdef and you
We shall extend the above problem to have an arbitrary number of functions, and still manage calling one at random. Though each function will have the same probability of being called this time to make things easier. Obviously we could do a massive set of ifs, or a huge switch-case set (what if there are 100 choices? who would want to write that out that switch-case?). We shall re-use some of our code from the first section, however, this time there are two new signature matching functions:
//This creates a signature called "SimpleFunction"
//Which matches functions which take no arguments, and return nothing.
funcdef void fdSimpleFunction();
//Such as these example functions
void sfHelloWorld() {
//Output hello world
AddDebugMessage("Hello World!",false);
}
void sfDisplayTimesCalled() {
//This function will output how many times it has been called
AddLocalVarInt("DTC_TimesCalled",1);
AddDebugMessage("DisplayTimesCalled, called: " +
GetLocalVarInt("DTC_TimesCalled") + " times", false);
}
void sfPlayScarySound() {
//Play the sound of an angry brute!
PlayGuiSound("enemy\\brute\\notice.snt",1.0f);
}
The problem is going to be solved by making an array, each index is going to contain one of the above functions. We just then pick a random index - It scales to any size too!
This is how to create an array of the function pointers:
fdSimpleFunction@[] simpleFunctions = { @sfHelloWorld, @sfDisplayTimesCalled, @sfPlayScarySound };
If you are not familiar with angelscript arrays, check out the
documentation, or the wiki. Now all is left to do is create a function that picks an index at random, and calls the function at that index:
void callRandomSimpleFunction() {
//Pick a random index from the array
uint index = RandInt(0,simpleFunctions.length()-1);
//Select that function
fdSimpleFunction @functionToCall = simpleFunctions[index];
//Call that function
functionToCall();
//Note this can be simplified down to one line:
//simpleFunctions[RandInt(0,simpleFunctions.length()-1)]();
}
If we also wanted to add a function that called each script, one per second, we could do that too:
void callEachFunction(string &in asTimerName) {
//Get the index
uint index = GetLocalVarInt("simpleFunctionIndex");
//Don't go any further if we have called all the functions
if(index >= simpleFunctions.length()) return;
//Access, and call like before:
fdSimpleFunction @functionToCall = simpleFunctions[index];
functionToCall();
//Increment the index
AddLocalVarInt("simpleFunctionIndex",1);
//Call this function again in 1 second
AddTimer(asTimerName,1.0f,"callEachFunction");
}
Below is a sample hps implementing all of the above - don't forget to visit the OnStart routine and uncomment one of the two lines - or you won't see anything!
//This creates a signature called "SimpleFunction"
//Which matches functions which take no arguments, and return nothing.
funcdef void fdSimpleFunction();
//Such as these example functions
void sfHelloWorld() {
//Output hello world
AddDebugMessage("Hello World!",false);
}
void sfDisplayTimesCalled() {
//This function will output how many times it has been called
AddLocalVarInt("DTC_TimesCalled",1);
AddDebugMessage("DisplayTimesCalled, called: " +
GetLocalVarInt("DTC_TimesCalled") + " times", false);
}
void sfPlayScarySound() {
//Play the sound of an angry brute!
PlayGuiSound("enemy\\brute\\notice.snt",1.0f);
}
//We now can create an array of these simple functions for further use.
//Note that fdSimpleFunction@ is like a type now - like string, or int!
fdSimpleFunction@[] simpleFunctions = { @sfHelloWorld, @sfDisplayTimesCalled, @sfPlayScarySound };
//Some example uses of this:
//Calling a random function
void callRandomSimpleFunction() {
//Pick a random index from the array
uint index = RandInt(0,simpleFunctions.length()-1);
//Select that function
fdSimpleFunction @functionToCall = simpleFunctions[index];
//Call that function
functionToCall();
//Note this can be simplified down to one line:
//simpleFunctions[RandInt(0,simpleFunctions.length()-1)]();
}
//Using a timer to call one function per second in sequence.
void callEachFunction(string &in asTimerName) {
//Get the index
uint index = GetLocalVarInt("simpleFunctionIndex");
//Don't go any further if we have called all the functions
if(index >= simpleFunctions.length()) return;
//Access, and call like before:
fdSimpleFunction @functionToCall = simpleFunctions[index];
functionToCall();
//Increment the index
AddLocalVarInt("simpleFunctionIndex",1);
//Call this function again in 1 second
AddTimer(asTimerName,1.0f,"callEachFunction");
}
//Using a timer to repeatedly call a random function
void callRandTimer(string &in asTimerName) {
callRandomSimpleFunction();
AddTimer(asTimerName,1.0f,"callRandTimer");
}
void OnStart() {
//Uncomment one of the following to test it out!
//callEachFunction("testTimer1");
//callRandTimer("testTimer2");
}
You should now understand a little about funcdefs. Have a play around, you can use this to make sequences, callbacks, and all sorts of fun stuff. Also, don't forget to check out the
documentation on the matter, a little example showing how to check if a function pointer doesn't actually point to anything.