[interfacekit] BLooper reusability

Content-Type: text/plain;
        charset="iso-8859-1"
Content-Transfer-Encoding: quoted-printable

Hello you guys!


    Please read the attachment!

    I've discused this for a few weeks with Erik.
        He said that it is OK.
        What do you say?



Adi. 


-- HTML Attachment decoded to text by Ecartis --
-- File: BLooperR.html

 







OpenBeOS 

IK Team 









BLooper reusability 









About: A BLooper object is a thread who creates a "message loop" that
receives messages that are sent or posted to the BLooper. 





The Problem: 

Is that this class leaves very few room for reusability! Why? Because we
cannot control the source of our incoming messages. For example: I would
liketo read raw data from a second port and use BLooper's ability to handle
messages(filtering and dispatching)! Or..., read raw data from a memory
area(or a serial port) translate it into a BMessage and then use BLooper to
dispatch that message! Now, how would I do that with our current BLooper
implementation? 





Well... the only solution is to... override 'task_looper()' method! Yes...
very ugly, because we don't know what happens in there! And, by doing that,
we destroy the hole purpose of BLooper –the ability to handle
messages!If, anyone, would like to extend the abilities of BLooper, he'll
have to learn the most basic principles of BLooper, restore the ability to
handle messages and then, add its specific code! He will have to (re)write
code for handling scripting messages, for filtering messages and for finding
the right handler for a certain message. Well... don't you think that is too
much for an average developer? I do! Because I had to face this issue when
implementing BWindow! 

There are also BIG problems if one overrides 'task_looper()' without
rewriting code for message handling. Because BApplication keeps track of
every BLooper that is instantiated, when quiting, it would send a _QUIT_
message to any one of them. Since 'task_looper()' is overridden, this would
accomplish nothing! The same would happen when a thread calls
BLooper::Quit(). Moreover, if the port queue is, for some reason, full,
both,the BApplication and the thread calling Quit(), would block! 





Since we do not want that, the most simple solution to is to copy-paste the
code from BLooper::task_looper() into OurClass::task_looper() and then
modifythis code to suite our needs. But, the average(and even a more
experienced) developer doesn't have to have OpenBeOS source code, so this
leads me to find an... 

Answer: 

We can modify BLooper's code like this: 





/*

        This is the original version!

*/

//-----------------------------------------------------------------------
class BLooper : public BHandler {
public:
    ...
protected:
        // called from overridden task_looper
        
    BMessage*       MessageFromPort(bigtime_t = B_INFINITE_TIMEOUT);

private:
    ...

virtual void            _ReservedLooper1();
virtual void            _ReservedLooper2();
virtual void            _ReservedLooper3();
virtual void            _ReservedLooper4();
virtual void            _ReservedLooper5();
virtual void            _ReservedLooper6();








    ... 

            void*           ReadRawFromPort(int32* code,
                                    bigtime_t tout = B_INFINITE_TIMEOUT);

            BMessage*       ReadMessageFromPort(
                                    bigtime_t tout = B_INFINITE_TIMEOUT);

    virtual BMessage*       ConvertToMessage(void* raw, int32 code);

    virtual void            task_looper();

    ...

}
//-----------------------------------------------------------------------

...

BMessage* BLooper::MessageFromPort(bigtime_t timeout)
{

    return ReadMessageFromPort(timeout);
}

//-----------------------------------------------------------------------

void* BLooper::ReadRawFromPort(int32* msgcode, bigtime_t tout)
{

    int8* msgbuffer = NULL;
    ssize_t buffersize;
    ssize_t bytesread;


    if (tout == B_INFINITE_TIMEOUT)
        buffersize = port_buffer_size(fMsgPort);
    else
    {                               this is a flaw  \/ should've been
B_TIMEOUT
        buffersize = port_buffer_size_etc(fMsgPort, 0, tout);
        if (buffersize == B_TIMED_OUT || buffersize == B_BAD_PORT_ID ||
            buffersize == B_WOULD_BLOCK)
        {
            return NULL;
        }
    }

    if (buffersize > 0){
        msgbuffer = new int8[buffersize];
    }
    if (tout == B_INFINITE_TIMEOUT)
    {
        bytesread = read_port(fMsgPort, msgcode, msgbuffer, buffersize);

    }
    else
    {
        bytesread = read_port_etc(fMsgPort, msgcode, msgbuffer, buffersize,
                                 B_TIMEOUT, tout);
    }

    return msgbuffer;
}

