Jump to content
IGNORED

IntyBASIC Keypad Read Single Controller?


Recommended Posts

Looking at the contrib\keypad.bas sample, line 29 gets the value from both controllers with "cont". Changing it to "cont1" does not get the value from the left controller only. What is the right way to update the sample to only get values from the left controller? Of course I'll ask how to do it for other controllers, later.

 

Thanks.

 

 

Link to comment
Share on other sites

As far as I understand, CONT1 and CONT2 contain the decoded value of hand-controller-1 and hand-controller-2, respectively; and CONT is the ANDed combination of both. The latter is useful for single-player games, because it gets input from whichever controller is active (of course, in a single-player mode, you only expect either of the hand-controllers to be used at a time, not both at once).

 

If you intend to accept input from each hand-controller discretely, then I recommend reading CONT1 and CONT2. Otherwise, you're safe with CONT.

 

Note that all of these variables are updated during IntyBASIC's main loop, so once per frame.

 

-dZ.

Link to comment
Share on other sites

  • 2 weeks later...

Thanks. But what I don't understand is why when I choose to read from cont1 instead of cont, the value is not read from cont1. I am only able to get a value from cont1 if I choose to read cont. I'm using the vanialla sample of keypad.bas.

 

Thanks.

 

 

As far as I understand, CONT1 and CONT2 contain the decoded value of hand-controller-1 and hand-controller-2, respectively; and CONT is the ANDed combination of both. The latter is useful for single-player games, because it gets input from whichever controller is active (of course, in a single-player mode, you only expect either of the hand-controllers to be used at a time, not both at once).

 

 

 

If you intend to accept input from each hand-controller discretely, then I recommend reading CONT1 and CONT2. Otherwise, you're safe with CONT.

 

Note that all of these variables are updated during IntyBASIC's main loop, so once per frame.

 

-dZ.

Link to comment
Share on other sites

Thanks. But what I don't understand is why when I choose to read from cont1 instead of cont, the value is not read from cont1. I am only able to get a value from cont1 if I choose to read cont. I'm using the vanialla sample of keypad.bas.

 

Thanks.

 

 

 

I guess I do not understand what you mean by "when I choose to read from cont1 instead of cont, the value is not read from cont1."

 

Do you mean that cont1 returns the value of cont2? Could it be that you have your controllers mixed up?

 

Keep in mind that what we think typically as "Player 1" (or left controller) is the second I/O port on the PSG. In reading the IntyBASIC manual, it states:

  CONT1             Contains complemented content of address $01ff (left controller)

So that's already accounted for unless there's a bug there.

 

Or perhaps I misunderstood your problem completely... Could you provide a bit more details?

 

-dZ.

Link to comment
Share on other sites

Apologies in advance if this doesnt come out right. I am in a doctor office waiting room... I probably should have led with this in the first place.

 

In the code below, there is a line keypad=cont. And further in the code, actions are taken based on the value of keypad. The value of keypad is populated regardless of whether a button is pushed on controller 1 or 2. However, if I change the code line to keypad=cont1, and then push a button on the left controller, I do not get a usable value in the keypad variable. If I change the line to keypad=cont2 and push a button on the right controller, I do not get a usable value either.

 

Im not sure what is happening.

 

Below is a copy-paste from the contrib/keypad.bas file included in the IntyBASIC disto.

 

Thanks.

 

 

 

 

 

 

REM Module: KEYPAD.BAS

REM

REM Description: Raw keypad demonstration - A demo for IntyBASIC.

REM Author: Mark Ball

REM Date: 01/01/16

REM Version: 1.03F

REM

REM HISTORY

REM -------

REM 1.00F 13/07/15 - First release

REM 1.01F 13/07/15 - Modified instruction text.

REM 1.02F 13/07/15 - Added side buttons.

REM 1.03F 01/01/16 - Added pause buttons.

REM

 

' We need some important constants.

include "constants.bas"

 

cls ' Clear the screen.

 

' Display some instructions.

print at screenpos(0,0), "Press buttons on a"

print at screenpos(0,1), "controller's keypad"

print at screenpos(0,4), "Button ..."

print at screenpos(0,5), "Value ...."

 

loop:

wait ' Wait for the vertical blank.

