"Async" Parchment update: live on iplayif.com (Jan 2025)

Two years ago I announced AsyncGlk, then at the beginning of the year I announced RemGlk-rs. Finally these have been brought together in a major update to Parchment, which is ready for testing!

The Parchment testing site

For years I’ve been slowly working away at my grand plan for “Async” Parchment - an overhaul where each component is made compatible with an asynchronous Dialog/file system. This is needed because several APIs I would like to eventually support for the Parchment file system are async, including IndexedDB (to allow storing more than ~9MB of files), Dropbox, and Cordova or Tauri. RemGlk-rs was designed for an async Dialog API, but it was only useable on Node, until I finished a MVP browser Dialog implementation this week.

I’ve never been good at designing user interfaces, but with Svelte I’ve finally found a framework that I think works with my design process. The file dialog is still pretty rough - things don’t line up properly. I will get to that, so don’t feel you need to point out all the misaligned things - I’m aware! But if there are quirks in the functionality then please raise them. I am also intending to some kind of preview system, to show you details of the file (like its size and date), as well as to allow you to download it or delete it.

There may be other small differences from the old Parchment due to different virtual machines being used: both my ZVM and Zarf’s Quixe are not async-ready, so they have been replaced with Bocfel and Glulxe respectively. But both interpreters are still very well tested so I’m confident they’ll serve us well. Special thanks to @cas for adding a no-stdio mode to Bocfel.

Known issues:

  • There are no autosaves yet.
  • iOS Safari doesn’t resize the dialog correctly when the virtual keyboard appears/disappears.
  • The Parchment for Inform 7 template isn’t updated yet.
  • If anyone has any existing files in testing.iplayif.com they won’t be migrated to the new dialog yet. Files are migrated, but if anyone saved anything in the last 24 hours they won’t be accessible now.

Thank you to the IFTF for a grant which allows me to test Parchment on iOS.

7 Likes

Does this mean that there will be sound support for gblorbs?

Not yet, RemGlk-rs and GlkOte don’t support sound yet. But I am intending to add it soon!

3 Likes

Cool to see the no-stdio stuff in action!

A couple of things I’ve noticed so far:

No reverse video in Z-machine games, either in the status bar or quote boxes. Bocfel has two approaches to reverse video:

  1. If GLK_MODULE_GARGLKTEXT is defined, use garglk_set_reversevideo()
  2. Otherwise, in standard Glk implementations, set style to style_Alert and hope it looks OK

Option 2 was done because GlkTerm sets reverse video for alerts. The quick fix is to set reverse video for the alert style when using Bocfel, but I’m not sure how feasible that is for you in RemGlk-rs, and I’m open to different approaches. This fine-grained control over text output for the Z-machine has always been a bit finicky.

Next, version 6 games: it looks like you’re not actively testing them (as you can’t load .z6 files directly), but before I realized that, I tried loading Blorb files with embedded .z6 files. And it works! Kind of!

Two major things which may already be on your radar:

  1. Images aren’t being shown. Images look fine for me in Glulx games so they’re working in general, but with Bocfel, images either aren’t shown at all, or if they’re inline, they show up as broken images. I’m attaching a screenshot below for reference.
  2. With Journey, at the title screen, Bocfel aborts with the error message “Fatal error: got unexpected evtype_Timer (pc = 0x6ad1)” during a @read_char call. Looks like a spurious timer event is being reported? This @read_char call doesn’t have an interrupt routine, so no timer should be running for it.

1 Like

The dialog is now much more polished, I’m pretty happy with it. Still a few small things that can be improved, but it’s much better than before.

Yep, I will be adding the Gargoyle extensions, just haven’t got to it yet. It won’t be hard, the underlying Glk implementation is ready, just the functions themselves need to be added.

Re: Z6, I’ve never tested, so it’s not surprising there are issues. But the phantom timer event is really weird.

3 Likes

I’ve added the Garglk extensions to RemGlk-Rs. This means Bocfel has reverse mode as well as colour.

