asychronous evaluation callback for Boosters

  • 3
  • Idea
  • Updated 3 years ago
I'm trying to create a booster script that will try multiple alternative designs and evaluate them one after the other. When I wrote a booster that called set_sequence() multiple times it only evaluated the last set design (presumably after the booster returned).

Is there some way to force evaluation of the set_sequence() such that it sets up an undoable state from within an asynchronous booster? Perhaps something like a async_evaluate(callback,context) function that calls callback(context) once the evaluation is finished? Or perhaps set_undoable() that checkpoints the undo state? If I call get_targets() and then get_free_energy(index) for each target index, will that force the evaluation in a way that will be cached for later reuse?

The idea would be to try multiple mutations or designs while having a coffee or meal or overnight and then come back later and cycle through them using "undo/redo" to see which ones worked. Alternatively, having designed them offline, to do a batch submit for final evaluation prior to submission.

Thanks.

Jeff
Photo of jandersonlee

jandersonlee

  • 554 Posts
  • 129 Reply Likes

Posted 3 years ago

  • 3
Photo of Omei Turnbull

Omei Turnbull, Player Developer

  • 977 Posts
  • 307 Reply Likes
Hi Jeff!

You should be able to do what you want, but it requires polling of the flash app to see when it is finished with one calculation before submitting the next.  (Initialization requires multiple steps with polling, also.)  LFP6 wrote a SynthStruct Determination script that works as a batch application that calculates the foldings for all the designs for one puzzle.  I think the only things you need that his script doesn't explicitly test is that the flash app is left in a state where it can be used interactively, and that the undo stack is what you would expect.  It took LFP6 and I quite some time to empirically determine what it took to keep the flash app happy. so any conditions he is testing for are probably necessary for robustness.

You'll see that he eventually coded it as a monolithic state machine.  It would be nice to have all the asynchronous activity wrapped up into one one object, where a booster could just submit a sequence and provide a callback function for getting the result.  If you feel inclined to do that, it could be the start of a booster support library.
Photo of jandersonlee

jandersonlee

  • 554 Posts
  • 129 Reply Likes
Thanks for the pointer. It sounds like a step in the right direction at least. I'll take a look if/when I can find/make some time.
Photo of LFP6

LFP6, Player Developer

  • 613 Posts
  • 109 Reply Likes
As a reference, here's what it would look like without using a state machine: http://www.eternagame.org/web/script/7376892/

This uses ES6 promises and generators, in lieu of async/await which is planned for ES7.

Once you get past the library necessities, it actually winds up being much cleaner, but I didn't know about this option at the time. However, while promises have a polyfill, generators would need to be transpiled in order for compatibility with some browsers (neither IE or Safari support it, actually).
Photo of nando

nando, Player Developer

  • 388 Posts
  • 71 Reply Likes
This is a complex topic, overall. But before I attempt to explain the details and work out an acceptable option, I have a quick question: are you in frozen mode at the moment you trigger your booster?
Photo of nando

nando, Player Developer

  • 388 Posts
  • 71 Reply Likes
I'm not sure I understand what "installing the booster" means... Maybe we're talking about different things. For the Flash applet, a booster is whatever appears in the menu with the lightning icon. EternaScripts are something different, based on many of the same libraries, but called in a different context.

This said, the reason why I was asking is following: the frozen mode is the only reason I could find from the source code as to why the set_sequence_string() API wouldn't populate the undo stack.

The other alternative to explain the phenomenon is that you're possibly calling another API which has the effect of clearing the undo stack. I'm not sure which one of your scripts in the list is your latest beta, but if you give me a pointer, I'll try to check which other API might be causing trouble here.
Photo of jandersonlee

jandersonlee

  • 554 Posts
  • 129 Reply Likes
The one I'm using most is http://www.eternagame.org/web/script/7330231/ "Toolbox (devops) (jandersonlee)".

The when run the first time, the script adds html to the web page, then uses a series of setTimeout() calls to wait for the html to appear and install callback hooks onto various buttons. After all of the buttons have callbacks added, it calls:

applet['end_'+toolboxSid](r);

The remaining interactions happen when an html button is clicked and the callback runs in whatever context is active at that time.  A button will call the callback function which scrapes the html form state, does some operation, usually calling set_sequence_string() from within setToolboxState() before the callback returns.

