Trigger/Alias Optimizations

by Unknown

Back to Mechanic's Corner.

Unknown2007-04-26 17:36:10
What follows may be old news to everyone, and if so, just pat me on the head, smile, and continue your other conversations.

There are a lot of factors that affect the speed of your curing/combat system, but possibly the largest one is the number of active aliases and triggers that your system has at one time. Every time you type a command, or every time you receive text from Lusternia, your system has to scan all through your aliases or triggers to see if there's a match. Obviously, the more you can pare this process down, the faster everything will go.

What follows are a few techniques I've begun to implement in my own system, and I thought I'd share. I use MUSHClient and VBScript, but the principles apply to most clients, even if the code to do them is going to be a little different. If you have other tips and tricks to share, please post them.

1. Minimize your number of triggers and aliases.

The most straightforward operation. If you're interested in speed, trim your system down to just the triggers and aliases that you need. While it might be nice, for example, to have a trigger that always displays Shayle's name with little <3 hearts around it or an alias that changes your description as your character's hair grows out, they may not be worth the tradeoff for speed.

Most clients allow you to enable and disable triggers and aliases without deleting them altogether, so use this feature as a trial run to see if you really need that alias or trigger.

2. Make sure to set your "Keep Evaluating?" flag.

In MUSHClient, at least, you have an option on your triggers to allow them to keep evaluating or not. In other words, if MUSHClient finds a match that triggers your trigger, you have an option that tells it whether it should look for more matches (other triggers for that same text) or not.

This feature allows you to create several different triggers off the same text or pattern, or different triggers for a basic form of the pattern and more specific forms of it. However, if you know you only have one trigger that's going to fire for that text, make sure it is set not to allow further evaluation. Otherwise, MUSHClient will continue to check all your triggers for a match, unnecessarily.

3. Use labels and groups combined with dynamic enabling/disabling.

In MUSHClient, these are actually called Labels and Groups. In ZMud, they use Names for their labels and Classes (or "folders" - if you like that GUI so much, why don't you marry it?) for their groups.

By doing this, you can dynamically enable and disable individual or entire groups of aliases and triggers. Why have all your aliases and triggers active when at least some of them only apply in certain situations?

For instance, you might have a number of triggers and aliases with the "Bashing" group (mob-only afflictions, corpse and gold pickup, different sip percentages, different defs, etc.). You could then use an alias to enable or disable that entire group/class (NOTE: You can use the same alias to turn them on and off, rather than creating two for every enable/disable), depending on if you were bashing or not. If you're not bashing, why should your "gold sovereigns spill out of the corpse" trigger be active?

Obviously, there are other examples that could fit your situation. Do you know certain affliction triggers only come from druids? Give yourself the option to disable them if you're fighting a warrior. Are there certain abilities you can only use when in a certain form or bonded to a certain spirit? Keep those aliases disabled until you need them. Are there only certain things that can happen while influencing? While flying? While in the trees? You get the idea.

Triggers can also enable/disable groups, of course. If I'm in choke, I can trigger off the message to disable certain things I don't want to be doing in choke and enabling others.

While this may seem like a bit of pedantic overkill, keep in mind we're talking about maximization of speed. You may very well choose to leave some things active continually either because they need to be, or just out of convenience. If you have to make an alias that enables or disables one trigger, you haven't gained anything. But being able to fluidly reduce your number of active aliases and triggers could have a notable effect on your speed.

One last word about this - in ZMud, you can put classes inside of other classes, allowing you to disable and enable groups of groups, or a particular group if and only if it's inside another one. MUSHClient does not allow for this (to my knowledge). However, you can give your groups names that will allow you to do this. For example, rather than having just a "Bashing" group, I might have:

Bashing.Afflictions.Venoms
Bashing.Afflictions.LimbDamage
Bashing.Autosip
Bashing.Pickup

and so on.

This will allow me to, for instance, disable my Bashing autosip without disabling all my other Bashing triggers or my regular autosip. In fact, MUSHClient offers functions like GetTrigger(). This would allow me to loop through all my triggers and disable all the ones that have "Autosip" somewhere in the name, regardless of where they belonged in my hierarchy. (NOTE: This is generally not the most efficient way to do this, but it just shows that using "nested" names as described above can open up some other options for you. If anyone is interested in seeing sample code to loop through triggers and act on a part of their name, let me know.)

4. Use one-shot triggers and temporary triggers.

There are several circumstances where you need a trigger to fire only once in a particular situation.

Let's take corpse pickup, for example. Here's what I want to happen: After seeing the "You have slain..." message, I want to get whatever's in my target variable after I see the "You have regained equilibrium" message. I do not want to pick up that corpse every time I regain equilibrium. It is a one-shot deal after the "You have slain..." message.

There are different ways to handle this situation. A very popular one is to have a variable that tracks whether you need to pick something up or not. Let's call it: pickup_needed.

So, you have a trigger for "You have slain..." that sets pickup_needed = 1. Then, you have another trigger for "You have regained equilibrium" that, as part of its routine, checks the pickup_needed variable and, if it's 1, sends the "get @target" (or whatever you call your target variable) command and sets pickup_needed back to 0.

This is fine and this works. However, notice that you're having to check that every single time you gain equilibrium. Yes, you could help the situation by putting it in the "Bashing" group and disabling it until you're bashing, but even then, you're checking it every time you regain eq while bashing.

Both MUSHClient and ZMud allow you to create triggers that, once they fire, will delete themselves. This situation is perfect for such a trigger.

It goes like this:

1) Your trigger for "You have slain..." creates a temporary trigger for "You have regained..." that will get @target.
2) The very next regaining of eq, your temporary trigger fires, picking up the corpse.
3) The "You have regained..." trigger deletes itself, not to be recreated until another "You have slain..." comes along.

