BLooper

Introduction

In Haiku OS, the BLooper class plays a central role in message handling and dispatching. As the heart of the messaging system, BLooper manages a set of message handlers and provides a message loop for processing incoming messages. In this article, we'll delve into the BLooper class, its key features, and its usage in Haiku OS.

What is BLooper?

  • It is inherited from the BHandler, means it have all the capabilities of the BHandler. BHandler is the class that can receive and handle messages.

    image-113.png
     
  • BLooper is the base class for creating the event loop in Haiku Applications.

This class is defined at location:

  • Header file: headers/os/app/Looper.h
  • src file: src/kits/app/Looper.cpp

The BLooper class is responsible for receiving, processing, and dispatching messages within an application. It creates a message loop for receiving and processing messages. By running in a separate thread, the message loop continuously waits for incoming messages and dispatches them to the appropriate message handlers. BLooper provides a flexible and efficient mechanism for asynchronous message handling within the system.

The message loop runs in a separate thread that's spawned (and told to run) when the BLooper receives a Run() call.

When an object of this class is created, the message loop can be started with Run(). This spawns the thread that receives messages and processes messages. Messages are actually passed on to handlers that are associated with this looper. By default there is always one handler available: the looper itself. To 'quit' a looper, you should pass a B_QUIT_REQUESTED message using one of the message post functions. When a looper receives such a request, it will delete itself. As such, looper should always be created on the heap (with new), and never on the stack i.e MyLooper* myLooper = new MyLooper();

Its Function

BLooper manages message handling for an application. It runs an event loop, waiting for messages to arrive and then dispatches them to the appropriate handler.

Key Components of BLooper

  1. Message Queue: Each BLooper has its own message queue where messages are stored until they can be processed.
  2. Message Handlers: Messages are dispatched to message handlers, which are BHandler objects that are attached to the BLooper.
    1. BLooper can have multiple BHandler objects attached to it.
  3. Event Loop: The BLooper runs an event loop, constantly checking for new messages in its message queue. When a message is received, it is removed from the queue and dispatched to the appropriate handler.

Real Life Example:

Imagine you are playing a game and you are the boss. You have a special mailbox where your friends can send you messages.

1 Your Mailbox (Message Queue):

Imagine you have a magical mailbox that's always open, waiting for messages from your friends. This mailbox represents the message queue in our example. It's like a special box in your room that collects all the messages.

2 Your Friends (Message Senders):

Your friends, who represent different parts of the computer, send messages to your magical mailbox. Each friend has something different to say, just like different parts of the computer send different types of messages.

3 Your Role Reading Messages as BLooper (Event Loop):

You sit by your mailbox all day, waiting for new messages. This is called the "event loop". Whenever a new message arrives in your mailbox, you take it out and read it.

You are like a special messenger who sits by your magical mailbox all day, waiting for new messages to arrive. When a new message arrives, you take it out of the mailbox and decide what to do with it.

4 Your Reaction to Messages (Message Handling):

When you receive a message from a friend, you read it and decide what to do next based on what your friend asked or said. Similarly, in the computer, when you, acting as the special messenger (BLooper), receive a message from the mailbox (message queue), you decide what action to take based on the content of the message.

Your Magical Mailbox (Message Queue):

    ---------------------
   |    Message Queue    |
   |---------------------|
   |   Message 1         |
   |   Message 2         |
   |   Message 3         |
   |        .            |
   |        .            |
   |        .            |
    ---------------------

You (BLooper):

      ----------------------------------------------
     |                 BLooper (You)                |
     |--------------------------------------------  |
     |   Check Mailbox (Message Queue) for          |
     |   new messages                               |
     |                                              |
     |   If a message arrives:                      |
     |   - Take the message out of the mailbox      |
     |   - Decide what to do with the message       |
      ----------------------------------------------

Functions

1 Constructor

BLooper(const char* name = NULL,
        int32 priority = B_NORMAL_PRIORITY,
        int32 portCapacity = B_LOOPER_PORT_DEFAULT_CAPACITY);
BLooper(BMessage* archive);

Use:

Constructs a BLooper object with the given name, priority, and port capacity.

  • name: A unique name for the looper, used for debugging purposes.
  • priority: Describes the amount of CPU attention the message loop will receive once it starts running. 
  • portCapacity: The number of messages the BLooper can hold in its "message port".
