forward class (cluster of callbacks) calling function of unknown params (...)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class forward {
public:
	vector<void *> functions;

	template<class FUNCTIONTYPE>
	inline bool call(...) { // tried to trick the compiler with those dots, eh didn't work.
	// It's marco tho, what do I expect. then again, class is another form of marco on its own
	// If only I could get class function act like a #define marco
		for (int i = 0; i < functions.size(); i++) {
			if ((FUNCTIONTYPE)functions[i](__VA_ARGS__)) return 1;
		}
		return 0;
	}

	void addFunction(void *function) {
		functions.push_back(function);
	}

	bool removeFunction(void *function) {
		iloop(functions.size()) {
			if ((char *)function == (char *)functions[i]) { // chars cause I don't recall if I could void * == void *
				functions.erase(functions.begin() + i); return true;
			}
		}
		return false;
	}

	char *forwardName;
	char functionStructure[64];
	char *description;
};


Idea here would be to create a forward callback cluster in engine and mod's could register their own function's into forward for callbacks or even create their own forwards however I ran into some troubles.

line(8):
 
if ((FUNCTIONTYPE)functions[i](__VA_ARGS__)) return 1;


visual studio says:
term does not evaluate to a function taking 0 arguments.

I have been messing around with machine code and something what I'm trying to do should be possible. I mean all I gotta do is locate the function address and push parameters onto it.
When I get their count/size right, I should be safe however, does c++ have implementation for doing such a thing? However, getting the count of params is tricky and I suppose I could just add an extra parameter indicating params count. Tho, I wanna keep things as simple as possible. Emiting forward to call all the functions in it should be as if calling a single function.


Last edited on
So pretty much you wanna queue up some functions coming from any classes and any arguments to be processed in one loop?

Not gonna happen, easily (to my limited knowledge).

The answer that comes to my mind is to make a common class shared class (like "EventData"), it is a polymorphic class and you create a class for every separate (or shared) function(event). The functions that you send has EventData as the parameter, you cast the EventData to the type you expect it to be. And you send it by creating the object, and queuing it to the forwarder. Then the forwarder processes it.

It sounds easy, but in reality this example is quite difficult to accomplish (and probably not blazingly efficient). If you use shared_ptr's I think that you could have a simple system working by looping through all the listeners and putting the event into it without caring whether it will work or not, and using <funtional> to bind a function with a placeholder for the EventData. There is a complex complete example of this that is open source you can take notes on (LGPL) GameCode4. But it uses a library called fastdelegate to handle function handles, not sure if it is replaceable with <functional> (probably is).

So why do you need a list of functions to be called? A faster and straightforward system could be a service locator, IE: for every subsystem you have a virtual class that is in a global "Interfaces.h" file with very, very general functions, and you just inherit it and fill it with proper code, then the service locator (also known as that class that contains "everything" and acts as a singleton), and it contains a pointer to the abstract classes, so other places in the code could call whatever you need. Then people who write mods (as DLL's I expect), just need the header files to the service locator and a call back to the main loop and an override for the initialization to the service locator, and they could do whatever they want (like they could completely replace a one of the services).

Overall I hope I didn't just jabber over something I miss understood, or you have a real good reason for why you need raw function pointers.
Design and simplicity would be the reason to use raw function pointers.
Doesn't matter how complex it is inside, as long as it looks simple from outside and your cpu is happy with it, its all good. Using raw function pointers is same like .. well calling a function like you would. And I mean function not inline/class function or any other macro.

I came up with something. It doesn't look pretty but it does the job.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// gonna make it visibly more acceptable
#define funcPtr( ret, ... ) (ret(*)(__VA_ARGS__))

// only modifying call function from my code above
void call(int params, ...){
	va_list vl;
	va_start(vl, params);

	switch(params){
		case 0:
			for(auto i : functions) (funcPtr( void, void )i)();
			break;
		case 1:
			for(auto i : functions) (funcPtr( void, void *)i)( va_arg(vl, void *) );
			break;
		case 2:
			for(auto i : functions) (funcPtr( void, void *, void *)i)( va_arg(vl, void *), va_arg(vl, void *) );
			break;
		// and so on ... until its enough
	}
	va_end(vl);
}


As long as we keep forward function parameters as pointers and feed right amount of parameters, we'll be alright, by theory.
Pointer is just an variable, depending on system environment, for 64bit program, we'll be having 64bit variables as a pointers.

Passing everything as pointers offers a lot more freedom to API anyways because who ever are going to receive the callback, can also change the values of parameters for who ever is going to get a callback next.

let me show you more in code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// our callback function A
void funcA( char *name ){	
	cout << "My name is " << name << endl; 
}

struct simple{
	int day;
	char month[32];
};

// our callback function B
void funcB( int *year, simple *restOfTheDate ){	
	cout << "Today's date is:" << *restOfTheDate.month << "/" << *restOfTheDate.day << "/" << *year << endl;
	*year = 2019;
}

// our callback function C
void funcC( long long age ){	
	cout << "Earth is  " << age << " years old." << endl; 
}

forward A, B, C;
 // let's imagine that I already added my functions into those forwards
// something like that: A.addFunction(funcA);

int main(){
	// lets just focus on calling them		
	A.call(1, "Fadey");

	int year = 2018; simple rdate = { 18, "April" }
	B.call(2, &year, date);
	cout << "The year is now:" << year << endl;

	long long ageOfEarth = 4540000000LL;
	C.call(1, ageOfEarth);
}


When running this on 64bit the output would be:

My name is Fadey
Today's date is:April/18/2018
The year is now:2019
Earth is  4540000000 years old.


but if we were to call this on 32bit we would .. well program would simply crash
because of calling C. Because pointers are now 32bit and we're writing 64bit variable long long into it, program would simply write this long long, 32bit of it into right place and the rest of the 32bit into where function's instructions are located. I think that memory part should be read only as well since it was not meant to be changed over the course of run time so that would trigger the crash, not the confusion of one of the instructions being change to do something .. else.
Last edited on
If you were casting a pointer to some sort of number, you can use intptr_t for that. But that is not the case here.

Did you actually test this on a 32 bit machine or a 32 bit compiler? I highly believe that it will still work. A pointer size may vary but the actual size of memory a pointer points to could be anything (like a structure), and just because 32 bit machines uses 2 instructions on a long long instead of 64 bit's single instruction, it doesn't really make a difference on 32 bit machines (maybe if you are dealing with volatile keyword multi threading). Even if it is a copy, it is making a copy onto the stack, which has a stack pointer.

Also why did you change your code all a sudden to not call through a loop in a vector? Its because your fix WONT WORK! I gave you the solutions to your problem but now you just completely flipped the problem into something completely different.

At this point, why not just make each individual forward into a <funtional> function with an auto keyword to keep things simple, because you are just reinventing the wheel here.

Being able to call handles in one loop is very useful for decoupling code, and preventing problems in video games (and very possibly other situations) like when 2 people attack each other at the same time only 1 person gets damaged and stunned. I don't see what you are trying to accomplish here, other than claim that C code is better than C++ code, while also writing a whole post to why your code is "supposedly" flawed.
You misunderstood a lot. I liked when you first stayed humble and stated
'to my limited knowledge'. I think we found were the limit begins.
It's alright, everybody was once there.


I'm leaving my code here for elder's to judge it.
I don't fully feel comfortable because mainly I've not seen anyone doing that.
could I be wrong? Could my way break down at some point?
Its not whether it works or not, its what the purpose of this is.
Topic archived. No new replies allowed.