Closed ToonTalk closed 6 years ago
I hope not; the stop sign should stop everything, even malicious runaway programs. My offhand guess, though, is that your code is never yielding, and so any listener you set up would never get to run anyway. If you hold down the stop sign button for a few seconds, it should stop everything. If that doesn't do it, post your code.
Hi Ken (@ToonTalk ), attaching user defined events to the red stop button is somewhat of a problematic design issue. Since the early days of Scratch hardware people have been asking for this, so they can stop all motors of a robot, and that makes perfect sense. After all, if you press the red stop button you'd expect your (tethered) device that's controlled by Snap to also stop. The reason we do have a "when the green flag is clicked" event, but not a "when the red stop sign is clicked" one is simply that there could be long running scripts attached to it, or scripts that somehow change something or - worse - throw an error, and then users might not be able to actually stop their project again.
So, if you're using JS functions, you can, of course, replace the stop-button's clicked() method with your own one, and then let it do something more. But that's more or less hacking the system, I guess.
That said, do you have any idea, how we might address the stopping problem? In general I'd be interested in supporting this...
That said, do you have any idea, how we might address the stopping problem? In general I'd be interested in supporting this...
One possible idea is to have 2 events, one fired "on stop" and one fired a short time after called "kill any bad acting stop scripts". This slightly ruins the semantics of the stop block in some cases, but only in edge cases where things would be iffy, at least assuming you only use it for hardware and cleanup scripts.
The other idea is to allow a stop handler to only invoke certain functions, but this seems problematic as well.
As far as the original question, this sounds like an infinite loop to me.
I thought about timing a stop script, but the problem is that if it's trying to stop hardware it'll send a request to a localhost web server and then wait however long it takes to get a reply. That could in principle take minutes. You may think if it's waiting, it yielded, so no problem, but it could do this in an infinite loop, so it's not as bad as freezing up the whole system, but it still goes on forever.
Thanks everyone.
In my case I am forced to use continuations that are called when some web service replies. Clicking the stop sign won't stop the callback from running (unless it knows that the stop sign has been clicked since the web service was contacted. Another example is using the browser's speech synthesis. One often needs to do things when the speech is finished so again a callback that will run even if the user clicks stop before the speech is finished. To make matters worse sometimes it is natural for one of these callbacks to make another use of a web service so the program keeps running in this way.
Regarding solutions why not after doing all the current stop actions to then inside a try/catch to fire any stop listeners? Long running scripts are still a problem, but they are problem with any user JavaScript including JavaScript blocks. I guess there is a problem as Brian mentions that if one wanted to turn off motors that if it used the current http:// block that won't work. But see #1718.
I thought about timing a stop script, but the problem is that if it's trying to stop hardware it'll send a request to a localhost web server and then wait however long it takes to get a reply.
Right now, when you stop a script it stops in progress http requests, so I don't think this matters. There's no waiting or yielding or any type of callback that will happen, at least for the built in http block. If you needed to actually process the response, I guess we're preventing that, but if all you need to do is send a command, then that should work just fine.
With a custom JS block, I think users might need to manually call .abort() or null out the XHR, but it should work.
Could we invent a convention that Snap! keeps a list of pending callbacks and zaps them all when you click stop?
Sure, but that doesn’t really solve the case of needing to stop hardware, where you need to execute something new on Stop.
-- Michael Ball From my iPhone michaelball.co
On Jul 7, 2017, at 12:29 PM, Brian Harvey notifications@github.com wrote:
Could we invent a convention that Snap! keeps a list of pending callbacks and zaps them all when you click stop?
— You are receiving this because you commented. Reply to this email directly, view it on GitHub, or mute the thread.
the convention is that callbacks don't trigger anything directly in Morphic, but only change the state of a variable that gets polled by Morphic's stepping mechanism, which in turns initiates an appropriate action, or decides not to when the time comes.
Could we invent a convention that Snap! keeps a list of pending callbacks and zaps them all when you click stop?
Theoretically; but in practice it takes a little more to actually stop things. For example, if you've started an <audio>
element from a JS function, you actually need to call .stop()
on that element for it to be stopped. Zapping the on-ended callback will do just that (the callback won't be activated); it won't actually stop the sound!
(This is a comparable problem to what @cycomachead noted.)
So, if you're using JS functions, you can, of course, replace the stop-button's clicked() method with your own one, and then let it do something more. But that's more or less hacking the system, I guess.
Note that you should consider running its old .clicked()
method as well (you do still want it to stop Snap! scripts like normal, right?); so, technically, you'd do this:
var oldClicked = stopSign.clicked;
stopSign.clicked = function() {
// ..Your code here..
oldClicked.call(this);
};
Why not add an on project loaded, on stop clicked and an on paused an on unpaused hat block? And maybe some emergency stop that wouldn't trigger the on stop clicked blocks after stopping. And a block for unpausing and starting.
You know, I'm always the one saying you should be able to do anything in a program that the UI can do, and yet this idea really scares me. "On pause" is even worse than "on stop," because when you click pause you really want it to pause right now before the program has time to change its state. But "on stop" is scary too, for the obvious sorcerer's-apprentice reason. I've actually wanted "on project loaded" but Jens doesn't like it because you might find a malicious project that someone has published, and now at least you get to examine it before it runs. Maybe someday we'll compromise with an "on first green flag" block that would also superimpose a big green flag on the stage.
We have when [boolean]
You can do a lot of this manually by writing a custom JS block that wraps Snap!s internal Stop call.
Not that we should encourage it, but this is all possible today.
-- Michael Ball From my iPhone michaelball.co
On Jun 5, 2018, at 3:17 PM, Brian Harvey notifications@github.com wrote:
You know, I'm always the one saying you should be able to do anything in a program that the UI can do, and yet this idea really scares me. "On pause" is even worse than "on stop," because when you click pause you really want it to pause right now before the program has time to change its state. But "on stop" is scary too, for the obvious sorcerer's-apprentice reason. I've actually wanted "on project loaded" but Jens doesn't like it because you might find a malicious project that someone has published, and now at least you get to examine it before it runs. Maybe someday we'll compromise with an "on first green flag" block that would also superimpose a big green flag on the stage.
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub, or mute the thread.
I've actually wanted "on project loaded" but Jens doesn't like it because you might find a malicious project that someone has published, and now at least you get to examine it before it runs.
Ask the user if the project wants to run JavaScript, fire HTTP request or start itself (i also said a block that can unpause and one that can start the project) in the on start function. If the user allows it, it's allowed in all on start scripts. (it would first let the user see the script if it contains any of these blocks - maybe in a dialog similar to the custom block editor, but read only and with a yes and a no button)
"On pause" is even worse than "on stop," because when you click pause you really want it to pause right now before the program has time to change its state
The built-in pause button could be used in a project for example as a way to pause a game, resume it and the game could show a pause menu when paused, not just freezing everything.
There could be an "emergency" version of the pause and the stop buttons that won't fire the events and forces all scripts to pause / stop. Any script somehow starting after stop or pause would get stopped / paused immediately until the user hits the start button (or the "emergency" stop / pause button again).
The following seems to be working. Is this a good solution?
let original_stopAllScripts = morph_ide.stopAllScripts.bind(morph_ide);
morph_ide.stopAllScripts = function () {
console.log("doing stuff before everything stops");
original_stopAllScripts();
};
Note that perhaps stage.fireStopAllEvent would be better for wrapping since the 'esc' key triggers it as well as the stop sign. But the stage can change after Snap! starts when a project is loaded. And also stopAllScripts does more:
if (this.stage.enableCustomHatBlocks) {
this.stage.threads.pauseCustomHatBlocks =
!this.stage.threads.pauseCustomHatBlocks;
}
What is that all about? And why does 'esc' and the stop sign behave differently?
Another example of the motivation for some sort of feature like this is the Snap! text-to-speech library
It can't be stopped. Though the following does stop when the stop sign is clicked.
You're right, Ken, these are good examples in favor of being able to let a script react to a stop "event". I'm going to run an experiment that will let us react to a "when I am stopped" event by executing a single step (atom) before actually stopping. This should be enough to turn off hardware and kill JS scripts without opening a sorcerer's apprentice can of worms.
What is that all about? And why does 'esc' and the stop sign behave differently?
@ToonTalk "pauseCustomHatBlocks" is a flag that enables or disables the "when" block. So if the stop sign is pressed, it prevents "when" blocks from running. That's so that the project definitely stops when you press the stop sign. The "escape" key is more of a shortcut for stopping the running scripts immediately (ala "oh no, I made a recursive block / loop that goes forever, and want to stop it now"), in my opinion, although obviously I'm not the one who made escape and the stop sign work differently so, Jens? :P
Thanks, @towerofnix , for the excellent explanation!
@gdavid04: In general, I'm against changing the user-visible meaning of built-in controls (such as pause). That just confuses the user. Remember that you can put a button-shaped sprite on the stage and give that the special meaning you want. (One of the things on my long-term list that Jens hates is the ability to add things to the menu bar, which would be even closer to what you want.) The stop-hardware thing that Ken wants is okay because it enhances, rather than preempting, what the user expects. (That is, a typical user would expect the robot to stop when clicking the stop sign.)
Being able to customize the menu bar would be even better. A when stop clicked and a when project loaded hat block would be cool anyways. Maybe limit the execution time of these hat blocks. Also why not add an ability to hide the menu bar when stage is in fullscreen mode?
I only comment (in case it's useful) that we have a 'hideControls' flag.
So, if you want to fire a project in fullscreen without control buttons, you can use it.
Just like ... https://snap.berkeley.edu/snapsource/snap.html#present:hideControls&Username=jens&ProjectName=Space%20Invaders
The stop-hardware thing that Ken wants is okay because it enhances, rather than preempting, what the user expects. (That is, a typical user would expect the robot to stop when clicking the stop sign.)
What about pause? What a typical user would except from pause? The robot stops it's motors until unpaused. What happens instead? The robot won't stop the motors and continues moving. (in some cases this could also damage the robot, for example, breaks an arm that was rotating when pause was pressed)
Same as stop, we should be able to run a few instructions before actually stopping / pausing.
Ups! I think we are mixing here too many different things ... and therefore, following this path we will go nowhere ...
Let me show you some comments...
Stop and Pause controls are doing their main task, controlling Snap! execution: stopping scripts, background clones, preventing new events execution... controlling the visible stepping mode... We may find some inconsistency... and then we wil do changes... but we do not mix it with other issues.
If you need to implement more "stop" actions (like a Panic Button) in a project, you can build your own button, adding actions to the "default stop".
var stage = this.parentThatIsA(StageMorph);
stage.fireStopAllEvent();
// Add here new "stop" actions
When we are playing with external devices (robots), things are not black or white. It may seem clear (stop button -> stop motors) but this is not true. Think about it...
Synchronous and asynchronous operation. Be careful. Some posted examples are confusing... Yes, you can stop this... Because this has already finished! Here, we can talk about speech lib and its buffer... but the script is just over. You can play the same examples with "play sound" and "play sound until done" and the results will be ok.
Controlling Snap! "core" actions
As I said, "play sound" is stopping well. Why? Because fireStopAllEvent()
function includes stopAllActiveSounds()
. We can think about these "stops" for all "core" elements... but as I said, "core actions" can not control all things added.
Example of the SpeechLibrary. Yes, we can do more things with this... But it's not in the core... and you will find other inconsistencies beyond the "stop issue". The posted example shows that it uses a 'buffer' and it is not supporting parallel operations. This is the reason that it is not a good example to analyze "stop issues": when you "repeat 5" without any "wait" you must expect 5 sounds together... but this library play texts one after the other.
And I stop my roll... but I insist that it is not a good idea to mix all these casuistics to discuss a central element in the control of Snap! programming
I agree about modifying the "core". As you point out stopping robots is ambiguous. But note the title of this issue - if the robotics primitives are written in JavaScript then maybe the author of the primitives knows what should happen when they are "stopped". Or if not then maybe the author of the robotics project knows what should happen and wants to express that using a new "when" block.
I now believe there are two sub-issues: (1) what hooks to give the JavaScript programmer to respond to stop, pause, resume events and (2) whether new "when" blocks are needed for the Snap! programmer who is using non-core functionality such as speech synthesis, robotics, etc.
A project-defined panic button is not a good solution to stopping lots of queued up speech utterances. Users will expect the stop sign to stop the speech just as it does for core sounds. Two stop buttons are likely to confuse users.
Regarding (1) wrapping stopAllScripts is hackish (depending upon internals that may change) and awkward but in my case adequate.
Regarding (2) I see how these "when" commands could be misused to cause problems. There have been suggestions to limit the risk but I'm not convinced they are needed. Maybe someone should generate some scenarios where bad things happen if a user misuses a when stopping block.
Regarding bad things happening often the page refresh is all that is needed. But it would be nice if the dialog saying do you really want to quit also offered to save things...
Why not just decorate ThreadManager >> stopAll
?
var stage = this.parentThatIsA(StageMorph);
if (!stage.threads.oldStopAll) {
stage.threads.oldStopAll = stage.threads.stopAll;
stage.threads.stopAll = function () {
this.oldStopAll();
alert("Potentially dangerous code that could create havoc.");
};
}
Attach a JS block that runs this to your initialization script and you're done:
It depends upon whether the JavaScript programmer knows how to implement robot.preventFromFallingFromTheTable or whether it is the Snap! programmer who is using some blocks provided by the JavaScript programmer. The Snap! programmer knows what the robot is doing and how it is built.
Okay, I've just added an - as of now experimental - feature that addresses this issue: d11ba70ac6840781f064cbf1e2866e2740207a15
You can test this at the dev url: http://snap.berkeley.edu/snapsource/dev
make sure to force a hard-reload to get the feature.
There's a new option in the dropdown menu of the When I am ...
hat block letting you specify a script that executes when the user presses the red stop button (stopped
). Scripts attached to such hat blocks will run when the user physically clicks on the red stop button or hits the esc-key on the keyboard for exactly one single atomic step ("frame") and then terminate. This means that e.g. only the first pass of a loop gets run. You can, however, use a WARP around the loop to force more iterations. Any broadcasts or forked threads will likewise run for exactly a single step within the main one (i.e. the whole operation lasts exactly one global atom). This is very experimental and might not make it into the release after all.
For now I'd be interested in your feedback, whether this addresses the issue at all.
Thanks!
Closing this, now since the feature was released. :)
I have JavaScript code that keeps running even when the user clicks the stop sign. Is there a way I can add a listener to the stop sign click?