Jump to content

Gearing Up For Games - Part 3: Difference between revisions

From EDM2
Prokushev (talk | contribs)
No edit summary
 
Ak120 (talk | contribs)
mNo edit summary
 
(9 intermediate revisions by 3 users not shown)
Line 1: Line 1:
Written by [[Michael T Duffy]]
''Written by [[Michael T. Duffy]]''


<h2>Introduction</h2>
==Introduction==
 
Welcome to the third instalment of Gearing Up For Games. The past couple of months have been really busy for me, and in August I took a two week trip to Japan that took me completely away from the computer. Add to this the fact that I'm trying desperately to get my own game out before the end of the year, and you begin to see why I didn't get a chance to finish this
<p>Welcome to the third installment of Gearing Up For Games. The past couple
of months have been really busy for me, and in August I took a two week
trip to Japan that took me completely away from the computer. Add to this
the fact that I'm trying desperately to get my own game out before the end
of the year, and you begin to see why I didn't get a chance to finish this
article.
article.


<p>I had planned on covering threads and semaphores, and basic sprites in this
I had planned on covering threads and semaphores, and basic sprites in this article. It turns out that I have only had time to write about threads and semaphores. However I wrote the code for the article before I started the text, so I finished all of the basic sprite code. Rather than pull the sprite code out, I have left it in. For those of you already familiar with sprites, you should be able to follow the code fairly easily. The next to last section of this article briefly describes how I approached sprites, though it is not meant to be an in-depth explanation. It will have to suffice for now.
article. It turns out that I have only had time to write about threads and
semaphores. However I wrote the code for the article before I started the
text, so I finished all of the basic sprite code. Rather than pull the
sprite code out, I have left it in. For those of you already familiar with
sprites, you should be able to follow the code fairly easily. The next to
last section of this article briefly describes how I approached sprites,
though it is not meant to be an in-depth explanation. It will have to
suffice for now.
 
<p>Also, I should point out that my discussion of threads and semaphores is
oriented towards games, and does not cover all aspects of threads or
semaphores.  A complete discussion is beyond the scope of this article, and
I would suggest that you look into the reference documentation in the OS/2
toolkit (namely Control Program Guide and Reference) as well as a good book
or two on OS/2 programming.  I have found both Petzold's OS/2 Presentation
Manager Programming (pub. Ziff- Davis Press) and Real World Programming for
OS/2 2.11  from SAMS Publishing to be useful references.  Each book covers
some material that the other doesn't, so it may be a good idea to look at
more than one explanation.  The material presented in this article should
be enough to give you a basic understanding of threads and along with the
accompanying sample code, you should be able to use threads and semaphores
in your own game programs.
 
<p>Enough for excuses...time to get to the meat of the matter!
 
<h2>Threads</h2>


<p>One of the advantages of an operating system like OS/2 is that the
Also, I should point out that my discussion of threads and semaphores is oriented towards games, and does not cover all aspects of threads or semaphores. A complete discussion is beyond the scope of this article, and I would suggest that you look into the reference documentation in the OS/2 toolkit (namely Control Program Guide and Reference) as well as a good book or two on OS/2 programming. I have found both Petzold's OS/2 Presentation Manager Programming (pub. Ziff-Davis Press) and Real World Programming for OS/2 2.11 from SAMS Publishing to be useful references. Each book covers some material that the other doesn't, so it may be a good idea to look at more than one explanation. The material presented in this article should be enough to give you a basic understanding of threads and along with the accompanying sample code, you should be able to use threads and semaphores in your own game programs.
programmer has available to him or her a multitasking environment.  This
not only means that more than one program can be running concurrently, but
also that different parts of the same program can execute concurrenly. In
DOS, programmers did not have multitasking and instead relied on interrupt
handlers and reprogramming the internal timer chip in order to achieve
this, e.g. reading the keyboard or joystick while blitting to the screen.
This is no longer necessary in OS/2, and part of the reason is because of
threads.


<p>What is a thread?  A thread is a separate unit of execution within a
Enough for excuses...time to get to the meat of the matter!
program. Each thread has its own set of CPU register variables and its own
stack. For the beginner, register variables are the variables used inside
of the CPU to perform calculations and keep track of where the program is
currently executing.  The stack is where local variables are allocated and
deallocated from when a new routine is entered.  Threads are owned by a
process, where a process is usually the program that you are running.


<p>Multitasking is handled by the CPU, with the operating system telling the
==Threads==
CPU how much time to give each thread.  It basically works like this: the
One of the advantages of an operating system like OS/2 is that the programmer has available to him or her a multitasking environment. This not only means that more than one program can be running concurrently, but also that different parts of the same program can execute concurrenly.
CPU executes a thread for a given amount of time, stopping the thread after
that time has elapsed. The CPU stops the thread dead in its tracks, even
if it is in the middle of executing a single line of C code, like
usVariable1 = usVariable2.  All of the variables in the CPU registers are
stored.  The OS then instructs the CPU which thread to run next, and the
CPU loads its registers with the saved registers of the next thread, then
executes that thread for a given amount of time, and so on.  An operating
system like OS/2 can determine how much CPU time a thread needs, and adjust
the amount of time it gives a thread for its next execution. The OS can
also suspend a thread if the CPU is needed elsewhere, such as reading
keyboard, COM port, or disk drive data.  This is equal to the interrupts of
the days of DOS.


<p>As a result, you never know when control will be given to or taken from
In DOS, programmers did not have multitasking and instead relied on interrupt handlers and reprogramming the internal timer chip in order to achieve this, e.g. reading the keyboard or joystick while blitting to the screen. This is no longer necessary in OS/2, and part of the reason is because of threads.
your thread. You can indirectly affect how much time your thread is given
by setting the priority level of the thread. A priority level basically
tells the CPU how important your thread is compared to other threads.
Threads that have a more important priority level are serviced before
threads of lower priority.  Threads of the same priority level are serviced
in a round-robin fashion.  For example, let's say you have two threads of
the priority, A and B, and one thread of a lower priority, C. Depending on
the load level of the machine and other settings, these three threads might
gain control in the order:  A B A B C A B A B A B A B C A B C A B C. Since
A and B are of the same priority, they will be serviced equally. Thread C
will be serviced when A and B have been handled, and there is CPU time left
over.


<p>As mentioned before OS/2 can adjust on the fly how much time each thread
What is a thread? A thread is a separate unit of execution within a program. Each thread has its own set of CPU register variables and its own stack. For the beginner, register variables are the variables used inside of the CPU to perform calculations and keep track of where the program is currently executing. The stack is where local variables are allocated and deallocated from when a new routine is entered. Threads are owned by a process, where a process is usually the program that you are running.
gets before the CPU stops it.  How does OS/2 determine how much time a
thread uses? A thread can signal that it has used enough CPU time for now
in two ways. One, if you have a message queue then OS/2 knows when you're
done processing because your message handling routine returns to the
operating system once it has handled the message. The second way is that
threads can be suspended, resumed, or paused (with DosSleep()).  This will
be discussed shortly.


Multitasking is handled by the CPU, with the operating system telling the CPU how much time to give each thread. It basically works like this: the CPU executes a thread for a given amount of time, stopping the thread after that time has elapsed. The CPU stops the thread dead in its tracks, even if it is in the middle of executing a single line of C code, like usVariable1 = usVariable2. All of the variables in the CPU registers are stored. The OS then instructs the CPU which thread to run next, and the CPU loads its registers with the saved registers of the next thread, then executes that thread for a given amount of time, and so on. An operating system like OS/2 can determine how much CPU time a thread needs, and adjust the amount of time it gives a thread for its next execution. The OS can also suspend a thread if the CPU is needed elsewhere, such as reading keyboard, COM port, or disk drive data. This is equal to the interrupts of the days of DOS.


<h4>How are threads useful, and when do you use one?</h4>
As a result, you never know when control will be given to or taken from your thread. You can indirectly affect how much time your thread is given by setting the priority level of the thread. A priority level basically tells the CPU how important your thread is compared to other threads.
Threads that have a more important priority level are serviced before threads of lower priority. Threads of the same priority level are serviced in a round-robin fashion. For example, let's say you have two threads of the priority, A and B, and one thread of a lower priority, C. Depending on the load level of the machine and other settings, these three threads might gain control in the order:
A B A B C A B A B A B A B C A B C A B C.
Since A and B are of the same priority, they will be serviced equally. Thread C will be serviced when A and B have been handled, and there is CPU time left over.


<p>Threads are needed when you must do two or more things at the same time. An
As mentioned before OS/2 can adjust on the fly how much time each thread gets before the CPU stops it. How does OS/2 determine how much time a thread uses? A thread can signal that it has used enough CPU time for now in two ways. One, if you have a message queue then OS/2 knows when you're done processing because your message handling routine returns to the operating system once it has handled the message. The second way is that threads can be suspended, resumed, or paused (with DosSleep()). This will be discussed shortly.
important thing to remember about threads however, is that on Intel based
machines, two threads don't really execute at the same time. The CPU runs
one thread for a short time, switches to other threads, switches back to
the first thread and so on.  Even on machines with SMP (Symmetrical
Multi-Processors) often all of the threads of a given process are run on a
single CPU. A CPU can only execute a certain number of instructions per
second, and all threads take up a share of those instructions.  It also
takes time to switch from one thread to another, even though this amount of
time is very, very short overall.


<p>Threads are therefore useful when you need to to several tasks at basically
====How are threads useful, and when do you use one?====
the same time, and you don't want to have to worry about giving each task
Threads are needed when you must do two or more things at the same time. An important thing to remember about threads however, is that on Intel based machines, two threads don't really execute at the same time. The CPU runs one thread for a short time, switches to other threads, switches back to the first thread and so on. Even on machines with SMP (Symmetrical Multi-Processors) often all of the threads of a given process are run on a single CPU. A CPU can only execute a certain number of instructions per second, and all threads take up a share of those instructions. It also takes time to switch from one thread to another, even though this amount of time is very, very short overall.
its share of the CPU.  Instead, OS/2 will handle the thread management.
Good examples of threads are the thread that handles the main message
queue, a thread that blits images to the screen, a thread that
loads/saves/sends/receives data in the background, a thread that mixes
sound, and perhaps a thread that handles the artificial intelligence (AI)
of the other opponents in a game.  In your games you will always want to
have at least two threads:  one to handle the main message queue, and one
to handle the game mechanics.  The reason for this is that you should
always spend a little time as possible in the message queue routine,
otherwise you risk slowing down overall system performance.  One of the
flaws of OS/2 is that it has a single system message queue. Since the OS
can't send out other messages until the message it just sent is processed,
a greedy program that uses a lot of time between when it is sent a message
and when it returns to the OS holds up other messages meant both for itself
and for other programs. If the processing of a message will take a long
time, it is better to instruct a second thread to handle the processing and
then return to the OS. This way the second thread can perform the
processing while the OS continues to send out other messages.


