INKEY$(): Feature or Bug?

Previous topic - Next topic

CW

The following is more of a feature request to improve Inkey$(), than a code-killing bug. There is a way to program around the issue, but it seems like such an ugly kludge. Could Inkey$() function as smoothly as Key()?
-CW
====================

Code (glbasic) Select

// Inkey$(): Feature or Bug?
//--------------------------
// First run using Test 1 code section
// Then turn it to commented lines,
// activate Test 2 code section and run again.
// Feature or Bug?
//---------------------------

SETCURRENTDIR(GETCURRENTDIR$()+"Media")
GLOBAL fontkey% = GENFONT()
LOADFONT "smalfont.png",fontkey


////////////////////////////////////////////////
//       Setup Static Background Screen                     
PRINT "GetKey$() Test",20,50
PRINT "Press [Backspace] key to quit.",20,75
USEASBMP;SHOWSCREEN
/////////////////////////////////////////////////
/////////////////////////////////////////////////

LOCAL K$ = ""
REPEAT

///////////////////////////
// Test 1: Refresh the screen only when a character is pressed, otherwise, continue to show the current screen, which displays the last key pressed.
//
// This test will fail for INKEY$() is never reset and so never checks the keyboard again.
// ------------------------
//

K$ = INKEY$()
IF K$ <> ""
PRINT "Key Press = "+K$,150,120
SHOWSCREEN
ENDIF


//
///////////////////////////////
// Test 2: Refresh the screen every loop. This seems to impose a huge overhead on using INKEY$() when the screen must be redrawn each and every
// time we check for a keypress. Shouldn't there be a way to reset INKEY$() directly, without calling SHOWSCREEN? Maybe something like K$=INKEY$(-1) ?
//
// This test will succeed, as INKEY$() is reset when SHOWSCREEN is called, and so is set to check the keyboard again the next time it comes around.
// ----------------------------
//

// K$ = INKEY$();SHOWSCREEN
// IF K$ <> ""
// PRINT "Key Press = "+K$,150,120
// ENDIF


//
////////////////////////////////

UNTIL KEY(14) // Exit when Del key is pressed.

Moru

You always have to do showscreen. I'm affraid you can't just leave the screen on and expect everything to work. Showscreen does a lot of things besides just switching to the back-buffer.

CW

#2
I see. But Moru, this seems like such a serious and puzzling bottleneck to execution speed.

With INKEY$() wedded (or welded) to SHOWSCREEN, the ability to execute code is limited to the maximum FPS because a program can only cycle code as fast as it can perpetually refresh the screen. Is there a good technical reason why this should be so? Could Inkey$() be revised to run independently of the screen, in the way Key() does?  Consider the following example which uses Key(). Notice how the screen is only updated when there is something to be updated, unlike INKEY$() which must update the screen every time the keyboard is checked.

Code (glbasic) Select
SETCURRENTDIR(GETCURRENTDIR$()+"Media")
GLOBAL fontkey% = GENFONT()
LOADFONT "smalfont.png",fontkey

////////////////////////////////////////////////
//       Setup Static Background Screen                     
PRINT "GetKey$() Test",20,50
PRINT "Press [Backspace] key to quit.",20,75
USEASBMP;SHOWSCREEN
/////////////////////////////////////////////////
/////////////////////////////////////////////////

REPEAT
LOCAL K%
FOR K = 0 TO 255
IF KEY(K) = TRUE THEN BREAK
NEXT
        IF K < 255
                PRINT "Key Press Code "+K+" Detected."50,120
                SHOWSCREEN
        ENDIF

UNTIL KEY(14) // Exit when [BackSpace] key is pressed.
/////////////////////////////////////////////////


Cheers!
-CW

kanonet

Can you use SHOWSCREEN in a separate thread? As far as I know everything graphic (and sound too?) related needs to be in main thread.
Lenovo Thinkpad T430u: Intel i5-3317U, 8GB DDR3, NVidia GeForce 620M, Micron RealSSD C400 @Win7 x64

Moru

