Thursday, April 21, 2016

Effective Enum Handling in UI

Enums in OpendTect have three different aspects:
  1. The enum value
  2. The Key string (mostly used in IOPar, also going to text files)
  3. The UiString as shown in the user interface, say in a uiComboBox.

To enable a convenient transformation from one aspect to another we have the class EnumDef which manifests itself in a derived template class  EnumDefImpl<ENUM>. For each enum type, the corresponding EnumDef should be declared in the header file using the macro mDeclareEnumUtils or mDeclareNameSpaceEnumUtils. See enums.h for details. For example:

class MyClass
{
    enum State    { Good, Bad, OK, Undef };
                   mDeclareEnumUtils(State)


Then in the source file we need to define the keys using the macro mDefineEnumUtils:

 mDefineEnumUtils(MyClass,State,"My class state")
    { "Good", "Bad", "OK", "Undefined", 0 }

The strings above are the keys that would be useful in an IOPar or while writing to a file. By default the same strings will also be used in the UI. But if you want to use a different set of strings in UI, you can optionally define the uiStrings:

template <>
void EnumDefImpl<MyClass::State>::init()

{
    uistrings_ += tr("Lovely");
    uistrings_ += tr("Terrible");
    uistrings_ += toUiString("OK");
    uistrings_ += tr("Not sure");  
}

To use them in the UI, you can directly pass the EnumDef to classes like uiComboBox and uiStringListInpSpec. To set the value you can use:

combobox_->setCurrentItem( MyClass::StateDef().indexOf(oldval) );

And to get the value from the UI as enum you can do:

MyClass::State inp = MyClass::StateDef().getEnumValForIndex(
                                combobox_->currentItem() );

While reading from or writing to an IOPar, you should always use the enum key and the functions:

    EnumDef::indexOf(const char*)
    EnumDef::getKeyForIndex(int)

If you want more customization for a particular UI class, you can make your own copy of the EnumDef and make modifications like changing the uiStrings, removing some of the enums etc. For example see uiprestackattrib.cc.

The bottom line is, we have a lot of tools available, but to keep the code safe we need to take care of the following:
  • Always use the enum type (and not their int value) as class members or function arguments.
  • In IOPar or text files always write the key strings (not the int values or strings derived from uiStrings).
  • For existing enum types you can modify the uiStrings but never change the key strings if you want to maintain any backward compatibility with old files.


Monday, April 4, 2016

Changes to the DataPack and DataPackMgr classes

Following up on last week's blog, we are going further with re-engineering of some basic classes in preparation for the next major release.

To ensure thread safety, the DataPack classes are receiving an overhaul. The largest change is that the DataPack class is now reference counted, sharing the code with the rest of all reference counted objects. This ensures thread safety and a central knowledge about reference counting.

The major changes are:
  • DataPack::obtain() is replaced by ref().
  • DataPack::release() is replaced by unRef()
  • DataPackMgr::addAndObtain() going away. Your code should instead simply call DataPackMgr::add(). Care should be taken that you reference your object apropriately, as add() only makes the DataPackMgr aware of the object, it does not reference it.
  • DataPackMgr::obtain() is replaced by DataPackMgr::get() to get the object.
  • DataPackMgr::release() should be replace by calling either DataPackMgr::unRef(ID), or calling the unRef() on the object itself.
The old functionality still works as before, but we are phasing out its usage over the coming weeks. For the next major release, these functions will be marked as obsolete, and eventually be removed.

There is also new, nice functionality in there, such as DataPackMgr::getAndCast<T>( id ). By using it the following code:

RefMan<PreStack::Gather> = 0;
DataPack* dp = DPM(DataPackMgr::FlatID).obtain( id );
if ( dp )
{
    mDynamicCast( PreStack::Gather*, gather, dp );
    if ( !gather ) DPM(DataPackMgr::FlatID).release( dp );
}

can be replaced by:

RefMan<PreStack::Gather> gather 
        = DPM(DataPackMgr::FlatID).getAndCast<PreStack::Gather>(id);

As part of these changes, we are also investigating the thread safety at various places. So you can expect some API changes. For example, we are more and more using RefMan<T> rather than bare pointers.

Friday, April 1, 2016

Weak pointers for reference counted objects

OpendTect has used reference counting for more than ten years in the visualization and the earth model libraries. We are now taking this one step further and introducing a weak pointer for reference-counted objects. In order to do so, we had to change the way reference counted objects work. Previously, our reference-counted objects used a macro which implemented the necessary functions. That has now been replaced by inheritance:

class A : public RefCount::Referenced
{
protected:
             ~A();
};

The good thing is that apart from this, the API remains the same, so no changes are needed by classes using reference counted classes.

This change enables us to do two things:

  1. Use RefMan<A> as class variables with a forward declared A. Previously, the compiler required a full definition of the A variable. Hence we can include less files and get slimmer dependency trees:

    #include <refcount.h>
    class A; //Forward declaration of A

    class B
    {
    public:
                    B();
    private:
        RefMan<A>   a_;
    };
  2. Use weak pointers. Weak pointers point to objects as long as objects are alive, but become NULL when the object they point to goes out of scope:

    #include <refcount.h>

    class A;

    class B 
    {
    public:
                    B(A* a) : a_(a) {}    void        compute();
    private:
        WeakPtr<A>  a_;
    };

    The variable a_ will be a valid pointer to the instance of A as long as the instance is alive. When the instance goes out of scope, it will be set to NULL. If you actually want to use the pointer in a_, you have to retrieve it using the WeakPtr<A>::get() function:

    void B::compute()
    {
        RefMan<A> a = a_.get();
        //Instance is reffed if still alive

        if ( !a ) //Check if it is alive
             return;

        a->doSomething();
    } //a goes out of scope, and A instance is unreffed
All reference counting is threadsafe, as it is managed by atomic variables. The new functionality is available in our master branch. It can unfortunately not be ported to the stable branch due to ABI restrictions.