Feature Adding "stop" for scripts to gracefully stop

Irrat

Member
There are two ways for a script to exit.

The first is on their own terms, the second is by aborting (Esc / Abort) which may leave the account in a bad state.

But a lot of scripts are something that can exit midway gracefully, yet there's no actual way for the user to do this. Even with autoscend, it requires you to go into the relay browser and run a relay page.

My thoughts are that it would be nice to have either a preference, or a new ash function in which a script can check if it should exit early, or continue.
After a fight, and before a fight. It checks the value, then continues.

The most obvious thing that comes to mind is the CLI command "stop", as well as a button to stop. The button could be located next to the "refresh" button beneath the GUI's charsheet.

There are several things that come to mind.

What happens if we're running multiple scripts?
Given that "stop" means we want to abort, but in a graceful way. I think it should clear the queue if any are queued.

The next is how we should request a stop, and check if a stop is requested.

A preference is one way to go, however we don't actually care about the stop state when there's no script running. It could also persist if something goes wrong.
So storing it internally that we wish to stop, sounds better.
Preferences would provide more backwards compatibility, but they can just remain on older versions of scripts if they insist on older versions of mafia.

stopping() : boolean - Has a stop been requested
(stop_requested(), is_stopping(), requested_stop()) - Potential names

stop(boolean) : boolean - Returns false if a stop was already requested
(request_stop) - Potential name
 
Last edited:

MCroft

Developer
Staff member
Hmm. I like the idea of having a way to stop all scripts, and I also find the autoscend way of doing thing clunky. I tried to add this to the daily deeds panel as an Autoscend Stop button, but it just queued the script for after Autoscend completed :)SadTrombone:). So I see a use for it.

So, UI wise, could this be incorporated into the existing "stop" buttons? or should this be an exception to the CLI processing that executed a stop instead of queuing the next command?

What would happen if it was called from a script?

Could we just give script writers a convention like a finally() block in main() and jump into it on abort?
 

VladYvhuce

Member
I'd suggest adding the functionality to the "stop after" button, so that one wouldn't have to either Stop Everything or close mafia to break a scripted automation. Allow the script to be stopped after its current turn is used. Currently, Stop After won't work for things like the Pandemonium quest script or Beachcomber, which I use often, but sometimes realize I'd goofed on some setup stuff, so have to Stop Everything or exit mafia to fix said mistake.
 

heeheehee

Developer
Staff member
What's a reasonable stopping point? Completion of the currently executing builtin function (e.g. by hooking into the ASH interpreter) or immediately if none is in progress?

This reminds me of POSIX signals, where killing Mafia would be a SIGKILL, abort would be a SIGABRT, and this might be a SIGINT.