KEY() tests against all keys at once, inkey$ inputs one character from the keyboard buffer. If you don't want to redraw the screen every VBL you need to USEASBMP. SHOWSCREEN has to be called every VBL no matter what.
I recall there was some other way of not clearing the screen on each SHOWSCREEN but can't find it right now.

MrTAToad


CW

#6
Hi all!

I've been away for a bit. (US holiday and a nephew's graduation.)
Here is a bit of code I created to benchmark the Inkey$() vs. Key() commands. We know Key() is faster, but how much faster is it? This program will give you some idea. (You may want to create a 'smalfont.png' file with somewhat larger text for ease of reading. Save it to the Media folder. The program should scale fine.)

Code (glbasic) Select

SETSCREEN 1366,768,FALSE
SETCURRENTDIR(GETCURRENTDIR$()+"Media")
GLOBAL fontkey% = GENFONT()
LOADFONT "smalfont.png",fontkey
SETFONT fontkey
LOCAL fw%,fh%;GETFONTSIZE fw,fh

LOCAL Cycle_Count% = 0,Test2_Cycle_Count%=0,EndTime# = GETTIMERALL()+30000
LOCAL MaxChars = 30

PRINT "Test 1: ShowScreen called every cycle.",1,0*fh
PRINT "        Processing speed limited by FPS.",1,1*fh
PRINT "-------------------------------------",1,2*fh

PRINT "-------------------------------------",1,10*fh

DRAWRECT 65+4,3*fh-1,5+(MaxChars+1)*fw+7,fh+2,RGB(255,255,255)
DRAWRECT 65+5,3*fh,5+(MaxChars+1)*fw+5,fh,RGB(0,0,255)

USEASBMP

LOCAL K$,String$,hold%

REPEAT

K$ = INKEY$()
IF K$ <> ""
IF LEN(String$)<=MaxChars THEN String$ = String$+K$
PRINT String$,75,3*fh
USEASBMP
ENDIF

PRINT ShowFPS()+" FPS = Program Cycles Per Second." ,10,12*fh
PRINT "Type Up to "+MaxChars+" Characters of Text.",65,4*fh+5
PRINT "Test will run for 30 seconds.",50,6*fh
PRINT Cycle_Count + " cycles completed so far.",50,7*fh
        SHOWSCREEN; Cycle_Count = Cycle_Count+1
UNTIL KEY(211) OR GETTIMERALL()>EndTime// Exit if [DELETE] key is pressed.

CLEARSCREEN
PRINT "Cycles in 30 seconds = "+Cycle_Count,10,200
PRINT "Press Key to begin Test 2.",10,230
SHOWSCREEN;KEYWAIT

String$ = ""
LOCAL K%,Count%
EndTime = GETTIMERALL()+30000

CLEARSCREEN
PRINT "Test 2: Showscreen called only when screen update needed.",1,0*fh
PRINT "        Processing speed not limited by FPS.",1,1*fh
PRINT "-------------------------------------",1,2*fh

PRINT "-------------------------------------",1,10*fh

DRAWRECT 65+4,3*fh-1,5+(MaxChars+1)*fw+7,fh+2,RGB(255,255,255)
DRAWRECT 65+5,3*fh,5+(MaxChars+1)*fw+5,fh,RGB(0,0,255)
PRINT "Type Up to "+MaxChars+" Characters of Text.",65,4*fh+5
PRINT "Test will run for 30 seconds.",50,6*fh
USEASBMP

REPEAT
FOR K = 1 TO 255
IF KEY(K)=TRUE THEN BREAK
NEXT

IF K<255
K$ = INKEY$()
IF K$ <> ""
IF LEN(String$)<31 THEN String$ = String$+K$
PRINT String$,75,3*fh
USEASBMP
ENDIF

PRINT ShowFPS()+" FPS, yet the program is cycling at max speed." ,10,12*fh
PRINT Test2_Cycle_Count + " cycles completed so far.",50,7*fh
SHOWSCREEN
ENDIF
Test2_Cycle_Count = Test2_Cycle_Count+1
UNTIL KEY(211) OR GETTIMERALL()>EndTime// Exit if [DELETE] key is pressed.