keypad=cont ' Get the raw hand controller value at this instant.

 

print at screenpos(11,5), <3>keypad ' Display the controller response.

 

' Check if a keypad button is down,

if keypad=0 then print at screenpos(11,4), " ": goto loop

 

' A keypad button is down, so display what it is.

if keypad=KEYPAD_0 then print at screenpos(11,4), "0"

if keypad=KEYPAD_1 then print at screenpos(11,4), "1"

if keypad=KEYPAD_2 then print at screenpos(11,4), "2"

if keypad=KEYPAD_3 then print at screenpos(11,4), "3"

if keypad=KEYPAD_4 then print at screenpos(11,4), "4"

if keypad=KEYPAD_5 then print at screenpos(11,4), "5"

if keypad=KEYPAD_6 then print at screenpos(11,4), "6"

if keypad=KEYPAD_7 then print at screenpos(11,4), "7"

if keypad=KEYPAD_8 then print at screenpos(11,4), "8"

if keypad=KEYPAD_9 then print at screenpos(11,4), "9"

if keypad=KEYPAD_ENTER then print at screenpos(11,4), "enter"

if keypad=KEYPAD_CLEAR then print at screenpos(11,4), "clear"

if keypad=KEYPAD_PAUSE then print at screenpos(11,4), "pause"

if keypad=BUTTON_1 then print at screenpos(11,4), "top"

if keypad=BUTTON_2 then print at screenpos(11,4), "b-left"

if keypad=BUTTON_3 then print at screenpos(11,4), "b-right"

 

 

' Keep checking for keypad buttons.

goto loop

Link to comment
Share on other sites

The sample works 100% for me. Are you mixing up left and right? It seems to me that in jzintv, the left controller is mapped by the cursor keys and numeric keyboard to the right of a PC keyboard, while the right controller is mapped by various keys on the left hand side of a PC keyboard + numeric keys on top of the keyboard.

Link to comment
Share on other sites

I'm not mixing things up. But if the sample works for you, I must have some other config thing happening. Thanks for verifying.

 

The sample works 100% for me. Are you mixing up left and right? It seems to me that in jzintv, the left controller is mapped by the cursor keys and numeric keyboard to the right of a PC keyboard, while the right controller is mapped by various keys on the left hand side of a PC keyboard + numeric keys on top of the keyboard.

Link to comment
Share on other sites

I'm not mixing things up. But if the sample works for you, I must have some other config thing happening. Thanks for verifying.

 

As far as I know, the only difference between using CONT1/CONT2 vs. CONT is that the latter merges both signals of the former (assuming that only one is used at a time). So it seems that it may be something strange with your key mappings.

 

Could you confirm that you can get a proper signal when using CONT and pressing the same button for each controller?

 

Try this experiment: locate the mapped key on your keyboard for the action button you desire, for each controller. Using CONT see if pressing each at a time will return the same value for the sample code above.

 

If that worked, do the same thing but using CONT1, then CONT2.

 

dZ.

Link to comment
Share on other sites

One thing I noticed: CONT1 and CONT2 issue sequences like this:

.

    MVI 511,R0
    COMR R0
    MVO R0,V1

.

If you assign to a 16-bit variable rather than an 8-bit variable, this will cause a problem for the comparisons that come later. The demo in contrib should work, as it assigns to an 8-bit variable. But, it wasn't obvious that the side-effect of assigning to an 8-bit variable is necessary to make CONT1 work like CONT does.

 

Likewise if you say something like:

.

   IF CONT1 = $FF THEN ....

.

That will also have problems, as that generates:

.

    MVI 511,R0
    COMR R0
    CMPI #255,R0
    BNE T1
;...

.

Again, that's because COMR inverts all 16 bits. In contrast, CONT generates:

.

    MVI 510,R0
    XOR 511,R0
    CMPI #255,R0
    BNE T2

.

That will work as expected, as it only (effectively) inverts the lower 8 bits.

 

The constants in constants.bas assume only the lower 8 bits have been inverted, not all 16 bits like COMR does.

 

Instead of CONT1 and CONT2, you could use these macros that only invert the lower 8 bits:

.

DEF FN LeftHand = (PEEK($1FF) XOR $FF)
DEF FN RightHand = (PEEK($1FE) XOR $FF)