<p>Threads are not very useful when you have a certain number of things that
Threads are therefore useful when you need to do several tasks at basically the same time, and you don't want to have to worry about giving each task its share of the CPU. Instead, OS/2 will handle the thread management. Good examples of threads are the thread that handles the main message queue, a thread that blits images to the screen, a thread that loads/saves/sends/receives data in the background, a thread that mixes sound, and perhaps a thread that handles the artificial intelligence (AI) of the other opponents in a game. In your games you will always want to have at least two threads: one to handle the main message queue, and one to handle the game mechanics. The reason for this is that you should always spend a little time as possible in the message queue routine, otherwise you risk slowing down overall system performance. One of the flaws of OS/2 is that it has a single system message queue. Since the OS can't send out other messages until the message it just sent is processed, a greedy program that uses a lot of time between when it is sent a message and when it returns to the OS holds up other messages meant both for itself and for other programs. If the processing of a message will take a long time, it is better to instruct a second thread to handle the processing and then return to the OS. This way the second thread can perform the processing while the OS continues to send out other messages.
must be done in a certain order.  For example, in an action game you may
have several tasks to complete in a certain time frame, and one must follow
the other. Every frame of a shooting game, you must read the joystick or
keyboard, update the player's position, call the enemy AI routines and
update their positions, move all missiles and bullets, and then draw the
new graphics with the new positions to the display. Even though you could
theoretically calculate player movement and enemy AI at the same time, the
player only sees the finished screen and everything else must be completed
before that screen is rendered.  No matter how you arrange the above tasks,
together all of the above tasks will require the same amount of CPU time
before the screen can be blitted.  Placing each task in a separate thread
would not speed things up, and in fact things would slow down because you
will have to synchronize the threads' access to variables, and the overhead
of switching threads itself will slow things down some.  The solution to
the above problem is to place all of the tasks in the same thread.


<p>Other games can have different tasks running at the same time. Consider a
Threads are not very useful when you have a certain number of things that must be done in a certain order. For example, in an action game you may have several tasks to complete in a certain time frame, and one must follow the other. Every frame of a shooting game, you must read the joystick or keyboard, update the player's position, call the enemy AI routines and update their positions, move all missiles and bullets, and then draw the new graphics with the new positions to the display. Even though you could theoretically calculate player movement and enemy AI at the same time, the player only sees the finished screen and everything else must be completed before that screen is rendered. No matter how you arrange the above tasks, together all of the above tasks will require the same amount of CPU time before the screen can be blitted. Placing each task in a separate thread would not speed things up, and in fact things would slow down because you will have to synchronize the threads' access to variables, and the overhead of switching threads itself will slow things down some. The solution to the above problem is to place all of the tasks in the same thread.
simulation or an adventure game.  These kind of games can have complex AI
for the computer opponents, and the calculations for the AI take a long
time. However, the player is not continually giving input and the opponents
are not continually moving like in a fast paced action game.  The player
might be studying graphics or maps, or even reading text while not
inputting requests for new actions. Rather than having the computer wait
for the player to make a move before deciding how the computer opponents
would move (as a DOS game would do), the computer can decide it's next move
while the player is involved with non-intensive CPU activities. If the
computer does not finish a task before the player makes a move, then it can
continue to process until it does make a decision. Depending on the type
of game, this would mean either stopping the game until the decision were
made, or continuing with the previous action this turn until a new action
is decided upon.


<h4>Types of Threads</h4>
Other games can have different tasks running at the same time. Consider a simulation or an adventure game. These kind of games can have complex AI for the computer opponents, and the calculations for the AI take a long time. However, the player is not continually giving input and the opponents are not continually moving like in a fast paced action game. The player might be studying graphics or maps, or even reading text while not inputting requests for new actions. Rather than having the computer wait for the player to make a move before deciding how the computer opponents would move (as a DOS game would do), the computer can decide it's next move while the player is involved with non-intensive CPU activities. If the computer does not finish a task before the player makes a move, then it can continue to process until it does make a decision. Depending on the type of game, this would mean either stopping the game until the decision were made, or continuing with the previous action this turn until a new action is decided upon.


<p>There are two basic types of threads: message queue and non- message
====Types of Threads====
queue. The type is determined obviously enough based upon whether or not
There are two basic types of threads: message queue and non-message queue. The type is determined obviously enough based upon whether or not that thread has created a message queue. The type of thread you use will depend on your purpose. Aside from having a message queue and a message queue handle, message queue threads differ from non-message queue threads in a few significant ways. Non-message queue threads cannot do the following: create windows, contain window procedures, send messages to window procedures with WinSendMsg() (although they can call WinPostMsg()), or call functions that cause messages to be sent to window procedures. On the other hand, message queue threads cannot be suspended or blocked, or else they will hold up the rest of the operating system. Also, it is important to note that you cannot use the message queue handle of another thread to call windowing procedures. The strengths and weaknesses of each type should be weighed carefully when organizing your program.
that thread has created a message queue. The type of thread you use will
depend on your purpose. Aside from having a message queue and a message
queue handle, message queue threads differ from non-message queue threads
in a few significant ways. Non-message queue threads cannot do the
following: create windows, contain window procedures, send messages to
window procedures with WinSendMsg() (although they can call WinPostMsg()),
or call functions that cause messages to be sent to window procedures. On
the other hand, message queue threads cannot be suspended or blocked, or
else they will hold up the rest of the operating system. Also, it is
important to note that you cannot use the message queue handle of another
thread to call windowing procedures. The strengths and weaknesses of each
type should be weighed carefully when organizing your program.


<h4>Using Threads</h4>
====Using Threads====
Threads can be created either through the functions in a standard library, such as C/C++'s _beginthread() function, or through the OS/2 API. We will be using the latter approach since the OS/2 API allows more control over threads than the former approach. I suggest that you obtain a copy of IBM's Control Program Guide and Reference for a complete reference to the thread and semaphore API.


<p>Threads can be created either through the functions in a standard library,
Note that when you use threads, you must compile with the switch that tells the compiler that you are working with a multithreaded program. This switch tells the compiler to use a special version of the standard libraries that are "multithread-safe". If two threads call the same or related functions in the standard C or C++ library at the same time, then there could be problems if this flag is not specified. With the Watcom C/C++ compiler, the flag is -bm.
such as C/C++'s _beginthread() function, or through the OS/2 API. We will
be using the latter approach since the OS/2 API allows more control over
threads than the former approach.  I suggest that you obtain a copy of
IBM's Control Program Guide and Reference for a complete reference to the
thread and semaphore API.


<p>Note that when you use threads, you must compile with the switch that tells
To create a thread we call DosCreateThread(). We tell this function what routine the thread is to run and give a single parameter to be passed to that routine, tell it what the stack size of the new thread is to be and whether or not to commit the pages of the stack, and whether the thread it to be executed immediately or created in a suspended state.
the compiler that you are working with a multithreaded program. This
switch tells the compiler to use a special version of the standard
libraries that are &quot;multithread-safe&quot;.  If two theads call the same or
related functions in the standard C or C++ library at the same time, then
there could be problems if this flag is not specified.  With the Watcom
C/C++ compiler, the flag is -bm.


Once a thread is created, you should set its priority status with DosSetPriority(). You will have to experiment to find out what priority levels to use for each of your threads. It is a good idea to have your message queue to be at a higher priority than your other threads so that the entire system will stay responsive.


<p>To create a thread we call DosCreateThread(). We tell this function what
A thread ends itself by either by returning from initial thread function, or calling DosExit(). A thread may kill another thread by calling DosKillThread(), though it is often a good idea to signal a thread that it should kill itself so that it may shut down what it is doing before it terminates.
routine the thread is to run and give a single parameter to be passed to
that routine, tell it what the stack size of the new thread is to be and
whether or not to commit the pages of the stack, and whether the thread it
to be executed immediately or created in a suspended state.


<p>Once a thread is created, you should set its priority status with
Threads can also be temporarily stopped and restarted with the functions DosSuspendThread() and DosResumeThread(). While a thread is suspended, it does not take up any CPU time; it is simply not called. When a message queue thread returns to the OS/2 kernel and there are no messages for it, the OS/2 suspends the thread until new messages are available. Note that threads obviously cannot resume themselves since they are not running, though they can suspend themselves and other threads. If a thread is created in a suspended state, you must call DosResumeThread() to begin its execution.
DosSetPriority(). You will have to experiment to find out what priority
levels to use for each of your threads. It is a good idea to have your
message queue to be at a higher priority than your other threads so that
the entire system will stay responsive.


<p>A thread ends itself by either by returning from initial thread function,
Another way to temporarily suspend a thread is to call DosSleep(). This function takes a single parameter that tells the system how many milliseconds the thread should be suspended before it is resumed. Note that this can not be used for exact timing purposes. As soon as the time runs out, the thread that called DosSleep() will be considered again for CPU time slices, but other threads may execute before the recently resumed thread gets it's turn, especially if the other threads have a higher priority. Calling DosSleep(0) causes the thread to release the remainder of it's CPU time for the current time slice back to OS/2 so that it can be used elsewhere. This should be used in non-message queue threads where you don't want to hog CPU time and you don't have anything special to do. It is especially useful at the bottom of a loop where you have just finished a task and you don't need to start the next task immediately. This is not needed if you suspend the thread through other means.
or calling DosExit(). A thread may kill another thread by calling
DosKillThread(), though it is often a good idea to signal a thread that it
should kill itself so that it may shut down what it is doing before it
terminates.


