Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feasability of asynchronous bindings? #46

Closed
RickCarlino opened this issue May 13, 2022 · 4 comments
Closed

Feasability of asynchronous bindings? #46

RickCarlino opened this issue May 13, 2022 · 4 comments

Comments

@RickCarlino
Copy link
Contributor

Currently, to add a binding to WAForth we use Forth#bind:

  forth.bind("prompt", (stack) => {
    const message = stack.popString();
    const result = window.prompt(message);
    stack.push(parseInt(result));
  });

This is good for 90% of cases out there, but I was curious how a developer might handle asynchronous binding usecases. Eg:

  • Loading a Forth snippet via AJAX at runtime.
  • Dealing with websockets as a TCP socket alternative.
  • Using asynchronous / promised-based JS APIs.

CASE 1: Inbound Async I/O

For dealing with inbound events (such as reacting to DOM click events, HTML5 geolocation events, etc..), I think it would be really easy to write an event handler:

' my-handler on-click

CASE 2: Outbound Async I/O

For outbound I/O operations like AJAX, things might not be so simple:

s" geocities.com/tamagotchi98/foo.json" json-get ( How would `bind()` deal with this? )

Ideas

  • 🤔 Use an existing solution? Is there one that I might not be aware of? I have not explored the codebase completely.
  • 💡 Allow bind to return a promise and block the Forth VM until the promise resolves.

Would something like this be reasonably possible with the current system?

@remko
Copy link
Owner

remko commented May 13, 2022

@RickCarlino Thanks a lot for taking the time to write this up.

Async bindings crossed my mind as well. I haven't thought things through yet, but here are my initial thoughts:

  • You can't just suspend WebAssembly, so there's no native support for promises or async. There are some discussions of coroutines/stack switching (e.g. here and here) that could help here, but nothing on the horizon AFAICT.

  • One solution would be to start your async operation from the binding, and when it finishes, you use push() on the WAForth class to push stuff on the stack, and then interpret() a callback word you defined. Here's a short example to illustrate. As with all async stuff (at least before the introduction of async/await), it makes it more tedious to write code this way instead of just using a blocking-style API.

  • Something I want to try is run WAForth from a worker thread, and use Atomics.wait to block and resume the thread ( I already had this in Investigate blocking design using threads #43 , but it makes even more sense now with the JS bindings). I think this would allow you to create a blocking API. I have it on my TODO list to try this, and create an example if this approach works out.

One thing that's missing to create useful examples is an API to easily push strings back into WAForth (maybe put them in the PAD area?)

@RickCarlino
Copy link
Contributor Author

RickCarlino commented May 14, 2022

Thanks for the detailed response, @remko that makes sense. In my case, I ended up bringing in DEFER / IS from the 20xx standard.

: DEFER! >BODY ! ;
: DEFER CREATE ['] ABORT , DOES> @ EXECUTE ;
: IS STATE @ IF POSTPONE ['] POSTPONE DEFER! ELSE ' DEFER! THEN ; IMMEDIATE

As far as string support goes, I think putting them in the PAD area would make sense.

Please let me know if you need any help!

@remko
Copy link
Owner

remko commented May 14, 2022

@RickCarlino Good tip!

I added a bindAsync to the wrapper that makes registering callbacks easier (and also added a pushString).
I updated the fetch example to illustrate.

This is the core of the code:

forth.bindAsync("ip?", async () => {
  const result = await (await fetch("https://api.ipify.org?format=json")).json();
  forth.pushString(result.ip);
});

And then in Forth:

: IP?-CB ( true c-addr n | false -- )
  IF 
    ." Your IP address is " TYPE CR
  ELSE
    ." Unable to fetch IP address" CR
  THEN
;

: IP? ( -- )
  ['] IP?-CB
  S" ip?" SCALL 
;

@RickCarlino
Copy link
Contributor Author

@remko Thanks so much- will give it a look this evening.

@remko remko closed this as completed May 20, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants