Using flags to handle concurrent operation of multiple events
Contents
Examples of the problem
Playing notes
- You want the up key to be play an A as long as the keypad is pressed and the left key to play a B as long as the keypad is pressed.
- If the user pressed up and left at the same time, you would like both A and B to sound.
To accomplish this, you might at first imagine something in your main loop like this:
MainLoop: ... call GetKeys push af and PADF_UP call nz,play_a pop af and PADF_LEFT call nz,play_b ... jr MainLoop play_a: <code here to start the note a> ret ... play_b: <code here to start the note b> ret
the problem with this is that it will continually restart the note a as long as the UP key is pressed. This won't sound very good. We just want the note to be triggered once. At first, you might think the solution for this is:
MainLoop: ... call GetKeys push af and PADF_UP call nz,play_a pop af and PADF_LEFT call nz,play_b ... jr MainLoop ... play_a: <code here to start the note a> <code here to sit in a tight loop until the keypad is released> <code here to stop the note a> ret ... play_b: <code here to start the note b> <code here to sit in a tight loop until the keypad is released> <code here to stop the note b> ret
- Now the note won't be retriggered continually, but there is also no way to play a and b at the same time.
- Perhaps we could solve this problem somehow, looking for the left keypress while in the play_a routine etc. but these kinds of solutions are not very scalable for multiple notes and multiple keypresses.
- What's a programmer to do?
shooting a bullet
- Imagine we have some sort of object on the screen and if you press the A key, the object shoots a bullet.
Upon initial consideration we might write some code like this:
MainLoop: ... call GetKeys push af and PADF_A call nz,shoot_bullet ... jr MainLoop ... shoot_bullet: <code here to shoot the bullet> ret
But this sort of logic means we do not loop through MainLoop at all for the duration that the bullet is shot.
If we do not loop through MainLoop, all objects will stop moving, the keypad will stop responding, etc.
- It would be a pretty lame game if all objects stop moving and become unresponsive whenever a bullet is shot.
- What's a programmer to do?
basic idea for the solution
The general concept is to have no loops other than MainLoop. If something has to happen inside a loop, such a moving a bullet, use a flag to use MainLoop as the loop which processes this, rather than some other loop effectively inside of MainLoop.
- when an event such as shooting a bullet, playing a note is supposed to happen, call a subroutine, as we have in the above examples. However, in this subroutine, instead of actually trying to fulfill the task of shooting a bullet, playing a note, etc.:
- check if a flag is set indicating that the bullet is already being shot, the note is already being played, etc. If this is the case, return.
- otherwise, set initial values needed for shooting a bullet, playing a note, etc.
- set a flag which indicates that a bullet should be shot, a note should be played, etc.
- return
within MainLoop, if needed, call a subroutine for shooting a bullet, playing a note, etc. if a flag indicating such an operation is set.
- the subroutine will not shoot the bullet, play the sound, etc. but instead process one iteration of doing so. For example, if we are shooting a bullet to the left, the subroutine will move the bullet one pixel to the left.
- the subroutine will do a test to see if the flag should be reset. For example, if the bullet has moved off the screen, the subroutine will reset the flag. In the sound example, the subroutine will reset the flag when the user releases the key.
example solutions
playing a note
... LoByteVar NoteFlags NOTE_A_ON EQU #00000001 NOTE_A_OFF EQU #00000000 NOTE_B_ON EQU #00000010 NOTE_B_OFF EQU #00000000 ... MainLoop: ... call GetKeys push af and PADF_UP call nz,play_a call z,stop_a ; stop the note if the user has released the key pop af and PADF_LEFT call nz,play_b ... jr MainLoop ... play_a: ld a,[NoteFlags] and NOTE_A_ON ; is note a already being played ret nz ; yep. Don't retrigger it. ld a,[NoteFlags] or NOTE_A_ON ; set the flag ld [NoteFlags],a ; save it <code here to start the note a> ret ... play_b: ld a,[NoteFlags] and NOTE_B_ON ; is note b already being played ret nz ld a,[Noteflags] or NOTE_B_On ld [NoteFlags],a <code here to start the note b> ret ... stop_a: ld a,[NoteFlags] and ~NOTE_A_ON ld [NoteFlags],a <code here to stop note a> ret
shooting a bullet
LoByteVar ProjectileFlags BULLET_SHOT EQU #00000001 MainLoop: ... call GetKeys push af and PADF_A call nz,shoot_bullet ... call processBullet jr MainLoop ... shoot_bullet: ld a,[ProjectileFlags] and BULLET_SHOT ret nz ; there's already a bullet out there. Don't shoot another ld a, [ProjectileFlags] or BULLET_SHOT ; set the bullet flag ld [ProjectileFlags],a ; set the flag saying the bullet is being shot <code here for initialization of the bullet, for example initial screen location> ret ... processBullet: ld a,[ProjectileFlags] and BULLET_SHOT ret z ; ain't no bullet. Go home. <move the bullet one iteration (one to left, one to right, one up, down, whatever)> <check if bullet has hit something or is off the screen or if it should die for some other reason. If so:> ld a,[ProjectileFlags] and ~BULLET_SHOT ; reset the flag ret <otherwise, just return> ...
tips
Sometimes you might not want something to move at the same speed as everything else in MainLoop. You can easily set things to move any power of 2 slower than the number of cycles in MainLoop. Just look at the macro called CALL_IF_NTH_TIME in the timer tutorial to do this.