nbcross - northern-bites/nbites GitHub Wiki

nbcross

nbcross allows the java tool to call c++ code with provided arguments, and receive the output of that call (if any). It is essentially localhost-only remote procedure calling.

Why do it this way?

We need to call robot code – c++ and python – offline. There are existing frameworks for calling c/c++ code from java, but none (that I know of) are remotely portable or simple. nbcross circumvents many of the difficulties such frameworks try to overcome by allowing the c++ and java code to exist in separate processes. Since the processes are still on the same machine, nbcross still has reasonable responsiveness – we use the host OS's inter-process communication. Since sending logs between processes in this manner resembles sending logs between network hosts, we re-use a significant amount of code. And finally, because almost all assumptions about language formatting/interaction must be chucked out the window as soon as you write to a network socket, we achieve a very portable interface.

How it works

The java tool acts as a server to which specific nbcross instances connect and register available functions. When the tool wishes to call a function, it marshals the necessary arguments and sends them to the nbcross instance. The nbcross instance then calls the appropriate function.

To connect a nbcross instance to the tool, simply run nbcross while nbtool is running. nbtool will recognize the instance via a specified port.

Writing nbcross functions

All nbcross function prototypes must be declared to to take no arguments and return an int – this does not mean the nbcross function will be so limited. Log arguments are supplied via the global vector args. If a function wishes to return logs to the tool, it pushes them onto the rets vector. The prototype limitation is due to the difficulty/instability of using variable arguments in c/c++.

Add your function's declaration to nbfuncs.h, and then add a CrossFunc instance to the FUNCS vector in that same file. The string argument declares the name of the function which will be what the nbtool code (in java) identifies it by. The function argument is a pointer to the code you want to be called. The vector of strings declares what logs the function wants from nbtool in order to run – each string refers to a type. For example,

CrossFunc("CrossBright", CrossBright_func, {"YUVImage"}) creates an nbcross function called "CrossBright", which when called should execute the function pointed to by CrossBright_func. This function expects to find a log of type "YUVImage" in the args vector upon execution.

Arguments and return values are passed by pointer. nbcross functions do not need to worry about deleteing any of these pointers – that is done by the calling code. Also, functions should not add argument pointers to the rets vector (modified or otherwise), as this results in over-freeing. Instead, functions should use Log's copy constructor to create a copy of arguments they wish to return, e.g.:

rets.push_back( new Log(*args[0]) );

Note that since nbtool needs to uniquely identify functions by their name, nbcross functions cannot be 'overloaded.'

calling functions from Java (nbtool)

** a simple example of this functionality is provided by CrossBright**

All java - nbcross communication is provided by CrossIO.java. To call an nbcross function, java code must first locate an nbcross instance. This is done by using

CrossIO.instanceByIndex() or CrossIO.instanceByName()

Both functions will return a CrossInstance if successful, or null if not. instanceByIndex(0) gets the first nbcross instance to have connected to the tool and still be connected, instanceByIndex(1) gets the second such instance, etc. instanceByName()` refers to the command line supplied names of nbcross instances.

For most nbcross users, instanceByIndex(0) will be sufficient. Calling code should always check the return – if no nbcross instance is connected, this call will return null.

Next, a java caller must get the appropriate function from the CrossInstance. This is done by calling functionWithName() on the CrossInstance. Again, if an appropriate function cannot be found, null is returned.

A CrossCall is then constructed like so:

CrossCall call = new CrossCall(listener, crossFunc, arguments...)

listener must implement IOFirstResponder and is typically this. Note that any number of log arguments may be supplied. The function is 'called' by executing

crossInstance.tryAddCall(call);

When the nbcross function returns, the listener will be notified via ioRecieved().

NOTE: since nbcross listeners are typically GUI elements, the boolean returned by IOFirstResponder.ioMayRespondOnCenterThread() is significant. If ioRecieved() will result in any GUI modifications, ioMayRespondOnCenterThread() should return false.