//--------------------------------------------------------------------------
----

BMessage* BLooper::ReadMessageFromPort(bigtime_t tout)
{
    int32       msgcode;
    BMessage*   msg;
    void*       msgbuffer;

    msgbuffer   = ReadRawFromPort(&msgcode, tout);

    msg         = ConvertToMessage(msgbuffer, msgcode);




    if (msgbuffer)
        delete[] msgbuffer;

    return msg;

}






























































//--------------------------------------------------------------------------
----

BMessage* BLooper::ConvertToMessage(void* raw, int32 code)
{

    BMessage* bmsg = new BMessage(code);

    if (raw != NULL)
    {
        if (bmsg->Unflatten((const char*)raw) != B_OK)
        {
            delete bmsg;
            bmsg = NULL;
        }
    }

    return bmsg;
}

//--------------------------------------------------------------------------
----

void BLooper::task_looper()
{

    //  Check that looper is locked (should be)
    AssertLocked();
    //  Unlock the looper
    Unlock();



    //  loop: As long as we are not terminating.
    while (!fTerminating)
    {

        // TODO: timeout determination algo
        //  Read from message port (how do we determine what the timeout
is?)
        BMessage* msg = MessageFromPort();

        //  Did we get a message?
        if (msg){
            //  Add to queue
            fQueue->AddMessage(msg);
        }

        //  Get message count from port
        int32 msgCount = port_count(fMsgPort);
        for (int32 i = 0; i < msgCount; ++i){
            //  Read 'count' messages from port (so we will not block)
            //  We use zero as our timeout since we know there is stuff
there
            msg = MessageFromPort(0);
            //  Add messages to queue
            if (msg)
                fQueue->AddMessage(msg);
        }

        bool dispatchNextMessage = true;
        //  loop: As long as there are messages in the queue and the port is
        //       empty... and we are not terminating, of course.
        while (!fTerminating && dispatchNextMessage)
        {
            //  Get next message from queue
            fLastMessage = fQueue->NextMessage();

            //  Lock the looper
            Lock();
            if (!fLastMessage)
            {
                // No more messages: Unlock the looper and terminate the
                // dispatch loop.
                dispatchNextMessage = false;
            }
            else
            {
                //  Get the target handler
                //  Use BMessage friend functions to determine if we are
using the
                //  preferred handler, or if a target has been specified
                BHandler* handler;
                if (_use_preferred_target_(fLastMessage))
                    handler = fPreferred;
                else
                    
gDefaultTokens.GetToken(_get_message_target_(fLastMessage),
                                             B_HANDLER_TOKEN,
                                             (void**)&handler);

                if (!handler)
                {
                    handler = this;
                }

                //  Is this a scripting message?
                if (fLastMessage->HasSpecifiers())
                {
                    int32 index = 0;
                    // Make sure the current specifier is kosher
                    if (fLastMessage->GetCurrentSpecifier(&index) == B_OK)
                        handler = resolve_specifier(handler, fLastMessage);
                }
                
                if (handler)
                {
                    //  Do filtering
                    handler = top_level_filter(fLastMessage, handler);

                    if (handler && handler->Looper() == this)
                        DispatchMessage(fLastMessage, handler);
                }
            }

            //  Unlock the looper
            Unlock();

            //  Delete the current message (fLastMessage)
            if (fLastMessage)
            {
                delete fLastMessage;
                fLastMessage = NULL;
            }

            //  Are any messages on the port?
            if (port_count(fMsgPort) > 0)
            {
                //  Do outer loop
                dispatchNextMessage = false;
            }
        }
    }
}

