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 theBHandler
.BHandler
is the class that can receive and handle messages.
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
- Message Queue: Each BLooper has its own message queue where messages are stored until they can be processed.
- Message Handlers: Messages are dispatched to message handlers, which are BHandler objects that are attached to the BLooper.
- BLooper can have multiple BHandler objects attached to it.
- 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".
name | The name of the looper. |
priority | The 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. |
portCapacity | Loopers 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 theBMessageQueue
. - 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:
Constant | Description |
---|---|
| For threads running in the background that shouldn't interrupt other threads. |
| For all ordinary threads, including the main thread. |
| For threads associated with objects in the user interface, including window threads. |
| For interface threads that deserve more attention than ordinary windows. |
| For threads that animate the on-screen display. |
| For threads performing time-critical computations. |
| 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:
- Event Loop Initialization:
When you callRun()
, theBLooper
object initializes its event loop. This event loop will continuously check for new messages in the message queue. - Message Processing:
As soon as a message arrives in the message queue, theBLooper
retrieves it and dispatches it to the appropriate message handler. - Blocking Function:
TheRun()
function typically blocks the calling thread until theBLooper
is told to quit. This means that the program will not continue beyond theRun()
call until theBLooper
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.
- This function creates a new thread that will execute 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 theBLooper
'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
: TheBHandler
to which the message should be sent.replyTo
(optional): TheBHandler
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 specifiedBHandler
. - This version of
PostMessage()
is useful when you need to send a message to a specificBHandler
attached to theBLooper
.
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 theBMessage
object to be posted.handler
: TheBHandler
to which the message should be sent.replyTo
(optional): TheBHandler
to which replies to this message should be sent.
- Return Value:
- Returns
B_OK
if the message was successfully posted, an error code otherwise.
- Returns
- Posts the specified
BMessage
to theBLooper
's message queue, directing it to the specifiedBHandler
. - Use this version of
PostMessage()
when you need to send a message containing more complex data to a specificBHandler
attached to theBLooper
.
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
, causingRun()
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.