<p>Threads can also be temporarily stopped and restarted with the functions
Finally, a thread can suspend itself until another thread has finished executing. When DosWaitThread() is called, a flag is passed that specifies whether the function should wait for end of execution or not. If DCWW_NOWAIT is specified, the function returns immediately and the error status tells whether or not the specified thread is still running. If DCWW_WAIT is specified, then the function does not return until the specified thread terminates. DosWaitThread() is useful when thread A wants to terminate thread B. Thread A signals thread B that it should terminate. Thread A then calls DosWaitThread() with the wait flag specified. Thread B receives the shutdown signal, cleans up what it needs to, and the exits gracefully. As soon as thread B exits, DosWaitThread() returns and thread A can continue about its business.
DosSuspendThread() and DosResumeThread().  While a thread is suspended, it
does not take up any CPU time; it is simply not called. When a message
queue thread returns to the OS/2 kernel and there are no messages for it,
the OS/2 suspends the thread until new messages are available. Note that
threads obviously cannot resume themselves since they are not running,
though they can suspend themselves and other threads. If a thread is
created in a suspended state, you must call DosResumeThread() to begin its
execution.


<p>Another way to temporarily suspend a thread is to call DosSleep(). This
As far as the creating, executing, suspending, and resuming the execution of threads, that's all there is too it. To communicate amongst different threads and to coordinate their use of global variables and memory, you will have to rely on semaphores.
function takes a single parameter that tells the system how many
milliseconds the thread should be suspended before it is resumed.  Note
that this can not be used for exact timing purposes. As soon as the time
runs out, the thread that called DosSleep() will be considered again for
CPU time slices, but other threads may execute before the recently resumed
thread gets it's turn, especially if the other threads have a higher
priority.  Calling DosSleep(0) causes the thread to release the remainder
of it's CPU time for the current time slice back to OS/2 so that it can be
used elsewhere. This should be used in non-message queue threads where you
don't want to hog CPU time and you don't have anything special to do.  It
is especially useful at the bottom of a loop where you have just finished a
task and you don't need to start the next task immediately.  This is not
needed if you suspend the thread through other means.


<p>Finally, a thread can suspend itself until another thread has finished
==Semaphores==
executing. When DosWaitThread() is called, a flag is passed that specifies
A semaphore is like a traffic cop that keeps threads from crashing into each other. They are needed because when the CPU switches from one thread to another, it may interrupt it anywhere. For example, Thread A might be in the middle of updating a structure. It gets halfway through the update, and then it is interrupted by the CPU and thread B gets control. Thread B tries to use the information in the structure, but only half of it is correct. Imagine if the structure contained a forward and backward linked list, and only one of the two pointers were updated. Behaviour of the program would be unpredictable and buggy. It gets even worse. Actions we consider atomic in C may actually be compiled down into several assembly language instructions. Consider the expression ++usVar1; The compiler might break it down like:
whether the function should wait for end of execution or not. If
<code>
DCWW_NOWAIT is specified, the function returns immediately and the error
  mov  eax, [usVar1]  ;eax = 0, usVar1 == 0
status tells whether or not the specified thread is still running. If
inc  eax     ;eax = 1, usVar1 == 0
DCWW_WAIT is specified, then the function does not return until the
mov  [usVar1], eax ;eax == 1, usVar1 = 1
specified thread terminates. DosWaitThread() is useful when thread A wants
</code>
to terminate thread B. Thread A signals thread B that it should terminate.
''Figure 1) Assembler for ++usVar1''
Thread A then calls DosWaitThread() with the wait flag specified. Thread B
receives the shutdown signal, cleans up what it needs to, and the exits
gracefully. As soon as thread B exits, DosWaitThread() returns and thread
A can continue about its business.


<p>As far as the creating, executing, suspending, and resuming the execution
What happens if Thread A is interrupted by Thread B during the middle of the command, and thread B tries something simple like incrementing usVar1. Remember that each thread has its own set of CPU register variables.
of threads, that's all there is too it.  To communicate amongst different
<code>
threads and to coordinate their use of global variables and memory, you
A - mov  eax, [usVar1] ;eax = 0, usVar1 == 0
will have to rely on semaphores.
A - inc  eax ;eax = 1, usVar1 == 0
 
<h2>Semaphores</h2>
 
<p>A semaphore is like a traffic cop that keeps threads from crashing into
each other.  They are needed because when the CPU switches from one thread
to another, it may interrupt it anywhere.  For example, Thread A might be
in the middle of updating a structure.  It gets halfway through the update,
and then it is interrupted by the CPU and thread B gets control. Thread B
tries to use the information in the structure, but only half of it is
correct.  Imagine if the structure contained a forward and backward linked
list, and only one of the two pointers were updated. Behavior of the
program would be unpredictable and buggy.  It gets even worse.  Actions we
consider atomic in C may actually be compiled down into several assembly
language instructions. Consider the expression ++usVar1; The compiler
might break it down like:
 
<pre><small>
mov  eax, [usVar1]  ;eax = 0, usVar1 == 0
inc  eax   ;eax = 1, usVar1 == 0
mov  [usVar1], eax  ;eax == 1, usVar1 = 1
</small></pre>
 
<small>Figure 1) Assembler for ++usVar1</small>
 
<p>What happens if Thread A is interrupted by Thread B during the middle of
the command, and thread B tries something simple like incrementing usVar1.
Remember that each thread has it's own set of CPU register variables.
 
<pre><small>
A - mov  eax, [usVar1] ;eax = 0, usVar1 == 0
A - inc  eax ;eax = 1, usVar1 == 0
     ---- switch threads ----
     ---- switch threads ----
B - mov  eax, [usVar1] ;eax = 0, usVar1 == 0
B - mov  eax, [usVar1] ;eax = 0, usVar1 == 0
B - inc  eax ;eax = 1, usVar1 == 0
B - inc  eax ;eax = 1, usVar1 == 0
B - mov  [usVar1],eax ;eax == 1, usVar1 = 1
B - mov  [usVar1],eax ;eax == 1, usVar1 = 1
     ---- other processing ----
     ---- other processing ----
     ---- switch threads ----
     ---- switch threads ----
A - mov  [usVar1],eax ;eax == 1, usVar1 = 1
A - mov  [usVar1],eax ;eax == 1, usVar1 = 1
</small></pre>
</code>
 
''Figure 2) Accessing usVar1 by multiple threads''
<small>Figure 2) Accessing usVar1 by multiple threads</small>
 
<p>Although at the end of two ++usVar1 instructions it should go from 0 to 2,
the final value comes out to be 1. This is obviously not good.


<p>What you would want to do to solve this problem is to make sure your thread
Although at the end of two ++usVar1 instructions it should go from 0 to 2, the final value comes out to be 1. This is obviously not good.
were not interrupted in the middle of using a variable, structure, or other
piece of data.  This can be accomplished in two ways.  One way is to call
DosEnterCritSec() before the calculation, and call DosExitCritSec()
afterwards.  This causes all thread switching to stop between the two
function calls.  All threads.  The threads that control OS/2, other threads
in your program, all threads in other programs, and all threads that handle
interrupt processing.  Use of these two functions may be fine for simple
operations such as a single increment or decrement of a variable, but
should never be used for processing that takes longer than just a few
assembly language instructions. If you suspend system threads for too long
you risk losing data coming in from the keyboard or serial ports, and you
will prevent other updates that need to be done on a regular basis.
Besides, you are only really worried about threads A and B accessing a
given piece of data.


<p>A much better solution is to use semaphores. Semaphores come in three
What you would want to do to solve this problem is to make sure your thread were not interrupted in the middle of using a variable, structure, or other piece of data. This can be accomplished in two ways. One way is to call DosEnterCritSec() before the calculation, and call DosExitCritSec() afterwards. This causes all thread switching to stop between the two
varieties: mutually-exclusive (mutex), event, and multiple-wait. Mutex
function calls. All threads. The threads that control OS/2, other threads in your program, all threads in other programs, and all threads that handle interrupt processing. Use of these two functions may be fine for simple operations such as a single increment or decrement of a variable, but should never be used for processing that takes longer than just a few assembly language instructions. If you suspend system threads for too long you risk losing data coming in from the keyboard or serial ports, and you will prevent other updates that need to be done on a regular basis.
semaphores are like a hall pass; only one thread may have the hall pass at
Besides, you are only really worried about threads A and B accessing a given piece of data.
any one time, and the others must wait until the pass is returned. Event
semaphores are like traffic lights where you can have a stop and a go
status. Multiple wait semaphores are like a collection of the other two
types; you have several hall passes or traffic lights to consider at the
same time.


<p>Semaphores may also be private or shared, and named or unnamed. Private
A much better solution is to use semaphores. Semaphores come in three varieties: mutually-exclusive (mutex), event, and multiple-wait. Mutex semaphores are like a hall pass; only one thread may have the hall pass at any one time, and the others must wait until the pass is returned. Event semaphores are like traffic lights where you can have a stop and a go status. Multiple wait semaphores are like a collection of the other two types; you have several hall passes or traffic lights to consider at the same time.
semaphores may only be used by a single process; shared semaphores may be
used by several processes. Other processes may obtain the handle of a
semaphore in a couple of ways. The handle of the thread can be passed in a
message.  If the semaphore is named, then the other process may use a known
name to request the handle from the operating system.


<p>The programming samples in Gearing Up For Games will use only private,
Semaphores may also be private or shared, and named or unnamed. Private semaphores may only be used by a single process; shared semaphores may be used by several processes. Other processes may obtain the handle of a semaphore in a couple of ways. The handle of the thread can be passed in a message. If the semaphore is named, then the other process may use a known name to request the handle from the operating system.
unnamed semaphores, so only they will be covered. Also, we will not be
using multiple wait semaphores. For information on named and unnamed,
private and shared, and multiple wait semaphores check out the OS/2 Toolkit
documentation, or a good third party book on OS/2 programming.


<h4>Mutex Semaphores</h4>
The programming samples in Gearing Up For Games will use only private, unnamed semaphores, so only they will be covered. Also, we will not be using multiple wait semaphores. For information on named and unnamed, private and shared, and multiple wait semaphores check out the OS/2 Toolkit documentation, or a good third party book on OS/2 programming.


<p>Mutex semaphores allow two or more threads to take turns in accessing
====Mutex Semaphores====
information. First, the mutex semaphore must be created with
Mutex semaphores allow two or more threads to take turns in accessing information. First, the mutex semaphore must be created with DosCreateMutexSem(). Often it is convenient to do this in the main thread during initialization before any threads are even created. When the
DosCreateMutexSem(). Often it is convenient to do this in the main thread
semaphore is no longer needed, DosCloseMutexSem() is called. This can also be done in the main thread during cleanup.
during initialization before any threads are even created. When the
semaphore is no longer needed, DosCloseMutexSem() is called. This can also
be done in the main thread during cleanup.


