Friday, February 7, 2014

Critical milestone reached: framework-independent cross-platform app development got the green light

It's been about two months since my libposif-0.9 library started to show its first signs of life, and now i have my first libposif-1.0 with [what i believe it is] a stable API. When i first defined the libposif API it wasn't totally clear to me how it will integrate in a message loop-based environment (such as is the case with all modern GUI frameworks, e.g. GTK, Borland, MS, Qt), so during the past couple of months i had to re[de]fine the API specifications and make the required implementation changes.

Well, libposif-1.0 is now here, together with my first fully-functioning libposif-1.0-based GUI application. By far the #1 candidate for a host environment is the Qt framework because it's both LGPL-ed and multi-platform, but by no means does a libposif-based application rely on any of the Qt-specific mechanisms (no signal/slot, event loop, etc dependencies whatsoever): in fact, all the fundamental multi-threading and messaging-related mechanisms available in Qt have have a [somewhat equivalent, or more sophisticated] native C++11 implementation in libposif, and all that is required for having a libposif-based application integrated in any host environment, may it be event loop-based or not, is a sequence of three next-to-trivial steps:
  1. Derive a host environment-specific class from a libposif-defined "MessagingInterface" abstract class - e.g. "MyMessagingInterface": this class is the host environment's messaging interface with the application, and it implements [pre-processing and] relaying of application-defined, environment-agnostic messages sent by back and forth between the libposif application's I/O buffer and the native environment's components (windows/widgets/etc)
  2. Instantiate a MyMessagingInterface object inside the host environment, e.g. by including it in a GUI application's main window or inside an invisible form, etc
  3. Cross-link the native environment's objects (i.e. windows, forms, dialogs, etc) with the MyMessagingInterface object (nothing weird or hi-thech here, just some plain old pointer cross-referencing between the host environment's native objects and the MyMessagingInterface object, say 10 minutes of typing some pointer variable declarations and some pointer assignments).
Sure enough, the list above is just a power point list and nothing more, but the point is that that's really all there is to integrating a libposif-based application into any host environment; once the above procedure has been walked though, the libposif application's output messages will trigger methods in the host environment, and host environment's events will be pushing messages to the libposif application's input buffer.

And because a picture's worth a thousand words, here's how a libposif-based application integrates in a host environment...


...and here's a screenshot of my test application, integrated in a Qt-based GUI:


A few words about the test application above and how it is internally implemented by making use of libposif's features:
  1. first of all, it is a proof of concept for an event loop-based environment integration:
    • the two "Send to" buttons in the main window actually send a message to the application, and the application sends back the message to the GUI which directs it to the proper window (as specified in the message)
    • all the other buttons send messages to the application, which then relays them to the appropriate Automaton for processing
  2. tests the threading/automata model, namely:
    • each counter is implemented as an Automaton in a separate Tread
    • each counter Automaton is implemented by having it send a scheduled message targeted to itself, with the schedule specifying that the message is to be actually sent with a specified delay after the SendMessage() method invocation (scheduled messages and targeted messages are examples of features that do not have a Qt equivalent)
      • counter 1 in the Main window sends a message to itself with precisely one-second delay, thus making the counter increment once/second
      • counter 2 in the Main window sends a message to itself with a three second average delay, and with a specified delay dispersion (in %), i.e. the messages will actually be sent with an average, but not precise, 3 second delay (e.g. for a 40% dispersion the successive delays might be e.g. 3, 2, 2, 4, 3, 4... seconds ), which will cause the counter to be incremented on average, but not precisely, every 3 seconds
    • the two counters can be started/stopped from their corresponding Start/Stop buttons: each button sends a message to the application, which then relays the message to the corresponding counter Automaton
    • counter 1 broadcasts a message to all the Automata in the application every time it increments; the libposif method for having an Automaton broadcast a message is BroadcastMessage(), and it is roughly equivalent to Qt's signal()
    • the two Reg/Unreg buttons register/un-register a connection between the "tick" messages broadcasted by the counter 1 Automaton and a corresponding "divider Automaton" represented by the yellow counters to the right of said buttons, where each "divider Automaton" is dividing the incoming "tick"s (broadcasted by counter 1) by the number specified in its corresponding drowp-down list (i.e. by 1 and by 2 in the screenshot above); the libposif methods for registering/un-registering an Automaton as a listener to a broadcasting source are AddMessagingRoute(src, dest) / RemoveMessagingRoute(src, dest), and are roughly equivalent to Qt's connect(src, dest) / disconnect(src, dest) methods
  3. finally, the Main window's "Show Clock Form" menu item illustrates how a message can be sent by a window (namely the Main window) to another window (namely the Form window) without going through the application's Messaging Interface, i.e. this is an internal Qt message which never leaves the Qt-based GUI