.

The parentheses appear to be necessary as it seems IntyBASIC just textually substitutes the macro in, and you want to be sure the XOR gets applied as expected. Without them, you get something like "IF PEEK($1FF) XOR $FF = $FF", which ends up comparing $FF to $FF and then XORing that result with the PEEK's result.

 

Another alternative that generates less code would be to create inverted versions of the constants, and compare those directly against the values PEEK'd from the PSG.

.

CONST I_KEYPAD_0              = $FF XOR 72
CONST I_KEYPAD_1              = $FF XOR 129
CONST I_KEYPAD_2              = $FF XOR 65
CONST I_KEYPAD_3              = $FF XOR 33
CONST I_KEYPAD_4              = $FF XOR 130
CONST I_KEYPAD_5              = $FF XOR 66
CONST I_KEYPAD_6              = $FF XOR 34
CONST I_KEYPAD_7              = $FF XOR 132
CONST I_KEYPAD_8              = $FF XOR 68
CONST I_KEYPAD_9              = $FF XOR 36
CONST I_KEYPAD_CLEAR          = $FF XOR 136
CONST I_KEYPAD_ENTER          = $FF XOR 40

DEF FN I_LeftHand  = PEEK($1FF)
DEF FN I_RightHand = PEEK($1FE)

IF I_LeftHand = I_KEYPAD_0 THEN ...

.

The I_ prefix means "inverted as compared to what CONTxx return". A quick pass through the compiler suggests this actually generates pretty decent code.

  • Like 1
Link to comment
Share on other sites

One thing I noticed: CONT1 and CONT2 issue sequences like this:

.

    MVI 511,R0
    COMR R0
    MVO R0,V1
.

If you assign to a 16-bit variable rather than an 8-bit variable, this will cause a problem for the comparisons that come later. The demo in contrib should work, as it assigns to an 8-bit variable. But, it wasn't obvious that the side-effect of assigning to an 8-bit variable is necessary to make CONT1 work like CONT does.

 

 

Ok, so either IntyBASIC should explicitly state the constraint in the manual (lame), or use "XOR $FF" to effectively invert only the lower byte of any size variable (good), right?

 

That sounds reasonable. The change to the Constants.bas is also very reasonable, we should do that.

 

Likewise if you say something like:

.

   IF CONT1 = $FF THEN ....
.

That will also have problems, as that generates:

.

    MVI 511,R0
    COMR R0
    CMPI #255,R0
    BNE T1
;...

 

 

Wait... IntyBASIC reads the I/O ports, stores the raw data and then proceeds to complement it every time it is accessed? That doesn't sound right. Why not invert it once, on read, and then let the program have access to the inverted raw data directly? That way, there's no concern whether it gets assigned to an 8-bit or 16-bit variable later on, since it will essentially be the correct effective 8-bit signal every time.

 

Or am I missing something?

 