<p>When a thread wants to access a piece of information that two or more
When a thread wants to access a piece of information that two or more threads use, it calls DosRequestMutexSem(). If this function returns without error, then the thread "owns" the semaphore. When this function is called, a timeout value is also specified. The timeout value is the number of milliseconds that DosRequestMutexSem() will wait for the semaphore to become available. If this time runs out, the function returns with a timeout error. The timeout value may also be given the values of SEM_IMMEDIATE_RETURN and SEM_INDEFINITE_WAIT. An immediate return request means that the function will not wait for the semaphore at all. An indefinite wait request means DosRequestMutexSem() will not return until it has gained ownership of the semaphore, and this is the setting we will use.
threads use, it calls DosRequestMutexSem(). If this function returns
without error, then the thread &quot;owns&quot; the semaphore. When this function is
called, a timeout value is also specified. The timeout value is the number
of milliseconds that DosRequestMutexSem() will wait for the semaphore to
become available. If this time runs out, the function returns with a
timeout error. The timeout value may also be given the values of
SEM_IMMEDIATE_RETURN and SEM_INDEFINITE_WAIT. An immediate return request
means that the function will not wait for the semaphore at all. An
indefinite wait request means DosRequestMutexSem() will not return until it
has gained ownership of the semaphore, and this is the setting we will use.


<p>When the thread is finished accessing the shared data, it calls
When the thread is finished accessing the shared data, it calls DosReleaseMutexSem(). This allows another thread to have access to the data. If two or more threads are waiting for a semaphore when it is released, the thread with the highest priority gains control. Note that an internal counter is kept for each semaphore that is incremented upon a request and decremented upon a release. Therefore you must call DosReleaseMutexSem() for each time that you called DosRequestMutexSem().
DosReleaseMutexSem(). This allows another thread to have access to the
data. If two or more threads are waiting for a semaphore when it is
released, the thread with the highest priority gains control. Note that an
internal counter is kept for each semaphore that is incremented upon a
request and decremented upon a release. Therefore you must call
DosReleaseMutexSem() for each time that you called DosRequestMutexSem().


<p>Let's consider two threads, A and B, trying to use the variable ulVar1.
Let's consider two threads, A and B, trying to use the variable ulVar1. During setup DosCreateMutexSem() is called and the handle is stored in hmtxSem1. Threads A and B run. Thread A comes to a place where it needs ulVar1, so it calls DosRequestMutexSem() with the semaphore handle stored in hmtxSem1. No other thread owns this semaphore, so A gains ownership. It can now safely access ulVar1. While thread A is using ulVar1, thread B comes to a place where it needs to access the same variable. Thread B calls DosRequestMutexSem() with hmtxSem1 and an indefinate wait specified. However since A already owns the semaphore, thread B blocks. When thread A finishes accessing ulVar1, it calls DosReleaseMutexSem(). Now thread B's call to DosRequestMutexSem() returns and thread B can safely access ulVar1. Thread B must release the semaphore when it is done accessing ulVar1.
During setup DosCreateMutexSem() is called and the handle is stored in
hmtxSem1. Threads A and B run. Thread A comes to a place where it needs
ulVar1, so it calls DosRequestMutexSem() with the semaphore handle stored
in hmtxSem1. No other thread owns this semaphore, so A gains ownership.
It can now safely access ulVar1. While thread A is using ulVar1, thread B
comes to a place where it needs to access the same variable. Thread B
calls DosRequestMutexSem() with hmtxSem1 and an indefinate wait specified.
However since A already owns the semaphore, thread B blocks. When thread A
finishes accessing ulVar1, it calls DosReleaseMutexSem(). Now thread B's
call to DosRequestMutexSem() returns and thread B can safely access ulVar1.
Thread B must release the semaphore when it is done accessing ulVar1.


<p>Mutex semaphores can also be used when one thread wants to stop another
Mutex semaphores can also be used when one thread wants to stop another thread, but wants to let the second thread finish what it is doing. An example of this is pausing a game. The main loop of the game thread looks something like this:
thread, but wants to let the second thread finish what it is doing. An
<code>
example of this is pausing a game. The main loop of the game thread looks
while (! bEndGameThread)
something like this:
 
<pre><small>
while (! bEndGameThread)
     {
     {
DosRequestMutexSem (hmtxPause, SEM_INDEFINITE_WAIT);
DosRequestMutexSem (hmtxPause, SEM_INDEFINITE_WAIT);
     DosReleaseMutexSem (hmtxPause);
     DosReleaseMutexSem (hmtxPause);
     // regulate the speed of the game
     // regulate the speed of the game
Line 399: Line 116:
     // blit screen.
     // blit screen.
     };
     };
</small></pre>
</code>
 
''Figure 3) Processing loop for a game thread''
<small>Figure 3) Processing loop for a game thread</small>
 
<p>Now the player presses a button that pauses the game.  The button press is
intercepted by the message processing routine in the main thread.  It
requests ownership of hmtxPause, and chances are that it will get it.  The
game thread will finish blitting the frame that it is working on, and
before it starts the next frame, it will request hmtxPause.  Since this
semaphore is already owned by the main thread, the main game thread will
block and the game will be paused; new positions will not be calculated,
and new screens will not be blitted.  Note that this means that if the game
is in a window and the window needs to be updated, then you will have to
blit the last completed frame elsewhere. Most likely this will be in the
handling of the WM_PAINT message. When the player presses the button to
unpause the game, the message handler in the main thread receives the
request.  If the game is paused, then the main thread releases hmtxPause,
and the game thread may continue.  Be certain to check to see if a game is
paused or unpaused before requesting or releasing the semaphore, because
you only want to have one request active at a time.


<p>Finally, you can use the function DosQueryMutexSem() to find out what (if
Now the player presses a button that pauses the game. The button press is intercepted by the message processing routine in the main thread. It requests ownership of hmtxPause, and chances are that it will get it. The game thread will finish blitting the frame that it is working on, and before it starts the next frame, it will request hmtxPause. Since this semaphore is already owned by the main thread, the main game thread will block and the game will be paused; new positions will not be calculated, and new screens will not be blitted. Note that this means that if the game is in a window and the window needs to be updated, then you will have to blit the last completed frame elsewhere. Most likely this will be in the handling of the WM_PAINT message. When the player presses the button to unpause the game, the message handler in the main thread receives the request. If the game is paused, then the main thread releases hmtxPause, and the game thread may continue. Be certain to check to see if a game is paused or unpaused before requesting or releasing the semaphore, because you only want to have one request active at a time.
any) process and thread owns the semaphore, as well as find out how many
outstanding requests for the semaphore exist (including the request made by
the thread that currently owns the semaphore).


Finally, you can use the function DosQueryMutexSem() to find out what (if any) process and thread owns the semaphore, as well as find out how many outstanding requests for the semaphore exist (including the request made by the thread that currently owns the semaphore).


<h4>Event Semaphores</h4>
====Event Semaphores====
Event semaphores are useful for one thread to let another know that something has happened. They are also useful because they can be hooked up to timers, and the timer can use the semaphore to let a thread know when a certain time period has passed.


<p>Event semaphores are useful for one thread to let another know that
Like mutex semaphores, event semaphores can be private or shared, named or unnamed. You create an event semaphore with DosCreateEventSem(), and destroy it when you are finished with DosCloseEventSem(). This is usually done in the main thread during setup and cleanup respectively. If you wish to hook a timer up to the semaphore, this can be done right after it is created with a call to DosStartTimer().
something has happened.  They are also useful because they can be hooked up
to timers, and the timer can use the semaphore to let a thread know when a
certain time period has passed.


<p>Like mutex semaphores, event semaphores can be private or shared, named or
Event semaphores have two states: posted and reset. You post to an event semaphore when an event occurs, and you reset it when you want to clear it and wait for the next event. The number of times an event semaphore is posted to is recorded and given upon request.
unnamed. You create an event semaphore with DosCreateEventSem(), and
destroy it when you are finished with DosCloseEventSem().  This is usually
done in the main thread during setup and cleanup respectively. If you wish
to hook a timer up to the semaphore, this can be done right after it is
created with a call to DosStartTimer().


<p>Event semaphores have two states:  posted and reset. You post to an event
You post an event semaphore with a call to DosPostEventSem(). If you have hooked the event semaphore up to a timer, then the timer will automatically post the semaphore after the requested period of time. You clear the semaphore with DosResetEventSem(), and this call will return the number of times the semaphore was posted since the last reset.
semaphore when an event occurs, and you reset it when you want to clear it
and wait for the next event. The number of times an event semaphore is
posted to is recorded and given upon request.


<p>You post an event semaphore with a call to DosPostEventSem(). If you have
Although you can use DosQueryEventSem() to find out how many times it has been posted, most often you will check an event semaphore with DosWaitEventSem(). DosWaitEventSem() blocks a thread like DosRequestMutexSem(), and it acts much the same way. You provide a timeout
hooked the event semaphore up to a timer, then the timer will automatically
value, SEM_INDEFINITE_WAIT, or SEM_IMMEDIATE_RETURN. If you specify an indefinite wait, the function will return immediately if the semaphore has already been posted, or it will block the calling thread until the semaphore is posted to by another thread or a timer. After
post the semaphore after the requested period of time. You clear the
DosWaitEventSem() returns, you will probably want to immediately reset it so that you know if any new posts have been made between this and the next call to DosWaitEventSem(). DosResetEventSem() clears the number of posts to 0.
semaphore with DosResetEventSem(), and this call will return the number of
times the semaphore was posted since the last reset.


<p>Although you can use DosQueryEventSem() to find out how many times it has
Often you will use event semaphores and timers together. There are only a limited number of timers available in OS/2. These timers are hooked up not to the Programmable Interrupt Timer (PIT) that is usually reprogrammed in DOS games, but rather to a different hardware timer that I believe is also used for managing multitasking. Although you request the time between
been posted, most often you will check an event semaphore with
timer posts in milliseconds, time is actually handled in units of clock ticks, which varies depending on the computer system. For Intel based systems, the resolution of this timer is about 32 milliseconds, and this gives a rate of about 31 or 32 posts per second.
DosWaitEventSem(). DosWaitEventSem() blocks a thread like
DosRequestMutexSem(), and it acts much the same way. You provide a timeout
value, SEM_INDEFINITE_WAIT, or SEM_IMMEDIATE_RETURN. If you specify an
indefinite wait, the function will return immediately if the semaphore has
already been posted, or it will block the calling thread until the
semaphore is posted to by another thread or a timer. After
DosWaitEventSem() returns, you will probably want to immediately reset it
so that you know if any new posts have been made between this and the next
call to DosWaitEventSem(). DosResetEventSem() clears the number of posts to
0.