// Port (Message Queue) Capacity
#defineB_LOOPER_PORT_DEFAULT_CAPACITY  200
nameThe name of the looper.
priorityThe priority of the message thread of this looper. The default priority should be good enough for most tasks. Also, some derived versions of BLooperwill use a specialized priority. So it is advised to leave this setting at the default, unless you know why you would like another setting.
portCapacityLoopers use ports to send and receive messages (see the kernel kit). Ports have a maximum capacity; if there are so many messages queued that the port is full, all other incoming messages are dropped. There are situations where the size of the port should be different from the default. This might be when your looper receives a lot of messages, or if the message handling thread runs at a lower priority than normal, which would decrease the processing speed. Finding a suitable value for these custom scenarios would be done by testing.

Port Capacity:

  • Purpose: Messages sent to a BLooper first show up in a port before they are moved to the BMessageQueue.
  • Blocking Behavior: If the port is full, subsequent message senders will be blocked until space becomes available.
  • Default Capacity: The default port capacity is B_LOOPER_PORT_DEFAULT_CAPACITY (200 messages).

Priority Levels:

A set of priority values are defined in kernel/OS.h from lowest to highest, they are:

ConstantDescription

B_LOW_PRIORITY

For threads running in the background that shouldn't interrupt other threads.

B_NORMAL_PRIORITY

For all ordinary threads, including the main thread.

B_DISPLAY_PRIORITY

For threads associated with objects in the user interface, including window threads.

B_URGENT_DISPLAY_PRIORITY

For interface threads that deserve more attention than ordinary windows.

B_REAL_TIME_DISPLAY_PRIORITY

For threads that animate the on-screen display.

B_URGENT_PRIORITY

For threads performing time-critical computations.

B_REAL_TIME_PRIORITY

For threads controlling real-time processes that need unfettered access to the CPUs.

2 Run()

The Run() method is called to start the event loop of the BLooper.

  • Internally it creates and starts a new thread which serves as the entry point of the event loop.

How it works:

  1. Event Loop Initialization:
    When you call Run(), the BLooper object initializes its event loop. This event loop will continuously check for new messages in the message queue.
  2. Message Processing:
    As soon as a message arrives in the message queue, the BLooper retrieves it and dispatches it to the appropriate message handler.
  3. Blocking Function:
    The Run() function typically blocks the calling thread until the BLooper is told to quit. This means that the program will not continue beyond the Run() call until the BLooper stops running.

Definition of Run():

thread_id
BLooper::Run()
{
    AssertLocked(); // Making sure the BLooper object is locked

    if (fRunCalled) {
        // Not allowed to call Run() more than once
        debugger("can't call BLooper::Run twice!");
        return fThread;
    }

    // Spawn a new thread to execute the _task0_ function
    fThread = spawn_thread(_task0_, Name(), fInitPriority, this);
    if (fThread < B_OK)
        return fThread;

    // Check if the message port is initialized
    if (fMsgPort < B_OK)
        return fMsgPort;

    fRunCalled = true; // Marking that Run() has been called
    Unlock(); // Unlocking the BLooper object

    // Resume the thread to start executing the event loop
    status_t err = resume_thread(fThread);
    if (err < B_OK)
        return err;

    return fThread; // Return the thread ID of the newly created thread
}

Explanation:

  • AssertLocked():
    • This function ensures that the BLooper object is locked when BLooper::Run() is called. It's a safety measure to ensure that the object is in a consistent state.
  • fRunCalled:
    • This is a boolean flag that indicates whether BLooper::Run() has been called before. If BLooper::Run() is called more than once, it will trigger a debugger breakpoint with an error message.
  • spawn_thread():
    • This function creates a new thread that will execute the _task0_ function.
      • _task0_ is a static function inside the BLooper class that serves as the entry point for the newly created thread.
      • Name():
        • This method returns the name of the BLooper object. It's used as the name of the thread.
      • fInitPriority:
        • This is the initial priority of the thread.
      • this:
        • The last argument passed to spawn_thread() is a pointer to the BLooper object. It is used to pass the BLooper object's address to the _task0_ function.
  • fMsgPort:
    • This is the message port associated with the BLooper object.
    • If it's not initialized (< B_OK), the BLooper::Run() method will return an error.
  • fRunCalled = true:
    • After successfully creating the new thread, fRunCalled is set to true to mark that BLooper::Run() has been called.
  • Unlock():
    • Once everything is set up, the BLooper object is unlocked, allowing other threads to access it.
  • resume_thread():
    • This function starts executing the thread that was created by spawn_thread().
  • Return Values:
    • If everything goes well, the function returns the thread ID (fThread) of the newly created thread.
    • If any error occurs during the process, an error code is returned.

