Table of Contents
Be Developer Classifieds Be Developer Classified are an opportunity for Be developers to tell each other about projects they're working on, are thinking about working on, or would like to work on. Here's your chance to request assistance on a project, or make your talents known to other projects. For more information, check out the Classified Ads section in the Registered Be Developer area. ------------------------------- Software engineer looking for full time work programming the BeOS. User interface and media technology development are of special interest. Previous BeOS projects include the launcher bar DeposIt and BeGoo, a collection of Replicant-enabled console utilities. Please contact Laurent Cook at lcook@mail.com, or visit the informational web page at http://wwwusers.imaginet.fr/~lcook/ BE ENGINEERING INSIGHTS: Fun with Threads, Part 2 By Pavel Císler -- pavel@be.com In the first part of this article we discussed fire-and- forget threads. These are pretty straightforward, with a very simple setup. You'll definitely want to have read that article before continuing. You can find it at: http://www.be.com/aboutbe/benewsletter/volume_II/Issue32.html#Insight Usually, though, you'll use threads that interact more with
the world around them and need some level of
synchronization. In our next example we'll implement a
simple mouse-tracking thread that can be used for, say,
implementing a nice button class. I'm sure you've done this
before -- you override All is well, except that the rest of your window is not
getting any updates, pulse tasks of views aren't being
called, etc. -- not surprising, since in the A better way to fix this is by spawning a thread that takes care of the mouse tracking asynchronously, allowing the rest of your window to be live. This thread is still pretty autonomous -- it can delete itself when you're done pressing and clean up its state, etc. It will, however, have to access the button it's tracking -- state owned by a different thread. Let's see how to do that properly. This time I'll write the Button class itself first and while I'm writing it I'll be able to decide on a convenient interface with the mouse-tracking thread: class SomeButton : public BControl { public: SomeButton(BRect frame, const char *name, uint32 resizeMask, uint32 flags) : BControl(frame, name, "", 0, resizeMask, flags), pressing(false) {} void Note the simple void DoneTracking(BView *view, BPoint point) { SomeButton *button = dynamic_cast<SomeButton *>(view); button->pressing = false; button->Invalidate(); if (button->Bounds().Contains(point)) { button->SetValue(!button->Value()); button->Invoke(); } } void Track(BView *view, BPoint point, uint32 /*unusedButtons*/) { SomeButton *button = dynamic_cast<SomeButton *>(view); bool newPressing = button->Bounds().Contains(point); if (newPressing != button->pressing) { button->pressing = newPressing; button->Invalidate(); } } That looks like an easy enough way to set up control
tracking. Here's what the tracking thread will be like: class Where is the synchronization? In the Note one very important point in this class. I could have
omitted the Think again about what happens when the tracking thread
wakes up from the snooze and the window our button is in has
closed for some reason in the mean time. The view pointer
will probably point to some random memory, possibly a corpse
of the original button view. Trying to call
Using In some cases synchronization like this is not good enough. You may, for some reason, need to quit the thread as a part of deleting the spawning thread. For one thing, the lock synchronization shown above doesn't cover one obscure case: if, while you're snoozing, your spawning window is deleted and a similar one is created in its place, aliased to the same pointer value. This is practically impossible in our mouse tracking sample, but may be very well possible if the thread heartbeat is longer in some other application. You may use a more sophisticated locking technique, involving a BMessenger -- a messenger-based lock has a more elaborate locking check and handles an aliasing issue like this completely. You may however have different reasons to have a
synchronization model where the spawning thread also quits
the new thread in, say, it's destructor. This time we'll
imagine a thread that calls a function object periodically
(the function object can be configured to traverse the disk,
a few entries each time it is called, deleting old query
files that are no longer needed). Again, if function objects
are too much for you, you can imagine a concrete subclass of
this example thread that gets all the parameters and the
function pointer passed as arguments and stores them until
the time comes to do the work inside class OwnedPeriodicThread : private ThreadPrimitive { public: static OwnedPeriodicThread *Launch(FunctionObject *functor, bigtime_t period, int32 priority = B_LOW_PRIORITY, const char *name = 0) { OwnedPeriodicThread *thread = new OwnedPeriodicThread( functor, period, priority, name); if (thread->Go() != B_OK) { // failed to launch, clean up thread->Die(); return 0; } return thread; } virtual ~OwnedPeriodicThread() { for (;;) { requestedToQuit = true; if (readyToQuit) break; snooze(period); } delete functor; } private: void Die() { readyToQuit = true; delete this; } OwnedPeriodicThread(FunctionObject *functor, bigtime_t period, int32 priority, const char *name) : ThreadPrimitive(priority, name), functor(functor), requestedToQuit(false), readyToQuit(false) {} virtual void Run() { for (;;) { if (requestedToQuit) { readyToQuit = true; break; } (*functor)(); // do the work snooze(period); } } FunctionObject *functor; // functor owned by the thread bigtime_t period; // the time period at which we call functor // a pair of synchronization bits bool requestedToQuit; bool readyToQuit; }; In this case the static Launch call actually returns a
pointer to the thread object. Also the destructor is public.
It is up to the thread owner -- the spawner -- to delete the
thread once all the work is done. The destructor waits for
the next If the task of the thread represented by the function object needs to access the spawning window, it may do so by locking down its own copy of the window pointer. It will always know that as long as the window got locked it is the same window, because the window wouldn't quit until it stopped the thread. And in that case we wouldn't be halfway through invoking the work task -- the function object, right? You could further enhance this thread by having the worker
function or function object return when it finishes or when
an error condition occurs. In that case you would just set
the readyToQuit to true, break out of the More Thread Ideas The Tracker uses a lot of "run later" tasks for things like
selecting the parent folder when you choose To make using threads like this easier, the Tracker uses a task queue class -- a thread and a list of tasks that wait to be executed periodically until they are done. This saves the overhead of creating a separate thread for each of them and of synchronizing the deletion of each of them individually -- you just delete the entire task queue when the Tracker is quitting. The task queue has different flavors of tasks -- one-shot, periodic, periodic with a timeout, etc. Too bad the class doesn't fit in here -- maybe I'll be able to sneak it into a future Newsletter article. The Tracker also uses the task queue to consolidate different tasks that require a common, expensive action. To give you an example, the Tracker has to update an icon of a file once it gets a node monitor notification about some attribute change. When mimeset runs, for instance, the Tracker gets several node monitors for the single mimeset call -- one about each icon being added, one about a type being set, one about a preferred app being set, etc. Each of these has an effect on what the resulting icon will be. If the Tracker responded to each of them, the icon would update four times. Instead, a task is enqueued for each of them, waiting around for a fraction of a second. If another task for the same attribute change gets generated, it merges with the previous one and only one update is performed. The worker function objects that make this happen have two virtual calls: virtual bool CanAccumulate(const AccumulatingFunctionObject *) const; virtual void Accumulate(AccumulatingFunctionObject *); These two virtuals are overridden in a specific flavor of a function object, implementing a method of determining whether two scheduled tasks can be coalesced into one and actually preforming the coalescing. Using a scheme like this dramatically reduces the ammount of updates performed when mimeset or other attribute modifying operation is in action. This concludes the second part of this article. At this point I wish to thank the Metrowerks compiler for kindly interpreting specialized templates and converting them into an executable binary for me. Further, I would like to note that this article was written mostly in Gobe Productive and using it has been a truly enjoyable experience. BE ENGINEERING INSIGHTS: Pixel Packing Mama By William Adams -- wadams@be.com As I write this I'm sitting by a pool typing on a Sony Vaio portable, which is of course running the BeOS. We've come a long way, baby! Why by a pool? Because my daughter Yasmin is taking swimming lessons, and seeing her and the other kids flailing around helplessly (with adult supervision) reminds me of my state of mind sometimes when I'm programming. So I'm here thinking, you know, I bet every developer in the world is wondering how to do something as esoteric as easily read and write pixels in a BBitmap. So I queried at least one developer as to how they would write 16-bit pixels in a BBitmap, and thus an article was born. In this article I would like to explore two issues. One is pixel formats, and the other is how to do a whole bunch of work without bothering the app_server. For the first topic, let's look at some pixel formats
typically found in the BeOS. In the file GraphicsDefs.h
you'll find this big enum that contains B_CMAP8 B_RGB32 B_RGB15 There are many others, but I'll focus on these for now. Are they big endian, little endian, what component goes where, and why should you care? You'll also find the BBitmap object in the Kits. This is the well-defined object used to display bitmap images in the BeOS interface. When you construct a BBitmap, you give it a size, one of these defined color spaces, and a couple of flags to do stuff we won't worry about at the moment. BBitmap(BRect bounds, color_space depth, bool accepts_views = false, bool need_contiguous = false); The Kits also supply some ways to draw a BBitmap in a BView. There are eight different calls, but we'll concentrate on one for now: void DrawBitmap(const BBitmap *aBitmap, BRect srcRect, BRect dstRect); With this method you take a BBitmap and you tell it what part of it you want to display where in the BView. Nothing could be simpler. End of article... But, in the interest of filling space, let's say you want to copy one BBitmap to another. Or even more interesting, you have some random bits lying around that aren't necessarily in a BBitmap right now, but you want to copy them into a BBitmap quickly and easily. First let's do some name wrangling. You can find all this code online at: ftp://ftp.be.com/pub/samples/r3/interface_kit/pixelbuff.zip class PixelBuffer { public: PixelBuffer(void *data, color_space, int32 width, int32 height, int32 bytesperRow); virtual ~PixelBuffer(); virtual void SetPixel( const int32 x, const int32 y, const rgb_color &); virtual void GetPixel( const int32 x, const int32 y, rgb_color &); int32 Width() {return fWidth;}; int32 Height() {return fHeight;}; int32 BytesPerRow() {return fBytesPerRow;}; color_space CSpace() {return fColorSpace;}; protected: void *fData; int32 fWidth; int32 fHeight; int32 fBytesPerRow; color_space fColorSpace; private: }; This is a convenience class that can be used to represent
any pixel buffer, including BBitmaps, BDirectWindow frame
buffers, or just random chunks of memory. The most
interesting thing it does is allow you to set and get pixels
using the coordinates and an void copyPixels(PixelBuffer *dest, PixelBuffer *source) { int numRows = source->Height(); int numCols = source->Width(); for (int row = 0; row < numRows; row++) { for (int col = 0; col < numCols; col++) { rgb_color aColor; source->GetPixel(col, row, aColor); dest->SetPixel(col, row, aColor); } } } You go row by row, pixel by pixel copying stuff as you need
to and you're done, right? This is probably the slowest
method on earth, but as long as the So what do these two methods have to look like? First, we'll introduce one more method. For any given location in a pixel buffer, we need to be able to find the pointer in memory that represents the start of that pixel. In all the cases we'll deal with here, pixels can align to byte boundaries. If we had 1-, 2-, or 4-bit colors, things would be slightly different. So here's a method to find the pointer to any particular pixel: void * GetPointer(const int32, const int32); With this method in hand, we can now do the union colorUnion { rgb_color color; uint32 value; }; void PixelBuffer::GetPixel( const int32 x, const int32 y, rgb_color &aColor) const { switch (ColorModel()) { case B_CMAP8: { const color_map *colors = system_colors(); uint8 indexValue; indexValue = *((uint8 *)GetPointer(x,y)); aColor = colors->color_list[indexValue]; } break; case B_RGB15: case B_RGBA15: case B_RGB16: { uint16 indexValue = *((uint16 *)GetPointer(x,y)); aColor.blue = (indexValue & 0x1f) << 3; // low 5 bits aColor.green = ((indexValue >> 5) &0x1f) << 3; aColor.red = ((indexValue >> 10) &0x1f) << 3; aColor.alpha = 255; } break; case B_RGB32: case B_RGBA32: { colorUnion aUnion.value = *((uint32 *)GetPointer(x,y)); aColor = aUnion.color; } break; } } What's going on here? We use the Of course, One more thing I'm after, though, is being able to draw icons into a BBitmap in a nice, efficient manner. I'll start by saying BBitmap objects are the greatest thing on earth, but they're not free. They are allocated in shared memory so that both the app_server and your application can have access to them. This saves on copying. Since they're allocated in shared memory, that means they
are of a minimum page size, which is 4K. If you have
hundreds of icons in an application, the easiest thing to do
is to create a BBitmap for each one of them. When it's time
to display them, you call You could load all your little icons into a single BBitmap
and keep track of their relative locations within it, and
use the void DisplayIcon(const int32 x, const int32 y, PixelBuffer *dest, PixelBuffer *icon) { int numRows = icon->Height(); int numCols = icon->Width(); for (int row = 0; row < numRows; row++) { for (int col = 0; col < numCols; col++) { rgb_color aColor; icon->GetPixel(col, row, aColor); dest->SetPixel(x+col, y+row, aColor); } } } #define img_width 25 #define img_height 11 unsigned char img_bits[] = {/* actual data goes here */}; PixelBuffer *icon = new PixelBuffer(img_bits, B_CMAP8, img_width, img_height, img_width); To display the icon on the offscreen display at any location, do this: DisplayIcon(10,10, fOffscreenBitmap, icon); In this way you can create an icon that takes exactly as much memory as you need it to, which saves on memory resources. The second benefit is that you don't actually have to talk to the app_server in order for your icon to be drawn into your pixel buffer. And why not talk to the app_server? Because it's a busy team and you would probably rather not make requests if you really don't have to. There's no real savings when all you're doing is drawing a single icon. As soon as you copy the bits for the icon, you'll be displaying the BBitmap, but when you want to batch the drawing of multiple icons, this will really help. That's typically the case when you're using an offscreen buffer to reduce flicker in your display. The third benefit of this method is that the destination pixel buffer could easily represent the frame buffer of your display. In that case, when you draw your icon, it's on screen. No further processing or copying is required. This is a tremendous boost if your icons happen to be frames of decoded video that you're trying to display in real time. And by the by, who needs a copy constructor or operator = on BBitmap when you have these mechanisms. So, there you have it. A little bit of abstraction on what a bitmap is, a little bit of color conversion, and you have a basic framework for flexibly dealing with bitmaps. There are many ways these methods can be optimized. For instance, you could add some methods to the PixelBuffer like this: void SetPixel8(const int32 x, const int32 y, uint8 color); void SetPixel15(const int32 x, const int32 y, uint16 color16); The same goes for the You can probably imagine other convenient things that can be done, like alpha blending and other point operations. But that's another story. I think I've been on the stage too long, so I'll get off now. The European Dream By Hans Speijer -- hspeijer@beeurope.com It takes more than a web site to make a company truly international. Sometimes you have to leave cyberspace and touch down in the real world. This is often the time when a computer company realizes it needs a presence where the market is. Jean-Louis Gassée understood this when he asked Jean Calmon to start a Be Europe office in 1994. The wisdom of this decision is borne out by the size of the European software market -- $37 billion in 1997. In 1997 EITO/IDC estimated that in Y2K there would be 41 million households with a PC, and 19 million would be connected to the Internet. This means that a company relying exclusively on the Internet for its sales and advertising would not be reaching more then 50 percent of its potential customers in Europe. Even if the average BeOS user is more net savvy than the average Windows 98 user, the a virtual presence is still essential if you want to sell products in Europe. Europe is also significant in software development, with many high-quality software products and engineers to offer. In fact, 26% of all registered BeOS developers, 37% of current BeWare entries, and 52% of the Master Award winners are from Europe. To name just a few prominent European developers: Attila Mezei (2 Master Awards and 2 Honorable Mentions), Maarten Hekkelman (1 Master Award and 1 Honorable Mention) and Marco Nelissen (1 Master Award and 7 BeWare entries). In addition to its developer community, Europe has active user and hobbyist communities. One example of this is the demo scene that originated on the Commodore 64 and later boomed on the Commodore Amiga and the PC. These demo coders, musicians, and graphic artists became important contributors to the gaming and multimedia industries. And they took their experience and beloved systems with them into their professional lives. Our first mission at Be Europe is to get as many shipping BeOS applications as possible. The BeOS platform needs applications to attract users. The more applications that show the power of the BeOS, the more copies of the BeOS we will sell, and the bigger the market for BeOS software developers. To help bring more applications to market we will assist current BeOS developers in any way we can. In addition, we're contacting companies in the multimedia sector about developing on the BeOS. There's also lot we can do for non-European developers who want to sell their products in Europe. If you are one of them, keep us up to date in your project status and tell us where and when you need help. This goes for companies as well as for individual developers. Our second mission is to build European distribution channels. Although the web is a good way to get products into customers' hands, many people (especially in Europe) are used to going to a shop, talking to a salesperson, looking at all the boxes, and playing with some demo systems. Not only do we need to get the BeOS into these shops, but we also need to make sure that people will be able to buy third-party products in the same shops. We've begun to explore the different channels and will gradually build indirect distribution, especially with the availability of a larger public BeOS release and more user-ready commercial applications in the near future. Our third and final mission is to spread BeOS awareness in Europe. We do this by keeping close contact with the press, going to trade shows and conferences, and adding a good healthy dose of evangelism. In addition to our own activities on behalf of the BeOS, we owe thanks to our many supporters who speak positively about the BeOS; word of mouth is as good -- or maybe even better than -- classical marketing. People often have more confidence in a friend's pitch about the virtues of a product than in an ad they see in the paper. To get the word out, we'll be on the road a lot, showing the BeOS at universities and informal gatherings of technology enthusiasts. Not everything smells of roses and the streets are not paved with gold. We deal with more then 30 different countries, more than 15 different languages, and at least 9 significantly different cultures. In some countries the law even prevents us from putting the BeOS on the shelves if we don't have packaging and manuals in the native language, and many people will not buy a product that is not localized to their own language in any case. We are only a small team (currently four persons), and we're all multitasking -- but the Be spirit burns brightly in all of us. Let us know how you're doing and help us spread the BeOS gospel in Europe. DEVELOPERS' WORKSHOP: The Slave of Duty By Doug Fulton -- lbj@be.com "Developers' Workshop" is a weekly feature that provides answers to our developers' questions, or topic requests. To submit a question, visit http://www.be.com/developers/suggestion_box.html Q: I've spent a decade stuffing the -- Mabel Stanley, Penzance, England A. Wander not, Mabel. The short answer: Type "/system/Tracker filename" at the command line prompt and your file will open. But, you object, that's a lot to type and hard-coded pathnames make you queasy? And when your tiny alabaster fingers slip while typing ".alias" such that you open the non-existent ".alais" instead, the Tracker doesn't object to your wrong tree up-barking, but bounds slobberingly to the front gate with no slippers in its mouth? And, what's more, you are an orphan? Then try spoon feeding the app roster. The little app shown below, which we'll call " #include <Roster.h> #include <Application.h> #include <signal.h> int main(int argc, char **argv) { int n; entry_ref ref; BEntry entry; status_t err; char *arv[1]; BApplication("application/x-open"); if (argc == 1) { printf("Usage: open <file1> <file2> <file3> ...\n"); return -1; } signal(SIGINT, SIG_IGN); for (n = 1; n < argc; n++) { get_ref_for_path(argv[n], &ref); entry.SetTo(&ref, false); if (entry.IsDirectory()) { arv[0] = strdup(argv[n]); err = be_roster->Launch("application/x-vnd.Be-TRAK", 1, arv); free(arv[0]); } else err=be_roster->Launch(&ref); if (err != B_OK && err != B_ALREADY_RUNNING) printf("open: %s\n", strerror(err)); } return 0; } The only line that needs an explanation is the signal() call: #include <signal.h> ... signal(SIGINT, SIG_IGN); This tells " When you launch " How is " Amiga Rumors... By Jean-Louis Gassée Lately, I've received several e-mail messages asking Amiga-related questions. Is it true we're going to rewrite the AmigaOS? Gateway "acquired" the Amiga -- what is our relationship with Gateway? As to the first one, no, we're not going to rewrite the AmigaOS. Personally, I don't think it needs rewriting. From the very beginning, it's been a modern OS. I remember how we feared its impact at Apple when the Amiga first came out in 1986. Multitasking, great graphics, sound, animation, and video. This was a gifted baby, and the Commodore family promptly adopted it. To make a long and sad story short, in spite of the Amiga achieving sales in the vicinity of 5 million units, Commodore failed to invest in its future, then went belly up. The Amiga was sold to Escom, a German company, and later to Gateway when Escom closed shop. To us at Be, the Amiga was an inspiration because of its audio and video capabilities. Also, we drew a distinction between Commodore and the Amiga, which investors didn't always do. I remember times during our fund raising when the mention of Amiga as a model drew alarmed looks. Telling them they were wrong wasn't really an option. Nevertheless, because of our old Amiga connection, I still have a license plate that reads AMIGA 96 -- given to me when we introduced the BeBox. So, as rumors often do, the BeOS/Amiga rumors have a foundation. We always liked the Amiga and were happy to see Gateway adopt it. This is a strong company and their interest in new approaches to new media -- as with the Destination PC -- creates a nice potential cultural fit for the Amiga heritage. As to the second question -- about a BeOS-Gateway relationship -- the recent availability of the BeOS on Intel-based PCs logically prompts sensible questions about Be, the AmigaOS, and Gateway. Does this mean that Gateway is going to announce tomorrow a BeOS-based Amiga computer? That would be nice, but I doubt it. Speaking only for Be, combining two operating systems -- or if you prefer, two sets of APIs -- looks like a major technical challenge. If the Amiga heritage isn't maintained in some way, the result of such a marriage is not an Amiga -- you might as well buy a straight PC. If, on the other hand, BeOS applications don't run on the new hybrid, it's a problematic proposition for Be developers. In any case, I can't remember an instance when combining two sets of very different APIs resulted in success. At first glance, combining the Amiga and Be cultures and technologies looks like a nice, almost natural idea. Unfortunately, though, while some crossbreeding produces stronger hybrids, I fear that this one might disappoint.
1997 Be Newsletters | 1995 & 1996 Be Newsletters Copyright ©1998 Be, Inc. Be is a registered trademark, and BeOS, BeBox, BeWare, GeekPort, the Be logo and the BeOS logo are trademarks of Be, Inc. All other trademarks mentioned are the property of their respective owners. Comments about this site? Please write us at webmaster@be.com. |