Introduction
Last month, we took a good look at the beginnings of a sprite library that
is well-suited for simple animation. We looked at:
- how a sprite is drawn
- how the drawing of sprites requires a mask, which adds an extra burden on
the management of the sprite data structure by the library
- how a sprite is moved in a manner that minimizes flicker
But even discussing these things left some holes, namely the background and
the workarea that enables the flicker-free movement. This month, we will
wrap up our discussion on the design of the library and will begin delving
into the code for the sprite library itself. Next month's conclusion will
look into the intricacies of animating a set of sprites using the i495.exe
sample as a starting point.
Ch-Ch-Ch-Changes
Ah, there's nothing like quintessential Bowie...
Since last month, I have integrated the sprite library into the Common/2
library that was on hobbes (if this new version isn't there already, wait
for a week and remind me if I still have forgotten). However, I have kept
the original version and have modified it to provide semaphore exclusion to
the data structures (actually, I surgically cut-n-pasted the related
functions from Common/2 <grin>), and it is this version that we will
present in this series. It should be noted, though, that the version in
Common/2 does come complete with an online reference.
Monkey Bars and the Like
A distinguishing characteristic between a sprite and a bitmap is that the
former has a transparency color which means that whatever is below the
sprite shows through it. If the background were simply a solid color, this
could be done through sleight-of-hand by drawing the bitmap with the
background color in the areas that are to be see-through. Movement would
be quite simple, reduced to a few WinFillRect() calls and a single
GpiWCBitBlt().
Unfortunately, a sprite can make no assumptions about the underlying
surface: it could be a solid color or it could be a complex drawing or it
even could be another sprite. Worse yet, we cannot simply take what is
already on the screen, for what shall we restore there if - for example -
the sprite has its visibility state set to FALSE?
Because of this (and other reasons), we need to maintain yet another data
structure which will, among other things, manage a static bitmap to be used
for the background; we will call it a playground since it is the area in
which we will let the sprites play. Conceptually, a playground is the
"blackboard" while each sprite is just a chalk drawing on the blackboard,
so all drawing related functions are considered to be associated with a
playground instead of the sprites that occupy a playground.
The playground support must provide the following functions:
- Create a playground
- Destroy a playground
- Add a sprite to the playground
- Remove a sprite from the playground
- Draw the entire playground
Adding and removing sprites from the playground is needed so that we can
access data structures in the playground given the sprite handle. For
example, only one work area is needed per playground since one thread at
most can access an HPS, so we put the work area in the playground, but need
to access this work area from each sprite.
Drawing a playground involves drawing the background and then looping
through its membership list to draw each sprite at its current position.
This is intended to provide a simple method of processing the WM_PAINT
message within an application.
If all of these design issues get confusing, remember that I have the power
of hindsight and you do not, so do not get dismayed or (worse yet) think
that I have some divine capability to think of these things before writing
a line of code; I had the same trial-n-error development process that you
would have were our places exchanged.
Final Design Considerations
In even the most elementary computer programming classes, the concept of
code reuse is hammered into the brains of the students; here, this concept
is stressed as well. There are many code blocks that are needed in various
places throughout the library, so these code blocks were turned into
functions.
Mutually exclusive access to the data structures is also an important part
of any good library, and it is implemented here through the use of a mutex
sempahore and a status flag; the status flag indicates if access has
already been granted to a function, i.e. if a function calls another
function, the called function will deadlock if it requests the semaphore.
The access-granting function is accessSem(), and it would be worth the time
to understand how this is implemented to avoid confusion later.
Where's Your Library Card?
Since utilitizing the library involves far more complex coding than the
code for the library itself (did I ever say that animation was easy?), we
will look at the library itself. If you have not already done so, unzip
the source.zip file and take a look at the files within.
- MAKEFILE
- the makefile for the library
- SPRITE.C
- the source file
- SPRITE.HPP
- the .H preprocessor file. This is fed to HEADER.EXE which generates an implementor's .H file and a user's .H file. You should change
the first two lines of this file so that the paths make sense.
//@HEADER OUTFILE INT D:\SOURCE\SPRITE\SPRITE.H
//@HEADER OUTFILE EXT D:\MYINC\SPRITE.H
The INT line defines the file name of the "internal" use .H file, while the
EXT line defines the file name of the "external" use .H file.
- SPRITE.H
- Generated by the command "HEADER -tINT SPRITE.HPP"
- SPRITE.OBJ
- Object file
- SPRITE.LIB
- Library file
- HEADER.EXE
- Header file preprocessor. I think I have placed this on
hobbes, possibly under the name HPREP. If not, this is an older version,
but it will do. (I keep forgetting to bring the newer version in to work.)
Before we can begin to understand the code, we must first understand the
data structures on which the code operates, so we first look at the
playground:
typedef struct _PLAYGROUND {
ULONG ulSig;
ULONG ulStatus;
HMTX hsmAccess;
HAB habAnchor;
HDC hdcWork;
HPS hpsWork;
HBITMAP hbmWork;
HBITMAP hbmBack;
BITMAPINFOHEADER2 bmihBack;
LONG lBackColor;
BOOL bUpdate;
HSPRITE ahsSprites[MAX_SPRITES];
ULONG ulNumMembers;
} PLAYGROUND, *HPLAYGROUND;
typedef HPLAYGROUND *PHPLAYGROUND;
- ulSig
- 4-byte signature for the data structure, used for parameter validation.
- ulStatus
- 32-bit status flags used for semaphore access at this time.
- hsmAccess
- mutex (mutual exclusion) semaphore handle.
- habAnchor
- anchor block handle of the calling thread.
- hdcWork
- OD_MEMORY device context handle for the work area.
- hpsWork
- presentation space handle associated with hdcWork.
- hbmWork
- bitmap handle set into hpsWork.
- hbmBack
- bitmap handle for the background bitmap. If NULLHANDLE, the playground has a background color instead (lBackColor).
- bmihBack
- bitmap information header for hbmBack. If hbmBack is NULLHANDLE,
the cx and cy fields indicate the size of the playground; otherwise the
size of the playground is specified by the size of the background bitmap.
- lBackColor
- the background color of the playground, if hbmBack is NULLHANDLE.
- bUpdate
- update flag. If FALSE, no drawing is actually performed. This is useful for changing sprites in place.
- ahsSprites
- array of sprite handles comprising the membership list.
- ulNumMembers
- current number of members.
We will see how these fields are used when we look at the code. But now,
we will look at the sprite structure:
typedef struct _SPRITE {
ULONG ulSig;
ULONG ulStatus;
HMTX hsmAccess;
HAB habAnchor;
HBITMAP hbmBitmap;
HBITMAP hbmMask;
BITMAPINFOHEADER2 bmihBitmap;
BITMAPINFOHEADER2 bmihMask;
struct _PLAYGROUND *hpgPlay;
POINTL ptlPos;
BOOL bVisible;
} SPRITE, *HSPRITE;
typedef HSPRITE *PHSPRITE;
- ulSig
- 4-byte signature for the data structure, used for parameter validation.
- ulStatus
- 32-bit status flags used for semaphore access at this time.
- hsmAccess
- mutex (mutual exclusion) semaphore handle.
- habAnchor
- anchor block handle of the calling thread.
- hbmBitmap
- bitmap handle which defines the sprite.
- hbmMask
- bitmap handle which defines the mask.
- bmihBitmap
- bitmap information header for hbmBitmap.
- bmihMask
- bitmap information header for hbmMask.
- hpgPlay
- playground handle of which the sprite is a member.
- ptlPos
- current position.
- bVisible
- current visibility state.
A comment on the list of exposed functions below: notice the symmetry of
the function names. For each create, there is a destroy; querys have
corresponding sets when appropriate; the add has a remove. While much is
often said about intuitiveness of the user-interface, the same concepts
along with the advantages gained can be applied to the
"programmer-interface". The non-exposed (internal) functions, obviously,
do not need to follow this guideline, although it does help if more than
one person is developing and/or maintaining the code.
Each function below is a hypertext link to its code and the explanation of
the code; feel free to explore the functions in any order.
Internal Functions
External Functions
Summary
This month, we finished our discussion of the design of the sprite library
by describing the need for a master data structure called the playground,
and the code to implement the library routines was presented to illustrate
the concepts that we have already learned in our discussions up to this
point. Next month, we will take these underpinings and will apply them to
the i495.exe application to see how they can be utililized to perform
rudimentary animation.
All comments, suggestions, bugs, etc. are welcome c/o the author.