3 MessageReceived()

virtual void BLooper::MessageReceived(BMessage* message);
  • This function is called whenever a message is received by the BLooper.
  • Override this function in your subclass to provide custom message handling.
void MyLooper::MessageReceived(BMessage* message) {
    // Check the "what" code of the message
    switch (message->what) {
        // Handle different types of messages differently
        case MESSAGE_TYPE_1:
            // Code to handle MESSAGE_TYPE_1
            break;
        case MESSAGE_TYPE_2:
            // Code to handle MESSAGE_TYPE_2
            break;
        default:
            // Code to handle other types of messages
            // If you don't handle a specific message type, call the parent class's MessageReceived
            BLooper::MessageReceived(message);
            break;
    }
}

4 PostMessage()

status_t		PostMessage(uint32 command);
status_t		PostMessage(BMessage* message);
status_t		PostMessage(uint32 command, BHandler* handler,
								BHandler* replyTo = NULL);
status_t		PostMessage(BMessage* message, BHandler* handler,
								BHandler* replyTo = NULL);

PostMessage(uint32 command):

  • Posts a message with the specified command to the BLooper's message queue.
  • This version of PostMessage() is used when you just need to send a simple command message without any additional data.

Example:

#include <Application.h>
#include <Looper.h>
#include <Message.h>
#include <stdio.h>

#define MESSAGE_COMMAND_1 'Cmd1'

class MyLooper : public BLooper {
public:
    MyLooper() : BLooper("MyLooper") {}

    virtual void MessageReceived(BMessage* message) {
        switch (message->what) {
            case MESSAGE_COMMAND_1:
                printf("Received Command 1\n");
                break;
            default:
                BLooper::MessageReceived(message);
                break;
        }
    }
};

int main() {
    MyLooper* myLooper = new MyLooper();
    
    // Post a message with command MESSAGE_COMMAND_1
    myLooper->PostMessage(MESSAGE_COMMAND_1);
    
    // Start the event loop
    myLooper->Run();
    
    delete myLooper;
    
    return 0;
}

PostMessage(BMessage* message):

  • It receives a pointer to the BMessage object to be posted.
  • Posts the specified BMessage to the BLooper's message queue.
  • Use this version of PostMessage() when you need to send a message containing more complex data

Example:

#include <Application.h>
#include <Looper.h>
#include <Message.h>
#include <stdio.h>

#define MESSAGE_TYPE 'Type'
#define MESSAGE_DATA 'Data'

class MyLooper : public BLooper {
public:
    MyLooper() : BLooper("MyLooper") {}

    virtual void MessageReceived(BMessage* message) {
        switch (message->what) {
            case MESSAGE_TYPE: {
                int32 data;
                if (message->FindInt32("data", &data) == B_OK) {
                    printf("Received Message Type with data: %d\n", data);
                }
                break;
            }
            default:
                BLooper::MessageReceived(message);
                break;
        }
    }
};

int main() {
    MyLooper* myLooper = new MyLooper();

    // Create a message
    BMessage* msg = new BMessage(MESSAGE_TYPE);
    msg->AddInt32("data", 42); // Add some data to the message
    
    // Post the message
    myLooper->PostMessage(msg);
    
    // Start the event loop
    myLooper->Run();

    delete msg;
    delete myLooper;
    
    return 0;
}

PostMessage(uint32 command, BHandler* handler, BHandler* replyTo = NULL):

  • Parameters:
    • command: A code indicating the type of the message.
    • handler: The BHandler to which the message should be sent.
    • replyTo(optional): The BHandler to which the replies to this message should be sent.
  • Posts a message with the specified command to the BLooper's message queue, directing it to the specified BHandler.
  • This version of PostMessage() is useful when you need to send a message to a specific BHandler attached to the BLooper.

Example:

#include <Application.h>
#include <Looper.h>
#include <Message.h>
#include <stdio.h>

#define MESSAGE_COMMAND_2 'Cmd2'