And because talk is cheap, here's a code snippet that illustrates how a counter automaton is actually implemented by having it send a scheduled message back to itself:
  • onMessageReceived() is a predefined virtual function of libposif's Automaton abstract class, and it is triggered (by libposif's internals) each time an Automaton object receives a message (this method has to be implemented by each specific automaton that is derived from libposif's Automaton abstract class)
  • SendIntercomMessage() sends a scheduled message to a specified Automaton (which can be - and in the code snippet below is - the sender Automaton itself)
  • BroadcastMessage() broadcasts a scheduled message to all the automata in the application, and the message will be actually received (and processed) by any/all other Automaton objects that have been set up to listen to the sender Automaton (via AddMessagingRoute())
  • SendIOMessage() sends a message to the application's Messaging Interface
  • State is the Automaton's [integer] state variable: it is a mandatory component
    of any automaton, declared in the Automaton base class

int MyClockAutomaton::onMessageReceived(
   const message_t& msg, 
   const alphanum_t& sourceThreadId, 
   const alphanum_t& sourceAutomatonId) 
{
   switch (State) {
   case counter_on:
      if (msg==CLOCK_TICK) {
         assert(sourceThreadId==parentThread()->threadId() &&
                sourceAutomatonId==automatonId());
         counter++;
         SendIOMessage((message_t)PRINT_COUNTER<<counter);
         BroadcastMessage(DIVIDER_TICK);
         SendIntercomMessage(CLOCK_TICK,"","",rate); //tick
      }
      if (msg==STARTSTOP_COUNTER) {
         assert(sourceAutomatonId=="" && sourceThreadId=="");
         State=counter_off;
      }
      break;
   case counter_off:
      if (msg==STARTSTOP_COUNTER) {
         assert(sourceAutomatonId=="" && sourceThreadId=="");
         State=counter_on;
         SendIntercomMessage(CLOCK_TICK,"","",rate); //start
      }
      break;
   }
   return 1;
}


Well, all in all it's been a bit long (and occasionally bumpy) a road to reaching this point, but given what i know is needed to implement the P2P OS algorithms, there was simply no way of cutting corners: i needed an asynchronous computing framework to implement autonomous agents that talk to each other, i needed this framework to be exclusively standards-based in order to be truly cross-platform, and i wanted complete control over how my applications will integrate in any host environment in order not to rely on any particular host framework (may it be open-source or not). And libposif-1.0 does just that. So, finally, i'm now ready to start working on P2P OS itself.

PS
It is my intention to release libposif as a stand-alone open-source module, but before doing that i'll have to write at least a brief documentation for its API, and i have no clue when/if i'll find enough energy to do that. Until then, as always, anyone interested just drop me a line.

No comments:

Post a Comment


IMPORTANT
You should receive an on-screen confirmation message after entering a comment in the comment form. If you do not see a confirmation message after you enter your comment, please make sure that you have both "cookies" and "third party cookies" enabled in your browser settings, as this is a mandatory condition for posting comments on all google-hosted blogs; additionally, if you found that the above-mentioned settings had to be changed, you'll have to close all browser windows and then restart the browser for the new settings to take effect.


PRIVACY NOTE
All comments on this blog are moderated, i.e. they are set to only appear visible to the public after i approve them. The main reason i enabled comment moderation is to allow you to provide a contact e-mail address if you chose to, and if you'll ask in your comment (which contains your email address) that you do not want your email to become public i will delete the comment and thus protect your email address from being published.