Opening the BeBox
A programmer's introduction to the Be operating
system.
By Peter Potrebic and Steve Horowitz, Be, Inc.
[Note: This article originally appeared in the January
1996 issue of MacTech
Magazine.]
Introduction
Be, Inc. was founded in 1990 by Jean-Louis Gassée,
former president of Apple Products, to design and build a
new computing platform. The first product offered by the
company is a multiprocessing computer called the BeBox. It
contains two PowerPC 603 processors and a new, modern
operating system.
This article will describe some of the philosophy behind
the design of the BeBox. It will go into some detail
explaining the system software architecture with an overview
of the key components. The article also includes source code
and a description of a sample application, to give you a
feel for writing software on the BeBox. For more detailed
information on Be and the BeBox, as well as a look at
developer documentation, please visit our web site:
http://www.be.com.
Design Philosophy
The BeBox was designed with the technology enthusiast in
mind. As Jean-Louis is fond of saying, "Only the lunatic
fringe will lead us to the truly innovative applications."
Every machine is programmable right out of the box, and
comes bundled with programming tools from Metrowerks and
full developer documentation. We do not expect that the
BeBox will be your first computer -- it will not run any
Macintosh software nor will it run Windows. However, if you
are excited by new technology, know how to write code, and
have a pioneering spirit, the BeBox may have something to
offer you.
History has shown that it is difficult to predict the
markets where a new computer might be successful. We believe
that developers will lead us, and so our goal is to provide
a unique set of system software features, a low-cost
hardware platform, great development tools, and an extensive
developer support program. Additionally, we plan to help our
developers reach their potential customers by offering
electronic distribution of their software on the
Be web site. From all of
this we hope to see new and innovative applications for
markets we could not possibly envision.
The BeBox was designed by an extremely small team over
approximately four years. We started with a clean slate and
focused on a couple of key ideas. One of the fundamental
design techniques was to give responsibility for each major
system component to one person. By concentrating the
decision-making process, the engineers were allowed a great
deal of freedom without having to justify and get consensus
for every design decision. However, since most of the
components were interrelated there was enough informal
interaction to assure that we were all on the same track.
This approach allowed us to avoid some of the pitfalls of
overdesign and resulted in what we believe is a simple,
efficient, and fast set of system software components.
Another decision we made early on was to take a C++ light
approach. We wanted to use an object-oriented programming
language, but not necessarily to use every feature the
language had to offer. Programmers need not be C++ experts
to program the BeBox. This decision makes the system more
approachable by a larger number of people with a variety of
programming experiences. It is also fairly easy to absorb
the Be class libraries and get a simple Be application up
and running quickly.
System Software Overview
The design of the Be operating system is based around a
client/server architecture, with a microkernel at the lowest
level providing all the OS functionality used by the higher
levels. The developer makes use of a set of client-side C++
class libraries and C functions to write Be applications.
The system software consists of a number of key
components. Several servers are built on top of the kernel
including an application server, storage server, network
server, and media server. The servers run in separate
protected memory areas and implement the majority of system
functionality available from the C++ class libraries. The
class libraries are broken up into a series of kits which
export the functionality from the servers to the client
applications. Among the kits available to Be applications
are an application kit, interface kit, OS kit, device kit,
media kit, midi kit, networking kit, and storage kit. The
details of all of these kits are beyond the scope of this
article. Instead, this article provides a brief summary of
each server's basic functionality, followed by a description
of the major pieces in a simple Be application.
The Be kernel provides many fundamental system services
to the higher-level servers including the ability to deal
with multiple processors, true preemptive multitasking and
multithreading, memory protection, shared memory,
semaphores, virtual memory, shared libraries, and loadable
drivers. The kernel scheduler supports multiple processors
in a manner completely transparent to the application. This
and other features of the kernel provide an extremely fast
and efficient foundation upon which the rest of the
operating system is built.
The application server is the main server with which
applications communicate. It is in charge of all the window
and view management, graphics rendering, font management,
user interface interaction, event handling, and so on. One
important aspect of the Be programming model implemented by
the application server and interface kit is that every
window in the Be system is given two separate threads. The
application server maintains a thread on the server side,
which communicates with a client-side thread running in the
application's address space. This allows all windows to
update their contents and respond to user interaction
asynchronously, and is a major factor in the overall
responsiveness of the Be system. This also means that any
multiwindowed Be application is automatically multithreaded
and multiprocessing with little work from the developer. You
can, of course, spawn more threads for your application
using other classes in the application framework or the
lower-level kernel functions.
Another important server is the storage server. It
implements the built-in database functionality in the Be
operating system. The storage server allows programmers to
design tables that describe records, create and delete
records, and receive notification of database changes. With
the integrated database, every file on the BeBox has its own
database record. This acts as an extensible storage system,
allowing programmers to create unique tables containing
whatever information they want about a file. The fields in
these records (as defined by the table) are then searchable
and viewable by all applications in the system.
Additionally, the storage server implements a "live" query
mechanism that allows any Be application to construct a
query and then, if desired, get notified continuously of any
changes to the set of records located by the query. This
behavior is seen in the system Browser (an application akin
to the Macintosh Finder) which is part of the operating
system, but this service is available to all Be
applications.
The network server provides basic system networking
services. It implements the TCP/IP protocol stack and
provides a socket interface resembling Berkeley sockets.
There is also a PPP layer included in the server, and
support for Ethernet. Standard networking tools such as ftp,
ftpd, and telnet are included with the Be system.
The media server provides a set of tools for manipulating
large streams of multimedia data. This server handles the
transportation of buffers of media data through a pipeline
of interested data handlers. Each handler can subscribe to a
media stream. By subscribing, the handler is given a chance
to manipulate or read the data as it passes through the
system. The buffers are implemented as shared memory areas,
which can be read by multiple applications in separate
address spaces without having to copy the buffers between
applications. Additionally, you can use the media server to
synchronize separate media streams, using a global
scheduling object that allows easy coordination of audio and
video data.
Application Basics
A simple Be application will primarily make use of the
features in the application kit and the interface kit. The
core objects used by an application are BApplication,
BWindow, BView, BMessage, and BLooper. BLooper (feel free to
call it "blooper") is the main object used to encapsulate
thread spawning and message handling. Each BLooper object
has its own thread that responds to messages sent to it by
other objects. Since BApplication and BWindow both inherit
from BLooper they are automatically message handlers running
in their own threads.
Generally, a BApplication object is created first. It
runs an event loop in the main thread of the application,
and is used for basic messaging between applications. It can
also be used to exchange messages with windows or other
threads in your application. Additionally, the BApplication
object receives notification of files to open at launch time
or once the application is running. The BApplication object
is the central control object from which other windows and
objects are created and can be used to synchronize sharing
of global objects within an application.
Most of the communication between threads, windows, and
applications in the Be system is done using a BMessage
object. A BMessage has the ability to contain an arbitrary
number of "data" entries to pass information around, as well
as a "what" field to tell the receiver what type of message
it is receiving. BMessages are the fundamental objects used
to share information between threads and applications. They
are also used to implement drag-and-drop and the clipboard.
To actually display anything in your application, you
must create a BWindow object and add a BView to it. Each
window you create, by virtue of its BLooper inheritance,
will spawn a new thread to handle drawing, messaging, and
user interaction -- without monopolizing your application's
main thread. Windows are also the containers of BView
objects, and handle the display of any drawing that takes
place in a view. A BWindow can contain multiple views and,
in turn, views can contain other views. Each view has its
own drawing environment and all drawing is automatically
clipped to the bounds of the view. When a window needs
updating, it calls the virtual Draw() function for all the
views in the update region. Within Draw() other functions
are used to render graphics primitives, such as bitmaps,
lines, shapes, and strings. Most of the user interface
components in the Be system such as buttons, scrollbars,
menus, and others are derived from BView, and handle drawing
and mouse events automatically.
There are many more classes in the Be application
framework available to developers. The ones described above
are simply the most fundamental to a Be application. They
provide the basic functionality for an application that
wants to launch and put up a window to do some drawing.
Since the entire Be operating system is relatively small, it
is easy for one person to understand it all. It is also easy
to learn and to get an application up and running quickly in
the Be environment.
The Bouncing Ball
Now let's cut to the chase and explore a sample
application. It seems that every new computer platform comes
with a bouncing-ball demo. Each platform uses this
application to show something particular or unique about
itself. The bouncing-ball demo on the BeBox, called
BeBounce, builds on this tradition -- and add adds a unique
twist.
The BeBounce application opens a small window that
contains a single bouncing ball. The twist is that launching
a second instance of the application opens a "gap" between
the windows, the size and location being determined by their
relative positions, and the ball is then able to bounce
between the two "partner" applications. As either window is
moved around the screen, the gap between the windows follows
the motion, growing larger or smaller and moving around each
window frame as dictated by Euclid (i.e., by geometry). (See
the screen shot, below.) If the ball happens to enter the
gap it is magically teleported to the other application.
The BeBounce application
In addition to showing some basic Interface Kit
techniques, the BeBounce application demonstrates the
straightforward way in which applications can communicate.
As explained earlier, each Be application runs in its own
address space and each window runs in a separate thread.
Additionally, each application has a "main thread"
represented by a BApplication object. The multithreaded
nature of an application is inherited from the BLooper
class. BLooper objects execute a message handling loop which
responds to messages posted to the looper using its
PostMessage function. The looper then dispatches those
messages, in sequence, to the "target" BReceiver object by
calling the virtual MessageReceived function. Note that
BLooper is a kind of BReceiver so a looper can also receive
messages.
The BeBounce application consists mainly of one object
derived from BApplication (TBounceApp), one object derived
from BWindow (TWindow), one BMessenger object, and one
object derived directly from BLooper (TBall). In the Be
system, BMessage objects can be sent around within an
application. However, to send messages between applications
a BMessenger object is needed. The messenger object is a
one-way connection between two applications. In BeBounce,
when the ball enters the gap a message is sent to the
partner using the messenger object, literally passing the
ball to the partner.
Let's take a look at some code, starting with the main()
function:
main(int argc, char* argv[])
{
// to randomize the movement of the bouncing ball
srand((long) sys_time());
// make the new application object and start it running
my_app = new TBounceApp();
my_app->Run();
// application is finished so cleanup and return
delete my_app;
return 0;
}
The basic idea is to create the application object and
start it running. The Run() function returns when the
application quits. The TBounceApp object is then deleted and
the program returns. Most applications on the BeBox will
have a similar main() function.
The Application Object
The BApplication object is the real launching point for
most applications, and BeBounce is no exception. Here is the
definition of the TBounceApp class:
class TBounceApp : public BApplication {
public:
TBounceApp();
virtual ~TBounceApp();
virtual void MessageReceived(BMessage *msg);
void InitPartner(thread_id tid);
void RemovePartner();
bool SendToPartner(BMessage *msg);
bool SendPosition(BRect rect);
private:
Twindow *fWindow;
Bmessenger *fPartner;
};
The application constructor does several interesting
things. First, when the application is launched it needs to
determine how many instances of the BeBounce application are
already running. Here is a portion of that function:
TBounceApp::TBounceApp()
: BApplication(MY_SIGNATURE)
{
... // some initialization code
/*
This version of BeBounce only supports 2 instances
of the application running at the same time. So if
there are already 2 instances running force a QUIT. */
BList list;
be_roster->GetThreadList(MY_SIGNATURE, &list);
long app_count = list.CountItems();
if (app_count > 2) {
// only support 2 applications at a time
PostMessage(QUIT_REQUESTED);
return;
}
... // more initialization to be discussed below
}
The above code uses the be_roster (aka "rooster") object,
a system-wide object that, among other things, maintains
information on all running applications. The roster returns
a list of all running applications that have the signature
MY_SIGNATURE. If there are more than two instances of the
application already running, this instance simply quits.
If this is the first instance of the application it
simply creates the window:
if (app_count == 1) {
// The first instance of BeBounce will have a ball.
fWindow = new TWindow(wpos, "Bouncing 1", TRUE);
} else {
... // the second instance of BeBounce
}
If there are two instances, things are trickier:
if (app_count == 1) {
... // the first instance of BeBounce
} else {
// This is the second instance of the BeBounce app
fWindow = new TWindow(wpos, "Bouncing 2", FALSE);
... // determine which of the 2 instances is myself
// and which is the partner.
// tid is the "thread id" of our partner!
InitPartner(tid);
/* Send the introductory message along with my thread
id and location in screen coordinates. */
BMessage *msg = new BMessage(BB_HELLO);
msg->AddLong("thread", Thread());
msg->AddRect("rect", wpos);
SendToPartner(msg);
}
Here, as before, the application creates the window. It
then determines the thread id of its partner application and
initializes a BMessenger object for communicating with the
partner. The code that creates the messenger is in the
utility member function TBounceApp::InitPartner() :
void TBounceApp::InitPartner(thread_id tid)
{
if (fPartner) // BB_HELLO race condition
return;
// establish a 'messenger' as the link to our partner
fPartner = new BMessenger(MY_SIG, tid);
if (fPartner->Error() != NO_ERROR) {
delete fPartner;
fPartner = NULL;
}
}
It is important to note that this function handles the
race condition surrounding the BB_HELLO message. Imagine
launching two instances of BeBounce at the same time. The
call to GetThreadList() could return two instances to each
application. In this case both applications will behave as
the second instance and both will send BB_HELLO messages.
This will result in TBounceApp::InitPartner being called
twice by each application. In this particular application
the only result would be a small memory leak of the first
BMessenger object. Understanding these types of race
conditions is one of the critical aspects of designing
software for the BeBox. Note that the code does not handle
three applications launching simultaneously. In this case
all three applications might decide that they are the "third
wheel" and quit.
After creating the messenger, the code constructs a
BB_HELLO BMessage object. The next step is to add the thread
id of this application to the message. The other application
uses this data to construct its own BMessenger. The window
position is also added to the message so that the partner
knows this window's initial position. The message is sent
using the utility function TBounceApp:: SendToPartner . Other
than error handling SendToPartner contains just one line:
bool TBounceApp::SendToPartner(BMessage *msg)
{
... // error handling
fPartner->SendMessage(msg);
... // error handling
}
The final steps of the constructor are to create a "Stop
Ball" menu item and show the window on screen:
// inside the TBounceApp constructor
/*
A little bit of menu code. Add a 'Stop Ball' menu item
to the application menu (found in the Dock window).
By default items in the "app" menu post messages to
the application. In this case the message should be
"targeted" to the window, not the app.
*/
BPopUpMenu *menu = AppMenu();
BMenuItem *item = new BMenuItem("Stop Ball",
new BMessage(BB_STOP), 'K');
item->SetTarget(fWindow);
menu->AddItem(item);
fWindow->Show();
// this is the end of the TBounceApp constructor
The protocol that the applications use to communicate
with each other is most apparent in the TBounceApp::
MessageReceived() member function:
void TBounceApp::MessageReceived(BMessage *msg)
{
switch (msg->what) {
case BB_HELLO:
... // a second BeBounce application is saying hello!
break;
case BB_GOODBYE:
...// our partner is quitting
break;
case BB_WINDOW_MOVED:
... // partner moved, and has given us its new position
break;
case BB_BALL:
... // we've just been given the ball
break;
}
}
Receiving a BB_HELLO message means that a second instance
of the application is introducing itself. Here is the code
for handling this message:
}case BB_HELLO:
if (fWindow->Lock()) {
/*
A new instance of BeBounce was just launched
and sent us the introductory message.
*/
InitPartner(msg->FindLong("thread"));
// Initialize our partner's current location.
pos = msg->FindRect("rect");
fWindow->PartnerMoved(pos);
// Tell our new partner our current location.
pos = fWindow->Frame();
SendPosition(pos);
fWindow->Unlock();
}
break;
This code initializes a BMessenger using
TBounceApp::InitPartner() and places the thread id in the
message. The partner's window position is also retrieved
from the message and saved. Finally the code determines the
position of its window and sends that to the partner. The
communication link between the two applications is now open.
The next message is BB_GOODBYE. This message is sent as
BeBounce applications quit. The code for responding to this
message is quite simple:
case BB_GOODBYE:
if (fWindow->Lock()) {
// Our partner is quitting.
RemovePartner();
if (msg->HasBool("ball"))
fWindow->AddBall();
fWindow->Unlock();
}
break;
The partner is removed using the function
TBounceApp::RemovePartner , the complement of the InitPartner
function seen earlier. In addition, if the quitting partner
currently had the ball a new ball is added to this
application's window. This ensures that there is always a
bouncing ball. The code that sends the BB_GOODBYE message is
described in the section on the window.
The final two messages sent between the applications,
BB_WINDOW_MOVED and BB_BALL, are status messages informing
the partner that either the window moved or the ball has
moved through the gap. Here is the code, without further
explanation:
case BB_WINDOW_MOVED:
/*
Our partner is informing us that it moved. This message is
continually generated as the other window is being moved.
TWindow::PartnerMoved redraws the window to reflect the
new position.
*/
if (fWindow->Lock()) {
pos = msg->FindRect("rect");
fWindow->PartnerMoved(pos);
fWindow->Unlock();
}
break;
case BB_BALL:
// Our partner just passed us the ball.
if (fWindow->Lock()) {
Bpoint speed = msg->FindPoint("speed");
float rel_loc = msg->FindFloat("rel_loc");
fWindow->AddBall(rel_loc, speed);
fWindow->Unlock();
}
break;
The Window Object
The next class of interest is TWindow, a subclass of
BWindow. In the BeBounce application the window is
responsible for managing the ball and for informing the
partner application of particular events (see description of
TBounceApp::MessageReceived() above). The window also
presents the UI for this application so there is some
description of how applications can create and manage UI on
the BeBox. In the Be operating system a BWindow object
provides an area that can display and retain rendered
images. A BWindow by itself cannot draw, only BViews can
draw. However, a BView must belong to a window in order to
draw. These two classes work hand in hand.
Here is a portion of the TWindow class definition:
class TWindow : public BWindow {
public:
TWindow(BRect frame, const char *title,
bool with_ball);
virtual ~TWindow();
virtual void MessageReceived(BMessage *msg);
virtual bool QuitRequested();
virtual void FrameMoved(BPoint new_position);
void AddBall();
void AddBall(float rel_location, BPoint speed);
... // a few more member functions
private:
void DrawOffScreen(BView *v);
... // a couple more private member functions
// and then some private data members
};
The TWindow constructor contains several code fragments
worth discussing:
TWindow::TWindow(BRect frame, const char *title, bool ball)
: BWindow(frame, title, TITLED_WINDOW, NOT_RESIZABLE)
{
... // some initialization removed
if (ball)
AddBall();
/*
The drawing takes place in the view fOffView that was added to
the offscreen bitmap fBitmap. In this way we'll do the drawing
offscreen and then just blit the result to the screen.
*/
fBitmap = new BBitmap(b, COLOR_8_BIT, TRUE);
fOffView = new BView(b, "", 0, WILL_DRAW);
fBitmap->Lock();
fBitmap->AddChild(fOffView);
DrawOffScreen(fOffView); // draw the initial contents offscreen
fBitmap->Unlock();
/*
This view is added to the visible window. Its only role is to blit the
offscreen bitmap to the visible window.
*/
fMainView = new TBitmapView(b, fBitmap);
AddChild(fMainView);
... // some initialization removed
}
Here is the first look at how windows and views are
created and used on the BeBox. To get smooth animation
TWindow uses an offscreen bitmap (a BBitmap object) and a
basic BView object (fOffView) for the rendering. It is in
the context of this view that all the actual drawing occurs.
The graphics primitives, like BView::FillRect() and
BView::FillArc() that create the effect of a ball bouncing
off real walls, takes place in this offscreen view. The last
view created, an instance of the TBitmapView class, is
simply the helper view that blits the bits from the
offscreen bitmap into the onscreen window. Since the
TBitmapView class (a subclass of BView) is so simple here is
all its code:
TBitmapView::TBitmapView(BRect frame, BBitmap *bitmap)
: BView(frame, "", FOLLOW_NONE, WILL_DRAW)
{
/*
The only job of this view is to blit the offscreen bits into the onscreen
view.
*/
fBitmap = bitmap;
}
void TBitmapView::Draw(BRect update)
{
// blit the bitmap with source and dest rectangle 'update'
DrawBitmap(fBitmap, update, update);
}
As stated previously, a BWindow is a kind of BLooper,
which in turn is a kind of BReceiver. As such, a window runs
in its own message loop and it can receive messages. A BView
is also a kind of BReceiver so it too can receive messages.
A message posted to a window (using PostMessage) can be
"targeted" to either the window or a view contained within
that window. Because of the connection between windows and
views, a view typically receives messages in the context of
its window. Said another way, the handling of messages
targeted to a view occurs in the window's thread.
On the BeBox, user actions on the keyboard and mouse are
turned into BMessages, called interface events. However,
these messages are not handled by MessageReceived() , like
the BeBounce message BB_HELLO. Instead, interface events are
dispatched to a set of virtual functions corresponding to
the action. Here are a few of those functions:
BView::MouseDown() // mouse down event in that view
BView::KeyDown() // keydown event while that view
// was the "focused" view
BView::FrameResized // the view changed size
BWindow::FrameMoved() // the window position moved
BWindow::QuitRequested() // click on close-box of window
It turns out that in BeBounce only two interface events
are of interest, the FrameMoved and QuitRequested events.
The system repeatedly generates the FrameMoved event as a
window is being dragged. This event is of interest because
as the window moves, so should the gap that exists between
the partner windows. Here is the code for responding to the
FrameMoved event:
void TWindow::FrameMoved(BPoint new_pos)
{
/*
As window is moved around the screen we inform our
partner of our new location. Also update our gap.
*/
fMyFrame = Frame();
if (my_app->SendPosition(fMyFrame))
WindowsMoved(fMyFrame, fPartnerFrame);
}
This code gets the window's current location and sends it
to our partner so that our partner can update its gap.
TBounceApp::SendPosition() sends the BB_WINDOW_MOVED message
to the partner (see the code in
TBounceApp::MessageReceived() for the code that handles this
message). Only if we have a partner will
TBounceApp::SendPosition() return TRUE. In this case the
code calls the TWindow::WindowsMoved() function, updating
this window's gap. As BeBounce windows move about the screen
everything is kept in synch.
The other event of interest is generated when the user
clicks on the close box of the window. In BeBounce closing
the window should also cause the application to quit. This
means that the window has to listen for the QuitRequested
event:
bool TWindow::QuitRequested()
{
/*
The window was asked to quit/close. Send a message
to our partner, giving him the ball if we've currently
got it.
*/
BMessage *m = new BMessage(BB_GOODBYE);
if (fBall) {
fBall->Quit();
fBall = NULL;
m->AddBool("ball", TRUE);
}
my_app->SendToPartner(m);
// Tell the app to go ahead and quit.
my_app->PostMessage(QUIT_REQUESTED);
return TRUE;
}
The first thing to do when the QUIT_REQUESTED message is
received is to tell our partner good-bye, passing the ball
along if it is in our possession. It would not be polite to
quit with ball in hand. Please note that the TBounceApp::
SendToPartner() does the correct thing if there is no
partner. Next the window posts a message to the application
telling it to quit as well. The return value of TRUE causes
to window to immediately quit.
The Ball
The last object to describe is the ball. Several factors
influenced the design of the ball. It is desirable to keep
the ball independent from the window, yet they still need to
communicate with each other. Also, the ball periodically
requires time to simulate motion. One design that
accommodates these guidelines is to construct a subclass of
BLooper called TBall. This means that the ball has its own
thread and it is able to receive messages. Here is part of
the class definition:
class TBall : public BLooper {
public:
TBall(TWindow *window, BRect bounds,
BPoint center, float radius,
BPoint speed);
void Draw(BView *view);
virtual void MessageReceived(BMessage *msg);
void Lock();
void Unlock();
void SetGap(float start, float end);
void SetEnabled(bool state);
... // a couple other member functions
private:
void NextPosition(bool *hit, float *angle);
void Tick();
Blocker fLock;
Twindow *fWindow;
... // a bunch of data members for "state" like size,
// speed/direction, postion, etc
};
Having a thread per ball would not scale to an
application that had 1000 bouncing balls, but for
demonstration purposes it works well.
Most of the constructor for the TBall class is fairly
simple, initializing the various data members. The most
interesting aspect of the constructor is that it starts the
looper thread running and posts the first BB_TICK message to
get the animation working:
TBall::TBall(TWindow *window, BRect bounds, BPoint center,
float radius, BPoint speed)
: fLock()
{
... // initialize parameters like size, location, and speed
/*
Get the looper thread rolling. Unlike the call to BApplication::Run, which does not
return, this call does return.
*/
Run();
// post initial message to get animation going
PostMessage(BB_TICK);
}
The TBall's MessageReceived function handles the BB_TICK
message:
void TBall::MessageReceived(BMessage *msg)
{
switch (msg->what) {
case BB_TICK:
Lock();
Tick();
Unlock();
... // sleep for a little bit of time
// post next message to continue animation
PostMessage(BB_TICK);
break;
}
}
The Lock() and Unlock() calls shown above deserve further
explanation. The BeBounce application was designed so that
the window "communicates" with the ball, not by posting
messages to it, but by calling specific member functions;
see the definition of the TBall class. For example, one of
those functions is TBall::SetGap() :
void TBall::SetGap(float start, float end)
{
Lock();
fGapStart = start;
fGapEnd = end;
Unlock();
}
A consequence of this design is that two separate threads
access the same data structure, so some form of
synchronization is required. The BLocker (also known as
"blocker") class provides this synchronization. Imagine if
the locking was not present. Then in the middle of the
looper thread's calculation to determine if the ball hit
that gap the window thread could come along and change the
gap. This would lead to undefined behavior. Code in
TBounceApp::MessageReceived() locks the window for this
same reason.
An alternate design would have the window sending
messages to the ball. In this case, locking would not be an
issue. The act of posting messages implicitly provides the
synchronization needed. The ball's looper thread can only
handle one message at a time. In this example the trade-off
might be the latency of messages changing the "gap" position
or starting and stopping the ball, with the Stop Ball menu
item. The preferred method should be determined on a case by
case basis. For educational purposes the BeBounce
application uses both styles of programming.
Back to the BB_TICK message. Most of the work in
animating the ball is done in TBall::Tick() , and most of
that code is the math and geometry needed to animate the
bouncing ball and determine when the ball either hits a wall
or flies through the gap. As little of this code is specific
to the BeBox we will not go into any more detail except to
show how the ball draws.
void TBall::Tick()
{
... // move ball to new position
// inform the window to redraw the window.
BMessage *msg = new BMessage(BB_DRAW);
msg->AddRect("update", updateRect);
fWindow->PostMessage(msg);
if (hit_hole) {
/*
The 'gap' was hit. So we package up the info like speed and relative location,
which gives our partner enough information to have the ball appear in the
correct place.
*/
BMessage *msg = new BMessage(BB_HIT_HOLE);
... // adding info to message
fWindow->PostMessage(msg);
}
}
The TBall::Tick() function moves the ball to the next
position and then posts a message to the window telling it
to redraw itself. Additionally, if the ball hits the "gap" a
BB_HIT_HOLE message is posted to the window:
When the window receives the BB_DRAW message (in its
MessageReceived function) it asks the ball to draw itself in
its current location. The drawing is done by TBall::Draw() :
void TBall::Draw(BView *view)
{
// The balls draws itself in the given view
Lock();
rgb_color c = view->FrontColor();
view->SetPenSize(1.0);
view->SetFrontColor(150, 30, 30);
view->FillArc(fCenter, fRadius, fRadius, 0.0, 360.0);
view->SetFrontColor(c);
Unlock();
}
The view passed to TBall::Draw() is the same view that
was added to the offscreen bitmap in the TWindow's
constructor, so the ball is being drawn offscreen. The
window then gets the TBitmapView (described earlier) to blit
the offscreen image onto the screen.
The code for handling the BB_HIT_HOLE message is in
TWindow::MessageReceived :
void TWindow::MessageReceived(BMessage *msg)
{
switch (msg->what) {
case BB_HIT_HOLE:
/*
The ball is telling us that it just hit the
hole. So it should get sent to the partner.
*/
BMessage *m = new BMessage(BB_BALL);
fBall->Lock();
m->AddPoint("speed", msg->FindPoint("speed"));
m->AddFloat("rel_loc", msg->FindFloat("rel_loc"));
fBall->Unlock();
// send the 'ball' to our partner
my_app->SendToPartner(m);
// delete the ball from this window
fBall->Quit();
fBall = NULL;
// redraw the window
Update();
break;
... // handling of other messages
}
}
The window responds to the BB_HIT_HOLE message by sending
the BB_BALL message to the partner application giving it the
ball. It puts the necessary information into the message so
that the partner can create the new ball in the correct
position, with the correct speed and direction. Finally, the
window deletes the ball.
That is how the BeBounce application works. Obvious
improvements to BeBounce would be to support more than two
applications, multiple windows within the same application,
and multiple balls. Another useful addition would be a
separate control window with some controls for things like
ball speed. All of these enhancements are feasible given the
design of the Be operating system. To support an arbitrary
number of applications each application would maintain a
list of partners, having a BMessenger object to each one.
Multiple balls in a window could either communicate with one
another (for hit testing purposes) directly or use the
window object as the arbitrator.
Hopefully this sample application has provided a taste of
developing an application for the BeBox. The next question
is: How do Be applications get built?
Development Tools
You write applications for the Be environment using tools
supplied by one of the leading tool suppliers in the
industry -- Metrowerks. Currently the preferred development
environment is a Macintosh with the latest version of
CodeWarrior and Be-supplied headers and libraries. Using
CodeWarrior on the Macintosh, a developer can write,
compile, and link a Be application. Of course, you have to
transfer the application to the BeBox before it will run.
Since the BeBox supports ftp and includes a command-line
shell (based on bash) the process of quitting your app,
bringing over a new one from the Macintosh, and running it
on the BeBox can be completely automated. There is also
support for using the source-level debugger on the Macintosh
to debug an application while it is running on the BeBox.
Metrowerks is currently working on a port of the full
CodeWarrior Integrated Development Environment to the Be
operating system. This native version of CodeWarrior, along
with full technical developer documentation, will be shipped
with every BeBox.
Conclusion
This is just a small taste of the BeBox. We have
presented an overview of the Be operating system along with
some sample code to give you the general feel of Be
application development. To learn more about the Be
operating system, please visit
our web site, where we provide on-line versions of our
developer documentation as well as information about our
product and company. We encourage developers interested in
joining our support program to mail in the developer form,
which you can find on the web site.
Be has many plans for the future. We are continuing to
improve and add features to the Be operating system and
development tools. We are also working on future versions of
the BeBox, which will include more and faster processors --
including a machine with four PowerPC 604 chips for the
horsepower hungry.
We hope this article has succeeded in whetting your
appetite for Be programming. We look forward to seeing all
the incredible applications we know developers will write
for the BeBox.
|