Mafia can have multiple threads executing scripts simultaneously (most notably, a relay script and the main thread, as used by autoscend's "stop" relay script; but also in cases like a chatbotScript). Do we only want to stop things on the main thread? All of these executing threads?

Note that we already have special handling for the abort CLI command, which bypasses the queue. I don't see any reason why this needs to be any different, or why we couldn't integrate it into the existing "stop after" button. (I'd personally opt for both. A silly wish I have is for this to mirror posix signals, but I don't feel very strongly about that)
 

MCroft

Developer
Staff member
What's a reasonable stopping point? Completion of the currently executing builtin function (e.g. by hooking into the ASH interpreter) or immediately if none is in progress?

This reminds me of POSIX signals, where killing Mafia would be a SIGKILL, abort would be a SIGABRT, and this might be a SIGINT.

Mafia can have multiple threads executing scripts simultaneously (most notably, a relay script and the main thread, as used by autoscend's "stop" relay script; but also in cases like a chatbotScript). Do we only want to stop things on the main thread? All of these executing threads?

Note that we already have special handling for the abort CLI command, which bypasses the queue. I don't see any reason why this needs to be any different, or why we couldn't integrate it into the existing "stop after" button. (I'd personally opt for both. A silly wish I have is for this to mirror posix signals, but I don't feel very strongly about that)
Isn't the reasonable stopping point determined by the script? you'd expect a script to either immediately exit or handle SIGINT and cascade up. You want a script to be able to clean itself up and exit, including the possibility of waiting to exit for child processes to exit.

I'm not sure what's best for chat or relay scripts. SIGINT is generally handled by the foreground app, so maybe it depends on where it's called from. Or maybe all scripts get SIGINT.
 

Irrat

Member
What's a reasonable stopping point? Completion of the currently executing builtin function (e.g. by hooking into the ASH interpreter) or immediately if none is in progress?

This reminds me of POSIX signals, where killing Mafia would be a SIGKILL, abort would be a SIGABRT, and this might be a SIGINT.

Mafia can have multiple threads executing scripts simultaneously (most notably, a relay script and the main thread, as used by autoscend's "stop" relay script; but also in cases like a chatbotScript). Do we only want to stop things on the main thread? All of these executing threads?

Note that we already have special handling for the abort CLI command, which bypasses the queue. I don't see any reason why this needs to be any different, or why we couldn't integrate it into the existing "stop after" button. (I'd personally opt for both. A silly wish I have is for this to mirror posix signals, but I don't feel very strongly about that)

I agree that the reasonable stopping point should be determined by the script, so this would be something the script itself has to support.
We have hard aborts, which forces the script to stop. And this is a graceful stop, which only the script would know.
Just because I finished a combat doesn't mean I'm ready to stop, I might want to switch back to my clan, or deposit some UR's where they belong.

If the script can't gracefully stop, then yeah you'd probably want to try abort.

We could possibly look at a new keyword that tells us if the script can gracefully stop, and make the button unclickable if nothing can be gracefully stopped.


I'd suggest adding the functionality to the "stop after" button, so that one wouldn't have to either Stop Everything or close mafia to break a scripted automation. Allow the script to be stopped after its current turn is used. Currently, Stop After won't work for things like the Pandemonium quest script or Beachcomber, which I use often, but sometimes realize I'd goofed on some setup stuff, so have to Stop Everything or exit mafia to fix said mistake.

I agree it should be added to the "stop after", and the CLI command "stop" would be the alternative for when you're in the CLI window.
 

heeheehee

Developer
Staff member
I'm not sure what's best for chat or relay scripts. SIGINT is generally handled by the foreground app, so maybe it depends on where it's called from. Or maybe all scripts get SIGINT.
Potentially overkill suggestion:

stop main
stop relay
stop handlers
stop => stop all

Although, the default behavior should depend on what people would expect to be a reasonable default.

My only objection to scripts being in charge of handling stop requests is that existing scripts will need to be updated for that situation, and legacy scripts that haven't been updated just won't be interrupted. (I guess that's a scenario where abort is a mostly reasonable alternative.)
 

VladYvhuce

Member
Isn't the reasonable stopping point determined by the script? you'd expect a script to either immediately exit or handle SIGINT and cascade up. You want a script to be able to clean itself up and exit, including the possibility of waiting to exit for child processes to exit.
That sort of gets into "what do you mean by stop?". I'd say that the script can do whatever to make the stopping "graceful", so long as it doesn't cost more than 1 adventure to do so. I'm fine with the script doing things like swapping back to the gear I was wearing before I activated it, and doing whatever it typically does when the process it's called to do is completed. I'm fine with a script not shutting down in the middle of a combat or choice adventure. But I don't want it to keep burning more adventures, because that's typically the reason I'm telling it to stop.
 

Veracity

Developer
Staff member
If you want to have your script do anything whatsoever when it exits, wrap the code in your main() function in a try/finally construct.

This is VeracityMeatFarm:

Code:
   try {
    set_property( COUNTER_SCRIPT, new_counter_script );
    run_tasks();
    } finally {
    set_property( COUNTER_SCRIPT, old_counter_script );

    int meat_delta = my_session_meat() - initial_meat;
    int turn_delta = total_turns_played() - initial_turns;
    int meat_total = increment_property( MEAT_SETTING, meat_delta );
    int turn_total = increment_property( TURN_SETTING, turn_delta );
    int run_mpa = mpa( meat_delta, turn_delta );
    int cumulative_mpa = mpa( meat_total, turn_total );

    print( "Net income = " + pnum( meat_delta ) + " Meat in " + pnum( turn_delta ) + " turns. Meat/Adventure = " + pnum( run_mpa ) );
    print( "Cumulative income = " + pnum( meat_total ) + " Meat in " + pnum( turn_total ) + " turns. Meat/Adventure = " + pnum( cumulative_mpa ) );
    }
}
We don't need a new ASH feature to enable this.
My script could "exit" or "abort" and it would still execute the finally block.
 

Irrat

Member
If you want to have your script do anything whatsoever when it exits, wrap the code in your main() function in a try/finally construct.

This is VeracityMeatFarm:

Code:
   try {
    set_property( COUNTER_SCRIPT, new_counter_script );
    run_tasks();
    } finally {
    set_property( COUNTER_SCRIPT, old_counter_script );

    int meat_delta = my_session_meat() - initial_meat;
    int turn_delta = total_turns_played() - initial_turns;
    int meat_total = increment_property( MEAT_SETTING, meat_delta );
    int turn_total = increment_property( TURN_SETTING, turn_delta );
    int run_mpa = mpa( meat_delta, turn_delta );
    int cumulative_mpa = mpa( meat_total, turn_total );

    print( "Net income = " + pnum( meat_delta ) + " Meat in " + pnum( turn_delta ) + " turns. Meat/Adventure = " + pnum( run_mpa ) );
    print( "Cumulative income = " + pnum( meat_total ) + " Meat in " + pnum( turn_total ) + " turns. Meat/Adventure = " + pnum( cumulative_mpa ) );
    }
}
We don't need a new ASH feature to enable this.
My script could "exit" or "abort" and it would still execute the finally block.

Yes, but the key idea of this is that a script can exit gracefully on its own terms.

If you really need to abort the script immediately, don't run anything more. Press escape. If you want to have the farming script stop midway after it's in a good state for the user to take over. Then use this new "stop" feature.

Example is my clockwork maid farming script. It has a ton of combat round handling just for the pickpockets and some other misc stuff, if I press escape then it'll be in the middle of a fight and I'll lose some potential drops.

If I press escape when it's not in a fight, then I run a chance of already being in a fight as I didn't add a delay between fights. If I press escape later when it's buying items, or asking for a buff. Then it won't remember that it's doing this, and will restart the process all over again. Especially if something else went wrong, like the buff not going through.

It's less that I want the script to exit, and more that I want the script to exit in a state that has nothing for me to clean up.
 

MCroft

Developer
Staff member
Yes, but the key idea of this is that a script can exit gracefully on its own terms.

If you really need to abort the script immediately, don't run anything more. Press escape. If you want to have the farming script stop midway after it's in a good state for the user to take over. Then use this new "stop" feature.
I think the point is that a try/finally construct gives you a place to write the "am I in a fight I need to finish?" handling code and to do any cleanup required. If the coder doesn't include that control structure, then it exits. If they do, it runs the finally code (tidying up as needed) and exits gracefully.

It seems like it's what you want.
 

Irrat

Member
I think the point is that a try/finally construct gives you a place to write the "am I in a fight I need to finish?" handling code and to do any cleanup required. If the coder doesn't include that control structure, then it exits. If they do, it runs the finally code (tidying up as needed) and exits gracefully.

It seems like it's what you want.

You're effectively proposing that scripters should be scared of the users, that they should wrap everything in a series of tries.

Abort is a pretty hard measure, while this proposal is about letting the program shut down safely.
When the script is doing something it expects to error, it doesn't check if the error was caused by the user trying to quit.

try/finally's are used to catch errors, and is the only method used to catch errors. A single try/finally may work fine, but if we're nesting it..

What if we're doing a loop with everything in a try/finally? The script can't be shut down unless I spam abort, and even then it's hit and miss.

Comparing "abort" to "stop" is like comparing ramming your car into a house, to honking your horn.
 

Magus_Prime

Well-known member
You're effectively proposing that scripters should be scared of the users, that they should wrap everything in a series of tries.
I think it's less being "scared of the users" and more trying to build a script, or program, that can, gracefully, handle most sorts of unexpected, by the programmer, events. Other terms might be "defensive programming" or "robust error handling". That said...safely, unwinding a loop can be a pain.

I haven't had to, seriously, code in quite a while but when I did I was endlessly amazed, and sometimes amused, by the number of unexpected things that my customers/users managed to do to break what I wrote.

That's not even considering the issues that I didn't think to mitigate when I wrote the code. Thankfully those became less common as I gained experience but they still happened.

Aside: One of my personal favorites was when someone complained that they tried to write a data export to an external device, removed the device before the export completed (there was a progress bar on the screen and logic to verify that the export was correct), and then complained that the data on the external device was "corrupt".
 
Last edited:

Irrat

Member
Bumping this instead of creating a new thread.

I think for a simpler implementation of this, we could add "stop" command.
And a [STOP] button next to the "[REFRESH]" button in the mafia GUI in the side pane.

Using either of these will set a preference asking the script to stop "stopRequested", and possibly clear the command queue (Option to toggle that?)

Only when the current script execution finishes or a new execution starts, will the pref "stopRequested" be set to false.
This means one script can call another script but one "stopRequested" will cancel everything until the first script that called everything else, finally exits.
When the Stop is requested, the button is greyed out.

Calling "Stop" does nothing beyond setting the pref to true. It does not abort a script.

If you want to abort, press escape or type "abort" as normal.

"Stop" does not promise that the script will listen, or that it will interrupt the script before it does something drastic. It does not stop it from starting another script. All it does is informs the script that a stop was requested.

The only question I have that adds on to this, is detecting when a script supports, and doesn't support "stop"
For that, we could introduce a new function that's simply "supports_stop(true/false)"

If you type "stop" and the script does not support it, a message in red could display "This script does not support 'stop'"
The "stop" button is grayed out, and will show the same message if clicked.
This way you don't waste your time waiting on a script you're not sure supports "stop"

Example usage.

Code:
void main() {
   // Stop requested cannot be true at this time
   supports_stop(true);

   while (get_property("stopRequested") != "true" && my_adventures() > 0) {
      adv($location[knob goblin treasury]);
   }
}

If this is acceptable, I could look at whipping up a PR that has said changes. Although, I'm not sure yet on detecting the parent runtime start/stop.
 

fronobulax

Developer
Staff member
Bumping this instead of creating a new thread.

The previous suggestion wasn't something I thought was especially necessary and bumping it has not changed my mind. My opinion and a fiver will get you some kind of hot caffeinated beverage in parts of the US.
 
Here is the problem that exists:
Because of the way sufficiently complicated scripts work, often times pressing escape (or stop) once is not sufficient to abort the script. Additionally, some scripts want to clean themselves up when they are done.

It would be really nice as a maintainer of a script that requires what we so eloquently call "spamming escape" to die if instead there was a GUI button that simply toggled a property to "true" saying "clean escape requested"

This partially happens because of the interplay between all of the various script hooks in mafia - killing a hooked script (for example, a CCS consult script that runs as a part of combat) may not actually kill the overarching script.
 
Top