User Tools

Site Tools


queueing_20event_20interrupts

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Next revision
Previous revision
queueing_20event_20interrupts [2018/03/31 13:19] – external edit 127.0.0.1queueing_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//\\ \\ //**Consider using the [[/Libraries|EVENTLIB library]] instead of the code listed in this article.**//\\ \\  The **ON CLOSE**, **ON MOUSE**, **ON MOVE**, **ON SYS** and **ON TIME** statements //interrupt// your program when one of the specified events occurs. Perhaps the most obvious way to use these statements is to cause a procedure (an //interrupt service routine//) to be called when the interrupt happens, as follows:\\ \\ +//by Richard Russell, June 2006; amended June 2009 and December 2011//\\ \\ //**Consider using the [[/Libraries|EVENTLIB library]] instead of the code listed in this article.**//\\ \\  The **ON CLOSE**, **ON MOUSE**, **ON MOVE**, **ON SYS** and **ON TIME** statements //interrupt// your program when one of the specified events occurs. Perhaps the most obvious way to use these statements is to cause a procedure (an //interrupt service routine//) to be called when the interrupt happens, as follows: 
 + 
 +<code bb4w> 
         ON SYS PROCsys(@wparam%,@lparam%) : RETURN         ON SYS PROCsys(@wparam%,@lparam%) : RETURN
-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> 
 + 
 +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%:lp%=@lparam%:PROCsys(wp%,lp%):RETURN : REM Don't do this!         ON SYS wp%=@wparam%:lp%=@lparam%:PROCsys(wp%,lp%):RETURN : REM Don't do this!
-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!**// To see why, consider the following code:\\ \\ +</code> 
 + 
 +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!**// To see why, consider the following code: 
 + 
 +<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(1)\\ +</code>
  
-  * 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: 
-  PRINT 2 + 
-  ENDPROC +  - PROCsys(1) 
-PRINT 1\\  ENDPROC"\\ \\  resulting in the following output:\\ \\ +  - PROCsys(2) 
 +  PRINT 2 
 +  ENDPROC 
 +  PRINT 1 
 +  ENDPROC" 
 + 
 +resulting in the following output: 
 + 
 +<code>
            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:\\ \\ +</code> 
 + 
 +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't sound too difficult - creating a FIFO queue in software is straightforward - but there is a major difficulty: we must transfer the event into the queue **//in just one statement//**. If we don't it won't work: either data could be lost or the events could be processed in the wrong order! Achieving this sounds like a tall order, but it can be done.\\ \\ +</code> 
 + 
 +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't sound too difficult - creating a FIFO queue in software is straightforward - but there is a major difficulty: we must transfer the event into the queue **//in just one statement//**. If we don't it won't work: either data could be lost or the events could be processed in the wrong order! Achieving this sounds like a tall order, but it can be done. 
 ==== 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:\\ \\ + 
 +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,@wparam%,Q%(1),Q%(2),Q%(3),Q%(4),Q%(5) : RETURN         ON SYS Q%()=Q%(0)+1,@wparam%,Q%(1),Q%(2),Q%(3),Q%(4),Q%(5) : RETURN
-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:\\ +</code> 
 + 
 +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:\\ \\ + 
 +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)<=DIM(Q%(),1) AND Q%(Q%(0) AND Q%(0)<=DIM(Q%(),1))           event% = Q%(0)<=DIM(Q%(),1) AND Q%(Q%(0) AND Q%(0)<=DIM(Q%(),1))
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' error doesn't occur if the queue overflows.\\ \\  Note that as Q%(0) is accessed //and// the queue's contents retrieved in the same statement, there is no possibility of reading the wrong event. Even if another interrupt has occurred since Q%(0) was tested in the previous line it makes no difference: the oldest event will always be read.\\ \\  Finally the next line simply decrements the pointer, so it points to the next event to be read (if any), and leaves the other elements in the array unchanged. Again, it doesn't matter if another interrupt occurs between the previous statement and this one.\\ \\  The array can, of course, be any length (within reason). If events occur more quickly than they can be processed the queue will eventually fill and the oldest event(s) will be discarded; in that case **event%** will be set to zero.\\ \\  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> 
 + 
 +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' error doesn't occur if the queue overflows.\\ \\  Note that as Q%(0) is accessed //and// the queue's contents retrieved in the same statement, there is no possibility of reading the wrong event. Even if another interrupt has occurred since Q%(0) was tested in the previous line it makes no difference: the oldest event will always be read.\\ \\  Finally the next line simply decrements the pointer, so it points to the next event to be read (if any), and leaves the other elements in the array unchanged. Again, it doesn't matter if another interrupt occurs between the previous statement and this one.\\ \\  The array can, of course, be any length (within reason). If events occur more quickly than they can be processed the queue will eventually fill and the oldest event(s) will be discarded; in that case **event%** will be set to zero.\\ \\  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>
         DIM Q%(18)         DIM Q%(18)
  
         ON SYS Q%()=Q%(0)+3,@msg%,@wparam%,@lparam%,Q%(1),Q%(2),Q%(3),Q%(4),Q%(5),...,Q%(15) : RETURN         ON SYS Q%()=Q%(0)+3,@msg%,@wparam%,@lparam%,Q%(1),Q%(2),Q%(3),Q%(4),Q%(5),...,Q%(15) : RETURN