You could do essentially the same thing with enabling and disabling as well, but I like this method, personally, as it keeps my overall trigger list shorter and ties what I want to happen directly to the trigger that causes it, without me having to hunt through my various triggers trying to piece together what does what.

In MUSHClient, the syntax for creating a temporary trigger looks like this:

world.AddTrigger("your trigger name", "the text that fires your trigger", "your response", A list of options that match the various trigger options MUSHClient offers you, a custom color, which wildcard if any from your trigger you want to copy to the Clipboard, "name of sound file to play", "name of script function to call")

So, for my example above, I might do this when my "You have slain..." trigger fires (NOTE: I'm doing this from memory - I don't promise this exact code is perfect).

world.AddTrigger("pickup", "You have regained equilibrium", "get @target", eEnabled Or eExpandVariables Or eTriggerOneShot, -1, 0, "", "")

"pickup" is the name of my trigger.

"You have regained equilibrium" is the text that will fire it.

"get @target" is what I want to happen when it fires.

My options are to set this trigger as enabled, expand variables (so I can use @target in it), and to make it a one-shot (meaning it will delete itself after firing). There are many options here, including making your trigger temporary, so that it will last for the rest of your gaming session, but not save itself as a permanent trigger.

I set -1 because I just want the regular color.

I set 0 because I'm not pulling any wildcards from the trigger.

I left the sound file and the script function blank, as I don't need either in this particular example.

For a complete overview of this function as well as all your options and various examples in different languages, try: MUSHclient script function: AddTrigger.

You can also do these exact same things with aliases. Although a one-shot alias may not be that useful, temporary aliases that do not persist from game to game or that only exist under certain conditions might.

Using this technique for either a one-shot or a temporary trigger/alias is another tool to help you keep your permanently active triggers/aliases to a minimum. It's not a bad idea to include a bit of cleanup in your QQ or UnLoad routine to delete temporary triggers and aliases, just in case something didn't fire when it should or what have you. In MUSHClient, this is done with the DeleteTemporaryAliases() and DeleteTemporaryTriggers() functions. This is another nice thing about temporary triggers - if one screws up, the condition will not continue into your next game.

