It is currently Fri Nov 15, 2019 10:20 am

All times are UTC - 7 hours





Post new topic Reply to topic  [ 51 posts ]  Go to page 1, 2, 3, 4  Next
Author Message
PostPosted: Fri May 17, 2019 8:56 am 
Online

Joined: Sun Sep 19, 2004 11:12 pm
Posts: 21690
Location: NE Indiana, USA (NTSC)
I was reading the manual of SGDK, a widely used software library for programming Sega Genesis homebrew, and the standard practice of reading the controllers in the vertical blanking interrupt handler and calling a button event listener from this handler surprised me. But in order to be prepared when I ask on SpritesMind as a new user why it's commonly done on Genesis, I'd like to have it articulated for the record why it's not commonly done on NES.

sys.c in SGDK has a standardized vblank interrupt handler whose NES counterpart would read as follows:
Code:
.zeropage
intTrace: .res 1  ; flags for what interrupts are being handled

.bss
user_vint_before_vram: .res 2
user_vint_after_vram: .res 2

.code
nmi_handler:
  pha
  txa
  pha
  tya
  pha

  ; Notify of entering interrupt context
  lda #IN_VINT
  ora intTrace
  sta intTrace

  ; Do vblank tasks
  jsr call_user_vint_before_vram
  jsr push_vram_updates
  jsr push_palette_updates
  jsr ppu_set_scroll
  jsr call_user_vint_after_vram
  jsr audio_update
  jsr joy_update

  ; Notify of leaving interrupt context
  lda #<~IN_VINT
  and intTrace
  sta intTrace
  pla
  tya
  pla
  txa
  pla
  rti

call_user_vint_before_vram:  jmp (user_vint_before_vram)
call_user_vint_after_vram:   jmp (user_vint_after_vram)


joy.c reads the controllers as part of the vblank handler using code which, glossing over support for controllers other than the standard controller, is analogous to this:
Code:
.zeropage
joy_state: .res 2

.bss
user_input_event_handler: .res 2

.code
joy_update:
  ldx #1  ; strobe both controllers
  stx $4016
  dex
  stx $4016

  jsr joy_update_port_x
  ldx #1
joy_update_port_x:
  ldy #$01
  buttonloop:
    lda $4016,x
    and #$03  ; nonzero if button on hardwired or plug-in controller is pressed
    cmp #$01  ; carry set iff pressed
    tya
    rol a     ; Save this bit
    tay
    bcc buttonloop  ; once initial $01 gets shifted to carry we're done
  tya
  eor joy_state,x
  sty joy_state,x
  bne no_buttons
  jmp (user_input_event_handler)
no_buttons:
  rts

; user_input_event_handler is called with
;   X: controller port (0 or 1)
;   Y: currently held buttons
;   A: buttons that have changed since last frame
; It is also called in interrupt context, which means behind
; the back of the game logic and with all the game logic's
; call stack on the stack.  A program might get confused
; in the case of a lag frame.


Why is a program structure like this, polling input in the NMI handler and calling a button state change event listener from the NMI handler, not commonly used on the NES? I know "lag frames" and "possibility of stack overflow" and the like, but explain it like I'm five because after I have coded for NES, Super NES, Game Boy, and Game Boy Advance, the Genesis will make five.

_________________
Pin Eight | Twitter | GitHub | Patreon


Top
 Profile  
 
PostPosted: Fri May 17, 2019 9:36 am 
Offline
User avatar

Joined: Sun Jan 22, 2012 12:03 pm
Posts: 7629
Location: Canada
I think regular polling via interrupt has one important use case: detecting a button press during "lag" frames.

If all your poll does is update the gamepad state variable, then it is useless. Unless you're specifically queuing an onset or release event that can be processed at the next opportunity, then there is no point in doing a poll that you're just going to discard.


In practice, on older systems this probably applies most to transition between rooms/screens where you don't normally do any input response for several frames. On modern systems, unpredictable multi-frame lag is commonplace even during regular gameplay, but event queues are also commonplace.

So I think the primary case here is: do you need to respond to a very transient button press and release (pair) that was made during some transition? If the answer is no, then you probably shouldn't be doing extra reads in NMI that you don't use. It can also open you up to threading issues with code that checks input state in various places (one more thing to be careful about).

I'd suggest that most NES games don't need this. "Normal" slowdown to only 30hz (or even 20hz) isn't really much of a use case for this. People just don't press and release buttons that fast. You need several consecutive frames without input response before it really matters.