//--------------------------------------------------------------------------
----

BLooper::~BLooper()
{
    if (fRunCalled && !fTerminating)
    {
        debugger("You can't call delete on a BLooper object "
                 "once it is running.");
    }

    Lock();

    // In case the looper thread calls Quit() fLastMessage is not deleted.
    if (fLastMessage)
    {
        delete fLastMessage;
        fLastMessage = NULL;
    }




    BMessage* msg;

    // Close the message port and read and reply to the remaining messages.
    if (fMsgPort > 0){
        close_port(fMsgPort);
    }






    // Clear the queue so our call to IsMessageWaiting() below doesn't give
    // us bogus info
    while ((msg = fQueue->NextMessage())){
        delete msg;         // msg will automagically post generic reply
    }

    do
    {
        msg = ReadMessageFromPort(0);
        if (msg)
        {
            delete msg;     // msg will automagically post generic reply
        }
    } while (IsMessageWaiting());

    delete fQueue;
    delete_port(fMsgPort);

    // Clean up our filters
    SetCommonFilterList(NULL);

    BObjectLocker<BLooperList> ListLock(gLooperList);
    RemoveHandler(this);

    // Remove all the "child" handlers
    BHandler* child;
    while (CountHandlers())
    {
        child = HandlerAt(0);
        if (child)
        {
            RemoveHandler(child);
        }
    }

    UnlockFully();
    RemoveLooper(this);
    delete_sem(fLockSem);
} 

/*

 This is the modified version!

*/

//-----------------------------------------------------------------------
class BLooper : public BHandler {
public:
 ...
protected:
 // for backward (& binary) compatibility only (!!!)
 // REMOVE in future versions
 BMessage*       MessageFromPort(bigtime_t = B_INFINITE_TIMEOUT);

private:
 ...

virtual void            _ReservedLooper1();
virtual void            _ReservedLooper2();
virtual void            _ReservedLooper3();
virtual void            _ReservedLooper4();


        // ADD this method
    virtual void CloseConnectionsToOtherSources();

        // ADD this method 
    virtual void*        ReadRawFromOtherSources(int32* code);

    ...
        // ADD this method
      BMessage*    ReadMessageFromOtherSources();

            void*           ReadRawFromPort(int32* code,
                               bigtime_t tout = 0); // for compatibility

            BMessage*       ReadMessageFromPort(
                               bigtime_t tout = 0); // for compatibility

    virtual BMessage*       ConvertToMessage(void* raw, int32 code);

    virtual void            task_looper();

    ...

}
//-----------------------------------------------------------------------

...

BMessage* BLooper::MessageFromPort(bigtime_t timeout)
{
     //for backward compatibility only (!!!)
    return ReadMessageFromPort(timeout);
}

//-----------------------------------------------------------------------

void* BLooper::ReadRawFromPort(int32* msgcode, bigtime_t tout)
{

    int8*           msgbuffer = NULL;
    ssize_t         buffersize;
    ssize_t         bytesread;


// NOTE: about 'tout'
        // it should ALWAYS be 0! We want to imediately return if
// there is no message in the port's queue!
//
// it can be different if called from the protected method
// 'MessageFromPort(bigtime_t)'
// THIS IS FOR BACKWARD COMPATIBILITY ONLY!!!!!

    buffersize = port_buffer_size_etc(fMsgPort, B_TIMEOUT, tout);

    if (buffersize == B_TIMED_OUT || buffersize == B_BAD_PORT_ID ||
        buffersize == B_WOULD_BLOCK)
    {
        *msgcode = _B_NO_MESSAGE_; // it should be <0

        return NULL;
    }

    if (buffersize > 0){
        msgbuffer = new int8[buffersize];
        read_port_etc(  fMsgPort, code, msgbuffer,
                        buffersize, B_TIMEOUT, tout);
    }



    return msgbuffer;
}

//--------------------------------------------------------------------------
----