<p>Often you will use event semaphores and timers together. There are only a
Before requesting a timer, you will want to make sure there are still some available. The number of available timers can be found with a call to WinQuerySysValue() to find the value of SV_CTIMERS. If no timers are available, you should exit the game with an error message. You request a timer with a call to DosStartTimer() after you have created the event semaphore. You pass the handle of the event timer to this function, along with a millisecond count. Now the event semaphore will be posted every x milliseconds, with x being the value you specified in the call to DosStartTimer(). When you are done with the timer, you call DosStopTimer() to stop it and release it for another program to use.
limited number of timers available in OS/2. These timers are hooked up not
to the Programmable Interrupt Timer (PIT) that is usually reprogrammed in
DOS games, but rather to a different hardware timer that I believe is also
used for managing multitasking. Although you request the time between
timer posts in milliseconds, time is actually handled in units of clock
ticks, which varies depending on the computer system. For Intel based
systems, the resolution of this timer is about 32 milliseconds, and this
gives a rate of about 31 or 32 posts per second.


<p>Before requesting a timer, you will want to make sure there are still some
You can use event semaphores and timers to regulate the speed of your game.
available. The number of available timers can be found with a call to
Let's consider an action game. You want the game to run at a constant frame rate regardless of the speed of the computer it is running on. Since the timer runs at around 32 posts per second, we will target our frame rate to this speed. For faster frame rates we need a higher resolution timer.
WinQuerySysValue() to find the value of SV_CTIMERS. If no timers are
I believe that at least two such timers currently exist; one is available on the DevCon Device Driver Kit and is designed for use by device drivers, and the other timer is accessed through the two DosTmr* functions. The DosTmr* functions require 128-bit division and multiplication to use. I have not used either of these timers yet however, so I will not cover them.
available, you should exit the game with an error message. You request a
timer with a call to DosStartTimer() after you have created the event
semaphore. You pass the handle of the event timer to this function, along
with a millisecond count. Now the event semaphore will be posted every x
milliseconds, with x being the value you specified in the call to
DosStartTimer(). When you are done with the timer, you call DosStopTimer()
to stop it and release it for another program to use.


<p>You can use event semaphores and timers to regulate the speed of your game.
To achieve a constant frame rate at the top of your main game loop, you check your timer. If the timer interval has passed, you reset the timer and continue on. Otherwise you wait until the required interval has passed. With a timer and an event semaphore, you know the correct interval has passed when the semaphore is posted. With a timer that only allows you to read its value and doesn't generate interrupts, post messages, or post semaphores (such as the DosTmr* functions), you will need to check the elapsed time yourself. If enough time has passed, update your timer variables and continue. If not, you will have to continue to poll the timer since you don't have a function that blocks the current thread. In order to keep the polling loop from burning up CPU time when it doesn't need to, call DosSleep (0) after every unsuccessful check.
Let's consider an action game.  You want the game to run at a constant
frame rate regardless of the speed of the computer it is running on. Since
the timer runs at around 32 posts per second, we will target our frame rate
to this speed. For faster frame rates we need a higher resolution timer.
I believe that at least two such timers currently exist; one is available
on the DevCon Device Driver Kit and is designed for use by device drivers,
and the other timer is accessed through the two DosTmr* functions. The
DosTmr* functions require 128-bit division and multiplication to use. I
have not used either of these timers yet however, so I will not cover them.


<p>To achieve a constant frame rate at the top of your main game loop, you
With the standard timer and event semaphore, our main game loop for an action game looks something like this:
check your timer.  If the timer interval has passed, you reset the timer
<code>
and continue on. Otherwise you wait until the required interval has
DosResetEventSem (hevTimerSem);
passed.  With a timer and an event semaphore, you know the correct interval
while (! bEndGameThread)
has passed when the semaphore is posted. With a timer that only allows you
to read it's value and doesn't generate interrupts, post messages, or post
semaphores (such as the DosTmr* functions), you will need to check the
elapsed time yourself. If enough time has passed, update your timer
variables and continue.  If not, you will have to continue to poll the
timer since you don't have a function that blocks the current thread.  In
order to keep the polling loop from burning up CPU time when it doesn't
need to, call DosSleep (0) after every unsuccessful check.
 
<p>With the standard timer and event semaphore, our main game loop for an
action game looks something like this:
 
<pre><small>
DosResetEventSem (hevTimerSem);
while (! bEndGameThread)
     {
     {
     // check for paused game
     // check for paused game
     // regulate the speed of the game
     // regulate the speed of the game
DosWaitEventSet (hevTimerSem, SEM_INDEFINITE_WAIT);
DosWaitEventSet (hevTimerSem, SEM_INDEFINITE_WAIT);
     DosResetEventSem (hevTimerSem);
     DosResetEventSem (hevTimerSem);
     // move player
     // move player
Line 526: Line 162:
     // blit screen.
     // blit screen.
     };
     };
</small></pre>
</code>
 
''Figure 4) Processing loop revisited''
<small>Figure 4) Processing loop revisited</small>
 
<p>One final note about blocking the game thread with either an event or a
mutex semaphore. When you request that the game ends (by setting
bEndGameThread to true), then you must make sure the game thread is not
blocked by either a pause or a speed regulating semaphore. You must
therefore release any mutex semaphores that the main thread owns, and post
any event semaphores that the game thread is waiting on.
 
<h2>Basic Sprites</h2>
 
<p>I have not had time to write the Basic Sprite explanation for this article,
but I have included the code.  I implemented sprite drawing in a very
simple way.  All sprites are stored in a PCX file.  This file is read into
a canvas, and the canvas is passed to the sprite class.  Each sprite is
defined as a rectangular region of the canvas. When the sprite is drawn,
the sprite is copied pixel by pixel from the sprite canvas over to the
destination canvas. If the pixel's value is greater or equal to 248, then
the pixel is not written, thus making those pixels clear since the
background remains.  This makes the top 8 colors clear.
 
<p>The background is stored in it's own PCX file and read into it's own
canvas.  To erase the sprites, I just copy part of the background canvas
over to the display canvas.  This covers everything that was written last
time, and then the new sprites are written directly to the display canvas.
This display canvas is then blitted to the screen.


<p>Sprites are referenced with a book number and a sprite number. A book is a
One final note about blocking the game thread with either an event or a mutex semaphore. When you request that the game ends (by setting bEndGameThread to true), then you must make sure the game thread is not blocked by either a pause or a speed regulating semaphore. You must therefore release any mutex semaphores that the main thread owns, and post any event semaphores that the game thread is waiting on.
collection of several sprites, up to 65,535 sprites per book.  There can be
up to 255 books of sprites. You would probably want to have a book of
sprites for the player, a book for each of the enemies, and a book for
missiles and explosions.  Each book will contain all of the frames of
animation for that object.


<p>When we get into more advanced sprites, we will actually encode them from a
==Basic Sprites==
canvas and store the encoded data. Then we can discard the canvas the
I have not had time to write the Basic Sprite explanation for this article, but I have included the code. I implemented sprite drawing in a very simple way. All sprites are stored in a PCX file. This file is read into a canvas, and the canvas is passed to the sprite class. Each sprite is defined as a rectangular region of the canvas. When the sprite is drawn, the sprite is copied pixel by pixel from the sprite canvas over to the destination canvas. If the pixel's value is greater or equal to 248, then the pixel is not written, thus making those pixels clear since the background remains. This makes the top 8 colours clear.
sprites were encoded from, thus saving memory. Furthermore, encoded
sprites will display much, much faster than the current method.  Also we
will store the books of encoded sprites to disk in library files. I will
be explaining library files in the future when I cover organizing game
data.


<p>The sprite class has a number of routines which are really just place
The background is stored in its own PCX file and read into its own canvas. To erase the sprites, I just copy part of the background canvas over to the display canvas. This covers everything that was written last time, and then the new sprites are written directly to the display canvas. This display canvas is then blitted to the screen.
holders for more advanced features. If a function doesn't seem to have a
use now then just ignore it; we will come back to it in the future.


<h4>The Canvas Class</h4>
Sprites are referenced with a book number and a sprite number. A book is a collection of several sprites, up to 65,535 sprites per book. There can be up to 255 books of sprites. You would probably want to have a book of sprites for the player, a book for each of the enemies, and a book for missiles and explosions. Each book will contain all of the frames of animation for that object.


<p>One small addition has been made to the canvas class as well.  I have added
When we get into more advanced sprites, we will actually encode them from a canvas and store the encoded data.
the routine CopyDirtyRec(), and this copies a rectangle from one canvas to
Then we can discard the canvas the sprites were encoded from, thus saving memory. Furthermore, encoded sprites will display much, much faster than the current method. Also we will store the books of encoded sprites to disk in library files. I will be explaining library files in the future when I cover organizing game data.
another.  This routine is used to copy part of the background canvas to the
display canvas when the display canvas needs to be erased. The code of
this routine is a good demonstration of how to move canvas data around, and
some of the speedups that canvases allow.


<h2>Those Silly Bugs</h2>
The sprite class has a number of routines which are really just place holders for more advanced features. If a function doesn't seem to have a use now then just ignore it; we will come back to it in the future.


<p>The MAKEFILE that accompanied my last article appears to have had a slight
====The Canvas Class====
bug in it. In calling the linker, I neglected to put the full path name
One small addition has been made to the canvas class as well. I have added the routine CopyDirtyRec(), and this copies a rectangle from one canvas to another. This routine is used to copy part of the background canvas to the display canvas when the display canvas needs to be erased.
before the &quot;mmpm2.lib&quot; file.  Therefore &quot;libr mmpm2.lib&quot; should read &quot;libr
The code of this routine is a good demonstration of how to move canvas data around, and some of the speedups that canvases allow.
c:\toolkit\lib\mmpm2.lib&quot; or add whatever path you have to the lib
directory of the OS/2 toolkit.