(Note those not familiar with the I/O port technical details: the signal is "active-low," meaning that it sends "1"s all the time until a switch is pressed, then it sends "0" for it. In other words, an active signal is a "0" and an inactive signal is a "1". This is why we complement the input because it's sort of bizarre that way, and we are all more comfortable assuming that a "1" means something is set. :))

 

 

One more thing, this entire "CONT" and "CONT1/CONT2" seems to me like a leaky abstraction. I see in the manual that there are actual decoded values available, such as "CONT.Up," "CONT.B0," "CONT.Key," etc., and then there's the "CONT" itself for the "raw" value.

 

I suppose the latter allows the programmer to do his own decoding of the signal in which ever way he wishes; but then, why the artifice of polling the port and storing it in memory? Why not make it a pass-through read from the I/O port itself?

 

It seems that all it offers is signal debouncing, which may be helpful, but not necessary in every case if -- as I suppose is expected -- the programmer knows what he is doing and wants to handle the signal decoding himself.

 

In the sample code I've seen posted in this forum, I see many programmers mostly using the "CONT" directive and then struggling to decode the signals when perhaps they should just let IntyBASIC decode it for them (it does already) in a perhaps more efficient and optimized manner, and be done with it. It seems to me that the distinction between plain "CONT" and, say, "CONT.Key," etc., is not very clear and that the trade-offs and expectations in potential opposition are not well understood by everyone.

 

Personally, I would just use the built-in decoder unless I actually needed a custom handler for a specific use case. (For example, in Christmas Carol, I don't bother with de-bouncing the signal because the grid-like nature of movements tend to quantize the input in an acceptable way; plus I need to handle some special heuristics for diagonals, and I poll at a faster rate than 60 Hz, etc.)

 

It is very possible that I am missing something obvious to everyone else and that I am completely off-base here, but decoding the signal on every single game (in mostly the same way, and re-using mostly the same code), seems strange to me, and suggests that people are not really grasping the complexity (and versatility) of IntyBASIC's model.

 

-dZ.

Edited by DZ-Jay
Link to comment
Share on other sites

For sake of sanity I think I'll implement the XORI #$FF,R0 instruction instead of COMR R0, even if it takes one word extra.

 

When I worked this, my "ultra-optimizer brain" thought everyone just would need to save the CONT state into a 8-bit variable and work from there.

 

But now given the more widely distribution it looks like non-sense.

  • Like 1
Link to comment
Share on other sites

 

Wait... IntyBASIC reads the I/O ports, stores the raw data and then proceeds to complement it every time it is accessed? That doesn't sound right. Why not invert it once, on read, and then let the program have access to the inverted raw data directly?

 

I'm personally a fan of working with the raw, active-low data from the I/O port myself. The active-high representation comes from the cute trick (that I also used in 4-Tris) for merging the I/O ports:

.

    MVI 510, R0
    XOR 511, R0

.

Of course, you could make CONT, CONT1, CONT2, etc. consistently active-low by having CONT simply do:

.

    MVI 510, R0
    AND 511, R0

.

The counter-argument arises if you're using a single disc as an 8-direction input with bit-tests. If you want to say something like IF CONT1.up THEN foo, the result needs to ultimately be "active high".

 

 

 

That way, there's no concern whether it gets assigned to an 8-bit or 16-bit variable later on, since it will essentially be the correct effective 8-bit signal every time.

 

Or am I missing something?

 

...

 

One more thing, this entire "CONT" and "CONT1/CONT2" seems to me like a leaky abstraction. I see in the manual that there are actual decoded values available, such as "CONT.Up," "CONT.B0," "CONT.Key," etc., and then there's the "CONT" itself for the "raw" value.

 

Adding to the bucket leakiness: CONT.key, CONT1.key, etc. are decoded once per frame. All other uses of CONT are decoded at the point of reference. Witness:

.

    X = CONT.up
    X = CONT.b0
    X = CONT.key

    X = CONT1.up
    X = CONT1.b0
    X = CONT1.key

    X = CONT
    X = CONT1

.

This generates:

.

    ;FILE cont.bas
    ;[1]     X = CONT.up
    SRCFILE "cont.bas",1
    MVI 510,R0
    XOR 511,R0
    ANDI #4,R0
    MVO R0,V1
    ;[2]     X = CONT.b0
    SRCFILE "cont.bas",2
    MVI 510,R0
    XOR 511,R0
    ANDI #224,R0
    CMPI #160,R0
    MVII #-1,R0
    BEQ $+3
    INCR R0
    MVO R0,V1
    ;[3]     X = CONT.key
    SRCFILE "cont.bas",3
    MVI _cnt1_key,R0
    CMPI #12,R0
    BNE $+4
    MVI _cnt2_key,R0
    MVO R0,V1
    ;[4] 
    SRCFILE "cont.bas",4
    ;[5]     X = CONT1.up
    SRCFILE "cont.bas",5
    MVI 511,R0
    COMR R0
    ANDI #4,R0
    MVO R0,V1
    ;[6]     X = CONT1.b0
    SRCFILE "cont.bas",6
    MVI 511,R0
    COMR R0
    ANDI #224,R0
    CMPI #160,R0
    MVII #-1,R0
    BEQ $+3
    INCR R0
    MVO R0,V1
    ;[7]     X = CONT1.key
    SRCFILE "cont.bas",7
    MVI _cnt1_key,R0
    MVO R0,V1
    ;[9]     X = CONT
    SRCFILE "cont.bas",9
    MVI 510,R0
    XOR 511,R0
    MVO R0,V1
    ;[10]     X = CONT1
    SRCFILE "cont.bas",10
    MVI 511,R0
    COMR R0
    MVO R0,V1

.

 

So, you have a mix of debounced and non-debounced for CONT.foo as well, and an implicit priority between CONT1 and CONT2 for CONT.key.

  • Like 1
Link to comment
Share on other sites

 

I'm personally a fan of working with the raw, active-low data from the I/O port myself. The active-high representation comes from the cute trick (that I also used in 4-Tris) for merging the I/O ports:

.

    MVI 510, R0
    XOR 511, R0
.

Well, that wasn't what I was actually referring to. Your example from before seems to suggest that the COMR was always happening, whether the programmer wanted it or not.

 

It seemed redundant to me to store it raw when every access requires the additional effort of the same transformation.

 

Yes, the raw active-low value is useful indeed by itself. I just thought from your example that CONT didn't offer it.

Link to comment
Share on other sites

Well, that wasn't what I was actually referring to. Your example from before seems to suggest that the COMR was always happening, whether the programmer wanted it or not.

 

It seemed redundant to me to store it raw when every access requires the additional effort of the same transformation.

 

Yes, the raw active-low value is useful indeed by itself. I just thought from your example that CONT didn't offer it.

 

What I was saying is that CONT merges the two controllers with XOR, which naturally gives an inversion, resulting in an active-high value. CONT1/CONT2 end up using COMR in a (failed) attempt to match the semantics of CONT.

Edited by intvnut
Link to comment
Share on other sites

Personally, I think IntyBASIC would do well to offer three separate and distinct models for hand-controller handling:

 

1. A low-level model, essentially a synonym to PEEK(<port address>), where the programmer can do whatever he wants with the raw input. No hand-holding, no abstractions; just raw, bare-to-the-metal access to the input signal.

 

2. A slightly higher level model, similar to what CONT tries to do today, where the signal is debounced, conditioned, quantized, and complemented to provide essentially stable "signal codes" which the programmer can decode himself. He still needs to understand how the individual 8-bits behave and relate to each other, and how to distinguish ambiguities when switching from one state to another.

 

3. A full-fledged, generalized signal decoding routine, where the conditioned signal from #2 is furthered processed to transform it into usable "key codes" (e.g., disc directions, keypad button, etc.) which the programmer can use directly in his program without having to know anything about the underlying machinery. This is similar to what CONT1/2 try to do.

 

Ideally, this third one would be an "event-driven" model, where a user-provided subroutine is called whenever a usable input is detected, or a change in input occurs.

 

It could also be provided as an ancillary library routine.

 

The difference of the above from today's API is that I think all three forms should be explicitly documented, and be clearly distinct for their particular use cases, in order to avoid confusion. The third one in particular would take care of 99% of the cases to date.

 

Like I said in my earlier post, I see much confusion from programmers trying to decode the raw signal, re-using the same copy-pasted code like some cargo cult, and not really understanding the implications of the mechanisms. The result is not only hard-to-troubleshoot code, but also flakey input handling that sometimes has an impact on playability.

 

I'd be willing to share and adapt my own code if warranted (to the extent that I can). :)

 

dZ.

Link to comment
Share on other sites

2. A slightly higher level model, similar to what CONT tries to do today, where the signal is debounced, conditioned, quantized, and complemented to provide essentially stable "signal codes" which the programmer can decode himself. He still needs to understand how the individual 8-bits behave and relate to each other, and how to distinguish ambiguities when switching from one state to another.

 

 

 

It's probably best to point out you mean CONT.key specifically here, as all the other variants of CONT are raw accesses with no debouncing. There's just some very basic open-coded decoding to extract a button or disc direction if you has for CONT.b0 or CONT.up, for example.

 

I agree the current model leaves something to be desired. I'd probably end up building something around PEEK() myself, such as the DEF FN macros I provided above.

 

IntyBASIC in general doesn't seem very well set up for an event driven model, which seems like a missed opportunity to me. Its support for assembly, though, should make it more doable, in particular of VARPTR can yield the address of a PROCEDURE. (I haven't tried.)

Link to comment
Share on other sites

 

 

It's probably best to point out you mean CONT.key specifically here, as all the other variants of CONT are raw accesses with no debouncing. There's just some very basic open-coded decoding to extract a button or disc direction if you has for CONT.b0 or CONT.up, for example.

 

Ah, OK. I haven't verified it myself. I assumed that CONT was "raw" and that CONTx.??? were actually debounced and decoded.

 

Thanks for the clarification.

 

I agree the current model leaves something to be desired. I'd probably end up building something around PEEK() myself, such as the DEF FN macros I provided above.

 

IntyBASIC in general doesn't seem very well set up for an event driven model, which seems like a missed opportunity to me. Its support for assembly, though, should make it more doable, in particular of VARPTR can yield the address of a PROCEDURE. (I haven't tried.)

