Function pointers

From ZDoom Wiki
Jump to navigation Jump to search
Note: This feature is for ZScript only.

Function pointers are useful for several things:

  • Detecting if certain objects have functions defined, allowing implementation of cross-mod support (similar to Service) without creating dependencies
  • Enable Callbacks - functions with the same name but different parameters.

Of note, these are pointers to standard ZScript functions that are already defined.

The only supported types of functions are static and normal functions. They cannot be action, virtual, or variadic functions (variadic meaning Console.Printf, Screen.DrawText, etc. Anything with an ellipsis ('...') that allows for expanding the function).

Definitions

Functions can be defined as such:

Function<void> myFunc;
Function<scope RetTypes(ArgTypes) myFunc;

Functions can also be used in properties with the format ClassName::FunctionName as a string.

Function casting

Casting is done similar to casting for classes (i.e. Class<>). This needs two sets of () such as:

(Function<...>)(value)

Functions can have their returns widened with casts:

Function<play Actor()> someFn = ...; // insert code for function here
Function<play Object()> someFn2 = (Function<play Object()>)(someFn); // widens the return from an Actor to an Object

And functions can have their argument types narrowed with casts:

Function<play void(Object)> someFn = ...;
Function<play void(Actor)> someFn2 = (Function<play void(Actor)>)(someFn);

A simpler way to think about it is basically performing casting like this.

Actor getActor() // assuming this is within an Actor class
{
    return self;
}
Object getObject()
{
    return getActor(); // Normally one would need to perform <Classname>() but not in this case.
}

Function finding

Functions can be found with this static function:

native static Function<void> FindFunction(Class<Object> cls, Name fn);

Or they can be referenced directly if the class is known.

let someFn = someObject.someFunction;
let someFn = someFunction; // Coming from the same class

Examples

version "4.12"
class TestHandler : StaticEventHandler
{
	int c;
	clearscope double AddDouble(double a, double b) {return a+b+c;}
	clearscope int AddInt(int a, int b) {return a+b+c;}
	
	override void OnRegister()
	{
		c = 25;
		
		let test_function_double = TestHandler.AddDouble;
		
		if(test_function_double.call(self, 10.5, 20) != (30.5 + c))
		{
			ThrowAbortException("Failed to call AddDouble");
		}
		
		let test_function_int = TestHandler.AddInt;
		
		if(test_function_int.call(self, 10, 20) != (30 + c))
		{
			ThrowAbortException("Failed to call AddInt");
		}
	}
}

class TestPlayer : DoomPlayer
{
	Function<play double(TestHandler,double,double)> test_function_double;
	Function<play double(TestHandler,double,double)> test_function_double2;
	Function<play int(TestHandler,int,int)> test_function_int;
	
	property test_function_double2 : test_function_double2;
	
	Default
	{
		TestPlayer.test_function_double2 "TestHandler::AddDouble";
	}
	
	
	Function<play double(TestHandler,double,double)>, Function<play int(TestHandler,int,int)> getFns()
	{
		return TestHandler.AddDouble, TestHandler.AddInt;
	}
	
	Function<play double(TestHandler,double,double)>, Function<play int(TestHandler,int,int)> getPtrs()
	{
		Function<play Function<play double(TestHandler,double,double)>, Function<play int(TestHandler,int,int)>(TestPlayer)> testfnptr = getFns;
		
		let [a, b] = testfnptr.call(self);
		
		return a, b;
		// doing return testfnptr.call(self); directly doesn't work, bug #2210 
	}
	
	override void PostBeginPlay()
	{
		super.PostBeginPlay();
		
		[test_function_double, test_function_int] = getPtrs();
	}
	
	override void Tick()
	{
		super.Tick();
		
		let handler = TestHandler(StaticEventHandler.Find("TestHandler"));
		
		console.printf("test_function_double.call(10.5, 20) = "..test_function_double.call(handler,10.5, 20));
		console.printf("test_function_double2.call(10.5, 20) = "..test_function_double2.call(handler,10.5, 20));
		console.printf("test_function_int.call(10, 20) = "..test_function_int.call(handler,10, 20));
	}
}