The version where I tried to add the ability to set multiple sequences is http://www.eternagame.org/web/script/7356918/ "Toolbox+Loader (devops) (jandersonlee)". This one adds a doToolboxBulkLoad() callback that takes the contents of a "ToolboxTextarea" input area, splits it into lines, and calls set_sequence_string() for each line. Once again, these cals are made *after* the asynchronous return.

Perhaps this is not the best way to do this?
Photo of LFP6

LFP6, Player Developer

  • 613 Posts
  • 109 Reply Likes
I'm not sure what the functionality is of end_id thing, so I'm not sure if you really want to call it when you do, as the script is still "active" on the page.

You may notice in my script that I enclose all the API calls in a try/catch. In my experience, the applet tends to error out a LOT. Not sure if you're running into that issue though.
Photo of jandersonlee

jandersonlee

  • 554 Posts
  • 129 Reply Likes
The end_ID thing is to terminate the asynchronous part of a Booster (versus an Etc script). Instead of running the Booster from the menu each time I was trying to extend the UI once and then run the Booster-added ops triggered by button presses. That works for simple ops, but not (apparently) for certain cases.

For a bulk loader Booster I might need to (1) extend the ui to add a Textarea for multi-line input plus a button to trigger the op, (2) poll checking for the button press to trigger action, (3) process the data in separate setTimeout() callbacks, and then (4) tear-down the extended ui, before (5) finally calling end_ID. That way it all happens inside the asynchronous setting with the rest of the game frozen.
Photo of nando

nando, Player Developer

  • 388 Posts
  • 71 Reply Likes
@jandersonlee: I checked the script, judging by the source code, I don't see a reason why the undo stack wouldn't be populated. I'm gonna need to run actual tests, which will have to wait until the weekend I'm afraid... will report asap tho
Photo of Omei Turnbull

Omei Turnbull, Player Developer

  • 977 Posts
  • 307 Reply Likes