But it’s not working completely right. For example, Photopia isn’t displaying its chapter headings correctly, and has a vestigial status bar: Parchment

Graphics in Z6 seem to be working as much as can be expected: Parchment
Maybe your blorb is different? Note that I’m not compiling Bocfel with ZTERP_GLK_GRAPHICS.

For Journey, I see that glk_request_timer_events is being called from start_story, though I assume that’s from function inlining, as well as from get_input. Parchment (If you know how to debug in a browser you could insert a breakpoint at the beginning of glk_request_timer_events; luckily I keep function names on.)

I also have updated the Parchment for Inform 7 template (now embedding Bocfel and Glulxe), though I’m intending to stop distributing it directly through Git, instead I’ll make it available through Github Releases.

Oh, and I just fixed a bug with the localStorage upgrading system, which unfortunately means any files saved to testing.iplayif.com in the last couple of weeks will be lost. (Well not actually lost, just hidden, as there won’t be any metadata records of them.) But no one should be doing anything on testing.iplayif.com that truly matters, so it’s better to fix these kinks now than on the main iplayif.com domain!

3 Likes

I’ve fixed Photopia’s vestigial status bar. The cause was that ZVM removes the upper window entirely when you call @split_window 0, but Bocfel keeps the window and just sets its height to 0. However even though the height is 0, RemGlk-rs and GlkOte still allocate space for the window’s padding. I now detect when the height (or width) is 0, and ensure that the window won’t have any padding.

2 Likes

After more testing, the issue is that a .zblorb extension is required for the graphics to work. Blorbs I created had .blb, and graphics weren’t working there. The game loaded, i.e. the Exec chunk was detected, but graphics just didn’t work. Renaming to .zblorb fixed it.

As for the timer events, I looked into it, and a timer event is being requested by the game, just not at the @read_char where the timer event arrives. For some reason Journey does <SET CHR <INPUT 1 1 ,I-RTRUE>> at the beginning of the “intro” routine (and all it does with CHR is check whether it’s null, which shouldn’t be possible, and if it’s null, skip the intro text…).

Anyway, a timer is being started here because of the interrupt routine. But it should be fully contained in this call to @read_char: if the timer fires, which it will unless you quickly hit a key, the stop_timer() function is called, which cancels timer events. And in any case, even if you do hit a key, stop_timer() is called unconditionally after input is processed, so no matter what, when @read_char is finished, timer events will have been canceled.

I can reproduce this with the following Inform program:

[RTrue;
    rtrue;
];

[Main;
    @read_char 1 1 RTrue -> sp;
    @read_char 1 -> sp;
];

A timer is set in the first @read_char, but arrives in the second.

2 Likes

Oh, I can see there’s a bug in my launcher code. I’m not giving the blorb to GlkOte if the file has a generic blorb extension.

Do you think this is a bug in Bocfel or in my Glk implementation? Either way, I can add some safeguards to ensure that timer events aren’t returned to the VM if they’ve been cancelled.

I think it’s a bug in the Glk implementation, because of the following from the Glk standard:

Call glk_request_timer_events(0) to stop getting timer events.

That indicates to me that any outstanding timer events won’t arrive at all after they’re disabled. But @zarf can provide a definitive answer.

If this is allowed per Glk, or if there’s something that makes it difficult to manage the timers to this level in Javascript, it should be a very easy fix in Bocfel; instead of the assertion in the evtype_Timer handler that’s stopping it now, you can do this:

if (timer == 0) {
    break;
}

It’s not something I’d thought about. But yeah, it makes sense for outstanding timer events to be ignored/dropped after you do glk_request_timer_events(0).

(If you then re-enable them, could a very late outstanding timer event come through and be delivered? Let’s call that implementation-dependent.)

It’s easy enough to get GlkOte to check before sending a timer event. My concern is on the RemGlk side because it’s not so easy to ignore an event. The GlkOte protocol requires there to be strictly one message each direction at a time, so simply ignoring a received event would mean the protocol would become frozen. But maybe that’s when a pass response would be useful? But it doesn’t have the generation number so I’m not sure it would help.

