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:
Be Engineering Insights: Fun with Threads, Part 1
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 MouseDown and keep looping until the user lets go of the mouse button. You animate your button, pressing it whenever the mouse tracks into its rectangle.
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 MouseDown()
you blocked the entire window thread.
You could use the Pulse calls, keep a tracking state in your button and
track it by getting pulse events. That's a little messy though. You have
to require pulse events for the view, but you don't really need them
unless you're actually tracking the button, and you end up wasting cycles.
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:
classSomeButton
: publicBControl
{ public:SomeButton
(BRect
frame
, const char *name
, uint32resizeMask
, uint32flags
) :BControl
(frame
,name
, "", 0,resizeMask
,flags
),pressing
(false
) {} voidMouseDown
(BPoint
) {MouseDownThread
::TrackMouse
(this
, &DoneTracking
, &Track
); } voidDraw
(BRect
) { // draw pressed/normal button based on value of // <pressing>, left as an exercise for the reader } private: boolpressing
; friend voidDoneTracking
(BView
*view
,BPoint
point
); friend voidTrack
(BView
*view
,BPoint
point
, uint32); };
Note the simple MouseDown()
call. The
TrackMouse()
call then targets two
callback functions:
voidDoneTracking
(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
(); } } voidTrack
(BView
*view
,BPoint
point
, uint32 /*unusedButtons*/) {SomeButton
*button
= dynamic_cast<SomeButton
*>(view
); boolnewPressing
=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. MouseDown()
just calls MouseDownThread
::TrackMouse()
, which passes in the functions
used to track the button as the mouse moves in an out of the control and
as the user lets go of the buttons.
Here's what the tracking thread will be like:
classMouseDownThread
: privateThreadPrimitive
{ public: static voidTrackMouse
(BView
*view
, void (*donePressing
)(BView
*,BPoint
), void (*pressing
)(BView
*,BPoint
, uint32) = 0, bigtime_tpressingPeriod
= 100000) {MouseDownThread
*thread
= newMouseDownThread
(view
,donePressing
,pressing
,pressingPeriod
); if (thread
->Go
() !=B_OK
) // failed to launch, clean up deletethread
; } protected:MouseDownThread
(
BView
*view
, void (*donePressing
)(BView
*,BPoint
), void (*pressing
)(BView
*,BPoint
, uint32), bigtime_tpressingPeriod
) :ThreadPrimitive
(B_LOW_PRIORITY
, "MouseTracker"),view
(view
),donePressing
(donePressing
),pressing
(pressing
),pressingPeriod
(pressingPeriod
) {parent
=view
->Window
(); } virtual voidRun
() { for (;;) { if (!parent
->Lock()) break; uint32buttons
;BPoint
location
;view
->GetMouse
(&location
, &buttons
,false
); if (!buttons
) { (*donePressing
)(view
,location
);parent
->Unlock
(); break; } if (pressing
) (*pressing
)(view
,location
,buttons
);parent
->Unlock
();snooze
(pressingPeriod
); } deletethis
; ASSERT(!"should not be here"); } private:BWindow
*parent
;BView
*view
; void (*donePressing
)(BView
*,BPoint
); void (*pressing
)(BView
*,BPoint
, uint32); bigtime_tpressingPeriod
; thread_idthreadID
; typedef ThreadPrimitive _inherited; };
Where is the synchronization? In the Run
call we lock the window of the
button we're operating on. If the window is gone when we wake up from our
snooze, the call to Lock will fail and the thread will exit, deleting
itself.
Note one very important point in this class. I could have omitted the
member—saving the BWindow
* parent
would have been enough
-- and I can get the window from the view by calling BView
* view
,
right? WRONG!
view
->Window
()
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 Window on it will most likely crash.
Lock()
is designed to handle being called on Loopers that have been
deleted, so if the window is gone, the lock will fail and we'll just
bail. In our mouse tracking example this would usually not happen. A
window is not likely to go away while you're tracking the mouse, so
you'll have a subtle, hard to reproduce bug waiting to fire when you're
doing that important demo.
Using Lock()
on the spawning thread like this is the most common way of
synchronizing. It serves two purposes: it serves as a lock for the shared
state, allowing the thread to access state in the spawning thread in a
mutually exclusive way; and it allows the thread to exit in a reasonably
clean way after the spawning thread dies itself.
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 Run()
.
classOwnedPeriodicThread
: privateThreadPrimitive
{ public: staticOwnedPeriodicThread
*Launch
(FunctionObject
*functor
, bigtime_tperiod
, int32priority
=B_LOW_PRIORITY
, const char *name
= 0) {OwnedPeriodicThread
*thread
= newOwnedPeriodicThread
(functor
,period
,priority
,name
); if (thread
->Go
() !=B_OK
) { // failed to launch, clean upthread
->Die
(); return 0; } returnthread
; } virtual~OwnedPeriodicThread
() { for (;;) {requestedToQuit
=true
; if (readyToQuit
) break;snooze
(period
); } deletefunctor
; } private: voidDie
() {readyToQuit
=true
; deletethis
; }OwnedPeriodicThread
(FunctionObject
*functor
, bigtime_tperiod
, int32priority
, const char *name
) :ThreadPrimitive
(priority
,name
),functor
(functor
),requestedToQuit
(false
),readyToQuit
(false
) {} virtualvoid
Run
() { for (;;) { if (requestedToQuit
) {readyToQuit
=true
; break; } (*functor
)(); // do the worksnooze
(period
); } }FunctionObject
*functor
; // functor owned by the thread bigtime_tperiod
; // the time period at which we call functor // a pair of synchronization bits boolrequestedToQuit
; boolreadyToQuit
; };
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 Run task to go through its next loop
and realize its work is over.
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 Run()
loop, and wait patiently for the spawner to delete it.
The Tracker uses a lot of "run later" tasks for things like selecting the parent folder when you choose Open Parent in a window, after the child window closes and the parent opens. This just begs using a separate thread; you're closing the current window—the same window that belongs to the thread you're executing in. Sometime in the near future the parent window will open because you sent the Tracker app a message to open it. The best way to do this is to spawn a thread that waits around until the parent window opens and then waits until it finds the right folder in it and selects it for you.
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 boolCanAccumulate
(constAccumulatingFunctionObject
*) const; virtual voidAccumulate
(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.
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 color_space types that we support or at least define. You'll
find these among them:
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_spacedepth
, boolaccepts_views
=false
, boolneed_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:
voidDrawBitmap
(constBBitmap
*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
classPixelBuffer
{ public:PixelBuffer
(void *data
, color_space, int32width
, int32height
, int32bytesperRow
); virtual~PixelBuffer
(); virtual voidSetPixel
( constint32x
, constint32y
, constrgb_color &); virtual voidGetPixel
( constint32x
, constint32y
, rgb_color &); int32Width
() {returnfWidth
;}; int32Height
() {returnfHeight
;}; int32BytesPerRow
() {returnfBytesPerRow
;}; color_spaceCSpace
() {returnfColorSpace
;}; protected: void *fData
; int32fWidth
; int32fHeight
; int32fBytesPerRow
; color_spacefColorSpace
; private: };
This is a convenience class that can be used to represent any pixel
buffer, including BBitmap
s,
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 rgb_color structure. So, if
you want to copy from anywhere to anywhere else, just do this:
voidcopyPixels
(PixelBuffer
*dest
,PixelBuffer
*source
) { intnumRows
=source
->Height
(); intnumCols
=source
->Width
(); for (introw
= 0;row
<numRows
;row
++) { for (intcol
= 0;col
<numCols
;col
++) { rgb_coloraColor
;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 GetPixel()
and SetPixel()
methods do what they're supposed to, it will
always be correct, no matter what the source and destination color spaces
are. And of course you'd want to throw in some error checking, boundary
constraints, and the like.
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
(constint32, constint32); void *PixelBuffer
::GetPointer
(constint32x
, constint32y
) { void *retValue
=0; switch (CSpace
()) { caseB_CMAP8
: { uint8indexValue
;retValue
= &((uint8 *)Data
())[y
*BytesPerRow
() +x
]; } break; caseB_RGB15
: caseB_RGBA15
: caseB_RGB16
: { uint32offset
= (y
*BytesPerRow
() / 2)+x
;retValue
= &((uint16 *)Data
())[offset
]; } break; caseB_RGB32
: caseB_RGBA32
: { uint32offset
= (y
*BytesPerRow
() / 4)+x
;retValue
= &((uint32 *)Data
())[offset
]; } break; } returnretValue
; }
With this method in hand, we can now do the GetPixel()
call:
union colorUnion { rgb_colorcolor
; uint32value
; }; voidPixelBuffer
::GetPixel
( constint32x
, constint32y
, rgb_color &aColor
) const { switch (ColorModel
()) { caseB_CMAP8
: { constcolor_map *colors
=system_colors
(); uint8indexValue
;indexValue
= *((uint8 *)GetPointer
(x
,y
));aColor
=colors
->color_list
[indexValue
]; } break; caseB_RGB15
: caseB_RGBA15
: caseB_RGB16
: { uint16indexValue
= *((uint16 *)GetPointer
(x
,y
));aColor
.blue
= (indexValue
& 0x1f) << 3; // low 5 bitsaColor
.green
= ((indexValue
>> 5) &0x1f) << 3;aColor
.red
= ((indexValue
>> 10) &0x1f) << 3;aColor
.alpha
= 255; } break; caseB_RGB32
: caseB_RGBA32
: { colorUnionaUnion
.value
= *((uint32 *)GetPointer
(x
,y
));aColor
=aUnion
.color
; } break; } }
What's going on here? We use the GetPointer()
method to find the location
of the pixel in the buffer, then we handle color conversion. That's
really the crux of this and the SetPixel()
calls. They do color conversion
so you don't have to worry about it. These conversions can be as optimal
as you like. The SetPixel()
method is similar but reversed, so check the
code online for that one.
Of course, SetPixel()
and GetPixel()
are the absolute minimum set of
operations required to build any graphics system. Once you have these you
can do primitive drawing of all kinds, but that's not what we're after
here.
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 BView
::DrawBitmap()
and
you're done with it. However, having hundreds of little 4K-minimum
bitmaps, even if the actual number of pixels is a paltry 1024, may not be
the most efficient thing to do.
You could load all your little icons into a single BBitmap
and keep track
of their relative locations within it, and use the DrawBitmap()
method
that takes a source rect and a destination rect. But let's say you want
to go one step further. Your interface uses an offscreen bitmap for
flicker-free updates. Many times you want to draw various icons into this
offscreen BBitmap
, and then display it to the screen. Well, the
PixelBuffer
object gives us the means to do the following:
voidDisplayIcon
(constint32x
, constint32y
,PixelBuffer
*dest
,PixelBuffer
*icon
) { intnumRows
=icon
->Height
(); intnumCols
=icon
->Width
(); for (introw
= 0;row
<numRows
;row
++) { for (intcol
= 0;col
<numCols
;col
++) { rgb_coloraColor
;icon
->GetPixel
(col
,row
,aColor
);dest
->SetPixel
(x
+col
,y
+row
,aColor
); } } } #defineimg_width
25 #defineimg_height
11 unsigned charimg_bits
[] = {/* actual data goes here */};PixelBuffer
*icon
= newPixelBuffer
(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:
voidSetPixel8
(constint32x
, constint32y
, uint8color
); voidSetPixel15
(constint32x
, constint32y
, uint16color16
);
The same goes for the GetPixel()
methods.
That way, the copyPixels()
function and the DisplayIcon()
functions can use an optimized interface
based on the color space of the source and destination. You could also
optimize the copyPixels()
method by introducing a row at a time copy into
the PixelBuffer
object that is optimized in a similar fashion as the
SetPixel8()
and SetPixel15()
methods. Once you have that, your life will be
better, the sun will shine brighter, and all will be right with the world.
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.
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.
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.