@nando For asynchronous boosters, the documentation you wrote prescribes

  • the script signals asynchronicity to the applet by using the statement:return {async: "true"}; 
  • the actual end of the execution is signalled by calling:applet['end_'+sid](r); where sid is the Drupal node ID of the script (here,  6713763)

    The asynchronous scripts that I wrote before there was documentation (or perhaps simply before I was aware of it) didn't do either of these, and yet seemed to work fine.  What specifically do each of these do?  And if a booster is permanently attached to the page's DOM, does it matter if applet['end_'+sid](r); never gets called?
    Photo of nando

    nando, Player Developer

    • 388 Posts
    • 71 Reply Likes
    Ok, a few clarifications:

    What I call a 'booster' is an EternaScript initiated by the applet. An EternaScript instantiating an applet and remote-controlling it, is a different story.

    Because of the browser-related requirement of responsiveness, a booster is more or less required to return pretty quickly. We have two options here: either the script is expected to be fast, or it is not. If it is indeed fast, the script returns some value (which may eventually be used by calling scripts). But if the script is expected to run for a long time, a number of additional steps are required to make it work properly.

    First, the UI needs to be locked, so as to prevent the user from modifying the internal context at the same time that the background-running script reads from or writes into the context (by 'context', I mean puzzle + sequence + other UI elements in the applet).

    Then, the script has to signal its 'intention' to keep running by returning a 'magical' value. When the applet detects that specific returned value, it will keep the UI locked. That's the only difference, nothing else is changed. In particular, other APIs are completely unaffected.

    The script keeps running in the background by means of events, like timers (setTimeout) or button-clicks.

    Finally, when the script 'knows' there's nothing more to do, it can instruct the applet to unlock the UI by calling a special callback, the end_ID one.

    It is up to the script author to decide when to release the UI lock, or whether to use it at all. All I can do is warn about possible collisions and probable crashes when both users and scripts are allowed to manipulate the applet's internals at the same time.
    Photo of Omei Turnbull

    Omei Turnbull, Player Developer

    • 977 Posts
    • 307 Reply Likes
    Thanks, @nando.  I hadn't appreciated the fact that LFP6's use of the API might not be relevant to Jeff's question.  Obviously, LFP6's use requires the additional code to make sure the app is initialized before using the API, which a booster doesn't need to do.  But beyond that, can you identify the differences between what is documented in the booster API and what is available to a batch process like LFP6's? (In case you don't remember you don't remember LFP6's script runs through all the submissions for a puzzle, submitting one sequence and waiting for the folding each time before proceeding.)

    As to locking the applet UI, it sounds like if the synchronous execution part of a booster simply sets up additional DOM elements and event handlers (which is what the script I demonstrated in the bluejeans video does), nothing is lost by not executing the return {async: "true"}; / applet['end_'+sid](r); handshake.  Is that correct?
    Photo of jandersonlee

    jandersonlee

    • 554 Posts
    • 129 Reply Likes
    I'm guessing that I could initialize the UI elements the first time it is run and skip it on subsequent runs, but to process a second "batch" I should restart the Booster from the lightning menu and not call applet['end_'+sid](r); until after any given batch has been completed.
    Photo of nando

    nando, Player Developer

    • 388 Posts
    • 71 Reply Likes
    If you guys are careful to not manipulate the applet while a script is running, the UI-lock mechanism can be omitted. It's "dangerous" in the sense that it leaves an open window for potential crashes (on top of those randomly caused by NuPACK, which I have been unsuccessful eliminating so far).

    @Omei: the exported APIs are the same in all cases.

    @jlee: please bug me again during the weekend, just to make sure I don't forget to work on tracking down that multiple-folding+undo stack problem.
    Photo of Omei Turnbull

    Omei Turnbull, Player Developer

    • 977 Posts
    • 307 Reply Likes
    Jeff, quite possibly, I've missed something, but when I read your original description: 
    I'm trying to create a booster script that will try multiple alternative designs and evaluate them one after the other. When I wrote a booster that called set_sequence() multiple times it only evaluated the last set design (presumably after the booster returned).
    it sounded like you had expected the flash app to buffer multiple evaluation requests, which I know it doesn't.  So I thought the solution was simply to wait until one request was completed before submitting the next.  Did you and Nando rule that out as being the prime issue?
    Photo of jandersonlee

    jandersonlee

    • 554 Posts
    • 129 Reply Likes
    How it seems to be working to me is that it doesn't start the evaluation until *after* it returns from the booster script button callback. This would make sense if the evaluation is being run internally as an asynchronous setTimeout() callback, since javascript is single threaded. So if I make multiple set_sequence_string() calls, it may schedule multiple evaluations, but not start any of them until after I'm done. Pure speculation.
    Photo of Omei Turnbull

    Omei Turnbull, Player Developer

    • 977 Posts
    • 307 Reply Likes
    Have you tried periodically polling the flash app to see if it is done with one folding before submitting the next sequence? e.g.
    // Callback function, executed from setTimeout
      // Try to get the structure
      var struct = applet.get_full_structure(structures.length); 
      if (struct != null) {  
      // success; submit another sequence, or return if done. } else { // not done yet with the current one; nothing to do but wait } // call setTimeout to (re)check the status later

    In LFP6's script, the structure was what he wanted, so he clearly had to wait for it.  In your case, the script doesn't really care about the structure, but it seems like you should be able to use its null/non-null status to know when the app is ready for another.

    My apologies if I am simply missing the point.
    Photo of nando

    nando, Player Developer

    • 388 Posts
    • 71 Reply Likes
    I think I've possibly found the culprit. @jandersonlee: could you please try your script again? 
    Photo of jandersonlee

    jandersonlee

    • 554 Posts
    • 129 Reply Likes
    I've rewritten the script, but now it uploads, evaluates, and saves the sequences in the undo list. Thanks Nando!
    Photo of mat747

    mat747

    • 130 Posts
    • 38 Reply Likes
    Thanks Dev
    Photo of jandersonlee

    jandersonlee

    • 554 Posts
    • 129 Reply Likes
    OK. I have a way to load the sequences now, and to make sure that they are evaluated, but I don't see a getter function that will tell me whether or not a design passes the lab constraints. It would be nice to be able to check from a script to see if a design is "OK" or not.
    Photo of nando

    nando, Player Developer

    • 388 Posts
    • 71 Reply Likes
    beta feature (which is why I had it still undocumented so far:
    http://eternawiki.org/wiki/index.php5/Flash_API_for_Boosters#check_constraints.28.29

    I'd be grateful for feedback with this one, if you get a chance.
    Photo of mat747

    mat747

    • 130 Posts
    • 38 Reply Likes
    Thanks Nando
    Photo of jandersonlee

    jandersonlee

    • 554 Posts
    • 129 Reply Likes
    It works for me in http://www.eternagame.org/web/script/7393004/
    Thanks Nando!
    Photo of Eli Fisker

    Eli Fisker

    • 2232 Posts
    • 494 Reply Likes
    Thx for that feature, Nando!