BMessage* BLooper::ReadMessageFromPort(bigtime_t tout){

    int32           msgcode;
    BMessage*       msg;
    void*           msgbuffer;

    msgbuffer       = ReadRawFromPort(&msgcode, tout);

    if (msgcode != _B_NO_MESSAGE_)
        msg         = ConvertToMessage(msgbuffer, msgcode);
    else
        msg = NULL;

    if (msgbuffer)
        delete[] msgbuffer;

    return msg;

}

//--------------------------------------------------------------------------
----

void* BLooper::ReadRawFromOtherSources(){

    *msgcode = _B_NO_MESSAGE_; // it should be <0
    return NULL;
}

//--------------------------------------------------------------------------
----

BMessage* BLooper::ReadMessageFromOtherSources(){

    int32           msgcode;
    BMessage*       msg;
    void*           msgbuffer;

    msgbuffer       = ReadRawFromOtherSources(&msgcode);

    if (msgcode != _B_NO_MESSAGE_)
        msg         = ConvertToMessage(msgbuffer, msgcode);
    else
        msg = NULL;

    if (msgbuffer)
        delete[] msgbuffer;

    return msg;

}

//--------------------------------------------------------------------------
----

void BLooper::CloseConnectionsToOtherSources(){
/*
   close connections
    examples:
      close_port(port);
      write a 'close' message then...
         set_area_protection( area, B_READ_AREA );
      [other write protection mechanism];

    handle(DELETE) messages that are still in the port queue, or in the
    specified memory area, posting replies as needed.
     examples:
       [if we read from a second port]
       BMessage *msg = ReadMessageFromOtherSources();
       delete msg; // msg will post generic reply.

       [if we read from a memory area]
       int32           msgcode;
       void*           msgbuffer;

       msgbuffer       = ReadRawFromOtherSources(&msgcode);
       while (msgcode != _B_NO_MESSAGE_){
          msgbuffer       = ReadRawFromOtherSources(&msgcode);
          if (msgbuffer)
             delete[] msgbuffer;
       }
*/
}

//--------------------------------------------------------------------------
----

BMessage* BLooper::ConvertToMessage(void* raw, int32 code)
{

    BMessage* bmsg = new BMessage(code);

    if (raw != NULL)
    {
        if (bmsg->Unflatten((const char*)raw) != B_OK)
        {
            delete bmsg;
            bmsg = NULL;
        }
    }

    return bmsg;
}

//--------------------------------------------------------------------------
----

void BLooper::task_looper()
{

    //  Check that looper is locked (should be)
    AssertLocked();
    //  Unlock the looper
    Unlock();

    bool dispatchNextMessage = false;

    //  loop: As long as we are not terminating.
    while (!fTerminating)
    {

        // TODO: timeout determination algo
        // Read from message port (how do we determine what the timeout is?)


        // read user-made BMessages - I think they should have higher
priority!
        while ( msg = ReadMessageFromOtherSources() ){
            fQueue->AddMessage(msg);
            dispatchNextMessage = true;
        }

        // read BMessages that BLooper receives
        while ( msg = ReadMessageFromPort(0) ){
            fQueue->AddMessage(msg);
            dispatchNextMessage = true;
        }

        // if there are no messages, tell the kernel to reschedule this
thread
        //    in order not to comsume its timeslice in vain.
        if (!dispatchNextMessage)
            snooze(5);


        //  loop: As long as there are messages in the queue and
        //       and we are not terminating.
        while (!fTerminating && dispatchNextMessage)
        {
            //  Get next message from queue
            fLastMessage        = fQueue->NextMessage();

            //  Lock the looper
            Lock();
            if (!fLastMessage)
            {
                // No more messages: Unlock the looper and terminate the
                // dispatch loop.
                dispatchNextMessage = false;
            }
            else
            {
                //  Get the target handler
                //  Use BMessage friend functions to determine if we are
using the
                //  preferred handler, or if a target has been specified
                BHandler* handler;
                if (_use_preferred_target_(fLastMessage))
                    handler = fPreferred;
                else
                   
gDefaultTokens.GetToken(_get_message_target_(fLastMessage),
                                             B_HANDLER_TOKEN,
                                             (void**)&handler);

                if (!handler)
                {
                    handler = this;
                }

                //  Is this a scripting message?
                if (fLastMessage->HasSpecifiers())
                {
                    int32 index = 0;
                    // Make sure the current specifier is kosher
                    if (fLastMessage->GetCurrentSpecifier(&index) == B_OK)
                        handler = resolve_specifier(handler, fLastMessage);
                }
                
                if (handler)
                {
                    //  Do filtering
                    handler = top_level_filter(fLastMessage, handler);

                    if (handler && handler->Looper() == this)
                        DispatchMessage(fLastMessage, handler);
                }
            }

            //  Unlock the looper
            Unlock();

            //  Delete the current message (fLastMessage)
            if (fLastMessage)
            {
                delete fLastMessage;
                fLastMessage = NULL;
            }







        }
    }
}

