 |
oldguy2 Wizard
Joined: 17 Jun 2006 Posts: 1201
|
Posted: Wed Nov 14, 2007 8:12 am
Background threads |
Can someone please explain to me how you abort background threads? If for example, I have a curing alias that sends to eat an herb and starts a loop to eat that herb every 2 seconds until I actually eat it, but that curing alias gets called again before I eat the herb, it creates another background thread doing the same thing. This can pile up fast and I can end up with 6 background threads doing the same exact thing except now instead of 2 seconds all the varied waits make me eat the herb like every few milliseconds.
So how do you keep this from happening? How do you abort or stop threads when you don't know what the ID is without typing #thread all the time? Sorry I am not a programmer. I've just used Zmud for years and now Cmud, but all the multithreaded stuff is confusing. |
|
|
|
 |
Fang Xianfu GURU

Joined: 26 Jan 2004 Posts: 5155 Location: United Kingdom
|
Posted: Wed Nov 14, 2007 10:40 am |
You'd probably be better off preventing a thread from being created at all. Add a check to something like AmEatingHerb that'll be set when you start attempting to eat and unset when you actually eat. You can then check that before starting another loop.
|
|
|
|
 |
Caled Sorcerer
Joined: 21 Oct 2000 Posts: 821 Location: Australia
|
Posted: Wed Nov 14, 2007 11:35 am |
Yeah. There are much better ways to failsafe your curing attempts.
#if (@herbb && @pherbb && @someafflictioncheck) {eataherb}
#al eataherb {eat the herb ; pherbb=0; #ala +1 {pherbb=1}
You should be able to figure out the rest from that. |
|
_________________ Athlon 64 3200+
Win XP Pro x64 |
|
|
 |
oldguy2 Wizard
Joined: 17 Jun 2006 Posts: 1201
|
Posted: Wed Nov 14, 2007 12:09 pm |
You guys don't pay attention. I already did that.
|
|
|
|
 |
Tech GURU

Joined: 18 Oct 2000 Posts: 2733 Location: Atlanta, USA
|
Posted: Wed Nov 14, 2007 1:14 pm |
OldGuy2 recently converted his stuff to loops to make them faster, but it has the unfortunate side of launching multiple threads for the same alias.
Borrowing from another post of his, try this in a new session and you'll see what he means.
| Code: |
<alias name="test" id="1">
<value>HerbEaten=0;#until (@HerbEaten=1) {#send {eat herb};#echo %time;#wait 2000}</value>
</alias>
|
If you type test 5 times really quickly you'll find yourself eating alot of herbs. OldGuy2 Fangs suggestion, is probably what you want or better yet use #SECTION. Try this.
| Code: |
<alias name="test" id="1">
<value>#SECTION AmEatingHerb {HerbEaten=0; #until (@HerbEaten=1) {#send {eat herb};#echo %time;#wait 2000; } }</value>
</alias>
|
Now when you type test 5 times really quickly only one thread will actually run at a given time, the others will wait. Of course in our contrived example they will each run completely because they each set HerbEaten. |
|
_________________ Asati di tempari! |
|
|
 |
oldguy2 Wizard
Joined: 17 Jun 2006 Posts: 1201
|
Posted: Wed Nov 14, 2007 2:37 pm |
Ah sorry I misunderstood.
However, if they are still all going to run after waiting what good will that do? Now they are all just stacked up. Lets say I get hit like 5 times in a row with herb affliction attacks. Each trigger will call the herb cure function, but at the same time I get afflicted with amnesia and stupidity as one of those afflictions. Well these two afflictions are two of the reasons I have to do this repeat eating in the first place because with stupidity it garbles your commands and you do some odd behavior instead of what you sent to the Mud, like kneel down or moo like a cow. So you have to send the command until it goes through. Anyway, using #wait and loops now I probably have like 10 or 20 threads all backed up waiting to run with #section or without it I am eating herbs so fast I use them all.
I'm just not understanding what is the right way here? Should I just stick to alarms? I know people keep saying loops would be faster. |
|
|
|
 |
Malach Apprentice
Joined: 03 Nov 2007 Posts: 132
|
Posted: Wed Nov 14, 2007 2:49 pm |
I think it depends on how much "faster" you mean. If you're adding a lot of complication for a minimal speed benefit you're probably going to frustrate yourself. Have it run as a loop with the debugger window open and see how long it takes. Then have it run as your normal alarm with the debugger window open and see how long it takes. If there is not a huge difference there, I'd stick with the alarms.
|
|
_________________ Intel Core2 Quad CPU @ 2.4 GHZ with Windows Vista Home Premium and 2 GB Ram |
|
|
 |
Larkin Wizard