CLEARSCREEN
PRINT "RESULTS",100,2*fh
PRINT "InKey$() allowed "+Cycle_Count+" cycles in 30 seconds.",10,5*fh
PRINT "    Key() allowed "+Test2_Cycle_Count+" cycles in 30 seconds.",10,6*fh
PRINT "Key() was "+(Test2_Cycle_Count / Cycle_Count)+" times faster than InKey$().",10,8*fh
PRINT "Click Mouse to Exit.",10,10*fh
SHOWSCREEN
MOUSEWAIT
END

// ------------------------------------------------------------- //
// ---  SHOWFPS  ---
// ------------------------------------------------------------- //
FUNCTION ShowFPS:
// FPS counter
LOCAL dtime#,fps#
STATIC delay#,FPS#

dtime = GETTIMER()
fps = ((1000/dtime)+fps)/2
delay=delay+dtime
IF delay>500 // 1/2 sec
  delay=0
  FPS=fps
ENDIF
RETURN FPS

ENDFUNCTION // SHOWFPS


If we could only unhitch Inkey$() from the necessity to call Showscreen each time, maybe by adding a Inkey$(-1) parameter, it could mean a real boost to program speeds when processing is done in the background while watching for input at the keyboard. With the tremendous overhead imposed by Showscreen, it may actually be quicker to duplicate all of the core features of Inkey$() with Key() using basic. (UGG!) 

Thank you all for your suggestions. Ocean, Kanonet, I don't know how to launch separate threads in GLbasic. Could you or someone do a tutorial on that topic? I would love to learn that ability. Mr. Toad, I tried CLEARSCREEN, but it doesn't seem to trigger a INKEY$() reset. Kanonet, I didn't know GLbasic used a keyboard buffer. If this is the hitch necessitating SHOWSCREEN with Inkey$(), then perhaps it begins to make some sense now. I don't know much about the inner workings of GLbasic, but I know my ignorance is a handicap. Still, if the command could be freed in some way to operate like Key(), it would be a powerful upgrade to the command and to the language version we both love.

I will try coding my input routines both ways to see which gives the bigger payoff. Given the overhead, maybe reinventing the wheel using K=key() really would yield a significant gain. If so, I will post the results to my other thread relating to this topic, rather than here under Bugs and Feature Suggestions. There is nothing more I can add to this thread, so I will leave off on it now.

Cheers!
-CW

Moru

Most keyboards are set to repeat a key less than once per VBL, why do we need to input faster than the screen actually can update or the keyboard can actually send characters to the OS? Since GLBasic is mainly geared against creating games, not word-processors I can't see a reason to optimize character input in favor of for example proper sound commands (impossible to code around in basic on all devices), proper depth-sorting of billboards in 3D and so on. I'm sure Gernot can add several hundreds of lines to this wish-list :-)

Besides, the keyboard controller is a very crappy cpu, it just can't send the data fast enough that is needed for a speed increase to do any good on our side :-)

Please still my curiosity, what is it you need this speed for?

CW

#8
Hi Moru.

I'm afraid I'm struggling to get my vision across. That is my fault. I'm sorry. Let me try again. It isn't a matter of inputting faster than the screens can be refreshed. It's a matter of checking the keyboard far faster than the user can type the keys and NOT refreshing the screen if no keys are pressed. With Inkey$() you MUST refresh every time you check, weather anything has changed on the screen or not. Either way there is a lot of idle time on the keyboard between presses which could be put to good use. Why waste that time refreshing the screen? Isn't basic slow enough already?  :bed:

Consider a chess program which must do in depth tree searches while the screen is otherwise idle, yet must be ready to break off its search the instant the user enters a new command. Any game which must do in depth parsing or heavy crunching could make very good use of the idle time. If the processing cycles are limited to however fast the screen can be refreshed, that is a serious limitation to computational speed. (Which can do more computing, 30 program cycles a second, or 60,000?) Or consider a typing game which instantly responds to a mistyped letter, perhaps with a sound or graphic explosions. The key issue here is that it is expensive to CPU cycles to refresh the screen more often than is actually needed to display the graphics, and InKey$ requires A LOT of refreshing. If the screen hasn't changed, why refresh it? That time can be better used rendering, searching, calculating, updating and so on.