<p>Aside from that, I don't think there were any other bugs in the article for
==Those Silly Bugs==
EDM/2 3-7. Am I mistaken?
The MAKEFILE that accompanied my last article appears to have had a slight bug in it. In calling the linker, I neglected to put the full path name before the <tt>mmpm2.lib</tt> file. Therefore <tt>libr mmpm2.lib</tt> should read <tt>libr c:\toolkit\lib\mmpm2.lib</tt> or add whatever path you have to the lib directory of the OS/2 toolkit.


<h2>News and other Nonsense</h2>
Aside from that, I don't think there were any other bugs in the article for EDM/2 3-7.Am I mistaken?


<p>By the time you read this, the Entertainment Toolkit for OS/2 may already
==News and other Nonsense==
be out on DevCon 8. It was not yet available at press time, so I am unable
By the time you read this, the Entertainment Toolkit for OS/2 may already be out on DevCon 8. It was not yet available at press time, so I am unable to cover anything on it with any certainty. I will be writing about it in the near future.
to cover anything on it with any certainty. I will be writing about it in
the near future.


<p>If you haven't visited it yet, I'd like to suggest that you drop by IBM's
If you haven't visited it yet, I'd like to suggest that you drop by IBM's Games Home Page.
Games Home Page. The URL is http://www.austin.ibm.com/os2games and you
The URL is http://www.austin.ibm.com/os2games and you will need a Web browser to look at it. IBM seems to be using this as their main notification to the public (that's us) of game related information, so it makes good sense to check it out every week or so for new stuff. Recently the beta documentation for the Entertainment Toolkit has been placed on this site, as well as the joystick drivers and documentation and other stuff.
will need a Web browser to look at it. IBM seems to be using this as their
main notification to the public (that's us) of game related information, so
it makes good sense to check it out every week or so for new stuff.
Recently the beta documentation for the Entertainment Toolkit has been
placed on this site, as well as the joystick drivers and documentation and
other stuff.


While we're on the topic of Web pages, I have finally put one up of my own. I hope to eventually have online versions of my EDM/2 articles up there, and I may even be placing code and text on the web page before it sees print in EDM/2. The page is really, really sparse right now, but it should grow as I find time to work on it. I also have a number of OS/2 game related links on my page already that you may want to check out.


<p>While we're on the topic of Web pages, I have finally put one up of my own.
This month I also did things a little bit differently in that I didn't distribute duplicates of code from previous articles when the code didn't change at all. The README.1ST file in the code archive will tell you which files you need from the last article. Simply place those files in the directory with the new code, and run the makefile (if of course you are using the Watcom compiler, otherwise the makefile will not work).
I hope to eventually have online versions of my EDM/2 articles up there,
and I may even be placing code and text on the web page before it sees
print in EDM/2.  The page is really, really sparse right now, but it should
grow as I find time to work on it. I also have a number of OS/2 game
related links on my page already that you may want to check out.


<p>This month I also did things a little bit differently in that I didn't
Next month I hope to cover sprites in depth, including how to construct, store, and display Run Length Encoded (RLE) sprites, and how to clip sprites. If I have time, I may show how dirty rectangles can be used in some cases to speed up sprite updating, though with a full scrolling background dirty rectangles don't do us much good. Also I should be able to cover DIVE's full-screen abilities in the 320x200x256 colour mode, since they are a breeze to implement.
distribute duplicates of code from previous articles when the code didn't
change at all.  The README.1ST file in the code archive will tell you which
files you need from the last article.  Simply place those files in the
directory with the new code, and run the makefile (if of course you are
using the Watcom compiler, otherwise the makefile will not work).


<p>Next month I hope to cover sprites in depth, including how to construct,
As always, I welcome comments. You can reach me at mduffy@ionet.net, or find me lurking on comp.os.os2.games among other places. Until next time...
store, and display Run Length Encoded (RLE) sprites, and how to clip
sprites. If I have time, I may show how dirty rectangles can be used in
some cases to speed up sprite updating, though with a full scrolling
background dirty rectangles don't do us much good. Also I should be able
to cover DIVE's fullscreen abilities in the 320x200x256 color mode, since
they are a breeze to implement.


<p>As always, I welcome comments.  You can reach me at mduffy@ionet.net, or
[[Category:Games Articles]]
find me lurking on comp.os.os2.games among other places.  Until next
time...

Latest revision as of 15:47, 10 October 2022

Written by Michael T. Duffy

Introduction

Welcome to the third instalment of Gearing Up For Games. The past couple of months have been really busy for me, and in August I took a two week trip to Japan that took me completely away from the computer. Add to this the fact that I'm trying desperately to get my own game out before the end of the year, and you begin to see why I didn't get a chance to finish this article.

I had planned on covering threads and semaphores, and basic sprites in this article. It turns out that I have only had time to write about threads and semaphores. However I wrote the code for the article before I started the text, so I finished all of the basic sprite code. Rather than pull the sprite code out, I have left it in. For those of you already familiar with sprites, you should be able to follow the code fairly easily. The next to last section of this article briefly describes how I approached sprites, though it is not meant to be an in-depth explanation. It will have to suffice for now.

Also, I should point out that my discussion of threads and semaphores is oriented towards games, and does not cover all aspects of threads or semaphores. A complete discussion is beyond the scope of this article, and I would suggest that you look into the reference documentation in the OS/2 toolkit (namely Control Program Guide and Reference) as well as a good book or two on OS/2 programming. I have found both Petzold's OS/2 Presentation Manager Programming (pub. Ziff-Davis Press) and Real World Programming for OS/2 2.11 from SAMS Publishing to be useful references. Each book covers some material that the other doesn't, so it may be a good idea to look at more than one explanation. The material presented in this article should be enough to give you a basic understanding of threads and along with the accompanying sample code, you should be able to use threads and semaphores in your own game programs.

Enough for excuses...time to get to the meat of the matter!

Threads

One of the advantages of an operating system like OS/2 is that the programmer has available to him or her a multitasking environment. This not only means that more than one program can be running concurrently, but also that different parts of the same program can execute concurrenly.

In DOS, programmers did not have multitasking and instead relied on interrupt handlers and reprogramming the internal timer chip in order to achieve this, e.g. reading the keyboard or joystick while blitting to the screen. This is no longer necessary in OS/2, and part of the reason is because of threads.

What is a thread? A thread is a separate unit of execution within a program. Each thread has its own set of CPU register variables and its own stack. For the beginner, register variables are the variables used inside of the CPU to perform calculations and keep track of where the program is currently executing. The stack is where local variables are allocated and deallocated from when a new routine is entered. Threads are owned by a process, where a process is usually the program that you are running.

Multitasking is handled by the CPU, with the operating system telling the CPU how much time to give each thread. It basically works like this: the CPU executes a thread for a given amount of time, stopping the thread after that time has elapsed. The CPU stops the thread dead in its tracks, even if it is in the middle of executing a single line of C code, like usVariable1 = usVariable2. All of the variables in the CPU registers are stored. The OS then instructs the CPU which thread to run next, and the CPU loads its registers with the saved registers of the next thread, then executes that thread for a given amount of time, and so on. An operating system like OS/2 can determine how much CPU time a thread needs, and adjust the amount of time it gives a thread for its next execution. The OS can also suspend a thread if the CPU is needed elsewhere, such as reading keyboard, COM port, or disk drive data. This is equal to the interrupts of the days of DOS.

As a result, you never know when control will be given to or taken from your thread. You can indirectly affect how much time your thread is given by setting the priority level of the thread. A priority level basically tells the CPU how important your thread is compared to other threads. Threads that have a more important priority level are serviced before threads of lower priority. Threads of the same priority level are serviced in a round-robin fashion. For example, let's say you have two threads of the priority, A and B, and one thread of a lower priority, C. Depending on the load level of the machine and other settings, these three threads might gain control in the order:

A B A B C A B A B A B A B C A B C A B C. 

Since A and B are of the same priority, they will be serviced equally. Thread C will be serviced when A and B have been handled, and there is CPU time left over.

As mentioned before OS/2 can adjust on the fly how much time each thread gets before the CPU stops it. How does OS/2 determine how much time a thread uses? A thread can signal that it has used enough CPU time for now in two ways. One, if you have a message queue then OS/2 knows when you're done processing because your message handling routine returns to the operating system once it has handled the message. The second way is that threads can be suspended, resumed, or paused (with DosSleep()). This will be discussed shortly.

How are threads useful, and when do you use one?

Threads are needed when you must do two or more things at the same time. An important thing to remember about threads however, is that on Intel based machines, two threads don't really execute at the same time. The CPU runs one thread for a short time, switches to other threads, switches back to the first thread and so on. Even on machines with SMP (Symmetrical Multi-Processors) often all of the threads of a given process are run on a single CPU. A CPU can only execute a certain number of instructions per second, and all threads take up a share of those instructions. It also takes time to switch from one thread to another, even though this amount of time is very, very short overall.

Threads are therefore useful when you need to do several tasks at basically the same time, and you don't want to have to worry about giving each task its share of the CPU. Instead, OS/2 will handle the thread management. Good examples of threads are the thread that handles the main message queue, a thread that blits images to the screen, a thread that loads/saves/sends/receives data in the background, a thread that mixes sound, and perhaps a thread that handles the artificial intelligence (AI) of the other opponents in a game. In your games you will always want to have at least two threads: one to handle the main message queue, and one to handle the game mechanics. The reason for this is that you should always spend a little time as possible in the message queue routine, otherwise you risk slowing down overall system performance. One of the flaws of OS/2 is that it has a single system message queue. Since the OS can't send out other messages until the message it just sent is processed, a greedy program that uses a lot of time between when it is sent a message and when it returns to the OS holds up other messages meant both for itself and for other programs. If the processing of a message will take a long time, it is better to instruct a second thread to handle the processing and then return to the OS. This way the second thread can perform the processing while the OS continues to send out other messages.

Threads are not very useful when you have a certain number of things that must be done in a certain order. For example, in an action game you may have several tasks to complete in a certain time frame, and one must follow the other. Every frame of a shooting game, you must read the joystick or keyboard, update the player's position, call the enemy AI routines and update their positions, move all missiles and bullets, and then draw the new graphics with the new positions to the display. Even though you could theoretically calculate player movement and enemy AI at the same time, the player only sees the finished screen and everything else must be completed before that screen is rendered. No matter how you arrange the above tasks, together all of the above tasks will require the same amount of CPU time before the screen can be blitted. Placing each task in a separate thread would not speed things up, and in fact things would slow down because you will have to synchronize the threads' access to variables, and the overhead of switching threads itself will slow things down some. The solution to the above problem is to place all of the tasks in the same thread.

Other games can have different tasks running at the same time. Consider a simulation or an adventure game. These kind of games can have complex AI for the computer opponents, and the calculations for the AI take a long time. However, the player is not continually giving input and the opponents are not continually moving like in a fast paced action game. The player might be studying graphics or maps, or even reading text while not inputting requests for new actions. Rather than having the computer wait for the player to make a move before deciding how the computer opponents would move (as a DOS game would do), the computer can decide it's next move while the player is involved with non-intensive CPU activities. If the computer does not finish a task before the player makes a move, then it can continue to process until it does make a decision. Depending on the type of game, this would mean either stopping the game until the decision were made, or continuing with the previous action this turn until a new action is decided upon.

Types of Threads

There are two basic types of threads: message queue and non-message queue. The type is determined obviously enough based upon whether or not that thread has created a message queue. The type of thread you use will depend on your purpose. Aside from having a message queue and a message queue handle, message queue threads differ from non-message queue threads in a few significant ways. Non-message queue threads cannot do the following: create windows, contain window procedures, send messages to window procedures with WinSendMsg() (although they can call WinPostMsg()), or call functions that cause messages to be sent to window procedures. On the other hand, message queue threads cannot be suspended or blocked, or else they will hold up the rest of the operating system. Also, it is important to note that you cannot use the message queue handle of another thread to call windowing procedures. The strengths and weaknesses of each type should be weighed carefully when organizing your program.

Using Threads

Threads can be created either through the functions in a standard library, such as C/C++'s _beginthread() function, or through the OS/2 API. We will be using the latter approach since the OS/2 API allows more control over threads than the former approach. I suggest that you obtain a copy of IBM's Control Program Guide and Reference for a complete reference to the thread and semaphore API.

Note that when you use threads, you must compile with the switch that tells the compiler that you are working with a multithreaded program. This switch tells the compiler to use a special version of the standard libraries that are "multithread-safe". If two threads call the same or related functions in the standard C or C++ library at the same time, then there could be problems if this flag is not specified. With the Watcom C/C++ compiler, the flag is -bm.

To create a thread we call DosCreateThread(). We tell this function what routine the thread is to run and give a single parameter to be passed to that routine, tell it what the stack size of the new thread is to be and whether or not to commit the pages of the stack, and whether the thread it to be executed immediately or created in a suspended state.

Once a thread is created, you should set its priority status with DosSetPriority(). You will have to experiment to find out what priority levels to use for each of your threads. It is a good idea to have your message queue to be at a higher priority than your other threads so that the entire system will stay responsive.

A thread ends itself by either by returning from initial thread function, or calling DosExit(). A thread may kill another thread by calling DosKillThread(), though it is often a good idea to signal a thread that it should kill itself so that it may shut down what it is doing before it terminates.

Threads can also be temporarily stopped and restarted with the functions DosSuspendThread() and DosResumeThread(). While a thread is suspended, it does not take up any CPU time; it is simply not called. When a message queue thread returns to the OS/2 kernel and there are no messages for it, the OS/2 suspends the thread until new messages are available. Note that threads obviously cannot resume themselves since they are not running, though they can suspend themselves and other threads. If a thread is created in a suspended state, you must call DosResumeThread() to begin its execution.

Another way to temporarily suspend a thread is to call DosSleep(). This function takes a single parameter that tells the system how many milliseconds the thread should be suspended before it is resumed. Note that this can not be used for exact timing purposes. As soon as the time runs out, the thread that called DosSleep() will be considered again for CPU time slices, but other threads may execute before the recently resumed thread gets it's turn, especially if the other threads have a higher priority. Calling DosSleep(0) causes the thread to release the remainder of it's CPU time for the current time slice back to OS/2 so that it can be used elsewhere. This should be used in non-message queue threads where you don't want to hog CPU time and you don't have anything special to do. It is especially useful at the bottom of a loop where you have just finished a task and you don't need to start the next task immediately. This is not needed if you suspend the thread through other means.

Finally, a thread can suspend itself until another thread has finished executing. When DosWaitThread() is called, a flag is passed that specifies whether the function should wait for end of execution or not. If DCWW_NOWAIT is specified, the function returns immediately and the error status tells whether or not the specified thread is still running. If DCWW_WAIT is specified, then the function does not return until the specified thread terminates. DosWaitThread() is useful when thread A wants to terminate thread B. Thread A signals thread B that it should terminate. Thread A then calls DosWaitThread() with the wait flag specified. Thread B receives the shutdown signal, cleans up what it needs to, and the exits gracefully. As soon as thread B exits, DosWaitThread() returns and thread A can continue about its business.

As far as the creating, executing, suspending, and resuming the execution of threads, that's all there is too it. To communicate amongst different threads and to coordinate their use of global variables and memory, you will have to rely on semaphores.

Semaphores

A semaphore is like a traffic cop that keeps threads from crashing into each other. They are needed because when the CPU switches from one thread to another, it may interrupt it anywhere. For example, Thread A might be in the middle of updating a structure. It gets halfway through the update, and then it is interrupted by the CPU and thread B gets control. Thread B tries to use the information in the structure, but only half of it is correct. Imagine if the structure contained a forward and backward linked list, and only one of the two pointers were updated. Behaviour of the program would be unpredictable and buggy. It gets even worse. Actions we consider atomic in C may actually be compiled down into several assembly language instructions. Consider the expression ++usVar1; The compiler might break it down like:

mov  eax, [usVar1]  ;eax = 0, usVar1 == 0
inc  eax	     ;eax = 1, usVar1 == 0
mov  [usVar1], eax  ;eax == 1, usVar1 = 1

Figure 1) Assembler for ++usVar1