Is there a good reason for Bocfel to raise a fatal error on an unexpected event type? Such events could just be entirely ignored, or logged, but a fatal error seems too much.

It looks like I added that 14 years ago, so I’m not sure of the motivation. Likely a check against bugs in Bocfel (i.e. ensuring it cancels timers properly). Since this hasn’t been an issue before, it’s just never been something that I’ve thought about since I added the check.

But if it makes life easier for RemGlk I’ve got no problem ignoring unexpected timer events. You can patch locally with the fix I noted above, and I’ll get it into the next Bocfel release. I can’t think of any problems that would be caused by doing this.

1 Like

I’ve fixed the .blorb launcher issue.

I’ve also fixed the timer bug (RemGlk-rs wasn’t telling GlkOte to cancel timer events, oops). But now Journey is running into some new RemGlk-rs bugs. I’ll keep investigating…

@cas Any ideas about the Photopia heading? Have you seen output like that with any other Glk implementations?

I’ve done some testing, and I have at least something worth looking at. If the cursor in the upper window is moved to column 2 or greater, text styles aren’t properly applied. This is seen in quote boxes, which aren’t in reverse video or, as in the case of Photopia, color. You can quickly see the non-reverse problem in Curses since it puts a box statement up immediately.

This is a simple Inform program that demonstrates it:

[Box text;
    @split_window 5;
    @set_window 1;
    @set_cursor 2 2;
    style reverse;
    print (string) text;
    style roman;
];

[Main;
    @set_colour 6 2;
    ! box "Hello";
    Box("Hello");
    @read_char 1 -> sp;
];

The Box routine does the bare minimum to show this. The result is:

If this is changed to @set_cursor 2 1:

That extension of color to the end of the line shouldn’t be happening, but the proper text style is being applied here.

I have a commented-out “box” statement just so you can compare with/test against Inform’s box.

Oh, I have seen that happening before, but I’d just thought the files were buggy. That was silly. I have an idea where it’s going wrong, even if I don’t know exactly what yet.

Okay, found the problem, when I was consolidating the grid window output and comparing adjacent characters to see if they had identical styles, when I came to a character with new styles, I was copying the previous character instead of the new one. :man_facepalming: So the window only ever had the styles of the very first character. Easy fix at least.

Now I’ve found something new, which is that the upper window never has its background colour set.

Screenshot from Parchment

In ZVM I destroy the window whenever @split_window 0 is called, then recreate it when you increase the size. Before recreating it I set a background colour hint based on the current character background colour.

Bocfel just creates the upper window when it starts. And, at least for Photopia, it never clears the window. This means the background colour is never changed to black.

I guess this is a quirk with the Garglk extension functions we’ve never realised before. Expanding a 0 row grid window should set the background colour just as if you had cleared it.

However that still doesn’t help with the very first title screen of Photopia (“Will you read me a story?”) as the expansion happens before the colours are set. It works in Gargoyle, but I’m not sure what else I could do detect when the background colour should be changed.

Apologies if I’m misunderstanding, but Photopia does things in this order for the first screen:

@set_colour 9 2 ! White on black
@split_window 10
@set_style 1 ! Reverse video
! Show quote box

Meaning that if you color the window on split it should be applied here.

Hmm, that’s not the order I’m seeing. Very weird…

From what I can see, the full set of (IO) operations are:

  1. @set_colour 9 2
  2. @erase_window -1
  3. @split_window 10
  4. @set_window 1

The @set_colour 9 2 op is only setting styles on the main window. At the time of @split_window 10, there are no colour styles on the upper window to use. And in any case, garglk_set_zcolors_stream is only called for the upper window after @set_window 1.

I’m perplexed what is happening in Garglk. I’ve looked all through Bocfel’s screen.cpp and can’t see how the colours could be set on the upper window.