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

Tell if Stopify is “running” #512

Open
jpolitz opened this issue May 29, 2024 · 1 comment
Open

Tell if Stopify is “running” #512

jpolitz opened this issue May 29, 2024 · 1 comment

Comments

@jpolitz
Copy link
Collaborator

jpolitz commented May 29, 2024

Here's a problem: We want Pyret's arrays to be the same kind of arrays as those on the rest of the page.

We don't want to enforce a wrapping/unwrapping step at the Pyret boundary; we've tried this in other implementations and it's a huge headache (think nested arrays, 3rd party libraries that .map, etc).

The current polyfill strategy for HoFs makes Stopified arrays special. If a client of some Stopified code tries to map or filter on an array, they'll almost certainly get (nondeterministically!) some kind of uncaught Capture or similar, because that array's callbacks have been replaced. For the same reason, it doesn't work to patch in the Stopified polyfills on the array prototype, since that would break innocuous non-stopped code.

One thing we'd like to try is writing polyfills with a dispatch, for example:

  function stopifyDispatch(stopped : any, native : any) {
    // @stopify flat
    return function(this : any) {
        // @stopify flat
        if(isStopifyRunning()) {
            return stopped.apply(this, arguments);
        }
        else {
            return native.apply(this, arguments);
        }
    }
  }

Array.prototype.map = stopifyDispatch(array_map_polyfill, Array.prototype.map);
Array.prototype.filter = ...

Thing is, we can't figure out how to write isStopifyRunning()

There's a lot of states Stopify can be in – is there a way provided by the Stopify runtime to write this predicate? We tried various combinations of eventMode and rts.mode and rts.capturing, but can't seem to express it. Is there a concise way to tell if the stack is currently a Stopify stack that's ready for captures/suspends, or if it's on the plain JS stack?

@jpolitz
Copy link
Collaborator Author

jpolitz commented May 30, 2024

OK, I think we have this working with this:

/**
 * This works around what may be state management issues related to eventMode. The relevant enum is
 * 
 * enum EventMode { Running, Paused, Waiting }
 * 
 * First, in its initial state, the runner is set to `RUNNING` when it should be set to `WAITING`
 * 
 * Second, when pausing with pauseK, it doesn't set the mode to `PAUSED` (there is a comment that this has to do with the debugger in that code)
 * 
 * The pauseK wrapper handles pausing/unpausing, and doing the run on the empty program sets things to `WAITING`.
 */
currentRunner = stopify.stopifyLocally("", { newMethod: 'direct' });
currentRunner.run(() => { });

let originalPauseK = currentRunner.pauseK;

currentRunner.pauseK = function patchedPauseK(k : any) {
  return originalPauseK.call(this, (resumer : (result: any) => void) => {
    const oldMode = currentRunner.eventMode;
    currentRunner.eventMode = 1;
    return k((result : any) => {
      currentRunner.eventMode = oldMode;
      return resumer(result);
    });
  })
}

Relevant comment for pauseK:

// NOTE: In both of the pause functions below, we don't switch the mode to

Then our predicate is:

  function isStopifyRunning() {
    /**
     * The relevant enum is
     * 
     * enum EventMode { Running = 0, Paused = 1, Waiting = 2 }
     * 
     * The check below returns true when that mode is Running, indicating the Stopify stack is live.
     * Paused means “a stack is captured and waiting to be resumed”
     * Waiting means “we are between/after a completed run of a stopify program/event handler”
     */
    // @ts-ignore
    return typeof $STOPIFY !== "undefined" && $STOPIFY.eventMode === 0;
  }

@blerner and I spent some time reconstructing this. I think this could be useful as an exposed API, though may end up finding other cases where the state needs to be managed a bit to have eventMode match the right definition of isStopifyRunning. But we pushed through a bunch of issues and got a .map that seamlessly works with this dispatch (and we had versions that very much didn't work, breaking both the stopped side and the page side).

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

1 participant