Callback(Callback())

Every good interpreted language needs to have a way to add hooks. Copper used to have one a long time ago, but it hadn’t been rewritten when I made significant changes to the engine. As of today, the callback system has been rebuilt and working!

A callback is basically a function made inside of the interpreter and called later using some outside function. These are very useful for things like button responses, game triggers, etc.

For the Copper engine, I created a method called “runFunctionObject” that accepts a single function-object as an argument and processes it much the same way it would be processed if it were run along with the rest of the Copper code. The only caveat is that it should be called after the engine finishes processing the rest of the code via Engine::execute. No big deal. Engine::execute is called inside of Engine::run – the main function you call when you want to run Copper code.

Of course, obtaining a native Copper function-object and passing it directly into runFunctionObject has its problems. Locally-created functions (those made inside other functions and saved to local variables) and owner-less functions (those not assigned to a variable) are going to be destroyed at the end of the engine running unless the ownership of the function is changed to something outside of the Copper code scope. Hence, the outside code has to set the ownership.

Since that would be tedious to ask the user to do, I decided to create a wrapper class for callbacks: “CallbackWrapper”. It’s included within the rest of the main codebase of Copper.

The usage is pretty easy, so let me give you a full example:

#include <Copper/src/Copper.h>
#include <Copper/stdlib/Printer.h>
#include <Copper/stdlib/InStreamLogger.h>
int main() {
 Cu::Engine engine;
 CuStd::Printer printer;
 CuStd::InStreamLogger streamLogger;
 engine.setLogger(&streamLogger);
 engine.addForeignFunction(util::String("print"), &printer);
 Cu::CallbackWrapper cbw(engine, util::String("set_callback"));
 while( engine.run() == Cu::EngineResult::Ok );
 cbw.run();
 return 0;
}

That’s it! Now within Copper itself, we can call the “set_callback” method and pass it any function we want!

a = "callback message"
set_callback( { print(a:) }

Assuming everything compiled correctly, our output will be “callback message”.

The astute reader may notice that we have used the global “a”. After a normal call to Engine::run(), all of the global variables remain intact. This allows callback functions to utilize them. The globals are only eliminated once the engine instance is finally destroyed or by using the method Engine::clearGlobals().

Methods?

Unless you write it C, you probably want to take advantage of methods in classes. It is rather tedious to inherit from the abstract class ForeignFunc for every little function you want to add. I decided to make it simpler by adding yet another helper class and function: ForeignMethod and addForeignMethodInstance.

The class ForeignMethod inherits ForeignFunc, but unlike the latter, it isn’t inherited from. Instead, the user is expected to pass it a method and the associated base class pointer before adding it to the engine.

All of the droning steps have been reduced to a simple function: addForeignMethodInstance. This function accepts as arguments a pointer to the Copper engine, the name of the foreign function (accessed from Copper), the base class pointer and the pointer to the method. Usage is like this:

struct MethodSource {
bool run( Cu::FFIServices& ffi ) {
ffi.printInfo("Inside method source");
return true;
}
};
int main() {
//...
MethodSource ms;
addForeignMethodInstance( engine, util::String("method"), ms, &MethodSource::run );
}

The pointer to the base class is necessary so that the correct owner of the method can be called.

Concluding Notes

I suspect the class method wrapper will be used quite frequently. Class bindings are one of the main things I had in mind when I designed Copper, but it seems like anytime you want to do something with such broad powers, C++ gives you a choice between code bloat (via templates), memory bloat, or speed sacrifice. I chose code bloat in this case.

I doubt this is the end of the improvements of the foreign function interface and helper classes. While I’m running out of ideas for the moment, the engine is in such a nice state that I already want to work with it on some project. At this point, only finishing the new type system remains.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s