Joined: 25 Mar 2003 Posts: 1113 Location: USA
|
Posted: Wed Nov 14, 2007 4:00 pm |
A common solution to this problem is to have a third value for a variable such as HerbEaten that indicates you're in your "failsafe" mode and have not lost balance officially yet. You then check HerbEaten for this intermediate value (such as 0.5) to prevent a new copy of your failsafe from running. Think it through logically and you realize this is exactly what you need: "I've got a new herb affliction, but I'm already retrying my herb eating, so I'll wait until that one's done to start another."
However, you probably want to retry the entire list of herbs each time rather than the first one over and over again. If you're hit with confusion and start trying to eat kelp (failing, of course), then when you're hit with paralysis (a higher priority, let's assume), you want to start trying to eat bloodroot and not continue retrying the kelp, right? |
|
|
|
 |
Vijilante SubAdmin

Joined: 18 Nov 2001 Posts: 5187
|
Posted: Wed Nov 14, 2007 4:24 pm |
The speed benefits are quite small and occur only in a few spots. The difficulty of writing it is quite high though. The actual speed gains are in activating the thread with #SIGNAL or #RESUME versus turning the alarm on with #T+, and the precision of #WAIT versus the precision of the alarm timer. Obviously if you use a temporary alarm then the speed gain from using a thread instead will be significant. I will start with an explanation of each piece of the puzzle and build along.
The first thing to understand is that the correct way to create any of these threading items is with the #THREAD command. A basic construct for the temporary alarm is:
#ALARM "name" {+time} {commands}
#THREAD name {#WAIT time;commands}
This does not cover the fact that alarms of identical names will overwrite each other and threads won't. In order to do it right we need the next conversion which is:
#UNTRIGGER name
#STOP name
We now have this as the creation of our temporary alarm thread:
#STOP name;#THREAD name {#WAIT time;commands}
Now we get into a recurring alarm, there are a few requirements that will make these very complex. I would suggest that unless you are a very savy programmer it is not worth the time to try to get these up to the full level of complexity that alarms can handle.
The basic level is rather simple and since #SUSPEND and #RESUME work with the threads the same way they do with alarms it is rather easy to handle. A few conversions for you are:
#ALARM "name" {*time} {commands}
#THREAD name {#WHILE (1) {#WAIT time;commands}}
#TRIGGER "name" {*time} {commands} "" {alarm|disable}
#THREAD name {#SUSPEND name;#WHILE (1) {#WAIT time;commands}}
#TRIGGER "name" {*time} {commands;#T- name} "" {alarm|disable}
#THREAD name {#SUSPEND name;#WHILE (1) {#WAIT time;commands;#SUSPEND name}}
#T+ name
#RESUME name
That ends the primer for the basic level complexity. A much more complex structure is the class of triggers with a timeout alarm. This actually requires a very backwards logic, but I will give a small example here
| Code: |
#CLASS complex
#TRIGGER {{list}} {TriggerCommands;#T- complex}
#ALARM {*time} {AlarmCommands;#T- complex}
#CLASS 0 |
To convert that, where one would use #T+ complex you end up with:
| Code: |
#THREAD complex {#WAITFOR {{list}} time;#STOP complexAlarm;TriggerCommands}
#THREAD complexAlarm {#WAITTHREAD complex;AlarmCommands} |
Obviously not a very pretty chunk of script, and if you have a lot of locations that would turn on the class it is much better to just use the settings structure. Also that only handles a single trigger and going to be very limited as to what it can do with the triggering text.
This doesn't cover all of the capability of threading, or when you need to use #SECTION. I consider it enough of a primer though for those that wish to use threads instead of alarms.
One final point that may help to make the decision, each time you use #THREAD to create a new set of commands those commands have to be compiled, a permanent setting gets compiled once. Unless you can move the creation of threads to a moment when it doesn't matter, or make them not have to be recreated the loss of time from compiling will likely be greater then the time gained. |
|
_________________ The only good questions are the ones we have never answered before.
Search the Forums |
|
|
 |
Zugg MASTER

Joined: 25 Sep 2000 Posts: 23379 Location: Colorado, USA
|
Posted: Wed Nov 14, 2007 6:46 pm |
Vijilante's response was an excellent complete discussion of this.
But as a simple answer to the original question: To stop a thread, use the #STOP ThreadName command. To stop a thread from within it's own script, use the "#ABORT all" command. This will abort the script and destroy the thread.
Finally, just a general comment: When we talk about speed improvements and performance with new features, we are often talking about small improvements. And these speed improvements can vary depending upon the exact script. I've said it many times before, but "Your Mileage May Vary". Even though a thread is theoretically faster than alarms, threads are more complex. There is almost always a pro and con for speed improvements...most do not come for free. For example, in the computer world, doing something faster often means that more memory is used (like for caching data in memory). There is rarely a "free lunch".
In CMUD (and zMUD) there is *always* more than one way to do something. In this case you can use #WAIT in a thread, or use #ALARMs, or even use Wait states in a multi-state trigger. And probably some ways I haven't even thought about. But you shouldn't just be worried about speed. It's often better to make sure you have a simpler and easier to maintain script.
Now, I know that in OldGuy2's case, he is having a specific problem with some of his scripts that are running *very* slow. And we have never been able to figure out why because none of us have been able to reproduce his exact problem. So he is modifying his scripts to try and figure out why they are slow. But the kind of speed changes between threads and alarms is generally in the milliseconds. And OldGuy2's prompt trigger it taking something like 6-8 seconds (from one of his other posts). And that kind of slow script isn't going to be easily explained by threads vs alarms...there is something more basically wrong with the script that is causing that kind of slow speed, but none of us have been able to reproduce it.
I just wanted to explain that to someone who might be reading this who hadn't read some of the other forum posts. |
|
|
|
 |
Caled Sorcerer
Joined: 21 Oct 2000 Posts: 821 Location: Australia
|
Posted: Wed Nov 14, 2007 8:36 pm |
| Larkin wrote: |
A common solution to this problem is to have a third value for a variable such as HerbEaten that indicates you're in your "failsafe" mode and have not lost balance officially yet. You then check HerbEaten for this intermediate value (such as 0.5) to prevent a new copy of your failsafe from running. Think it through logically and you realize this is exactly what you need: "I've got a new herb affliction, but I'm already retrying my herb eating, so I'll wait until that one's done to start another."
However, you probably want to retry the entire list of herbs each time rather than the first one over and over again. If you're hit with confusion and start trying to eat kelp (failing, of course), then when you're hit with paralysis (a higher priority, let's assume), you want to start trying to eat bloodroot and not continue retrying the kelp, right? |
^ Exactly (I call it predicted herb balance)
That's why, instead of attempting to re-eat the same herb, you should run the central curing alias again. And why wait two whole seconds? If you have a low ping to your mud, 500ms is heaps, as Larkin said above. I use 1000ms (Aussie, my lowest ping is 300ms).
My example with the alarm is the simplest way to failsafe this. There is no need at all for a loop - just for a check to make sure it goes through, and if not, run the healing alias again (which will either resend the eat command or a different one if things have changed). I use a wait state in a multistate trigger, but now that I know about #T+/- on alarms I would (if rewriting) use alarms, most likely.
If you consider it carefully, you can even work out how to use this predicted balance flag for protection against a few illusions. |
|
_________________ Athlon 64 3200+
Win XP Pro x64 |
|
|
 |
oldguy2 Wizard
Joined: 17 Jun 2006 Posts: 1201
|
Posted: Thu Nov 15, 2007 12:32 am |
| Quote: |
| That's why, instead of attempting to re-eat the same herb, you should run the central curing alias again. |
Well normally that is what I do, and I have it that way in the package that I use alarms in. Although I still don't know how to get around the ridiculous illusions which just get harder all the time. If it were not for those stupid illusions I could strip probably at least 30% of my package away and make it much faster. Serpents still obliterate me in combat and there is no way I can just manually try to cure because I can't keep up with the screen scrolling.
| Quote: |
| And why wait two whole seconds? If you have a low ping to your mud, 500ms is heaps, as Larkin said above. I use 1000ms (Aussie, my lowest ping is 300ms). |
My ping is 300-330ms too. So I am having to compete with people on 50ms ping times. It sucks yes, but it's a text game. I thought I could still play a text based game at least since there is such a small amount of data being transferred. Anyway, how are you using 1 second? Anything below 2 seconds and I end up double eating normally. Herb balance in the game is 1.5 seconds I believe. |
|
|
|
 |
Caled Sorcerer
Joined: 21 Oct 2000 Posts: 821 Location: Australia
|
Posted: Thu Nov 15, 2007 7:45 am |
#IF (@herbB && @pHerbB && %isnumber(@herbaffs)) {herbaffs=%sort(@herbaffs, 1) ; #EXEC %item(@herbaffs, 1)}
Once I've eaten, @herbB being 0 stops me eating again.
Until I have eaten, @pHerbB being reset to 1 makes me retry until I get confirmation of eating.
The only time I double eat is when a lag spike makes it take longer than a second for my command to eat to go through, and I can deal with that (so long as the affliction gets cured).
I've experimented with lower values as well. I used 800ms for ages, but put it up to 1s when my connection got a bit less stable.
As for illusion detection, this will detect illusions that mimick you applying a salve or eating a herb - illusions designed to convince your system that you do not have a particular balance. It can help separate reality from illusion because if the trigger fires while @pHerbB is 1, then it means you have never attempted to eat the herb, and that means you can flag it as illusion and ignore it. |
|
_________________ Athlon 64 3200+
Win XP Pro x64 |
|
|
 |
|
|
|