Flip Programming Guide

 

Writing a Flip Class Part 2

This chapter follows the previous in which we declared a Flip class. In this chapter we will see how to manipulate a flip class.

In the following, we will show the step by step construction of an hypothetic MyClass class.

This chapter, like the previous, will assume that your company is called ACME making the application “Product”.

Understanding Transactions

Before reading this section, you should be familiar with the Flip Overview: How it Works chapter. The following will describe the transaction system in greater depth.

The Flip framework works in a transactional way. When you manipulate the model, Flip will build a transaction that will be sent to the server and kept for the undo/redo system.

However when manipulating the framework, you must tell it to begin a transaction. Multiple concurrent transactions cannot occur in Flip (unless using the Flip thread system) and you cannot manipulate the framework outside a transaction.

Flip delivers markers for the beginning of a transaction as well as its end. To ensure that multiple concurrent transactions do not occur, Flip offers a convenient class named TxSessionGuard .

However it is not unrare to cascade several transactions between Flip classes. For example if you have a class MyClass and another MyAggregateClass where the latter is an aggregate of the former, you might have a function in Myclass that start a transaction and call another function of MyAggregateClass . In that case, you won’t want the latter class to start a transaction, but rather manipulate the current opened transaction.

Rather that maintaining a recursive count (like the one you can find in the implementation of recursive mutexes), it was decided that the client of the framework would separate the code that manipulate the start and end of a transaction from the code that actually fills the transaction.

Also, if most of the model functions will start a transaction, fill the transaction and end the transaction in one call, the client of the Flip framework will usually also want to have a transaction that span accross multiple calls to the model. For example if the user click & drag the mouse on screen and the transaction is filled while the mouse is being dragged.

All those cases are handled by the Flip framework. To make the code easier to read and to maintain, we elaborated a robust workflow accross the years, which is essentially a set of conventions to use.

The function to manipulate the model are separated in 3 categories :

Conventions

By convention, the code that setup a transaction and fill it in one call will be prefixed by tx_ . The code that will only fill the model will be prefixed by ext_ . Finally the code that spanned accross multiple calls will contain session .

Internally, the tx_ prefixed and session code will call the ext_ prefixed code. Therefore, if you read code where a tx_ prefixed function calls a tx_ prefixed function, you know that something is wrong. The same goes for a ext_ prefixed function that would call a tx_ prefixed function, and so on.

Also, this means that the only function that can be called outside of the model (that is in the client code) are only the ones prefixed by tx_ and the ones containing session . Calling an ext_ prefixed function outside of the model can be considered as an error. Any developer not following this rule shall be severly punished.

Example

Practically, if we take the example of the class exposed in the previous chapter, MyClass and wish to manipulate the _my_int member, the code would go like this if we were to use all the transaction system that Flip handles.

#include "ohm/flip/Object.h"
#include "ohm/flip/Float64.h"
class MyClass
:  public ohm::flip::Object
{
public:
   static void          declare ();
                        MyClass (ohm::flip::DocumentBase & document);
   virtual              ~MyClass () {}
   void                 tx_set_my_float (float val);
   void                 ext_set_my_float (float val);
   bool                 start_session ();
   void                 commit_session ();
   void                 revert_session ();
   void                 session_set_my_float (float val);
private:
   ohm::flip::Float64   _my_float;
   ohm::flip::TxSessionGuard
                        _tx_session_guard;
};

Note the addition of the _tx_session_guard member. This member does not need to be declared in the declare function, but needs to be linked to the document in MyClass constructor.

MyClass::MyClass (ohm::flip::DocumentBase & document)
:  ohm::flip::Object (document)
,  _my_float (document)
,  _tx_session_guard (document)
{
}

The following will show the implementation of the function members added in the listing above. The function is simple, but this shows clearly the pattern that will be used in every transaction manipulation functions.

ListingMyClass::tx_set_my_float implementation

void  MyClass::tx_set_my_float (float val)
{
   if (!_tx_session_guard.start ())    // 1.
   {
      return;                          // 2.
   }
   ext_set_my_float (val);             // 3.
   _tx_session_guard.commit ();        // 4.
}
  1. Try to start a new transaction...
  2. ...A transaction was already started (for example one that spans accross multiple calls)
  3. Cascade the actual transaction fill to the ext_ prefixed function
  4. End the current transaction. After this call, a new transaction can be started.

ListingMyClass::ext_set_my_float implementation

void  MyClass::ext_set_my_float (float val)
{
   _my_float = val;
}

The above listing is quite forward : _my_float is assigned val . This will generate a piece of the transaction.

The following listing will show the implementation of the session based function. This is essentially a scattered version of the tx_ prefixed function, with an additionnal guard.

ListingMyClass::session_* implementation

bool  MyClass::start_session ()
{
   return _tx_session_guard.start ();
}
void  MyClass::commit_session ()
{
   _tx_session_guard.commit ();
}
void  MyClass::revert_session ()
{
   _tx_session_guard.revert ();
}
void  MyClass::session_set_my_float (float val)
{
   flip::TxSessionGuard::AutoWield auto_wield (_tx_session_guard);   // 1.
   ext_set_my_float (val);                                           // 2.
}
  1. The guard will ensure that the a transaction is present and will assert otherwise
  2. We call the function that fill the transaction

Note that a session can be reverted. If the user cancel the action after it was initiated (for example by switching to another application while dragging an object on screen), the transaction can be cancelled, and will be automatically rollbacked from where it was.

The value types, enum types and opaque data classes are quite simple in their use. They just hold data. However correct design considerations should be taken when using container classes or the signaling type. This will be discussed in the next 2 chapters.