We could set a timer and check the keyboard every quarter second, or half-second to reduce the drag on the system. (You would still have a lot of unnecessary screen refreshes, just not as many.) But even here you run into the problem of the keyboard being unresponsive if a user presses a key during the dead times.  The goal is to check the keyboard on the fly, but the requirement to refresh the screen weather it needs it or not is a serious drag on a program.

I'm not currently writing a chess program, but I am working on a custom user interface with an animated cursor. (I am writing a game utility which can handle a lot of the bookwork associated with live games (D&D, StarFire, WarHammer, ect.) Any game which has a lot of units. I'll post on it when I get the basic framework hammered out. It's a type of spreadsheet program with interactive health and stat bars, and a library of units which can be loaded to any of eight rosters. Anyway, back to the current topic.)

The problem is, the Showscreen overhead associated with Inkey$() drags the program to a crawl, while trying to use K=Key() requires heavy parsing of the key codes, in Basic, which would be specific to my particular keyboard; which is almost as bad. So if it is possible, perhaps even easy to gain a significant boost to processing speed when using Inkey$(), why wouldn't I ask if it can be done? When GLbasic improves, we all win.

I don't mean this as a criticism, just an observation, and I'm reluctant to even mention it for fear of offending anyone, but it seems to me the response to my question has largely been "That's the way GLbasic is, and maybe you can live with it", rather than "Yeah, that is a good idea and maybe we can get to it eventually" or "It is a necessary evil and here is why it can't be done." But anyway, that is why I brought it up, and now it's time to let it go.
-CW

MrTAToad

I suspect it may be a bit of work to separate it out.

The only thing to can do is make sure that you run your program as fast as possible and check each loop unfortunately.

fuzzy70

Checking for input, be it from keyboard/mouse/gamepad etc has always been a balancing act. Checking to often steals cpu cycles that could have been used better elsewhere, checking not often enough causes lag.

It might be a case that the current key() or inkey$() are coded the way they are to aid supporting the multitude of platforms GLB supports. Then again it might be a restriction of the OS that the code is running on.

Lee
"Why don't you just make ten louder and make ten be the top number and make that a little louder?"
- "These go to eleven."

This Is Spinal Tap (1984)

CW

Ok, fair enough. Thanks Toad and Fuzzy. Thanks all.
-CW  8)

fuzzy70

semi  :offtopic: I look forward to the program you are developing CW. I used to write (& still do rarely) RPG tools for friends ranging from NPC generators to combat tools to help them manage a campaign while being GM. Games varied from Warhammer, GURPS, Marvel to name but a few.

Nothing overly fancy but enough to take the tedious parts away so they could focus more on the campaign at hand.

Lee
"Why don't you just make ten louder and make ten be the top number and make that a little louder?"
- "These go to eleven."

This Is Spinal Tap (1984)

CW

simi  :offtopic:

Yeah Fuzzy! That's exactly what I have in mind.  :good:

Sadly, I can only code in my spare time and I keep getting derailed by side projects, so it's a long-term goal.
I may have something worth showing off in a month or so. So far all I have are a set of working tabs and a bunch of dreams.
I would love to see any sample code you have cooked up, if it is still around. You might inspire me with some good ideas.

-CW

fuzzy70

I have not created anything in GLB yet for RPG tools, most was done in Blitz3D (without using any 3D just the programming language) or Delphi. Both of which I used before shifting over to GLB, Delphi mainly for ones that needed a Windows GUI look/feel.

I should have the code buried somewhere on a backup, practically all where written ad-hoc for a specific RPG with little or no code reuse  :D

Funnily enough it is one of the things on my to-do list to port/re-write some generic RPG code, especially as I feel more comfortable with the workings of GLB & how it handles things.

Any questions or problems you are having please PM me & I will see what I can do to help.

Lee
"Why don't you just make ten louder and make ten be the top number and make that a little louder?"
- "These go to eleven."

This Is Spinal Tap (1984)