-To read from the queue:\\ +</code> 
 + 
 +To read from the queue: 
 + 
 +<code bb4w>
         WHILE Q%(0)         WHILE Q%(0)
           lpar% = Q%(0)<=DIM(Q%(),1) AND Q%(Q%(0)-0 AND Q%(0)<=DIM(Q%(),1))           lpar% = Q%(0)<=DIM(Q%(),1) AND Q%(Q%(0)-0 AND Q%(0)<=DIM(Q%(),1))
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):\\ +</code> 
 + 
 +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,@msg%,@wparam%,@lparam%,Q%(1),Q%(2),Q%(3),Q%(4),Q%(5),...,Q%(30) : RETURN         ON SYS Q%()=Q%(0)+3,@msg%,@wparam%,@lparam%,Q%(1),Q%(2),Q%(3),Q%(4),Q%(5),...,Q%(30) : RETURN
-To create a longer queue split the line using line-continuation characters (//BBC BASIC for Windows// version 5.91a or later only):\\ +</code> 
 + 
 +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%(31),Q%(32),Q%(33),Q%(34),Q%(35),Q%(36),Q%(37),Q%(38),Q%(39),Q%(40),Q%(41),Q%(42),Q%(43),Q%(44),Q%(45),Q%(46),Q%(47),Q%(48),Q%(49),Q%(50),Q%(51),Q%(52),Q%(53),Q%(54),Q%(55),Q%(56),Q%(57),Q%(58),Q%(59),Q%(60),Q%(61),Q%(62),Q%(63),\         \ Q%(30),Q%(31),Q%(32),Q%(33),Q%(34),Q%(35),Q%(36),Q%(37),Q%(38),Q%(39),Q%(40),Q%(41),Q%(42),Q%(43),Q%(44),Q%(45),Q%(46),Q%(47),Q%(48),Q%(49),Q%(50),Q%(51),Q%(52),Q%(53),Q%(54),Q%(55),Q%(56),Q%(57),Q%(58),Q%(59),Q%(60),Q%(61),Q%(62),Q%(63),\
         \ Q%(64),Q%(65),Q%(66),Q%(67),Q%(68),Q%(69),Q%(70),Q%(71),Q%(72),Q%(73),Q%(74),Q%(75),Q%(76),Q%(77),Q%(78),Q%(79),Q%(80),Q%(81),Q%(82),Q%(83),Q%(84),Q%(85),Q%(86),Q%(87),Q%(88),Q%(89),Q%(90),Q%(91),Q%(92),Q%(93),Q%(94),Q%(95),Q%(96) : RETURN         \ Q%(64),Q%(65),Q%(66),Q%(67),Q%(68),Q%(69),Q%(70),Q%(71),Q%(72),Q%(73),Q%(74),Q%(75),Q%(76),Q%(77),Q%(78),Q%(79),Q%(80),Q%(81),Q%(82),Q%(83),Q%(84),Q%(85),Q%(86),Q%(87),Q%(88),Q%(89),Q%(90),Q%(91),Q%(92),Q%(93),Q%(94),Q%(95),Q%(96) : RETURN
-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.\\ \\ +</code> 
 + 
 +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:\\ \\ + 
 +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$+4) = 4         !^wParam$ = ^@wparam% : ?(^wParam$+4) = 4
         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:\\ \\ +</code> 
 + 
 +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:\\ +</code> 
 + 
 +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$+4) = 4         !^lParam$ = ^@lparam% : ?(^lParam$+4) = 4
         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:\\ \\ +</code> 
 + 
 +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$,12)           event$ = LEFT$(Queue$,12)
Line 104: Line 171:
           REM Do something with msg%, wpar% and lpar%           REM Do something with msg%, wpar% and lpar%
         ENDWHILE         ENDWHILE
 +</code>
 +
 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)