Well, I think it could offer something similar to the "ON FRAME..." directive. Perhaps "ON CONTx ..."

 

In any case, I agree that interfacing with Assembly should be satisfactory. I just think that it is such a common function and such an obvious source of confusion and issues for many, that it warrants a built-in support function, like SCANHAND, with a generalized, all-purpose decoder. In my opinion 99% of IntyBASIC programs to date could employ that to better effect. :)

 

dZ.

 

 

P.S. Stupid auto-correct keeps changing "debounce" to "denounce." Blah.

Link to comment
Share on other sites

Well, I think it could offer something similar to the "ON FRAME..." directive. Perhaps "ON CONTx ..."

 

ON CONTx GOSUB seems like a good concept, but the question is where you put that dispatch. The natural place to want it is in an interrupt handler, which basically just makes it part of whatever ON FRAME GOSUB routine you already have. Also, like ON FRAME GOSUB, it introduces new concurrency issues that would lead to lots of different kinds of programming headaches.

 

I suppose if your game comes down to a main "event loop" instead, it could do the decode and ON-GOSUB from within there. You can build that today within the existing IntyBASIC. A more general model would have an event queue (something both you and I are rather familiar with), and that would want either something like function pointers, or would require assigning all the dispatches "dispatch numbers" that feed into a giant "ON event GOSUB ...." statement.

 