Anyway, like I said, these techniques may be old hat to many of you, but perhaps something in there may have sparked your creativity in streamlining your own system.
Unknown2007-04-26 18:13:17
I've never really noticed any slowdown due to the number of active triggers with MUSHclient, not even on my lousy old computer; however, there's definitely a big effect with zMUD. Keep in mind that the triggers that fire most often are those which match on your prompt - so you don't want many of those, nor to execute a ton of checks on every prompt. Disabling classes/groups of triggers when you don't need them is also an excellent form of illusion protection. When you're fighting a mage, having triggers on which respond to warrior/druid afflictions only makes you vulnerable to illusions.

For MUSHclient, I think - but could stand to be corrected by someone who knows better - that you're better off making functions in a script file and calling them from your triggers, rather than including chunks of code directly in your triggers. That's because the code in your script file gets compiled when you open the world, whereas the code inside triggers gets interpreted whenever the trigger matches. (Also, if you have any syntax errors in your script file, you'll get an error upon opening the world and have time to fix it, whereas if you have syntax errors in your triggers, you'll get errors when your triggers match - and mid-combat is not when you want to be seeing nasty dialog boxes.) I'm sure Zarquan can correct me if I'm wrong about that?
Unknown2007-04-26 18:21:56
QUOTE(vale_kant @ Apr 26 2007, 01:13 PM) 401886
For MUSHclient, I think - but could stand to be corrected by someone who knows better - that you're better off making functions in a script file and calling them from your triggers, rather than including chunks of code directly in your triggers. That's because the code in your script file gets compiled when you open the world, whereas the code inside triggers gets interpreted whenever the trigger matches. (Also, if you have any syntax errors in your script file, you'll get an error upon opening the world and have time to fix it, whereas if you have syntax errors in your triggers, you'll get errors when your triggers match - and mid-combat is not when you want to be seeing nasty dialog boxes.) I'm sure Zarquan can correct me if I'm wrong about that?


I can't speak for the other MUSH languages, but VBScript isn't compiled. However, I agree that for organization, modularity, repair, and that error-checking you mentioned (especially as it happens before it gets used), it's a good idea to tuck away code into a script file and keep your triggers lean. I'm unsure if that's faster than having the code in the actual triggers (it may be, but I could also see it being slower - I don't really know), but for anything that'll take me more than one or two lines, I like to send it to script.

What they need is that error-checker that'll look over your code and say, "Ah, I see that you're trying to do THIS, but you're doing it wrong." wink.gif
Unknown2007-04-26 18:26:29
Putting code in files is marginally faster because it's parsed when loaded rather than when executed. It's not necessarily compiled either way, but it always has to be parsed and stored.

Nick fixed the nasty modal error dialog problem (after I begged him for a while to fix it) by allowing you to display errors inline with your output instead. Very nice change!
Unknown2007-04-26 18:28:10
QUOTE(Zarquan @ Apr 26 2007, 01:26 PM) 401894
Putting code in files is marginally faster because it's parsed when loaded rather than when executed. It's not necessarily compiled either way, but it always has to be parsed and stored.


Just found this response from Nick on a question as to whether having code in the alias or in a script called by the alias is faster. He says:

QUOTE
It is faster (I'm not sure by how much) to put the script into the script file (and not have it directly in the alias).

The reason is compilation time - the script is compiled (checked for errors, comments discarded, etc.) once if in the script file, but every time if it is directly in a trigger/alias/timer.

The difference may be minor for small scripts, but would be noticeable for a lengthy one.


He's using the word "compiled" in sort of a weird way, but he affirmed your point, Vale.
Charune2007-04-26 18:28:17
Is anyone solidly using CMUD and if so what are the speed improvements of the events like?

Btw, this is another very good thread, keep at it.
Unknown2007-04-26 19:02:59
I know they're not actually compiled. The word I was looking for is "parsed", but I'm not fluent enough in computerese. icecream.gif
QUOTE(Zarquan @ Apr 26 2007, 07:26 PM) 401894
Nick fixed the nasty modal error dialog problem (after I begged him for a while to fix it) by allowing you to display errors inline with your output instead. Very nice change!

OMG, thank you! I hadn't even noticed that option, had to go looking for it pretty closely in fact. For anyone who's interested, go to Game > Configure... > Scripting and check "Note errors". Down with the nasty error dialogs! Woot! (This also means errors will show up in logs now... Yay!)
Unknown2007-04-26 19:57:38
I haven't noticed a huge problem with this even in zMUD, but I'm using a pretty fast computer so I might not. All of these are very good suggestions. I used to have problems with my systems where I used variables to check whether I wanted to act. For example, when harvesting, I set a variable to true, then every time I regained balance I checked that variable and tried to harvest the herb again. Even beyond the speed issues, when I rewrote everything to simply enable/disable classes and create alarms (one-time triggers, for those who don't speak zMUD-ese), it cut down on a lot of errors and other inefficiencies (the variable getting set sometimes when I didn't want it to, etc). Even if you have a fast computer and don't have major speed issues, it is well worth minimizing your aliases and triggers just to make for cleaner scripts, easier debugging, and fewer accidents.
Theomar2007-04-26 20:23:34
Also, for MUSHclient, if you aren't an extremely good programmer, Lua is the way to go.

Its .dlls come pre-loaded, which makes it virtually no processor time when you use it. Aside from that, Lua is an incredibly fast language already (I believe it was clocked at 9mil checks in a second, or there abouts), and it is an OOP language, which can be a heck of a lot faster than a non-OOP language, if implemented correctly.

Basically, Lua is the way to go for novice programmers who don't know the run-time efficiency differences in uses (i.e. using the Bubble sort or te Quicksort in a table).

Also, MUSHclient *really* starts to slow down at about 1000+ triggers, if they are all running at the same time. I know that on my computer, I can't do anything when I have 1.7k triggers running.
Unknown2007-04-26 20:26:32
CMUD is ten times faster than zMUD, when it works right. If you write new scripts for CMUD from scratch and make use of the new engine features (local variables, events, #SWITCH, etc), you'll notice the biggest improvements in speed then. I do not recommend that anyone attempt to convert their old zMUD settings file with CMUD's conversion utility unless you just want to see the compatibility report for your own curiosity.

Zugg says there's a pretty good chance that he'll add Lua to CMUD, so I'm definitely looking forward to that.

Optimizing loops so that you're looping on the least amount of iterations (use your current afflictions list rather than a list of all possible afflictions, for example) and reducing the number of alias/function calls are two very big speed boosters.

Triggers firing in zMUD can be a real drag, especially when you've got triggers for just about everything that could ever happen in Lusternia.
Unknown2007-04-26 20:30:17
QUOTE(Theomar @ Apr 26 2007, 03:23 PM) 401945
Its .dlls come pre-loaded, which makes it virtually no processor time when you use it. Aside from that, Lua is an incredibly fast language already (I believe it was clocked at 9mil checks in a second, or there abouts), and it is an OOP language, which can be a heck of a lot faster than a non-OOP language, if implemented correctly.


Lua is OOP?

Now, that's interesting.

That's a good enough reason to switch right there, in my book.
Unknown2007-04-26 20:32:17
Lua isn't truly 100% OOP, but it allows you to "fake" objects and class-like behavior with special syntax and the use of the self variable.
Unknown2007-04-26 20:37:33
QUOTE(Zarquan @ Apr 26 2007, 03:32 PM) 401953
Lua isn't truly 100% OOP, but it allows you to "fake" objects and class-like behavior with special syntax and the use of the self variable.


Some of the Lua pages seem to indicate that you can communicate with DLLs using Lua. Am I reading that right?

So, could I create a bunch of classes in C# and use them in Lua?
Forren2007-04-26 20:41:10
QUOTE(Zarquan @ Apr 26 2007, 04:26 PM) 401949
CMUD is ten times faster than zMUD, when it works right. If you write new scripts for CMUD from scratch and make use of the new engine features (local variables, events, #SWITCH, etc), you'll notice the biggest improvements in speed then. I do not recommend that anyone attempt to convert their old zMUD settings file with CMUD's conversion utility unless you just want to see the compatibility report for your own curiosity.


What causes the efficiency improvement from using events? I can see the improvements from #switch and local variables, but I'm not sure how an #event speeds it up.
Unknown2007-04-27 01:18:27
QUOTE(Demetrios @ Apr 26 2007, 04:37 PM) 401956
Some of the Lua pages seem to indicate that you can communicate with DLLs using Lua. Am I reading that right?

So, could I create a bunch of classes in C# and use them in Lua?


.NET assemblies aren't standard DLLs that you can load in something like Lua (which is written in standard C). You can, however, write your own DLLs that export functions compatible with Lua. Using C is the easiest way to interface, but other languages have been used successfully with the right abstraction layers.

QUOTE(Forren @ Apr 26 2007, 04:41 PM) 401957
What causes the efficiency improvement from using events? I can see the improvements from #switch and local variables, but I'm not sure how an #event speeds it up.


The efficiency of events comes from the change in logic (paradigm shift, to use a common cliche) over the non-events way of doing things. You can break apart an alias or trigger into several tiny event handlers that do things in turn and then have better control over which pieces execute when.

The real power of events comes in the decoupling, though. If you raise an event for something instead of calling an alias, you allow other packages/modules to hook into that event very easily. For example, I might have a module for tracking wounds based on hits taken and raise events for every time I'm wounded. I could then write a completely separate, and optional, package that handles those same events to display messages to me about the wounds or update a status window display without interrupting the tracking and curing at all.
Razenth2007-04-27 02:07:01
On a 2.0 Ghz computer with around a gig of RAM, Eth's basic system noticably lags my system. Not much, maybe .2-.5 seconds, but it's very noticable if you compare the return speed with system on to return speed with some bashing triggers on. Thanks. Every bit of speedupness helps.
Rothmar2007-07-19 11:49:32
Sorry if this is irrelevant to the topic, please move this elsewhere if so.

With proper scripting, can CMUD be as fast as Mushclient?

And is the speed significant? I'm a novice and I don't know which client to use (assuming I'd need to learn about the lua language and such for Mushclient)

Also, in zmud I can do input the command #30 eat mushroom, to eat a mushroom 30 times.
Is it possible to do that in mushclient? I can only use that during speedwalking #3 north, for example.

Thanks
Unknown2007-07-19 14:06:31
CMUD is much faster than zMUD, and has more features already than zMUD has. Comparing it to MUSHclient, however, depends on what you look for most in a client. Personally, I love the GUI in zMUD/CMUD and the ease of scripting most things in them. Yes, MUSHclient is very fast and has great support for third party scripting languages (and is now freeware), but every client has advantages and disadvantages.

I'd say that if you're a novice coder, you'd do better with CMUD. (It will have Lua on the 2.0 release, if that's a selling point for you.) If you are familiar with coding and/or would like to spend the time and effort to learn, MUSHclient is a great, free client.

You can make an alias in MUSHclient that emulates the "#30 eat mushroom" syntax, yes. I believe it's been posted on the MUSHclient forums once or twice, so you might want to search over there for it.
Unknown2008-05-31 22:13:56
Sorry for resurrecting an old thread, but I figure this would be a better option than making a new one, since it deals with optimization.

For MUSHclient, would it be better to have a trigger that can match all of the lines of of an affliction (i.e. it would match (^You gasp as your fine-tuned reflexes disappear into a haze of confusion$)|(^You lash out clumsily)) opposed to having two triggers?
Unknown2008-06-01 11:28:50
If MUSHclient supports atomic grouping in the regex, I'm more inclined to say you should use one trigger instead of several. Otherwise, I'd go with the several.