queueing_20event_20interrupts
Differences
This shows you the differences between two versions of the page.
Next revision | Previous revision | ||
queueing_20event_20interrupts [2018/03/31 13:19] – external edit 127.0.0.1 | queueing_20event_20interrupts [2024/01/05 00:21] (current) – external edit 127.0.0.1 | ||
---|---|---|---|
Line 1: | Line 1: | ||
=====Queueing event interrupts===== | =====Queueing event interrupts===== | ||
- | //by Richard Russell, June 2006; amended June 2009 and December 2011//\\ \\ // | + | //by Richard Russell, June 2006; amended June 2009 and December 2011//\\ \\ // |
+ | |||
+ | <code bb4w> | ||
ON SYS PROCsys(@wparam%, | ON SYS PROCsys(@wparam%, | ||
- | It is important that any event parameters in which you are interested (**@msg%**, **@wparam%** and/or **@lparam%**) are read //in the statement immediately following the **ON** statement// otherwise they might have changed as a result of a subsequent interrupt. For example the following code will __not__ work reliably:\\ \\ | + | </ |
+ | |||
+ | It is important that any event parameters in which you are interested (**@msg%**, **@wparam%** and/or **@lparam%**) are read //in the statement immediately following the **ON** statement// otherwise they might have changed as a result of a subsequent interrupt. For example the following code will __not__ work reliably: | ||
+ | |||
+ | <code bb4w> | ||
ON SYS wp%=@wparam%: | ON SYS wp%=@wparam%: | ||
- | Although **wp%** //will// contain the @wparam% parameter relevant to the ON SYS call, **lp%** //may not// contain the relevant value of @lparam%, because another interrupt may have occurred in between.\\ \\ So with care it is possible to ensure that every event is processed by your program, with no chance of any being missed, but //**not necessarily in the order in which they occurred!**// | + | </ |
+ | |||
+ | Although **wp%** //will// contain the @wparam% parameter relevant to the ON SYS call, **lp%** //may not// contain the relevant value of @lparam%, because another interrupt may have occurred in between.\\ \\ So with care it is possible to ensure that every event is processed by your program, with no chance of any being missed, but //**not necessarily in the order in which they occurred!**// | ||
+ | |||
+ | <code bb4w> | ||
ON SYS PROCsys(@wparam%) : RETURN | ON SYS PROCsys(@wparam%) : RETURN | ||
Line 11: | Line 21: | ||
PRINT W% | PRINT W% | ||
ENDPROC | ENDPROC | ||
- | Suppose two ON SYS interrupts happen in quick succession, with @wparam% values of 1 and 2 in that order. The first will result in PROCsys being called, but before the PRINT statement gets a chance to be executed the second interrupt will occur. So the actual sequence of events will be:\\ \\ " | + | </ |
- | * PROCsys(2) | + | Suppose two ON SYS interrupts happen in quick succession, with @wparam% values of 1 and 2 in that order. The first will result in PROCsys being called, but before the PRINT statement gets a chance to be executed the second interrupt will occur. So the actual sequence of events will be: |
- | | + | |
- | | + | - PROCsys(1) |
- | PRINT 1\\ | + | - PROCsys(2) |
+ | | ||
+ | | ||
+ | | ||
+ | - ENDPROC" | ||
+ | |||
+ | resulting in the following output: | ||
+ | |||
+ | < | ||
2 | 2 | ||
1 | 1 | ||
- | So the events were processed in the opposite order to that in which they occurred!\\ \\ Often this won't matter, and indeed in many circumstances it will be irrelevant because there is no likelihood of two interrupts happening sufficiently close together. For example this will be the situation if you are using **ON SYS** only to respond to menu selections, since you should only get one interrupt for each selection. In such a case you can safely use an alternative approach where the interrupt simply sets a global variable that you can poll elsewhere:\\ \\ | + | </ |
+ | |||
+ | So the events were processed in the opposite order to that in which they occurred!\\ \\ Often this won't matter, and indeed in many circumstances it will be irrelevant because there is no likelihood of two interrupts happening sufficiently close together. For example this will be the situation if you are using **ON SYS** only to respond to menu selections, since you should only get one interrupt for each selection. In such a case you can safely use an alternative approach where the interrupt simply sets a global variable that you can poll elsewhere: | ||
+ | |||
+ | <code bb4w> | ||
ON SYS Click% = @wparam% : RETURN | ON SYS Click% = @wparam% : RETURN | ||
Line 32: | Line 54: | ||
ENDCASE | ENDCASE | ||
UNTIL FALSE | UNTIL FALSE | ||
- | This routine conveniently uses **INKEY** as both a delay (to avoid using too much CPU time) and to monitor for keypresses, which is handy for implementing keyboard shortcuts for menu items.\\ \\ However life isn't always this easy! Occasionally it may be necessary to ensure that every event is registered, even if two or more occur in very quick succession, **and** to ensure that the events are processed in the order in which they happen. An example might be handling events from a dialogue box. One way of dealing with this is to implement a First In First Out **queue** of events which can be //written// as the events occur (however fast) and //read// as they are processed, even if relatively slowly.\\ \\ At first thought this doesn' | + | </ |
+ | |||
+ | This routine conveniently uses **INKEY** as both a delay (to avoid using too much CPU time) and to monitor for keypresses, which is handy for implementing keyboard shortcuts for menu items.\\ \\ However life isn't always this easy! Occasionally it may be necessary to ensure that every event is registered, even if two or more occur in very quick succession, **and** to ensure that the events are processed in the order in which they happen. An example might be handling events from a dialogue box. One way of dealing with this is to implement a First In First Out **queue** of events which can be //written// as the events occur (however fast) and //read// as they are processed, even if relatively slowly.\\ \\ At first thought this doesn' | ||
==== Method 1: Using an array ==== | ==== Method 1: Using an array ==== | ||
- | \\ | + | |
+ | For a queue with six entries the code for writing into the queue would be as follows: | ||
+ | |||
+ | <code bb4w> | ||
DIM Q%(6) | DIM Q%(6) | ||
ON SYS Q%()=Q%(0)+1, | ON SYS Q%()=Q%(0)+1, | ||
- | This may need a little explanation. To do it all in a single statement we use the ability to load an entire array from a comma-separated list of values. The clever bit is that some of the values we load into the array depend on elements of that same array, as follows:\\ | + | </ |
+ | |||
+ | This may need a little explanation. To do it all in a single statement we use the ability to load an entire array from a comma-separated list of values. The clever bit is that some of the values we load into the array depend on elements of that same array, as follows: | ||
* Q%(0) = Q%(0)+1 | * Q%(0) = Q%(0)+1 | ||
Line 47: | Line 77: | ||
* Q%(5) = Q%(4) | * Q%(5) = Q%(4) | ||
* Q%(6) = Q%(5) | * Q%(6) = Q%(5) | ||
- | \\ | + | |
+ | Hopefully you can see what is happening here. Elements Q(1) to Q(6) act as a **shift register**: each time an event occurs the previously-stored data is shifted one place along the queue (the data in element Q%(6) is discarded) and the new data is stored in Q%(1). Element Q%(0) is loaded with the value Q%(0)+1, in other words it is **incremented**. This zeroth element of the array acts a **pointer** to the oldest event stored in the queue.\\ \\ So by using this cunning method we manage to store each event into the queue, and increment a pointer, in just one statement! How, then, do we read the data out? This is the code: | ||
+ | |||
+ | <code bb4w> | ||
WHILE Q%(0) | WHILE Q%(0) | ||
event% = Q%(0)< | event% = Q%(0)< | ||
Line 53: | Line 86: | ||
REM Do something with event% | REM Do something with event% | ||
ENDWHILE | ENDWHILE | ||
- | Firstly we examine **Q%(0)**, which is the pointer. If this is zero the queue is empty and we need take no further action. If it is non-zero there is at least one event in the queue. The next line reads the //oldest// event in the queue, by using **Q%(0)** as the subscript; the comparisons ensure that a 'Bad subscript' | + | </ |
+ | |||
+ | Firstly we examine **Q%(0)**, which is the pointer. If this is zero the queue is empty and we need take no further action. If it is non-zero there is at least one event in the queue. The next line reads the //oldest// event in the queue, by using **Q%(0)** as the subscript; the comparisons ensure that a 'Bad subscript' | ||
+ | |||
+ | <code bb4w> | ||
DIM Q%(18) | DIM Q%(18) | ||
ON SYS Q%()=Q%(0)+3, | ON SYS Q%()=Q%(0)+3, | ||
- | To read from the queue:\\ | + | </ |
+ | |||
+ | To read from the queue: | ||
+ | |||
+ | <code bb4w> | ||
WHILE Q%(0) | WHILE Q%(0) | ||
lpar% = Q%(0)< | lpar% = Q%(0)< | ||
Line 65: | Line 106: | ||
REM Do something with msg%, wpar% and lpar% | REM Do something with msg%, wpar% and lpar% | ||
ENDWHILE | ENDWHILE | ||
- | The maximum length of queue that can be fitted into one line is 11 events (33 values):\\ | + | </ |
+ | |||
+ | The maximum length of queue that can be fitted into one line is 11 events (33 values): | ||
+ | |||
+ | <code bb4w> | ||
DIM Q%(33) | DIM Q%(33) | ||
ON SYS Q%()=Q%(0)+3, | ON SYS Q%()=Q%(0)+3, | ||
- | To create a longer queue split the line using line-continuation characters (//BBC BASIC for Windows// version 5.91a or later only):\\ | + | </ |
+ | |||
+ | To create a longer queue split the line using line-continuation characters (//BBC BASIC for Windows// version 5.91a or later only): | ||
+ | |||
+ | <code bb4w> | ||
DIM Q%(99) | DIM Q%(99) | ||
Line 75: | Line 124: | ||
\ Q%(30), | \ Q%(30), | ||
\ Q%(64), | \ Q%(64), | ||
- | Using this technique you can make the queue as long as you like, within reason. However, requiring a very long queue is suggestive that you may be able to find a better solution by restructuring your program. For example you may be able to increase the frequency with which you poll the queue, or use an interrupt approach rather than polling.\\ \\ | + | </ |
+ | |||
+ | Using this technique you can make the queue as long as you like, within reason. However, requiring a very long queue is suggestive that you may be able to find a better solution by restructuring your program. For example you may be able to increase the frequency with which you poll the queue, or use an interrupt approach rather than polling. | ||
==== Method 2: Using a string ==== | ==== Method 2: Using a string ==== | ||
- | \\ | + | |
+ | This method is easier to understand than the foregoing one, and the maximum practical queue length is greater (over 5000 events), but it is more expensive of CPU time and memory.\\ \\ The code for writing into the queue is as follows: | ||
+ | |||
+ | <code bb4w> | ||
Queue$ = "" | Queue$ = "" | ||
!^wParam$ = ^@wparam% : ? | !^wParam$ = ^@wparam% : ? | ||
ON SYS Queue$ += wParam$ : RETURN | ON SYS Queue$ += wParam$ : RETURN | ||
- | Here **wParam$** is a global string variable containing four characters, corresponding to the 4-byte (32-bit) value of **@wparam%**.\\ \\ This is the code for reading the data out:\\ \\ | + | </ |
+ | |||
+ | Here **wParam$** is a global string variable containing four characters, corresponding to the 4-byte (32-bit) value of **@wparam%**.\\ \\ This is the code for reading the data out: | ||
+ | |||
+ | <code bb4w> | ||
WHILE Queue$<>"" | WHILE Queue$<>"" | ||
event% = !!^Queue$ | event% = !!^Queue$ | ||
Line 87: | Line 146: | ||
REM Do something with event% | REM Do something with event% | ||
ENDWHILE | ENDWHILE | ||
- | If events occur more quickly than they can be processed the queue will eventually fill and a **String too long** error will result.\\ \\ If you need to know the value of **@msg%** and **@lparam%** as well as **@wparam%** you can extend the technique as follows. To write into the queue:\\ | + | </ |
+ | |||
+ | If events occur more quickly than they can be processed the queue will eventually fill and a **String too long** error will result.\\ \\ If you need to know the value of **@msg%** and **@lparam%** as well as **@wparam%** you can extend the technique as follows. To write into the queue: | ||
+ | |||
+ | <code bb4w> | ||
Queue$ = "" | Queue$ = "" | ||
!^iMsg$ = ^@msg% : ?(^iMsg$+4) = 4 | !^iMsg$ = ^@msg% : ?(^iMsg$+4) = 4 | ||
Line 93: | Line 156: | ||
!^lParam$ = ^@lparam% : ? | !^lParam$ = ^@lparam% : ? | ||
ON SYS Queue$ += iMsg$ + wParam$ + lParam$ : RETURN | ON SYS Queue$ += iMsg$ + wParam$ + lParam$ : RETURN | ||
- | Here **iMsg$**, **wParam$** and **lParam$** are global string variables containing the values of **@msg%**, **@wparam%** and **@lparam%** respectively.\\ \\ To read from the queue:\\ \\ | + | </ |
+ | |||
+ | Here **iMsg$**, **wParam$** and **lParam$** are global string variables containing the values of **@msg%**, **@wparam%** and **@lparam%** respectively.\\ \\ To read from the queue: | ||
+ | |||
+ | <code bb4w> | ||
WHILE Queue$<>"" | WHILE Queue$<>"" | ||
event$ = LEFT$(Queue$, | event$ = LEFT$(Queue$, | ||
Line 104: | Line 171: | ||
REM Do something with msg%, wpar% and lpar% | REM Do something with msg%, wpar% and lpar% | ||
ENDWHILE | ENDWHILE | ||
+ | </ | ||
+ | |||
Note that the use of the temporary string **event$** is important because the memory address of the string **Queue$** alters as it changes length, and so could change between reading the **msg%**, **wpar%** and **lpar%** values. | Note that the use of the temporary string **event$** is important because the memory address of the string **Queue$** alters as it changes length, and so could change between reading the **msg%**, **wpar%** and **lpar%** values. |
queueing_20event_20interrupts.1522502375.txt.gz · Last modified: 2024/01/05 00:16 (external edit)