Glulx Inform 7: How to restart line input after canceling

I’m trying to figure out a good way to allow real-time events to interrupt command line input. For example, you have a bomb wired to go off in 30 seconds–every second, the status line is updated to show the time remaining. If the player can’t stop the bomb in time, it goes off. When it does, we want the text announcing the bomb to print a blank line at the command prompt to show that the command was interrupted, then (if the player survives) request new input:

It’s that last part that’s the rub. It’s illegal in Glulx to print text to a window while input is being accepted, so you need to cancel the line event at the first prompt in order to call the bomb’s output routine. And that’s easy enough, but once canceled, how do we restart line input nicely? The timed event has intervened and we are now running a new rulebook (in the I6-level HandleGlkEvent routine), but presumably when this rulebook ends, we will be back in whatever routine we were in while the game was awaiting input, but with no input. The result is that the game freezes: with no input, the player is unable to do anything. (Note that the game doesn’t crash–there’s just no way to enter input.)

Does anyone know how to get the parser to re-request input? Would it be necessary to hack the parser/keyboard routines with checks for a flag, like these guys did a decade ago? (In other words, pepper the keyboard routines with checks for a flag set by the real-time event; if ever the flag is true, immediately return to restart the process? I’d like to do this without hacking the parser, but if it’s necessary I guess it’s necessary!

Here’s some code, in case it helps:

[code]Include Glulx Entry Points by Emily Short.

Test is a room.

To decide whether the event is text-producing:
decide on produces-text.

Produces-text is a truth state variable.

The button is a device in Test.

Instead of pushing the button:
say “Click. Is that a clock ticking?”;
start a 2000 millisecond timer.

A glulx timed activity rule:
if the reading a command activity is going on:
cancel line input in the main window;
cancel character input in the main window;
say “[line break][line break]”;
say “Boom! There’s an explosion somewhere else in the complex.”;
stop the timer;
[Do something to get a new prompt;]
now produces-text is false;

To start a/-- (T - a number) millisecond/ms timer:
(- if (glk_gestalt(gestalt_Timer, 0)) glk_request_timer_events({T}); -)

To stop the/-- timer:
(- if (glk_gestalt(gestalt_Timer, 0)) glk_request_timer_events(0); -)[/code]

Any ideas welcome!

Thanks,
Erik

eblong.com/zarf/glk/glk-spec-070_4.html#s.2

You’ll need to save the buffer and then pass it back again, setting initlen. This will almost certainly involve changes to the Parser, but probably not very big ones.

Thanks; this reminded me that I’d actually gotten part of the way there a few months ago. Input “freezes” because we are trapped in the following loop in VM_ReadKeyboard():

[code][snip]
glk_request_line_event(gg_mainwin, a_buffer+WORDSIZE, INPUT_BUFFER_LEN-WORDSIZE, 0);

while (~~done) {
    glk_select(gg_event);
    switch (gg_event-->0) {
      5: ! evtype_Arrange
        DrawStatusLine();
      3: ! evtype_LineInput
        if (gg_event-->1 == gg_mainwin) {
            a_buffer-->0 = gg_event-->2;
            done = true;
        }
    }

[snip][/code]

So enabling input again should really be as simple as re-requesting line input, using initlen as Dannii suggested. This seems to work perfectly in Zoom:

To re-request line event in main window: (- glk_request_line_event(gg_mainwin, buffer + WORDSIZE, INPUT_BUFFER_LEN - WORDSIZE, buffer->1); -)

However, it doesn’t work in anything else. To get other interpreters to work (I’m testing with Gargoyle and Quixe), you have to change the address in one of two ways, either adding another WORDSIZE, or removing the WORDSIZE addition entirely:

glk_request_line_event(gg_mainwin, buffer + (WORDSIZE * 2), INPUT_BUFFER_LEN - WORDSIZE, buffer->1); glk_request_line_event(gg_mainwin, buffer, INPUT_BUFFER_LEN - WORDSIZE, buffer->1);

Unfortunately, neither of these works in Zoom. Am I making a fundamental mistake here, or is there a bug in Zoom (or possibly in the other terps)? Doe’s infglk summary says that “Pointers (addresses) need to be incremented by WORDSIZE on successive calls to the same function.” I’m not sure whether this counts as second call to the same function (since the first request was canceled), but possibly this is where the problem lies?

The other difficulty I’m having is that I want to print the buffered input to the new command line for the player’s benefit, e.g.:

Zoom does this itself, but while the other terps keep the “loo” in the buffer so that the player needs only to type “k” at the new prompt to complete the command, it looks like this:

Clearly, that’s less than ideal. The spec implies that the library ought to reprint the “loo” itself. At least, that’s the only way I can make sense of the parenthetical’s statement that the player can delete the pre-entered input from initlen:

But again, only Zoom does this. For the other interpreters, it looks like I’ll need to print the contents of the buffer myself. How can I do that? I’ve tried iterating through the buffer array and print (char) entries, but I’ve gotten no where with that. I can’t even get the number of characters in the buffer, as this code never returns anything but zero:

To decide what number is the buffer length: (- buffer->1; -)

Can anyone give any insight into any of these issues?

Thanks!
Erik

Sorry for the quick follow-up. It only seems courteous to post some example code to contextualize all the specifics in the previous post. Here it is:

[code]Include Glulx Entry Points by Emily Short.

Test is a room.

The button is a device in Test.

Instead of pushing the button:
say “Click. Is that a clock ticking?”;
start a 2000 millisecond timer.

A glulx timed activity rule:
if the reading a command activity is going on:
cancel line input in the main-window;
say “[line break]”;
say “Boom! There’s an explosion somewhere else in the complex.”;
stop the timer;
say “[line break]”;
say “[command prompt][run paragraph on]”;
[Print output for ]
re-request line event in main window.

[Zoom only:]
To re-request line event in main window:
(- glk_request_line_event(gg_mainwin, buffer + WORDSIZE, INPUT_BUFFER_LEN - WORDSIZE, buffer->1); -)

[Other terps only:]
To re-request line event in main window:
(- glk_request_line_event(gg_mainwin, buffer, INPUT_BUFFER_LEN - WORDSIZE, buffer->1); -)

To cancel line input in the/-- main-window:
(- glk_cancel_line_event(gg_mainwin, GLK_NULL); -)

To start a/-- (T - a number) millisecond/ms timer:
(- if (glk_gestalt(gestalt_Timer, 0)) glk_request_timer_events({T}); -)

To stop the/-- timer:
(- if (glk_gestalt(gestalt_Timer, 0)) glk_request_timer_events(0); -)[/code]

–Erik

Whoo. This is a nest of different issues, most of which I can’t diagnose off the top of my head. I will try to investigate tonight.

Zarf, TIA. I neglected to mention that the issue seems to be interpreter independent–that is, Zoom behaves the same whether we are using glulxe or git under the hood, and ditto for Gargoyle. So the differences are likely to be at the glk level.

–Erik

I know that Zoom has at least one Glk-level incompatibility (concerning what’s echoed after interrupted line input).

The rest is probably various I6 library and veneer code, disagreeing about array formats. Which should be consistent between implementations, but maybe the Zoom problem is amplifying it.

…I’m not ruling out a Quixe bug, by the way.

Gargoyle’s behavior is the same as Quixe’s (at least at the level of output), so it doesn’t look like a Quixe problem. I’ll fire up Windows tonight and try WinGlulxe and WinGit to see whether they follow the same pattern; I’m guessing they will.

–Erik

WinGlulxe and WinGit both act as Quixe and Gargoyle do, with either of these working:

glk_request_line_event(gg_mainwin, buffer, INPUT_BUFFER_LEN - WORDSIZE, buffer->1)
glk_request_line_event(gg_mainwin, buffer + (WORDSIZE*2), INPUT_BUFFER_LEN - WORDSIZE, buffer->1)

but this not working:

glk_request_line_event(gg_mainwin, buffer + WORDSIZE, INPUT_BUFFER_LEN - WORDSIZE, buffer->1)

I’m still not sure which form of addressing is correct according to spec, but it seems clear that Zoom is the odd man out when we look at de facto rather than de juris “correctness”.

Hope this helps with the diagnosis…

–Erik

Erik,

I’m not sure I understand what you want to do, but as luck would have it, i’m currently working on a real time implementation in Gluxl.

Here’s a little example
http://www.sendspace.com/file/7tikra

  1. the download link is at the bottom of the page (don’t pay attention to the other links)
  2. Why can’t I upload gblorbs to this forum ? I thought there were known to be virtually virus-free.

Walkthrough:

open box
x device
x button
push button

Got it. Here you go:

To re-request line event in main window:
	(-  glk_request_line_event(gg_mainwin, buffer + WORDSIZE, INPUT_BUFFER_LEN - WORDSIZE, buffer-->0); -)

To cancel line input in the/-- main-window:
	(- glk_cancel_line_event(gg_mainwin, gg_event); buffer-->0 = gg_event-->2; -)

I’ve tested this in the IDE, Zoom, Quixe, and GlkTerm.

I don’t know where you got “buffer->1” from, but it wasn’t right.

The deeper problem was that your cancel routine was throwing away information: specifically, the number of characters in the incomplete input. It’s a library convention that that number lives in buffer–>0. So it’s the library’s responsibility to store that value there; you can see VM_ReadKeyboard() doing it. The cancel-line-input phrase must do it too.

Once that’s done, the re-request phrase can pull the value out, and pass it to glk_request_line_event().

That leaves the Zoom problem that I mentioned, but it’s merely cosmetic. Zoom doesn’t echo the partial input on the (original) command prompt line. Other interpreters do echo it. Zoom is wrong here (sorry!). However, a future Glk revision will allow the game to specify whether that echo occurs, and then hopefully we can all get back onto the same page.

Thanks! This is definitely closer (and teaches me something about what you can do with events in these calls–very flexible!), but unfortunately it isn’t right either. We aren’t distinguishing between the length of the buffer (defined by the previous input) and the number of characters typed into the interrupted line. Here’s the output I get using this code:

I don’t remember where I got it either, but my understanding was that buffer->1 is the number of characters typed in the interrupted input, whereas buffer->0 is the length of the buffer, which in practice is set by the previous completed command. It seems that that is wrong, but conceptually that’s the problem we’re be dealing with–we’re still getting the wrong value. I think that the information being stored by the cancel event code is really just 0–you’ll note that the game reacts exactly the same if you get the fourth entry from the event array (gg_event–>3) instead of from gg_event–>2. According to the spec, gg_event–>3 in this case returns 0.

I’ll keep futzing–and try to figure out where I got the idea that buffer->1 stores the number of characters typed–tomorrow.

–Erik

Yes I am. One is INPUT_BUFFER_LEN-WORDSIZE, the other is gg_event–>2.

I’ve pasted my whole program below. The phrase “show the buffer” displays what’s really going on inside the system. After the “cancel line input”, the length of the interrupted input is pushed into buffer–>0, and then “show the buffer” prints it.

"Test Case" by Andrew Plotkin

Include Glulx Entry Points by Emily Short.

Test is a room.

The button is a device in Test.
   
Instead of pushing the button:
    show the buffer;
    say "Click. Is that a clock ticking?";
    start a 2000 millisecond timer.

A glulx timed activity rule:
    if the reading a command activity is going on:
        cancel line input in the main-window;
        say "[line break]";
        show the buffer;
        say "Boom! There's an explosion somewhere else in the complex.";
        stop the timer;
        say "[line break]";
        say "[command prompt][run paragraph on]";
        re-request line event in main window.

Include (-

[ DebugShowBuffer  ix len ch;
    len = buffer-->0;
    print "Buffer contains ", len, " chars: ";
    for (ix=0 : ix<len : ix++) {
        ch = buffer->(4+ix);
        if (ch < 32)
            print "(", ch, ") ";
        else
            print (char) ch, " ";
    }
    new_line;
];

-).

To show the buffer:
    (- DebugShowBuffer(); -).

To re-request line event in main window:
    (-  glk_request_line_event(gg_mainwin, buffer + WORDSIZE, INPUT_BUFFER_LEN - WORDSIZE, buffer-->0); -)

To cancel line input in the/-- main-window:
    (- glk_cancel_line_event(gg_mainwin, gg_event); buffer-->0 = gg_event-->2; -)

To start a/-- (T - a number) millisecond/ms timer:
    (- if (glk_gestalt(gestalt_Timer, 0)) glk_request_timer_events({T});  -)

To stop the/-- timer:
    (- if (glk_gestalt(gestalt_Timer, 0)) glk_request_timer_events(0); -)

Ugh, very sorry about that! When I pasted this new code in, I forgot that my example code was now using the input cancellation phrase that is built into Glulx Entry Points, so the new one (the one that saves the information about the length of the buffer) was not being used.

Fixing that, this code works great for me too.

Thank you so much for the help!

Erik

I figured out where I got this: in the section on buffers in the DM4 (section 2.5):

I assumed that this must be the structure of all buffers, but I’m guessing now that it’s the structure of text_array only…

–Erik

Aha – right.

That describes the behavior of the I6 “read” statement, which generates the Z-code @aread opcode. In Glulx, the “read” statement doesn’t compile at all. (Line-reading is too complicated for the compiler to handle without library support.) So there’s no exact equivalent to that section in the Glulx world. The library’s code, as you’ve seen, uses buffer–>0 (a four-byte length value) followed by one-byte characters.

I just found a bug in this setup. Unfortunately, fixing it will be ugly.

When Inform goes into a disambiguation prompt, it’s actually working off of a different character array. (buffer2 rather than buffer.) It’s also at a different point in the main turn loop.

The result is that if you add “The red ball is in Test. The blue ball is in Test.” to my game, and then do “push button” followed (quickly) by “get ball”, the event will hang up – it won’t reach the timer event. If you either complete the disambig, or type a completely new command, the event will fire during the next input prompt. (Merely hitting Return isn’t enough to move forward, since the parser is still waiting.)

(I have not diagnosed exactly what’s going on in the library guts in this case.)

Entering a YesOrNo input also freezes the event.

Ah.

The YesNo() routine uses buffer rather than buffer2, so the yes-no problem is really caused by the line that checks to see whether or not the reading a command activity is going on, not by any deeper issue.

Would it be possible to solve the buffer2 issue by hacking VM_ReadKeyboard to store a reference to the passed array as a global? (VM_ReadKeyboard is also used for the disambiguation questions. The temporary variable that represents the buffer array in VM_ReadKeyboard is a_buffer.) These two lines could then refer to that global:

To re-request line event in main window:
(- glk_request_line_event(gg_mainwin, global + WORDSIZE, INPUT_BUFFER_LEN - WORDSIZE, global–>0); -)

To cancel line input in the/-- main-window:
(- glk_cancel_line_event(gg_mainwin, gg_event); global–>0 = gg_event–>2; -)

I’m not sure how to do this, though–would it be a pointer? (This–pointers, stacks, memory management–is where my lack of knowledge of C and real programming in general really ducks in to bite me!)

Alternatively, surrounding the code that generates disambiguation input with flags and then testing for those when the timed event fires might also work, e.g.:

disambiguating = true; answer_words=Keyboard(buffer2, parse2); disambiguating = false;

When disambiguating is true, we use alternate versions of the cancel line input and re-request input phrases.

–Erik

Either strategy will work. (You just declare an ordinary global. I6 doesn’t have memory management.)

However, I’m still worried about running arbitrary code inside the disambig prompt or the yesorno prompt. As long as you’re just printing text, it’s fine. If you rearrange the world (move objects around), you could produce some funky results. (Imagine having both the red and blue balls explode in a timed event… I think the worst outcome is an incongruous “You see no such thing” error, but I’d have to do a lot of code tracing to prove it to myself.)

The scariest situation is trying to end the game (set deadflag) in a timed event. Test that very carefully.