What happens if Thread A is interrupted by Thread B during the middle of the command, and thread B tries something simple like incrementing usVar1. Remember that each thread has its own set of CPU register variables.

A - mov   eax, [usVar1] ;eax = 0, usVar1 == 0
A - inc   eax		 ;eax = 1, usVar1 == 0
    ---- switch threads ----
B - mov   eax, [usVar1] ;eax = 0, usVar1 == 0
B - inc   eax		 ;eax = 1, usVar1 == 0
B - mov   [usVar1],eax	 ;eax == 1, usVar1 = 1
    ---- other processing ----
    ---- switch threads ----
A - mov   [usVar1],eax	 ;eax == 1, usVar1 = 1

Figure 2) Accessing usVar1 by multiple threads

Although at the end of two ++usVar1 instructions it should go from 0 to 2, the final value comes out to be 1. This is obviously not good.

What you would want to do to solve this problem is to make sure your thread were not interrupted in the middle of using a variable, structure, or other piece of data. This can be accomplished in two ways. One way is to call DosEnterCritSec() before the calculation, and call DosExitCritSec() afterwards. This causes all thread switching to stop between the two function calls. All threads. The threads that control OS/2, other threads in your program, all threads in other programs, and all threads that handle interrupt processing. Use of these two functions may be fine for simple operations such as a single increment or decrement of a variable, but should never be used for processing that takes longer than just a few assembly language instructions. If you suspend system threads for too long you risk losing data coming in from the keyboard or serial ports, and you will prevent other updates that need to be done on a regular basis. Besides, you are only really worried about threads A and B accessing a given piece of data.

A much better solution is to use semaphores. Semaphores come in three varieties: mutually-exclusive (mutex), event, and multiple-wait. Mutex semaphores are like a hall pass; only one thread may have the hall pass at any one time, and the others must wait until the pass is returned. Event semaphores are like traffic lights where you can have a stop and a go status. Multiple wait semaphores are like a collection of the other two types; you have several hall passes or traffic lights to consider at the same time.

Semaphores may also be private or shared, and named or unnamed. Private semaphores may only be used by a single process; shared semaphores may be used by several processes. Other processes may obtain the handle of a semaphore in a couple of ways. The handle of the thread can be passed in a message. If the semaphore is named, then the other process may use a known name to request the handle from the operating system.

The programming samples in Gearing Up For Games will use only private, unnamed semaphores, so only they will be covered. Also, we will not be using multiple wait semaphores. For information on named and unnamed, private and shared, and multiple wait semaphores check out the OS/2 Toolkit documentation, or a good third party book on OS/2 programming.

Mutex Semaphores

Mutex semaphores allow two or more threads to take turns in accessing information. First, the mutex semaphore must be created with DosCreateMutexSem(). Often it is convenient to do this in the main thread during initialization before any threads are even created. When the semaphore is no longer needed, DosCloseMutexSem() is called. This can also be done in the main thread during cleanup.

When a thread wants to access a piece of information that two or more threads use, it calls DosRequestMutexSem(). If this function returns without error, then the thread "owns" the semaphore. When this function is called, a timeout value is also specified. The timeout value is the number of milliseconds that DosRequestMutexSem() will wait for the semaphore to become available. If this time runs out, the function returns with a timeout error. The timeout value may also be given the values of SEM_IMMEDIATE_RETURN and SEM_INDEFINITE_WAIT. An immediate return request means that the function will not wait for the semaphore at all. An indefinite wait request means DosRequestMutexSem() will not return until it has gained ownership of the semaphore, and this is the setting we will use.

When the thread is finished accessing the shared data, it calls DosReleaseMutexSem(). This allows another thread to have access to the data. If two or more threads are waiting for a semaphore when it is released, the thread with the highest priority gains control. Note that an internal counter is kept for each semaphore that is incremented upon a request and decremented upon a release. Therefore you must call DosReleaseMutexSem() for each time that you called DosRequestMutexSem().

Let's consider two threads, A and B, trying to use the variable ulVar1. During setup DosCreateMutexSem() is called and the handle is stored in hmtxSem1. Threads A and B run. Thread A comes to a place where it needs ulVar1, so it calls DosRequestMutexSem() with the semaphore handle stored in hmtxSem1. No other thread owns this semaphore, so A gains ownership. It can now safely access ulVar1. While thread A is using ulVar1, thread B comes to a place where it needs to access the same variable. Thread B calls DosRequestMutexSem() with hmtxSem1 and an indefinate wait specified. However since A already owns the semaphore, thread B blocks. When thread A finishes accessing ulVar1, it calls DosReleaseMutexSem(). Now thread B's call to DosRequestMutexSem() returns and thread B can safely access ulVar1. Thread B must release the semaphore when it is done accessing ulVar1.