Something like:

.

CONST EV_Q_SZ = 16
DIM EV_Q_NUM(EV_Q_SZ)
DIM #EV_Q_ARG(EV_Q_SZ)
EV_Q_RD = 0
EV_Q_WR = 0
EV_BUSY = 0


'...

event_loop:
    EV_BUSY = 0
    WAIT
    EV_BUSY = 1
    WHILE EV_Q_RD <> EV_Q_WR
        EV_NUM  =  EV_Q_NUM(EV_Q_RD % EV_Q_SZ)
        #EV_ARG = #EV_Q_ARG(EV_Q_RD % EV_Q_SZ)
        EV_Q_RD = EV_Q_RD + 1
        ON EV_NUM GOSUB \
            ' list of procedures to dispatch to based on EV_NUM
    WEND
    ' Do fixed per-frame stuff here
    GOTO event_loop

'...

ON FRAME GOSUB FrameEvent

FrameEvent PROCEDURE
           IF EV_BUSY THEN RETURN
           IF ((EV_Q_WR - EV_Q_RD) % (2*EV_Q_SZ)) >= EV_Q_SZ THEN RETURN
 
           ' decode controllers here
           ' count down timers here
           ' queue up other per-frame events that make sense.
           
           END


' ... and of course, other code could queue up events as it discovers them.
' Put event number in EV_NUM, and optional argument into #EV_ARG.
QueueEvent PROCEDURE
           ASM DIS
           IF ((EV_Q_WR - EV_Q_RD) % (2*EV_Q_SZ)) < EV_Q_SZ THEN
               EV_Q_NUM(EV_Q_WR) = EV_NUM
               #EV_Q_ARG(EV_Q_WR) = #EV_ARG
               EV_Q_WR = EV_Q_WR + 1
           END IF
           ASM EIS
           END

.

That's just a high-level sketch, naturally. Completely untested.

  • Like 1
Link to comment
Share on other sites

 

ON CONTx GOSUB seems like a good concept, but the question is where you put that dispatch. The natural place to want it is in an interrupt handler, which basically just makes it part of whatever ON FRAME GOSUB routine you already have. Also, like ON FRAME GOSUB, it introduces new concurrency issues that would lead to lots of different kinds of programming headaches.

 

