I was wandering if you could offer any guidance. I am currently writing a sprite class for a simple game engine. Previous engines I have used allow functionality to "Connect" an external function to a signal, emitted when an animation finishes.
E.g.
create the sprite object
create the external function
connect the external function to the sprites completion signal
When the signal is emitted the external function is called.
This function does not necessarily share any data with the sprite, its purely game logic timing functionality. E.g player scores a goal, "Congrats" sprite animation is triggered, then the on completion function will add an amount to the players score.
I have looked into it and it looks like I need to use a callback function/ function ptr but I don't have any experience using them as of yet.
Any help would be greatly appreciated.
In c++ a function pointer can be used like this:
#include <iostream>
//defining a type that is a 'function that returns void and takes an int'
typedef void FnPtr( int i );
//this is a 'function that returns void and takes an int'
void AFunction( int i )
{
std::cout << "AFunction Called!";
}
//a function that accepts a pointer to a 'function that returns void and takes an int'
void AFunctionThatTakesAFnPtrAsAnArg( FnPtr* fn )
{
//store the pointer to use "later".
FnPtr* local = fn;
.
.
.
//use it.
local( 3 );
}
int main( int, char** )
{
AFunctionThatTakesAFnPtrAsAnArg( AFunction );
}
Note the function can be global, or a static class member. If you want to call into a function in an object instance then see this - particularly my answer! :-)
What is a C++ delegate?
EDIT: To better fit questions asked in comments:
#include <iostream>
typedef void FnPtr();
void AFunction()
{
std::cout << "Animation done";
}
class Sprite
{
public:
void SetFnPointer( FnPtr* fn )
{
m_Fn = fn;
}
void DoAnimation()
{
m_Fn();
}
private:
FnPtr* m_Fn;
};
int main( int, char** )
{
Sprite s;
s.SetFnPointer( AFunction );
s.DoAnimation();
}
Take a look at http://www.boost.org/doc/libs/1_49_0/doc/html/signals.html
or http://libsigc.sourceforge.net/
The boost::signals library is really easy to use:
boost::signal<void(float)> update_sig;
Then somewhere in your game logic (some_client_class):
update_sig.connect( boost::bind(&some_client_class::callback, this, _1) );
And finally in your core update (main run loop of your game for example):
float delta = cur_time - last_update_time;
update_sig(delta); // executes all connected slots
Good luck :)
Related
I am searching for a solutiuon to assign a memberfuction Callback to the extLibrary->OnNewFrame where OnNewFrame is a pointer-to-function type.
class Test
{
public:
void init();
void Callback(ImageDataType, int);
private:
};
void Test::init(){
extLibrary=new CameraLib;
extLibrary->OnNewFrame = Test::Callback;
//OnNewFrame is a pointer-to-function type
}
void Test::Callback(ImageDataType data, int n){
doImageProc();
}
If I change the Code above to static void Callback(ImageDataType, int), the code runs fine, but of course I can not access the variables of the Test Class in Callback .
I think I have to init the Class and pass the memberfunction to the lib's pointer-to-function type after, but I don't know how to do that properly.
EDIT
Sorry, this is my first post and I provided wrong and incomplete information. I will try to improve this post as far as I am able to.
To clarify what the library actually needs:
typedef void (__stdcall *GrabNewFrame) (HANDLE h, int i);
//this is the pointer to function definition
...
//after that in the lib-class:
class CameraLib
{
...
public:
GrabNewFrame OnNewFrame;
...
}
OnNewFrame gets called by the lib every time a frame arrives from the camera.
So as far as I know, I have to assign my own void function to OnNewFrame . Without OOP this works like a charm. But with classes I do not get a memberfunction assigned to OnNewFrame ...
My C/C++ skills are a bit rusty, and I've mostly been working in Java for the past few years. Now I just started playing around with Arduino, and made a simple button class. I want to add an event listener, so I did something like this:
class MyButton{
public:
MyButton(byte pin);
bool isPressed();
bool wasToggled();
bool wasPressed();
void eventLoop();
inline void setListener(MyButtonListener* listener) { _listener = listener; }
private:
byte _pin;
boolean _lastToggledState = false;
MyButtonListener* _listener;
};
class MyButtonListener{
public:
virtual void onPressed() = 0;
private:
};
The eventLoop() method (which is intended to be called from the Arduino loop() function ), invokes the onPressed() method in the listener class:
void MyButton::eventLoop(){
if( wasPressed() && _listener ){
_listener->onPressed();
}
}
So far, things are okay. But I can't figure out how to actually assign and use the listener in the main Arduino file. Coming from Java, I'm used to just doing something like
myBtn.setListener( new MyButtonListener(){
void onPressed(){
Serial.println("Pressed");
toggleLed(); // toggleLed() is a method in the main Arduino file
}
});
I got it to work in a very convoluted way, by declaring a new class which takes the toggleLed() method as an argument (because it can't be accessed from within the new class otherwise):
class BtnListener : public MyButtonListener{
public:
BtnListener(void* toggleFunction) : _toggleFunction(toggleFunction){ };
private:
void (*_toggleFunction)();
void onPressed(){
Serial.println("Pressed");
_toggleFunction();
};
};
myBtn.setListener( new BtnListener(toggleLed) );
Surely there must be a more convenient way of doing something like this in C++? It's doable (but ugly) with one listener - I can't even imagine the horror of having 10 buttons which all need different listener implementations...
In your case, one or the simplest methods would be to store the listener as a std::function<void()> and don't have an actual class to model the buttonlistener at all (you can still have that if you really want to encapsulate that, but it's not neccesary). Then use a lambda function to the setListener call, something like this:
myBtn.setListener( [this]{
Serial.println("Pressed");
toggleLed(); // toggleLed() is a method in the main Arduino file
});
Since the Arduino IDE by default doesn't seem to include <functional.h>, I wasn't able to use the answer using std::function<void()>. However, after some experimenting I realized there was an easier way, which also has the benefit of being able to model the listener.
The listener class simply contains function pointers to each listener callback function, and a constructor that takes an argument for each callback. Then it's very convenient to just create a new instance of the listener class and pass each callback as a lambda.
class MyButton{
public:
inline void setListener(MyButtonListener* listener) { _listener = listener; }
private:
MyButtonListener* _listener;
}
class MyButtonListener{
public:
MyButtonListener(void* onPressed, void* onToggled) : onPressed(onPressed), onToggled(onToggled) {};
void (*onPressed)();
void (*onToggled)();
};
void MyButton::eventLoop(){
if( _listener ){
if( wasPressed() ){
_listener->onPressed();
}
if( wasToggled() ){
_listener->onToggled();
}
}
}
myBtn.setListener(
new MyButtonListener(
// onPressed
[](){
Serial.println("Pressed");
toggleLed();
},
// onToggled
[](){
Serial.println("Toggled");
}
)
);
Not sure if there are any drawbacks with this solution, but it works, is readable and is fit for use on Arduino.
I'm using an API that requires me to pass a function pointer as a callback. I'm trying to use this API from my class in C++ but I'm getting compilation errors.
The API definition is:
typedef void (__stdcall *STREAM_CALLBACK)(void *userdata);
__declspec(dllimport) int __stdcall set_stream_callback(
STREAM_CALLBACK streamCB, void *userdata);
One example file, provided by the third party, is:
void __stdcall streamCB(void *userdata)
{
// callback implementation
}
int main(int argc, const char argv[])
{
int mid = 0;
set_stream_callback(streamCB, &mid);
}
And that works fine.
However when I try to use that in a class, I have an error:
error C3867: 'MyClass::streamCB': function call missing argument list;
use '&MyClass::streamCB' to create a pointer to member
The suggestion to use
&MyClass::streamCB
doesn't work.
I understood that the set_stream_callback only accepts a non-member function.
The problem is very similar to
How can I pass a class member function as a callback?
in which Johannes makes a concise suggestion, however I do not understand it very well. Could anyone expand a bit, if I am correct that it is relevant to this question?
I have tried:
void __stdcall MyClass::streamCB(void *userdata)
{
// callback implementation
}
static void MyClass::Callback( void * other_arg, void * this_pointer ) {
MyClass * self = static_cast<ri::IsiDevice*>(this_pointer);
self->streamCB( other_arg );
}
//and in the constructor
int mid = 0;
set_stream_callback(&MyClass::Callback, &mid);
But
error C2664: 'set_stream_callback' : cannot convert parameter 1 from
'void (__cdecl *)(void *,void *)' to 'STREAM_CALLBACK'
How do I get around this?
Edit1: Also, I want to use userdata inside the streamCB callback.
The idea of calling a member function from a callback taking only non-member functions is to create a wrapper for you member function. The wrapper obtains an object from somewhere and then calls the member function. If the callback is reasonably well designed it will allow you to pass in some "user data" which you'd use to identify your object. You, unfortunately, left out any details about your class so I'm assuming it looks something like this:
class MyClass {
public:
void streamCB() {
// whatever
}
// other members, constructors, private data, etc.
};
With this, you can set up your callback like so:
void streamCBWrapper(void* userData) {
static_cast<MyClass*>(userData)->streamCB()
}
int main() {
MyClass object;
set_stream_callback(&streamCBWrapper, &object);
// ...
}
There are various games you can play with how to create the streamCBWrapper function (e.g., you can make it a static member of your class) but all come down to the same: you need to restore your object from the user data and call the member function on this object.
You can achieve what you want to do by turning the userdata into a property of MyClass. Then you don't have to pass it to MyClass::Callback, which would be impossible, since you can only pass one parameter, and it would be the object instance.
Here's an example.
void __stdcall MyClass::streamCB()
{
// callback implementation
}
static void MyClass::Callback(void * this_pointer ) {
MyClass * self = static_cast<MyClass>(this_pointer);
self->streamCB();
}
MyClass::MyClass(void *userdata) {
// do whatever you need to do with userdata
// (...)
// and setup the callback at C level
set_stream_callback(&MyClass::Callback, (void *)this);
}
In your example, the int mid variable would become a property of that class, and thus be accessible from the callback implementation streamCB.
I have a question about how to define the callback for trackbars in OpenCV when working with classes in C++.
When I define my trackbar let's say in the constructor method of my .cpp class how can I define the callback?
I have been trying to work with function pointers but it doesn't work out. I guess I must be doing something very wrong :-)
This is my header file:
class SliderwithImage {
public:
SliderwithImage(void);
~SliderwithImage(void);
void sliderCallBack(int pos);
};
This is the implementation file:
#include "SliderwithImage.h"
void SliderwithImage::sliderCallBack(int pos) {
}
SliderwithImage::SliderwithImage(void) {
const char* windowName = "window";
int lowvalue =1;
namedWindow(windowName, CV_GUI_EXPANDED);
createTrackbar("mytrackbar", windowName, &lowvalue, 255, sliderCallBack);
}
SliderwithImage::~SliderwithImage(void) {
}
Obviously the createTrackbar method does not recognize sliderCallBack... I guess it's a problem of scope. But I am not sure how to solve this?
Any help would be appreciated.
Thank you very much.
The callback function must be static or global, but you can pass it a reference to an object you want to operate on (see this post on the OpenCV Users mailing list).
The createTrackbar method has a userdata parameter which is passed to the calling function. In C there is an undocumented cvCreateTrackbar2 method, defined in highgui_c.h, which has the same functionality:
CVAPI(int) cvCreateTrackbar2( const char* trackbar_name, const char* window_name,
int* value, int count, CvTrackbarCallback2 on_change,
void* userdata CV_DEFAULT(0));
These methods let you create a class with a static callback function that takes a pointer to an object of that class. You can create the trackbar like so:
cv:createTrackbar("Label", "Window" &variable, MAX_VAL, &MyClass::func, this);
The callback would look something like this:
void MyClass:func(int newValue, void * object) {
MyClass* myClass = (MyClass*) object;
// ...do stuff.
}
Note that you don't need to explicitly update the variable yourself as long as you provided a pointer to it when creating the trackbar (as above), but if you need to process it first I suggest you set it explicitly in the callback function.
You have to implement the callback function either as a global function or a static member function. To make it more OOP look, you might prefer to implement it as a static member function:)
I am using a different solution to obtain the slider value in a class variable (in my case to obtain chosen rotation angle of a live video stream). The int* value in the createTrackbar function is a public class variable which is then used within a loop (while the video is acquired, but this might messily work repeatedly redrawing a single image).
Not the best solution but it works for me.
cv::createTrackbar("Rotation Angle(deg)", "Preview", &rotationAngle,
alpha_slider_max, NULL);
for(;;)
{
int rotAngle = this -> rotationAngle;
cv::Mat frame;
cv::Mat rot_frame;
this -> capture >> frame;
rot_frame = rotateVideo (frame, rotAngle);
imshow("Preview", rot_frame);
if(cv::waitKey(30) >= 0) break;
}
Obligatory - I'm a newbie. Have a job that involves programming and I'm teaching myself as I go. Needless to say as a teacher I get things wrong frequently and thoroughly.
Where I'm at right now: I've created the class "Graph", it (surprisingly enough) makes graphs. But now I want to make it so that on a mouse click I modify the graph. But I can't seem to get a mouse handler to be a member function of the class.
cv::setMouseCallback(windowName, onMouse, 0); // Set mouse handler to be onMouse
Doesn't work with
cv::setMouseCallback(windowName, Graph::onMouse, 0);
It gives me lack of parameter errors. According to this I can't make it a member function. After following the answer given, it compiles but my this pointer is nulled. Ugh.
OnMouse looks like this:
void onMouse(int event, int x, int y,int, void*)
{
if (event == CV_EVENT_LBUTTONDOWN)
{
cvMoveWindow("Window", 500, 500); //Just to see if stuff happened
}
return;
}
I don't care about moving the window, I want to modify the graph itself - which is stored as a cv::Mat variable in a Graph object. And I can't figure out how to do it.
Any help would be appreciated, and I really hope this wasn't just gibberish.
Yes callback functions in C++ are a joy, aren't they? You actually have to give OpenCV a function (not a class method) as you've already found out. However, you can hack around this awfulness using the following technique:
class MyClass
{
public:
void realOnMouse(int event, int x, int y, int flags)
{
// Do your real processing here, "this" works fine.
}
};
// This is a function, not a class method
void wrappedOnMouse(int event, int x, int y, int flags, void* ptr)
{
MyClass* mcPtr = (MyClass*)ptr;
if(mcPtr != NULL)
mcPtr->realOnMouse(event, x, y, flags);
}
int main(int argv, char** argc)
{
// OpenCV setup stuff...
MyClass processor;
cv::setMouseCallback(windowName, wrappedOnMouse, (void*)&processor);
// Main program logic
return 0;
}
That last parameter on setMouseCallback is quite useful for overcoming some of the problems you usually encounter like this.
You can also use the onMouse method as a static method.
class Graph
{
public:
static void onMouse(int event, int x, int y, void* param)
{
//Your code here
}
//Everything else you may need
}
Now you should be able to call the onMouse method with:
cv::setMouseCallback(windowName, onMouse, (void*) param);
The param can be NULL or whatever you want to pass as parameter to the method, but you'll need to make a type-cast to the desired type.
Hope this was useful.
Bye.