Glulx Inform 7: How to restart line input after canceling

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.

Thanks for correcting my overthinking of global variables in I6–I don’t know why I started thinking that they were values rather than references (sheesh)–and also for the good points about the dangers of firing real-time events during disambiguation. I think you’re right that the worst anyone is likely to see out of it is “you can’t see that here,” but users should be aware of it all the same (some version of this code will end up in the next version of Sarah Morayati’s Basic Real-Time extension, which is why I’m phrasing this in terms of users).

I can think of a lot of reasons not to end the game from a real-time event. What happens if the player types UNDO during the final question, for example?

Anyway, here’s a beefed-up version of the sample code that will work across both disambiguation and yes-no questions (spoilered for length):

[spoiler][code]Include Glulx Entry Points by Emily Short.

Test is a room. The red ball is in Test. The blue ball is in Test.

The button is a device in Test.

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

Instead of waving hands:
say “Do you want to press the button?”;
if player consents:
say “Well, push it. No one’s stopping you.”;
otherwise:
say “Don’t, then.”

A glulx timed activity rule:
cancel line input in the main window, preserving keystrokes;
say “[line break]”;
say “Boom! There’s an explosion somewhere else in the complex.”;
stop timer;
say “[line break]”;
say “[if we are disambiguating]Which?[end if]”;
say “[command prompt][run paragraph on]”;
re-request line event in main window.

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

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

To decide whether we are disambiguating:
(- stored_buffer == buffer2 -)

[Timer code: pulled from Real-Time Delays]

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); -)

Include (-

[ VM_KeyChar win nostat done res ix jx ch;
jx = ch; ! squash compiler warnings
if (win == 0) win = gg_mainwin;
if (gg_commandstr ~= 0 && gg_command_reading ~= false) {
done = glk_get_line_stream(gg_commandstr, gg_arguments, 31);
if (done == 0) {
glk_stream_close(gg_commandstr, 0);
gg_commandstr = 0;
gg_command_reading = false;
! fall through to normal user input.
} else {
! Trim the trailing newline
if (gg_arguments->(done-1) == 10) done = done-1;
res = gg_arguments->0;
if (res == ‘’) {
res = 0;
for (ix=1 : ix<done : ix++) {
ch = gg_arguments->ix;
if (ch >= ‘0’ && ch <= ‘9’) {
@shiftl res 4 res;
res = res + (ch-‘0’);
} else if (ch >= ‘a’ && ch <= ‘f’) {
@shiftl res 4 res;
res = res + (ch+10-‘a’);
} else if (ch >= ‘A’ && ch <= ‘F’) {
@shiftl res 4 res;
res = res + (ch+10-‘A’);
}
}
}
jump KCPContinue;
}
}
done = false;
glk_request_char_event(win);
while (~~done) {
glk_select(gg_event);
switch (gg_event–>0) {
5: ! evtype_Arrange
if (nostat) {
glk_cancel_char_event(win);
res = $80000000;
done = true;
break;
}
DrawStatusLine();
2: ! evtype_CharInput
if (gg_event–>1 == win) {
res = gg_event–>2;
done = true;
}
}
ix = HandleGlkEvent(gg_event, 1, gg_arguments);
if (ix == 2) {
res = gg_arguments–>0;
done = true;
} else if (ix == -1) done = false;
}
if (gg_commandstr ~= 0 && gg_command_reading == false) {
if (res < 32 || res >= 256 || (res == ‘’ or ’ ')) {
glk_put_char_stream(gg_commandstr, ‘’);
done = 0;
jx = res;
for (ix=0 : ix<8 : ix++) {
@ushiftr jx 28 ch;
@shiftl jx 4 jx;
ch = ch & $0F;
if (ch ~= 0 || ix == 7) done = 1;
if (done) {
if (ch >= 0 && ch <= 9) ch = ch + ‘0’;
else ch = (ch - 10) + ‘A’;
glk_put_char_stream(gg_commandstr, ch);
}
}
} else {
glk_put_char_stream(gg_commandstr, res);
}
glk_put_char_stream(gg_commandstr, 10); ! newline
}
.KCPContinue;
return res;
];

[ VM_KeyDelay tenths key done ix;
glk_request_char_event(gg_mainwin);
glk_request_timer_events(tenths*100);
while (~~done) {
glk_select(gg_event);
ix = HandleGlkEvent(gg_event, 1, gg_arguments);
if (ix == 2) {
key = gg_arguments–>0;
done = true;
}
else if (ix >= 0 && gg_event–>0 == 1 or 2) {
key = gg_event–>2;
done = true;
}
}
glk_cancel_char_event(gg_mainwin);
glk_request_timer_events(0);
return key;
];

[ VM_ReadKeyboard a_buffer a_table done ix;
if (gg_commandstr ~= 0 && gg_command_reading ~= false) {
done = glk_get_line_stream(gg_commandstr, a_buffer+WORDSIZE,
(INPUT_BUFFER_LEN-WORDSIZE)-1);
if (done == 0) {
glk_stream_close(gg_commandstr, 0);
gg_commandstr = 0;
gg_command_reading = false;
! L__M(##CommandsRead, 5); would come after prompt
! fall through to normal user input.
}
else {
! Trim the trailing newline
if ((a_buffer+WORDSIZE)->(done-1) == 10) done = done-1;
a_buffer–>0 = done;
VM_Style(INPUT_VMSTY);
glk_put_buffer(a_buffer+WORDSIZE, done);
VM_Style(NORMAL_VMSTY);
print “^”;
jump KPContinue;
}
}
done = false;
stored_buffer = a_buffer;
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;
}
}
ix = HandleGlkEvent(gg_event, 0, a_buffer);
if (ix == 2) done = true;
else if (ix == -1) done = false;
}
if (gg_commandstr ~= 0 && gg_command_reading == false) {
glk_put_buffer_stream(gg_commandstr, a_buffer+WORDSIZE, a_buffer–>0);
glk_put_char_stream(gg_commandstr, 10); ! newline
}
.KPContinue;
VM_Tokenise(a_buffer,a_table);
! It’s time to close any quote window we’ve got going.
if (gg_quotewin) {
glk_window_close(gg_quotewin, 0);
gg_quotewin = 0;
}
#ifdef ECHO_COMMANDS;
print "** ";
for (ix=WORDSIZE: ix<(a_buffer–>0)+WORDSIZE: ix++) print (char) a_buffer->ix;
print “^”;
#endif; ! ECHO_COMMANDS
];

-) instead of “Keyboard Input” in “Glulx.i6t”

Include (- Global stored_buffer = 0 ; -) after “Definitions.i6t”[/code][/spoiler]

To really make this work nicely, a few more features would be nice: allow for the repetition of the disambiguation question (could be done by printing it to a buffer, or using the Text Capture extension) and for the use of custom prompts for yes/no questions so that it will be obvious to the player that the game is still awaiting special input. It would probably also be good if we could distinguish line input from char input, to treat things differently in the timer event rulebook. This would probably require setting more flags in the I6 code.

–Erik