Of course there's also your OAM DMA synchronization method for avoiding DPCM conflict, but if you know why and how you're doing this you don't need to refer to general rules, you need to think very critically about it. That is a special case technique, and certainly not for beginners.


Top
 Profile  
 
PostPosted: Fri May 17, 2019 9:48 am 
Offline

Joined: Sun May 03, 2015 8:19 pm
Posts: 100
Tecmo Super Bowl checks the joypad as part of the NMI unless the "task busy flag is set" indicating a task is in the process of being created/ended/suspended.

There is one mechanic in the game that requires this though. How fast you press the a button determines whether or not you throw or get tackled by another man player. It counts the number of A presses in a 64 frame window. Fast A tappers can hit 18-20 presses in a second.


Top
 Profile  
 
PostPosted: Fri May 17, 2019 9:49 am 
Offline

Joined: Thu Apr 18, 2019 9:13 am
Posts: 161
tepples wrote:
I was reading the manual of SGDK, a widely used software library for programming Sega Genesis homebrew, and the standard practice of reading the controllers in the vertical blanking interrupt handler and calling a button event listener from this handler surprised me. But in order to be prepared when I ask on SpritesMind as a new user why it's commonly done on Genesis, I'd like to have it articulated for the record why it's not commonly done on NES.


Ideally, the game would read the player control and process the player's movements as the last step before committing to the contents of the next video frame. Reading the controller in NMI, after everything about the upcoming frame has been determined, would add more controller lag than reading it later in the frame. The controller lag might be more consistent, however, which could be a good thing for some games. If one is using the DMC for raster-split effects, the controller should be read from within an IRQ to ensure reliable results, but one could use an IRQ which is rather far down the frame so as to make the controller lag be short as well as consistent.

Note that if the main-line code is responsible for acting upon controller inputs, and if there's any possibility that it might lag, one should not have the controller-scanning logic try to set a "new button push" flag for a frame and clear it afterward, since a new button push that occurs on a lag frame might get missed. Instead, it may be better to either have the main-line code decide whether a button push is new based upon how the button state compares with the last state it saw from the IRQ/NMI poll, or have the IRQ/NMI keep a count of how many button pushes it has seen and have the mainline keep a count of how many it has processed; the main line could then process a button push any time the counts don't match without ever missing any events unless the button gets pushed 256 times between polls.


Top
 Profile  
 
PostPosted: Fri May 17, 2019 10:11 am 
Offline
User avatar

Joined: Sun Jan 22, 2012 12:03 pm
Posts: 7629
Location: Canada
hackfresh wrote:
Fast A tappers can hit 18-20 presses in a second.

For most people the number is more like 10, or lower. Here's a famous video of a former mashing champion doing 13 (supposedly he did 16 in his prime). Here's a vibrator assisted attempt that actually gets 20.

Though, personally, I think it's immoral to ask a user to destroy their controller and/or hands by rewarding them for doing harshly repetitive actions, but sure, if this is what you want to encourage polling in NMI can make this lag-resistant. (Alternatively you could just ensure the mashy part of your game doesn't lag-- and if your whole game is mashy, then I hate you.)


If you're not rewarding a player more for demi-human mashing speeds, then it's unimportant whether you're capturing the mash at 60 or 30 or 20 or even 15hz... all of these are capable of detecting a normal "rapid" mashing perfectly well. (BTW, a lot of people, especially young children, will have trouble doing even 5 taps per second.)

Otherwise, the question is for situations like: if I quickly tap and release B during the fadeout between rooms, should I fire a bullet immediately when the next room fades in? ("No" is probably a perfectly reasonable answer, but it's really up to you.)


Top
 Profile  
 
PostPosted: Fri May 17, 2019 10:43 am 
Offline

Joined: Sun May 03, 2015 8:19 pm
Posts: 100
rainwarrior wrote:
For most people the number is more like 10, or lower. Here's a famous video of a former mashing champion doing 13 (supposedly he did 16 in his prime). Here's a vibrator assisted attempt that actually gets 20.

Though, personally, I think it's immoral to ask a user to destroy their controller and/or hands by rewarding them for doing harshly repetitive actions, but sure, if this is what you want to encourage polling in NMI can make this lag-resistant. (Alternatively you could just ensure the mashy part of your game doesn't lag-- and if your whole game is mashy, then I hate you.)