Mutex semaphores can also be used when one thread wants to stop another thread, but wants to let the second thread finish what it is doing. An example of this is pausing a game. The main loop of the game thread looks something like this:

while (! bEndGameThread)
    {
DosRequestMutexSem (hmtxPause, SEM_INDEFINITE_WAIT);
    DosReleaseMutexSem (hmtxPause);
    // regulate the speed of the game
    // move player
    // move enemies
    // move bullets, etc.
    // compose graphics screen.
    // blit screen.
    };

Figure 3) Processing loop for a game thread

Now the player presses a button that pauses the game. The button press is intercepted by the message processing routine in the main thread. It requests ownership of hmtxPause, and chances are that it will get it. The game thread will finish blitting the frame that it is working on, and before it starts the next frame, it will request hmtxPause. Since this semaphore is already owned by the main thread, the main game thread will block and the game will be paused; new positions will not be calculated, and new screens will not be blitted. Note that this means that if the game is in a window and the window needs to be updated, then you will have to blit the last completed frame elsewhere. Most likely this will be in the handling of the WM_PAINT message. When the player presses the button to unpause the game, the message handler in the main thread receives the request. If the game is paused, then the main thread releases hmtxPause, and the game thread may continue. Be certain to check to see if a game is paused or unpaused before requesting or releasing the semaphore, because you only want to have one request active at a time.

Finally, you can use the function DosQueryMutexSem() to find out what (if any) process and thread owns the semaphore, as well as find out how many outstanding requests for the semaphore exist (including the request made by the thread that currently owns the semaphore).

Event Semaphores

Event semaphores are useful for one thread to let another know that something has happened. They are also useful because they can be hooked up to timers, and the timer can use the semaphore to let a thread know when a certain time period has passed.

Like mutex semaphores, event semaphores can be private or shared, named or unnamed. You create an event semaphore with DosCreateEventSem(), and destroy it when you are finished with DosCloseEventSem(). This is usually done in the main thread during setup and cleanup respectively. If you wish to hook a timer up to the semaphore, this can be done right after it is created with a call to DosStartTimer().

Event semaphores have two states: posted and reset. You post to an event semaphore when an event occurs, and you reset it when you want to clear it and wait for the next event. The number of times an event semaphore is posted to is recorded and given upon request.

You post an event semaphore with a call to DosPostEventSem(). If you have hooked the event semaphore up to a timer, then the timer will automatically post the semaphore after the requested period of time. You clear the semaphore with DosResetEventSem(), and this call will return the number of times the semaphore was posted since the last reset.

Although you can use DosQueryEventSem() to find out how many times it has been posted, most often you will check an event semaphore with DosWaitEventSem(). DosWaitEventSem() blocks a thread like DosRequestMutexSem(), and it acts much the same way. You provide a timeout value, SEM_INDEFINITE_WAIT, or SEM_IMMEDIATE_RETURN. If you specify an indefinite wait, the function will return immediately if the semaphore has already been posted, or it will block the calling thread until the semaphore is posted to by another thread or a timer. After DosWaitEventSem() returns, you will probably want to immediately reset it so that you know if any new posts have been made between this and the next call to DosWaitEventSem(). DosResetEventSem() clears the number of posts to 0.

Often you will use event semaphores and timers together. There are only a limited number of timers available in OS/2. These timers are hooked up not to the Programmable Interrupt Timer (PIT) that is usually reprogrammed in DOS games, but rather to a different hardware timer that I believe is also used for managing multitasking. Although you request the time between timer posts in milliseconds, time is actually handled in units of clock ticks, which varies depending on the computer system. For Intel based systems, the resolution of this timer is about 32 milliseconds, and this gives a rate of about 31 or 32 posts per second.

Before requesting a timer, you will want to make sure there are still some available. The number of available timers can be found with a call to WinQuerySysValue() to find the value of SV_CTIMERS. If no timers are available, you should exit the game with an error message. You request a timer with a call to DosStartTimer() after you have created the event semaphore. You pass the handle of the event timer to this function, along with a millisecond count. Now the event semaphore will be posted every x milliseconds, with x being the value you specified in the call to DosStartTimer(). When you are done with the timer, you call DosStopTimer() to stop it and release it for another program to use.

You can use event semaphores and timers to regulate the speed of your game. Let's consider an action game. You want the game to run at a constant frame rate regardless of the speed of the computer it is running on. Since the timer runs at around 32 posts per second, we will target our frame rate to this speed. For faster frame rates we need a higher resolution timer. I believe that at least two such timers currently exist; one is available on the DevCon Device Driver Kit and is designed for use by device drivers, and the other timer is accessed through the two DosTmr* functions. The DosTmr* functions require 128-bit division and multiplication to use. I have not used either of these timers yet however, so I will not cover them.

To achieve a constant frame rate at the top of your main game loop, you check your timer. If the timer interval has passed, you reset the timer and continue on. Otherwise you wait until the required interval has passed. With a timer and an event semaphore, you know the correct interval has passed when the semaphore is posted. With a timer that only allows you to read its value and doesn't generate interrupts, post messages, or post semaphores (such as the DosTmr* functions), you will need to check the elapsed time yourself. If enough time has passed, update your timer variables and continue. If not, you will have to continue to poll the timer since you don't have a function that blocks the current thread. In order to keep the polling loop from burning up CPU time when it doesn't need to, call DosSleep (0) after every unsuccessful check.

With the standard timer and event semaphore, our main game loop for an action game looks something like this:

DosResetEventSem (hevTimerSem);
while (! bEndGameThread)
    {
    // check for paused game
    // regulate the speed of the game
DosWaitEventSet (hevTimerSem, SEM_INDEFINITE_WAIT);
    DosResetEventSem (hevTimerSem);
    // move player
    // move enemies
    // move bullets, etc.
    // compose graphics screen.
    // blit screen.
    };

Figure 4) Processing loop revisited

One final note about blocking the game thread with either an event or a mutex semaphore. When you request that the game ends (by setting bEndGameThread to true), then you must make sure the game thread is not blocked by either a pause or a speed regulating semaphore. You must therefore release any mutex semaphores that the main thread owns, and post any event semaphores that the game thread is waiting on.

Basic Sprites

I have not had time to write the Basic Sprite explanation for this article, but I have included the code. I implemented sprite drawing in a very simple way. All sprites are stored in a PCX file. This file is read into a canvas, and the canvas is passed to the sprite class. Each sprite is defined as a rectangular region of the canvas. When the sprite is drawn, the sprite is copied pixel by pixel from the sprite canvas over to the destination canvas. If the pixel's value is greater or equal to 248, then the pixel is not written, thus making those pixels clear since the background remains. This makes the top 8 colours clear.

The background is stored in its own PCX file and read into its own canvas. To erase the sprites, I just copy part of the background canvas over to the display canvas. This covers everything that was written last time, and then the new sprites are written directly to the display canvas. This display canvas is then blitted to the screen.

Sprites are referenced with a book number and a sprite number. A book is a collection of several sprites, up to 65,535 sprites per book. There can be up to 255 books of sprites. You would probably want to have a book of sprites for the player, a book for each of the enemies, and a book for missiles and explosions. Each book will contain all of the frames of animation for that object.

When we get into more advanced sprites, we will actually encode them from a canvas and store the encoded data. Then we can discard the canvas the sprites were encoded from, thus saving memory. Furthermore, encoded sprites will display much, much faster than the current method. Also we will store the books of encoded sprites to disk in library files. I will be explaining library files in the future when I cover organizing game data.

The sprite class has a number of routines which are really just place holders for more advanced features. If a function doesn't seem to have a use now then just ignore it; we will come back to it in the future.

The Canvas Class

One small addition has been made to the canvas class as well. I have added the routine CopyDirtyRec(), and this copies a rectangle from one canvas to another. This routine is used to copy part of the background canvas to the display canvas when the display canvas needs to be erased. The code of this routine is a good demonstration of how to move canvas data around, and some of the speedups that canvases allow.

Those Silly Bugs

The MAKEFILE that accompanied my last article appears to have had a slight bug in it. In calling the linker, I neglected to put the full path name before the mmpm2.lib file. Therefore libr mmpm2.lib should read libr c:\toolkit\lib\mmpm2.lib or add whatever path you have to the lib directory of the OS/2 toolkit.

Aside from that, I don't think there were any other bugs in the article for EDM/2 3-7.Am I mistaken?

News and other Nonsense

By the time you read this, the Entertainment Toolkit for OS/2 may already be out on DevCon 8. It was not yet available at press time, so I am unable to cover anything on it with any certainty. I will be writing about it in the near future.

If you haven't visited it yet, I'd like to suggest that you drop by IBM's Games Home Page. The URL is http://www.austin.ibm.com/os2games and you will need a Web browser to look at it. IBM seems to be using this as their main notification to the public (that's us) of game related information, so it makes good sense to check it out every week or so for new stuff. Recently the beta documentation for the Entertainment Toolkit has been placed on this site, as well as the joystick drivers and documentation and other stuff.

While we're on the topic of Web pages, I have finally put one up of my own. I hope to eventually have online versions of my EDM/2 articles up there, and I may even be placing code and text on the web page before it sees print in EDM/2. The page is really, really sparse right now, but it should grow as I find time to work on it. I also have a number of OS/2 game related links on my page already that you may want to check out.

This month I also did things a little bit differently in that I didn't distribute duplicates of code from previous articles when the code didn't change at all. The README.1ST file in the code archive will tell you which files you need from the last article. Simply place those files in the directory with the new code, and run the makefile (if of course you are using the Watcom compiler, otherwise the makefile will not work).

Next month I hope to cover sprites in depth, including how to construct, store, and display Run Length Encoded (RLE) sprites, and how to clip sprites. If I have time, I may show how dirty rectangles can be used in some cases to speed up sprite updating, though with a full scrolling background dirty rectangles don't do us much good. Also I should be able to cover DIVE's full-screen abilities in the 320x200x256 colour mode, since they are a breeze to implement.

As always, I welcome comments. You can reach me at mduffy@ionet.net, or find me lurking on comp.os.os2.games among other places. Until next time...