I’ve been long waiting for sharing SkyPlanner source code in a public repository.
Problem is, I had to fix a few copyright headers, cleanup some stuff, and, you know, laziness.
Now I finally published them on my GitHub account: https://github.com/GuLinux/SkyPlanner.
It’s still missing a README file for compiling and all, but if someone is curious about how SkyPlanner works, this is a huge start for poking it.
Happy hacking!
When programming in C++ it can often happen to be using C-style API.
These usually come in the form:
int some_api_call(char *inputParameter, char **outputParameter);
where the return value is never a real output value, but instead an exit code, and usually 0 means success.
To handle such API in a sequence of operations, one is then usually blinded to do something like this:
int result = first_c_api_call();
if(result != 0) {
cerr << "Error executing first_c_api_call: " << result << endl;
return;
}
result = second_c_api_call();
if(result != 0) {
cerr << "Error executing second_c_api_call: " << result << endl;
return;
}
result = third_c_api_call();
.....
and so on, which is kinda boring when you have to call lots of API functions in one method.
I have been trying to write some kind of wrapper that can help making this a bit easier.
In a real life example, I’ve been trying to use gphoto2 api in a c++11 application.
Using c++11 lambdas and RAII this is what I’ve been able to do:
void GPhotoCamera::connect() {
CameraAbilities abilities;
GPPortInfo portInfo;
CameraAbilitiesList *abilities_list = nullptr;
GPPortInfoList *portInfoList = nullptr;
CameraText camera_summary;
CameraText camera_about;
int model, port;
gp_api{ {
sequence_run( [&]{ return gp_abilities_list_new (&abilities_list); } ),
sequence_run( [&]{ return gp_abilities_list_load(abilities_list, d->context); } ),
sequence_run( [&]{ model = gp_abilities_list_lookup_model(abilities_list, d->model.toLocal8Bit()); return model; } ),
sequence_run( [&]{ return gp_abilities_list_get_abilities(abilities_list, model, &abilities); } ),
sequence_run( [&]{ return gp_camera_set_abilities(d->camera, abilities); } ),
sequence_run( [&]{ return gp_port_info_list_new(&portInfoList); } ),
sequence_run( [&]{ return gp_port_info_list_load(portInfoList); } ),
sequence_run( [&]{ return gp_port_info_list_count(portInfoList); } ),
sequence_run( [&]{ port = gp_port_info_list_lookup_path(portInfoList, d->port.c_str()); return port; } ),
sequence_run( [&]{ return gp_port_info_list_get_info(portInfoList, port, &portInfo); return port; } ),
sequence_run( [&]{ return gp_camera_set_port_info(d->camera, portInfo); } ),
sequence_run( [&]{ return gp_camera_get_summary(d->camera, &camera_summary, d->context); } ),
sequence_run( [&]{ return gp_camera_get_about(d->camera, &camera_about, d->context); } ),
}, make_shared<QMutexLocker>(&d->mutex)}
.on_error([=](int errorCode, const std::string &label) {
qDebug() << "on " << label << ": " << gphoto_error(errorCode);
emit error(this, gphoto_error(errorCode));
}).run_last([&]{
d->summary = QString(camera_summary.text);
d->about = QString(camera_about.text);
emit connected();
});
// TODO d->reloadSettings();
gp_port_info_list_free(portInfoList);
gp_abilities_list_free(abilities_list);
}
I can then declare some variables in the first part of the method, and inside the “gp_api” block i can execute a sequence of operation, each one returning an int value. This value is automatically checked for an error, and if it it’s a success exit code, the next sequence block is executed.
run_last is finally executed if all steps are completed successfully. An optional mutex locker (QMutexLocker) is passed to the gp_api block as the last constructor argument, to automatically lock the c api for multithreading.
How have I accomplished this?
This is the main class so far:
#include <functional>
#include <list>
#include <mutex>
typedef std::shared_ptr<std::unique_lock<std::mutex>> default_lock;
template<typename T, T defaultValue, typename check_operator = std::equal_to<T>, typename RAII_Object = default_lock>
class sequence {
public:
typedef std::function<T()> run_function;
typedef std::function<void(const T &, const std::string &)> on_error_f;
struct run {
run_function f;
std::string label;
T check;
run(run_function f, const std::string &label = {}, T check = defaultValue) : f(f), label(label), check(check) {}
};
sequence(const std::list<run> &runs, const RAII_Object &raii_object = {}) : runs(runs), _check_operator(check_operator{}), raii_object(raii_object) {}
~sequence() {
for(auto r: runs) {
T result = r.f();
if(! _check_operator(result, r.check)) {
_run_on_error(result, r.label);
return;
}
};
_run_last();
}
sequence &on_error(on_error_f run_on_error) { _run_on_error = run_on_error; return *this; }
sequence &run_last(std::function<void()> run_last) { _run_last = run_last; return *this; }
sequence &add(run r) { runs.push_back(r); }
private:
std::list<run> runs;
on_error_f _run_on_error = [](const T&, const std::string&) {};
check_operator _check_operator;
std::function<void()> _run_last = []{};
RAII_Object raii_object;
};
#define sequence_run(...) { __VA_ARGS__ , #__VA_ARGS__}
The sequence class accepts a list of runs as construction parameters. These are stored as a class field, and sequentially executed at class destruction.
Sequence is a template class: you can define the return value type, the success value, a comparison operator to check each function result code against the success value, and finally a generic RAII_Object, which can be as previously told a mutex locker, or some other kind of resource to unlock after API executions.
The define directive at the end of the code is used to automatically create a run object which already contains a description of the code being executed (stringified).
You get this description in the on_error callback.
Near my gphoto class I also added a typedef to conveniently call the proper sequence template class with correct template parameters:
typedef sequence<int, GP_OK, std::greater_equal<int>, std::shared_ptr<QMutexLocker>> gp_api;
Which means that gp_api accepts code blocks returning int values, that the “ok” value is GP_OK (0), and that the returned value must be equal or greater than GP_OK to be considered a success run.
It also accepts a QMutexLocker shared pointer for thread locking.
As you can see in my first example I didn’t assign the gp_api object to any variable; this means that it is immediatly created, executed and destructed, for synchronous run.
So this is a simplified usage example:
gp_api{ {
sequence_run([&]{ return first_c_api_call(); }),
sequence_run([&]{ return second_c_api_call(); }),
}, std::make_shared<QMutexLocker>(&&;mutex)}
.on_error([=](int errorCode, const std::string &label) {
std::cerr << "Error at code block " << label << ": " << errorCode << std::endl;
})
.run_last([&]{
// run when everything runned smoothly
});