Agreed. I hate the mechanic myself especially the way the game is played competitively it it definitely skews things quite a bit to those who can tap fast. Also I should have said 18-20 only happens occasionally and its technically not in one second but 64 frames. The top guys avg ~ 13-16 per second.


Top
 Profile  
 
PostPosted: Fri May 17, 2019 11:21 am 
Offline
User avatar

Joined: Sun May 27, 2012 8:43 pm
Posts: 1348
Without getting into why games that require you to tap at inhuman speeds might have problems (there's no room for debate, that kind of design sucks) I will say that I read the controller inputs immediately at the start of vblank in my megadrive framework. I have not had problems with this design.


Top
 Profile  
 
PostPosted: Fri May 17, 2019 11:27 am 
Offline
User avatar

Joined: Sun Jan 22, 2012 12:03 pm
Posts: 7629
Location: Canada
tepples wrote:
But in order to be prepared when I ask on SpritesMind as a new user why it's commonly done on Genesis, I'd like to have it articulated for the record why it's not commonly done on NES.

Looking back toward the context of this original question...

Well, just look at joy.c. This probably works OK on Genesis, but would be ludicriously overcomplicated for an NES input handler. It's probably somewhat overcomplicated for Genesis, even, but it's not at all overcomplicated compared to a generic library like SDL.

What's appropriate for a very generic library on a (relatively) high powered platform is simply not appropriate in all cases. There are no one size fits all rules for this.

So... apples to oranges, IMO. Yes there's some overlap between what NES or Genesis might do, but SGDK is completely outside that overlap. SGDK looks OK to me for what it is, though.


For SGDK it's not just simply "polling in vblank" or not, there's a bunch of library functionality built specifically on top of polling in vblank (event callbacks, waiting on button presses and counting time held, etc.). The alternative isn't an option here, you can't just flick a switch, that'd be a big redesign. The choice was made, and a bunch of stuff was built upon it. Features you don't necessarily need, but are okay to have in this kind of library context.


I'm not trying to make an argument that polling the controller in vblank (on NES) is bad, it can work perfectly fine, but I do think it brings a layer of complexity (threading) to input that you don't need otherwise... so I really wouldn't recommend it as a default option (on NES).

(...and on a modern platform, it's a very standard thing to do, but like I said so are event queues and library APIs and other related things that are totally out of scope for NES.)


Edit: clarify where I'm talking about NES specifically.


Last edited by rainwarrior on Fri May 17, 2019 4:11 pm, edited 2 times in total.

Top
 Profile  
 
PostPosted: Fri May 17, 2019 11:49 am 
Online

Joined: Sun Sep 19, 2004 11:12 pm
Posts: 21690
Location: NE Indiana, USA (NTSC)
rainwarrior wrote:
For SGDK it's not just simply "polling in vblank" or not, there's a bunch of library functionality built specifically on top of polling in vblank (event callbacks, waiting on button presses and counting time held, etc.). The alternative isn't an option here, you can't just flick a switch, that'd be a big redesign. The choice was made, and a bunch of stuff was built upon it. Features you don't necessarily need, but are okay to have in this kind of library context.

And which get linked into the executable, occupying ROM and RAM, even if unused.

rainwarrior wrote:
(...and on a modern platform, it's a very standard thing to do, but like I said so are event queues and library APIs and other related things that are totally out of scope for NES.)

Any platform that uses optical discs or a multitasking OS probably provides library support for a thread-safe queue data structure, which makes this sort of event-based paradigm practical in the first place.

_________________
Pin Eight | Twitter | GitHub | Patreon


Top
 Profile  
 
PostPosted: Fri May 17, 2019 11:50 am 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 11434
Location: Rio de Janeiro - Brazil
One reason not to read controllers in the NMI handler is the consistency of controller state within the same logic frame. If a lag frame happens, and an NMI fires in the middle of that logic frame, it's possible that different controller states will be readable in that same logic frame. Of course you could circumvent that by not accessing the raw controller state byte directly from the game logic loop, instead making a copy of it when each logic frame begins and use that, but what good would that do if states would still be dropped during lag frames?

I guess that reading controllers in the NMI would only make sense if you OR'ed the new state with the previous one so that button presses would accumulate during lag frames until the game logic was ready to copy that accumulated state for consumption, clearing the buffer to receive new button presses. This would ensure that presses no shorter than 1/60th were always detected and handled, but you'd still have problems with quick tapping.

mikejmoffitt wrote:
I read the controller inputs immediately at the start of vblank in my megadrive framework. I have not had problems with this design.

I don't know about the Mega Drive, but on the NES, where vblank time is at a premium, wasting part of it on a task that could literally be performed at any other time, would be completely nonsensical.


Top
 Profile  
 
PostPosted: Fri May 17, 2019 12:09 pm 
Online

Joined: Sun Sep 19, 2004 11:12 pm
Posts: 21690
Location: NE Indiana, USA (NTSC)
tokumaru wrote:
consistency of controller state within the same logic frame.

Thank you. This paragraph is the sort of reply I was looking for.

tokumaru wrote:
I guess that reading controllers in the NMI would only make sense if you OR'ed the new state with the previous one so that button presses would accumulate during lag frames until the game logic was ready to copy that accumulated state for consumption, clearing the buffer to receive new button presses.

Which leads me to the bigger problem that I found with SGDK's approach: how to safely "copy that accumulated state for consumption". Normally I guess the event handler would do something like this:
Code:
input_callback:
  pha  ; 1 bits correspond to buttons whose state changed
  and joy_state,x  ; 1 bits correspond to buttons just pressed
  ora pressed_keys,x
  sta pressed_keys,x
  pla
  eor #$FF  ; 0 bits correspond to buttons whose state changed
  ora joy_state,x  ; 0 bits correspond to buttons just released
  eor #$FF
  ora released_keys,x
  sta released_keys,x
  rts


Then the main thread would do this:
Code:
  ldy #0
  lda pressed_keys,x
  sty pressed_keys,x


But consider what happens in a lag frame when an NMI occurs at exactly the wrong place. The main thread would do this:
Code:
  ldy #0
  lda pressed_keys,x
  <NMI>
  sty pressed_keys,x


If the input callback ran during NMI and added key press or release events, those events just got clobbered.

rainwarrior wrote:
So... apples to oranges, IMO.

Would it be likewise "apples to oranges" between the NES and Super NES?

_________________
Pin Eight | Twitter | GitHub | Patreon


Top
 Profile  
 
PostPosted: Fri May 17, 2019 12:28 pm 
Offline
User avatar

Joined: Sun May 27, 2012 8:43 pm
Posts: 1348
tokumaru wrote:
mikejmoffitt wrote:
I read the controller inputs immediately at the start of vblank in my megadrive framework. I have not had problems with this design.

I don't know about the Mega Drive, but on the NES, where vblank time is at a premium, wasting part of it on a task that could literally be performed at any other time, would be completely nonsensical.


The big difference here is just how much time is allotted during the vblank in terms of CPU cycles. Input reading is something that is so quick that it makes sense to square it away without a lot of thought. However, my response was just pertaining to the Megadrive, as tepples asked about a detail in SGDK. I am fairly against the idea of calling an input handler, though, and prefer to allow game logic to read the last-good input state.


Top
 Profile  
 
PostPosted: Fri May 17, 2019 12:33 pm 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 11434
Location: Rio de Janeiro - Brazil
tepples wrote:
But consider what happens in a lag frame when an NMI occurs at exactly the wrong place. The main thread would do this:
Code:
  ldy #0
  lda pressed_keys,x
  <NMI>
  sty pressed_keys,x

Yes, that'd be a problem. Thankfully, at least on the NES, that copy would almost certainly happen at the very start of the logic frame, which takes place right after the previous NMI, so there'd be absolutely no chance of the pictured situation ever happening. That's just something the programmer needs to be conscious about, I guess.


Top
 Profile  
 
PostPosted: Fri May 17, 2019 2:41 pm 
Offline
User avatar

Joined: Fri Nov 19, 2004 7:35 pm
Posts: 4231
If you read controller in NMI, your controller data can change in the middle of a frame. You don't want race conditions.

_________________
Here come the fortune cookies! Here come the fortune cookies! They're wearing paper hats!


Top
 Profile  
 
PostPosted: Fri May 17, 2019 3:23 pm 
Offline
User avatar

Joined: Sun Sep 19, 2004 9:28 pm
Posts: 4213
Location: A world gone mad
I imagine there are fighting games in the 90s era that quite possibly poll the joypad once in NMI and later somewhere in the main loop, or some similar model. Games that involve complication combo moves would fall under this category I'd think.


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 51 posts ]  Go to page 1, 2, 3, 4  Next

All times are UTC - 7 hours


Who is online

Users browsing this forum: No registered users and 11 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB® Forum Software © phpBB Group