class MyHandler : public BHandler {
public:
    MyHandler() : BHandler("MyHandler") {}

    virtual void MessageReceived(BMessage* message) {
        switch (message->what) {
            case MESSAGE_COMMAND_2:
                printf("Received Command 2\n");
                break;
            default:
                BHandler::MessageReceived(message);
                break;
        }
    }
};

int main() {
    BLooper* myLooper = new BLooper();
    MyHandler* myHandler = new MyHandler();

    // Add the handler to the looper
    myLooper->AddHandler(myHandler);
    
    // Post a message with command MESSAGE_COMMAND_2 to myHandler
    myLooper->PostMessage(MESSAGE_COMMAND_2, myHandler);
    
    // Start the event loop
    myLooper->Run();

    delete myHandler;
    delete myLooper;
    
    return 0;
}

status_t PostMessage(BMessage* message, BHandler* handler, BHandler* replyTo = NULL):

  • Parameters:
    • message: A pointer to the BMessage object to be posted.
    • handler: The BHandler to which the message should be sent.
    • replyTo (optional): The BHandler to which replies to this message should be sent.
  • Return Value:
    • Returns B_OK if the message was successfully posted, an error code otherwise.
  • Posts the specified BMessage to the BLooper's message queue, directing it to the specified BHandler.
  • Use this version of PostMessage() when you need to send a message containing more complex data to a specific BHandler attached to the BLooper.

Example:

#include <Application.h>
#include <Looper.h>
#include <Message.h>
#include <stdio.h>

#define MESSAGE_TYPE 'Type'
#define MESSAGE_DATA 'Data'

class MyHandler : public BHandler {
public:
    MyHandler() : BHandler("MyHandler") {}

    virtual void MessageReceived(BMessage* message) {
        switch (message->what) {
            case MESSAGE_TYPE: {
                int32 data;
                if (message->FindInt32("data", &data) == B_OK) {
                    printf("Received Message Type with data: %d\n", data);
                }
                break;
            }
            default:
                BHandler::MessageReceived(message);
                break;
        }
    }
};

int main() {
    BLooper* myLooper = new BLooper();
    MyHandler* myHandler = new MyHandler();

    // Add the handler to the looper
    myLooper->AddHandler(myHandler);

    // Create a message
    BMessage* msg = new BMessage(MESSAGE_TYPE);
    msg->AddInt32("data", 42); // Add some data to the message
    
    // Post the message to myHandler
    myLooper->PostMessage(msg, myHandler);
    
    // Start the event loop
    myLooper->Run();

    delete msg;
    delete myHandler;
    delete myLooper;
    
    return 0;
}

5 Quit()

virtual void BLooper::Quit();
  • Stops the event loop of the BLooper, causing Run() to return.
  • Cleans up resources used by the BLooper.
#include <Application.h>
#include <Message.h>
#include <Looper.h>
#include <stdio.h>

#define MESSAGE_TYPE_1 'Msg1'
#define MESSAGE_TYPE_2 'Msg2'
#define QUIT_MESSAGE 'Quit'

class MyLooper : public BLooper {
public:
    MyLooper() : BLooper("MyLooper") {}
    
    virtual void MessageReceived(BMessage* message) {
        switch (message->what) {
            case MESSAGE_TYPE_1:
                printf("Received Message Type 1\n");
                break;
            case MESSAGE_TYPE_2:
                printf("Received Message Type 2\n");
                break;
            case QUIT_MESSAGE:
                printf("Quitting...\n");
                Quit(); // Quit the event loop
                break;
            default:
                BLooper::MessageReceived(message);
                break;
        }
    }
};

int main() {
    MyLooper* myLooper = new MyLooper();
    
    // Create and send a message of type MESSAGE_TYPE_1
    BMessage* msg1 = new BMessage(MESSAGE_TYPE_1);
    myLooper->PostMessage(msg1);
    
    // Send a message to quit the event loop
    BMessage* quitMsg = new BMessage(QUIT_MESSAGE);
    myLooper->PostMessage(quitMsg);

    // Start the event loop
    myLooper->Run();

    // Cleanup
    delete myLooper;
    delete msg1;
    delete quitMsg;

    return 0;
}

6 Lock() and Unblock()

status_t BLooper::Lock();
void BLooper::Unlock();
  • Locks and unlocks the BLooper object, allowing thread-safe access to it.