I suppose if your game comes down to a main "event loop" instead, it could do the decode and ON-GOSUB from within there. You can build that today within the existing IntyBASIC. A more general model would have an event queue (something both you and I are rather familiar with), and that would want either something like function pointers, or would require assigning all the dispatches "dispatch numbers" that feed into a giant "ON event GOSUB ...." statement.

 

Something like:

.

CONST EV_Q_SZ = 16
DIM EV_Q_NUM(EV_Q_SZ)
DIM #EV_Q_ARG(EV_Q_SZ)
EV_Q_RD = 0
EV_Q_WR = 0
EV_BUSY = 0


'...

event_loop:
    EV_BUSY = 0
    WAIT
    EV_BUSY = 1
    WHILE EV_Q_RD <> EV_Q_WR
        EV_NUM  =  EV_Q_NUM(EV_Q_RD % EV_Q_SZ)
        #EV_ARG = #EV_Q_ARG(EV_Q_RD % EV_Q_SZ)
        EV_Q_RD = EV_Q_RD + 1
        ON EV_NUM GOSUB \
            ' list of procedures to dispatch to based on EV_NUM
    WEND
    ' Do fixed per-frame stuff here
    GOTO event_loop

'...

ON FRAME GOSUB FrameEvent

FrameEvent PROCEDURE
           IF EV_BUSY THEN RETURN
           IF ((EV_Q_WR - EV_Q_RD) % (2*EV_Q_SZ)) >= EV_Q_SZ THEN RETURN
 
           ' decode controllers here
           ' count down timers here
           ' queue up other per-frame events that make sense.
           
           END


' ... and of course, other code could queue up events as it discovers them.
' Put event number in EV_NUM, and optional argument into #EV_ARG.
QueueEvent PROCEDURE
           ASM DIS
           IF ((EV_Q_WR - EV_Q_RD) % (2*EV_Q_SZ)) < EV_Q_SZ THEN
               EV_Q_NUM(EV_Q_WR) = EV_NUM
               #EV_Q_ARG(EV_Q_WR) = #EV_ARG
               EV_Q_WR = EV_Q_WR + 1
           END IF
           ASM EIS
           END

.

That's just a high-level sketch, naturally. Completely untested.

 

I was thinking more like an "ON CONT Gosub ..." as just a dispatch from the ISR to whatever sub-routine is specified. The "ON CONT Gosub" would just be a directive, (just like "ON FRAME..."), so it doesn't matter where it appears in the code, the last one found sets the event handler.

 

An event queue for IntyBASIC would be a great thing, so that it could support generalized timers and other event-driven features. However, I think that goes a wee-bit out of the way that IntyBASIC works today, and perhaps could be an add-on library. The kernel could provide some special hooks for it, though.

 

-dZ.

Link to comment
Share on other sites

 

I was thinking more like an "ON CONT Gosub ..." as just a dispatch from the ISR to whatever sub-routine is specified.

 

That was my initial thought as well; however, if there are any shared variables between what gets called there and the rest of the program, wackiness ensues. That's why I was saying it would be better to keep all the processing outside interrupt context.

 

Imagine this scenario: A bit of foreground code doing something innocuous, such as X = X + 1. The ON CONT handler called from interrupt context just reinitializes with X = 42. Sometimes, though, it just doesn't work: The update gets lost. Adding and removing an unrelated PRINT statement makes it come and go. Or, it works on NTSC but not on PAL.

 

Those sorts of concurrency races are no fun to debug.

 

For the X = X + 1 case above, I'm imagining something like this:

.

    ;FILE x.bas
    ;[1] X = X + 1
    SRCFILE "x.bas",1
    MVI V1,R0
    INCR R0             ; <<== Interrupt here; update to X from ON CONT handler in interrupt gets lost.
    MVO R0,V1

.

If the X = 42 happens during the interrupt handler, that update will get lost when you return from the interrupt handler.

 

If the goal is to simplify the programming model, adding concurrency issues of this flavor feels like the wrong direction. :-)

 

AFAICT, the ON FRAME GOSUB feature already needs to be carefully used for similar reasons.

 

That's why I was suggesting that a dispatch model makes sense for CONT, but not from interrupt context. Whether or not you drag in an event queue is a different question. In my event queue example, I tried to carefully ensure the same variable isn't updated by both interrupt and non-interrupt context except at the RD and WR pointers of the queue where ownership is clear.

  • Like 1
Link to comment
Share on other sites

An event queue for IntyBASIC would be a great thing, so that it could support generalized timers and other event-driven features. However, I think that goes a wee-bit out of the way that IntyBASIC works today, and perhaps could be an add-on library. The kernel could provide some special hooks for it, though.

 

Also, I don't think you need to add anything to IntyBASIC to support an event queue; my example code is all stock IntyBASIC. You could library-ize it pretty easily.

  • Like 1
Link to comment
Share on other sites

 

That was my initial thought as well; however, if there are any shared variables between what gets called there and the rest of the program, wackiness ensues. That's why I was saying it would be better to keep all the processing outside interrupt context.

 

Imagine this scenario: A bit of foreground code doing something innocuous, such as X = X + 1. The ON CONT handler called from interrupt context just reinitializes with X = 42. Sometimes, though, it just doesn't work: The update gets lost. Adding and removing an unrelated PRINT statement makes it come and go. Or, it works on NTSC but not on PAL.

 

Those sorts of concurrency races are no fun to debug.

 

For the X = X + 1 case above, I'm imagining something like this:

.

    ;FILE x.bas
    ;[1] X = X + 1
    SRCFILE "x.bas",1
    MVI V1,R0
    INCR R0             ; <<== Interrupt here; update to X from ON CONT handler in interrupt gets lost.
    MVO R0,V1

.

If the X = 42 happens during the interrupt handler, that update will get lost when you return from the interrupt handler.

 

If the goal is to simplify the programming model, adding concurrency issues of this flavor feels like the wrong direction. :-)

 

AFAICT, the ON FRAME GOSUB feature already needs to be carefully used for similar reasons.

 

That's why I was suggesting that a dispatch model makes sense for CONT, but not from interrupt context. Whether or not you drag in an event queue is a different question. In my event queue example, I tried to carefully ensure the same variable isn't updated by both interrupt and non-interrupt context except at the RD and WR pointers of the queue where ownership is clear.

 

That's true, because using an event-driven model for a single function while still treating the rest of the program procedurally, leads down that black hole. Yeah, that wouldn't help if we're trying to simplify the programming model. It was just a random thought. :dunce:

 

I just think there should be a way to deal with controller events without having to poll/test yourself for them, since the kernel is already doing that. Perhaps it's a matter of exposing a flag that lets you know whether there was an I/O event in the last ISR, or some other elegant way for hand-controller information to be integrated into game logic.

 

When I say "I/O events" I mean "press" and "release" of any of the input surfaces, since having to keep track of state so that you don't "double-book" them is additional incidental complexity on the program and a burden that the framework should take itself. If you have to do something like:

If (CONT <> PrevCont) Then
  ' process new input
End If

then it's just more boilerplate.

 

Anyway, I agree that a task queue is a natural solution for this -- but then I treat my games as event-driven state machines where the task queue serializes the handling of events to avoid concurrency problems. That's one way of doing games, I don't know if it's appropriate for every one (since it's definitely a different programming model from the one most IntyBASIC programs follow).

 

That's why I think including it in a library would be great. I know it could be implemented purely in IntyBASIC (as you just did above), but I thought it could be better optimized by writing it in Assembly Language. I also mentioned "hooks" from the kernel in case there may be any special processing that could be integrated into the queue, such as some of its internally buffered functions. That way, you avoid having a queue to serialize game events and maintain state, and a separate mechanism for the kernel to do the same (e.g., P-Machinery uses two priority-sensitive queues with a single queue processing loop for both kernel and user events). That may or may not be necessary or even warranted. It was just a thought. ;)

 

Anyway, all this discussion, although highly stimulating, means little without the support of other folks and of nanochess himself. I would be happy to hear what others have to say, or if they have additional suggestions. I also would like to hear Oscar's thoughts on improving the I/O handling model baked into IntyBASIC.

 

-dZ.

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...