//--------------------------------------------------------------------------
----

BLooper::~BLooper()
{
    if (fRunCalled && !fTerminating)
    {
        debugger("You can't call delete on a BLooper object "
                 "once it is running.");
    }

    Lock();

    // In case the looper thread calls Quit() fLastMessage is not deleted.
    if (fLastMessage)
    {
        delete fLastMessage;
        fLastMessage = NULL;
    }

    // close other connections and delete the messages that are pending
    CloseConnectionsToOtherSources();

    BMessage* msg;

    // Close the message port and read and reply to the remaining messages.
    if (fMsgPort > 0){
        close_port(fMsgPort);
    }

    // delete messages form BLooper's port queue
    while (msg = ReadMessageFromPort(0)){
        delete msg
    }
    delete_port(fMsgPort);

    // delete BMessages from BLooper's queue
    while ((msg = fQueue->NextMessage())){
        delete msg;         // msg will automagically post generic reply
    }
    delete fQueue;












    // Clean up our filters
    SetCommonFilterList(NULL);

    BObjectLocker<BLooperList> ListLock(gLooperList);
    RemoveHandler(this);

    // Remove all the "child" handlers
    BHandler* child;
    for (int i=0; (child = HandlerAt(0)); i++ ){
        RemoveHandler(child);
    }






    UnlockFully();
    RemoveLooper(this);
    delete_sem(fLockSem);
} 





The idea, is to use 2 private virtual functions: 'ReadRawFromOtherSources'
and 'CloseConnectionsToOtherSources'. 

The first, will give to our developer, the ability to read from other
sources, and the second, will help him in doing whatever it must be done
whenBLooper has to quit(close connections, post replies for unhandled
messages, etc.). 

ReadMessageFromOtherSources is a simple private function, similar to
ReadMessageFromPort. The only difference between them is that, the first
getsits raw data from ReadRawFromOtherSources. I considered adding this
function, because, after getting raw datas, it uses ConvertToMessage to
translate that raw data into a BMessage. Thus, the code needed for
translation between regular messages and BMessages, can be entirely written
in ConvertToMessage. 

Our task_looper() will be modified, to read with a timeout of 0 from
BLooper's standard port and from other incoming sources. If no message is
received, in order not to use our timeslice in vain, we will 'snooze' for a
bit, making the kernel to reschedule our thread. 

In BLooper destructor, before purging unhandled messages from BLooper's
queues (port &BMessage), we will make a call to
CloseConnectionsToOtherSources, to give the developer the possibility to
cleanup his work. 





Advantages and Disadvantages: 

In its current implementation, BLooper is poorly designed for being
reusable.I think the architecture I proposed, resolves this problem. It
givesthe developer the ability to read data from other sources, while, at
thesame time take advantage of BLooper's powerful messaging infrastructure. 

As disadvantages, the biggest one that I see now, is the fact that
'task_looper()' is virtual!!! As explained before, if someone overrides this
method, BAD things could happen. I think this should be resolved in OBOS R2!
How? Well... Erik's idea was very interesting: BThread! 









Thank you for you patience, 

Adi. 


Other related posts: