I have multiple characters on my mud, not on at the same time but using the same world file. I know I could make multiple worlds with seperate .mca and .mct files. But
many aliases/triggers need to be shared between characters while others do not. And some aliases and triggers would outright conflict if they were enabled for one character but I am logged in with another. Plus I have some triggers and aliases that might be complex and time consuming to make but only used very, very infrequently.
This leads my trigger and alias setup menu to be extremely cluttered and confusing. I tried sorting some triggers that don't need the option of sequence to different values but bleh that's not a real solution.
I'm sure I could write a startup script that enables and disables the relevent and irrevelent triggers and aliases based on the character I log in with, but that doesn't really solve the clutter issue. (Okay I'm a moron, edited similiar but the wrong triggers and aliases too many times :D )
I use the 'group' option in triggers very frequently. Is there a way to assign a trigger to be included in more than one group? (without having to make a second trigger, trying to reduce clutter here :) )
And that second group name in my case would be character specific. Enabled many triggers in many differnt groups under this 2nd group name umbrella. For example in taxonomy, all groups of mammals and all groups of fish are still grouped in eukaryots. heehe. Eiither a hierarchy or just a second group name.
Second that group name could also be used to 'hide' unneeded triggers and aliases etc. world.hideTrigger, world.hideTriggerGROUP :D
It would be automatically disabled and out of sight (but would still save and load) on the setup menu. Of course there could be a show all option on the setup too.
The solution to your problem is plugins. You can take things out of worlds and into plugins easily with the plugin wizard. Make the plugins with triggers for multiple players in their own plugin, a core plugin, or leave them in the world. Then have a plugin for each character.
Actually, wait. Youre still using mca and mct? UPGRADE your mushclient to the XML version. Unless theres some pressing reason not to.
While talking about other solutions, Why exactly do you want only one world for the multiple characters? You can just open new worlds, and preload them from this existing one (it basically copies the world) then delete what isnt needed, etc.
"Actually, wait. Youre still using mca and mct? UPGRADE your mushclient to the XML version. Unless theres some pressing reason not to."
I am using 3.49. I always save the world as .mcl but I thought the triggers and aliases were saved in the .mct and .mca?
Thats the only option I see here.
"
While talking about other solutions, Why exactly do you want only one world for the multiple characters? You can just open new worlds, and preload them from this existing one (it basically copies the world) then delete what isnt needed, etc."
I understand how to open new worlds, that how I started off from the original character. But I don't want multiple worlds because if I make a new trigger or alias that I want for all the characters, how do I export it easily into the other character's worlds?
I'll take a look at the plugin option. I've only used the .xml files such as chat and calender, never the wizard thanks.
No, we dont use mct or mca anymore. Its all stored in your world file, in XML format. Technically you could include the things you want, directly into the worlds, but plugins would probably be more modular for you with a world file that shares everything.
What I do is make a file with all the triggers/aliases/whatever, for example one for the thief characters, one for the cleric characters, etc, and include each one in the world file. That way, when I change a trigger in the world file, it is changed in all the relevant worlds. Is that what you want to do?
I am using 3.49. I always save the world as .mcl but I thought the triggers and aliases were saved in the .mct and .mca?
Thats the only option I see here.
The .mct and .mca files are not *primarily* used to save your triggers and aliases, they are part of your world file. They were originally intended to allow you to export (say) your triggers from one world and import them into another.
However nowadays a simpler way is to simply highlight the ones you want, click the "copy" button, and then go to the other world and click the "paste" button. That copies them via the clipboard.
However as the other posters here have said, plugins are far simpler for this purpose. You can use the plugin wizard to select a batch of triggers, aliases etc. (you can sort by group during this process) and then generate a plugin from them. It is just point-and-click unless you have scripting as well.
Then that plugin can be used in other worlds. In fact by making it a "global plugin", things that you always want can be automatically available in all worlds.
In the early days of plugins, I had an Email discussion with Nick, which I ended up posting to the forums. In it, I discuss my philosophy that World files aren't really World files, but rather, Player files. (Or at least, it's best to think of them that way).
Optimally, one would do best to put 'stuff' that is appropriate only for a single character into one "World" file [per player]. Any other 'stuff' that could possibly be shared amongst different players, you put that stuff into Plugin files. The others here have already said that.
Anyway, here's the link to me saying it first, a long time ago. Heh. ;)
Quote: The biggest challenge to all of that is the interdependency between the plugins. All of those proposed plugins require certain levels of interdependency on the others. The "monitor" calls the "Spellqueue" when my health drops too low, the "Autobots" only function when they are toggled on, and depend on "Monitor" data, and issue calls to the "SpellQueue", etc. Given the stickiness of making cross-plugin calls, this could require some interesting and
innovative subroutines. Also, because of that interdependency, I pretty much need all 3 to in production at the same time, else I go without a lot of functionality while I develop the next one. It's enough to make me want to just keep them all in the current state they are in... but again, it's not practical if I want to develop ALT characters.
Heh, since we are having flashbacks... I'll bring up another thing in this connection - the EventManager plugin. Here's a snippet from one of my EventManager instances, which allows a plugin to report a certain value changing to any other plugins (IDs - since the world can be called also) and point those other plugins in the direction of that value, so they can use world.GetPluginVariable() on it.
'Register a new event
RegisterEvent "OnVariableChanged"
'Register a plugin to listen for the event, supplying the plugin's ID and the event's name.
'The listening plugin must have a sub with the same name as an event it is listening to and
'this sub must accept exactly 1 argument, otherwise the event will cause an error
RegisterListener "9ba81f81508fb8a03adac921", "OnVariableChanged"
The plugin being called is responsible for knowing how to handle the offered value, and therefore should only retrieve the contents of variables whos names it is familiar with. The event includes the caller's ID and a variable name as a '_' delimited list.
Since all the EventManager needs to know in order to keep the whole setup running are IDs, you can even go so far as to create multiple "instances" for the same IDs, by assinging different names to plugins but keeping their IDs, each slightly modifying the ID's behaviour. If you plan it right, you could even make a plugin to switch alts on the fly by switching ID implementations with a single alias, though that would require that your world file contains ONLY stuff that is 100% guaranteed to be used by all your alts unmodified, meaning - practically nothing, which is about as much as mine does nowadays ;)
Hmm. I don't remember the specific post, but a method using alises within plugins made this more or less redundant. In most cases things like a change to a variable are handled by the function/application in which the change happens. The suggested solution is for you to add something like this to any plugin that needs to be informed of the activities of anther plugin:
<aliases>
<alias
script="HandleEvents"
match="PluginEvent * *"
enabled="y"
omit_from_command_history="y"
omit_from_log="y"
omit_from_output="y"
ignore_case="y"
keep_evaluating="y"
sequence="1"
>
</alias>
</aliases>
sub HandleEvents(aname, output, wilds)
Firedfrom = wilds(1)
if instr(wilds(2),"Partlist") = 1 then
Params = split(wilds(2),",")
...
end if
...
end sub
Then in the sub that fires an event, for instance:
It is up to the plugin designer to figure out what information needs to be sent, how it is sent, how the plugins that recieves it deal with the information, etc., but you are not limited in the same way as CallPlugin for handling the information. You could even make the handler optionally look for multiple wildcards, instead of just two, so each piece of information arrives seperately, no need to split it. It will simply ignore events that don't match that pattern.
In any case, this trick kind of makes a seperate event handler useless. The only real reason for such a handler would be to capture events from programs called from the script, a capability that MS bent over backwards to prevent you from doing from inside of a script. I haven't found a solution to that one yet, short of the lame method of creating another program/dll to sit in between, that 'can' respond and talk to Mushclient. This is really lame in cases like WinAmp, where such a middleman becomes just one more thing that can fail in a process. A process that starts with Mushclient talking to a script, which would talk to your middleman, which talks to a middleman designed to talk to WinAmp, then finally WinAmp itself. lol Heh.. I've got an idea... Lets stick a 6th or 7th application into the works and see if we can crash things even faster. ;)
But seriously, an event manager for external apps would be nice, but you can build one for plugins that is very easy, if not necessarilly standard for every person designing one.
We are actually talking about the same thing, which is this:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE muclient >
<muclient>
<plugin name="EventManager1" author="Keldar" language="vbscript" purpose="Managing custom script events"
save_state="y" date_written="2004-03-11" requires="3.32" version="1.0" id="fc8161da9a2a5d7f56a03600">
</plugin>
<aliases>
<alias
name="register_listener"
match="fc8161da9a2a5d7f56a03600 reg_listener */*"
enabled="y"
sequence="0"
send_to="12"
><send>call RegisterListener("%1", "%2")</send></alias>
<alias
name="register_event"
match="fc8161da9a2a5d7f56a03600 reg_event *"
enabled="y"
sequence="0"
send_to="12"
><send>call RegisterEvent("%1")</send></alias>
<alias
name="dispatch_event"
match="^fc8161da9a2a5d7f56a03600 dispatch_event (.*?)/(.*?)$"
enabled="y"
regexp="y"
sequence="0"
send_to="12"
><send>call DispatchEvent("%1", "%2")</send></alias>
</aliases>
<script>
<![CDATA[
option explicit
dim evt_list
set evt_list = CreateObject("Scripting.Dictionary")
'Register a new event
'RegisterEvent "OnPrompt"
'Register a plugin to listen for the event, supplying the plugin's ID and the event's name.
'The listening plugin must have a sub with the same name as an event it is listening to and
'this sub must accept exactly 1 argument, otherwise the event will cause an error
'RegisterListener "9ba81f81508fb8a03adac921", "OnPrompt"
sub RegisterListener (id, evt)
dim listeners, new_listeners
if evt_list.Exists(evt) then
listeners = evt_list.Item(evt)
if ubound(listeners) > 0 then
new_listeners = join(listeners, "_")
new_listeners = new_listeners + "_" + id
evt_list.Item(evt) = split(new_listeners, "_")
else
new_listeners = id
end if
evt_list.Item(evt) = split(new_listeners, "_")
else
world.Colournote "red", "white", "EventManager plugin: " & evt & " event not registered"
end if
end sub
sub RegisterEvent(evt_name)
dim listeners(0)
if evt_list.Exists(evt_name) then
exit sub
end if
evt_list.Add evt_name, listeners
end sub
sub DispatchEvent(evt, args)
dim listeners, listener
if evt_list.Exists(evt) then
listeners = evt_list.Item(evt)
for each listener in listeners
world.Execute listener & ":" & evt & " " & args
next
else
world.Colournote "red", "white", "EventManager plugin: " & evt & " event not registered"
end if
end sub
]]>
</script>
</muclient>
I think that the handler is needed for 2 reasons:
1) it provides a central spot for declaring both events and listeners associated with them, thus letting you see the entire infrastructure at a glance;
2) it ensures that the events stay as generic as possible - it's really simple to get carried away with calling between plugins and end up in the same situation of not knowing which plugins need each other to work, and event handler introduces a middle layer that mediates between the plugins and hides them from each other, keeping them independent at least at the most basic level.
There's no need to mess with 3 plugins that depend on some other plugin, just because that latter plugin needs to be changed - you can just change the plugin and replace the old version when done, since EventManager enforces a protocol for firing and capturing events that you can't go around without rendering your plugin incompatible with the rest of the system. And passing IDs in event arguments is also essential, since it ensures that no plugin needs to know about any other installed plugins and at the same time can expose itself for whoever would be interested in noticing it, as in the example from the previous post, where a plugin notifies any listeners about one of its variables changing, while listeners don't have a clue about that plugin's existance and don't care if it suddenly disappears and is replaced by something completely different, even with a different ID, as long as it exposes a variable with the expected name.
Hmm. What I've actually done is simply duplicate the information tracking in each plugin, generally with the exact same triggers & script within two or more plugins. Although this may not seem efficient, it is more user friendly, since other people using your plugin(s) don't need to worry about interdependency. I don't do this often anyway. Generally, just the health\mana tracking.
Now, I still may have one plugin make a call to another plugin's routine, but for that I simply use "CallPlugin". If the target plugin is not installed, then the call silently fails, and it's no biggie for the user. I guess that's because of the nature of my plugins.
One example of this, is my "Statline" plugin. Almost all of my other plugins will make a call to my "Statline" plugin to display information on the status line. However, if "Statline" isn't installed, then I guess the user doesn't care for that feature. No catastrophic failures, which is nice. :)
Well Ked.. I admit that as a plugin yours could act as a middleman for all other plugins. One built into Mushclient would add an extra layer of complexity though that is quite silly, especially since mine works well enough, though imho I forgot that you at minimum need the plugin firing an event to also capture them, if for no other reason than to make sure at least one alias matches and the text isn't sent to the mud.
A better implimentation would be a special callback/Mushclient command:
sub OnPluginHandleEvent (ID, Type, Data)
...
end sub
Boolean = FireEvent(Type, Data)
If true, something responded to it. Their is no need to register anything in this case. Mushclient should know which plugin fired the event and thus what ID to send the plugin. "Type" would be a string like 'changed', 'attacked', etc. Basically a short description of what is happening. Data would be a joined array or single string containing what ever data the plugin needs to recieve.
The point here is that there is absolutely no reason in either my design or something built into Mushclient "must" register which plugins need to recieve it. Mushclient can call every plugin with a OnPluginConnect in it, there is nothing preventing it from automatically calling a OnPluginHandleEvent function the same way. I admit that is would definitely make things more consistant, where my idea works exactly the same way, but requires everyone to use the same aliases or it won't work at all.
In fact.. On thing I have wondered for a long time is if you can take an object from another program, like say the script, and register it with your own internal event manager in a second program. This would kill two birds with one stone. Plugins would automatically have their FireEvent calls responded to, but objects that fire events, but VBScript and the like currently can't recieve, could be responded to as though they where registered to Mushclient, instead of the script itself.
In effect, the same OnPluginEvent could handle both script generated events needed for interplugin communications, but also events from objects that are normally unable to get the script to respond. The only drawback is that such objects "would" need to have CaptureEvents(<object>) and Release(<object>) commands, since you would need to make Mushclient aware of them. I need to look around again and see if I can find out if this is possible. However, I suspect it may be, I am just not sure if it is as simple as I hope...
A better implimentation would be a special callback/Mushclient command:
sub OnPluginHandleEvent (ID, Type, Data)
...
end sub
Boolean = FireEvent(Type, Data)
If true, something responded to it.
How do you know it handled it in this case? Perhaps a better method would be:
Function OnPluginHandleEvent (ID, Type, Data)
if ' I handled this event
OnPluginHandleEvent = vbTrue
else
OnPluginHandleEvent = vbFalse
end if
end Function
Boolean = FireEvent(Type, Data)
That way the OnPluginHandleEvent function can indicate if it was interested in this particular event. By returning false (which would be the default) MUSHclient can assume that the plugin didn't handle it, and call another plugin's OnPluginHandleEvent until it gets a true return.
Quote: That way the OnPluginHandleEvent function can indicate if it was interested in this particular event. By returning false (which would be the default) MUSHclient can assume that the plugin didn't handle it, and call another plugin's OnPluginHandleEvent until it gets a true return.
Do I sense an implication that as soon as a plugin handles the event, that event's propagation is stopped? If so then it wouldn't be very consistent with the idea of events.
Yes. Events don't care if they are recieved and are never responded to unless by the responding function firing another event. I was thinking of it as a case of if any single plugin's event manager was successfully called, it might be useful to know if something actually responded. This is actually sort of stupid, since the only time it would happen is if you had a plugin that actually recieved the event, in which case it is true, even if not handled. As for your suggestion Nick, that is even worse, since one plugin could respond to the event and set it true, then the next plugin could decide that it didn't like that event and return a 'false' value. You would have no idea if it was caught or not. Better to just make such a system work like normal events and only fire the event, and leave it up to the plugins to tell the event sender, 'Heh, I heard you!" in some way. For instance a standard 'event' that plugins may want to respond to would be:
sub OnPluginHandleEvent (ID, Type, Data)
select case Type
case "GetID"
World.FireEvent("ID", PluginID)
end select
end sub
And the plugin generating the event would do:
sub OnPluginHandleEvent (ID, Type, Data)
select case Type
case "GetID"
World.FireEvent("ID", PluginID)
case "ID"
setvariable "Plugin_Count", getvariable("Plugin_Count") + 1
end select
end sub
sub CountPlugins
setvariable "Plugin_Count",0
World.FireEvent("GetID", "")
end sub
Of course you can do this another easier way, but the point is that you have no idea how many, if any, event managers will respond, so the boolean idea was really dumb of me. Can't always be right. ;) lol
Hmm. True, in an OS you can capture an event and then remove it, so other applications don't recieve it. In general though, this is not usually done, since a message like "close all" could then be itercepted and disabled, before the OS successfully shuts down all the applications. This is frankly a bad thing. In the case of this idea, you may end up with some clown always returning true whenever his plugin recognizes an event, then wondering why the heck his new plugin or someone elses, which just happens to use the same event name, doesn't work. Or worse, they could stupidly return true for every event, and prevent all other plugins from working when theirs happens by chance to be the first one to recieve it. I think one that you can't intercept and cancel is much better in this environment imho.
Quote: since a message like "close all" could then be itercepted and disabled, before the OS successfully shuts down all the applications. This is frankly a bad thing.
No it's not. There are many times when you might want to have a program intercept the close-all and cancel it under some circumstances. If the OS really wants things to die it can just kill the process.
Same for a plugin - there might be really good reasons for a plugin to cancel the close-all (or whatever) event and not let other people see it.
Basically what it comes down to is that yes, if programmers do something stupid then stupid things happen; but isn't that always the case? :) In any event it would seem that you're removing a lot of power and simplicity for the sake of making something fool-proof. If somebody writes a plugin and does something stupid like that, do you really want to be using the rest of their plugin?
Do I sense an implication that as soon as a plugin handles the event, that event's propagation is stopped? If so then it wouldn't be very consistent with the idea of events.
I don't see how the FireEvent call can return "true" to indicate the event was handled if the event handler doesn't return true (I handled it) or false (I didn't).
Merely having an event handler hardly guarantees the event is handled, especially if every plugin has a OnPluginHandleEvent function in it.
Then the question arises, once an event is handled, do you keep trying other plugins? It seems to me the answer would be "no", because "handled" implies "dealt with". This is actually quite consistent with the way a lot of Windows event handlers work. Events are passed to various places in the hierarchy (eg. child window, parent window, document, application) until something says it has handled it.
Say you had an event handler for "I am poisoned". Once a plugin deals with it (eg. "drink green potion") you don't want it re-handled.
*However* a plugin can always "do something" but still return false. eg. Plugin A might log to a file that you had drunk poison (and returns false), but Plugin B actually drinks the antidote.
Quote:
In general though, this is not usually done, since a message like "close all" could then be itercepted and disabled, before the OS successfully shuts down all the application
No, this is not true. For instance, if you shutdown MUSHclient, and a world asks you to save your changes, but you cancel the save then the shutdown itself is cancelled. In effect you have changed your mind.
Quote: Then the question arises, once an event is handled, do you keep trying other plugins? It seems to me the answer would be "no", because "handled" implies "dealt with".
That's exactly the contrary of how I use events currently. If I wanted to have a plugin as a container for a large number of triggers that almost never get modified and only exist for the sole purpose of tracking a number of certain boolean states, and use that plugin to notify others about those states changing then I would want the "StateChangeNotify" event to propagate as long as there are still plugins left which want to hear it. That unties my hands, since I've dealt with the problem of saving states once and for all and from now on I don't have to worry about them when adding plugins which require state information - I just add the plugin with the event handler, implement the processing and storage of the states locally (using a dict, array, list, struct - whatever) and go on my way of doing something productive. If events would block however, I would need to either do what Magnum suggests - duplicate the state saving mechanism (with over 200 triggers, and multiple scripts) in every plugin, bundle everything into a single plugin (in which case it would make more sense to just keep it in the world), or go through every existing plugin which handles the "StateChangeNotify" event and make sure it doesn't block. Another example is the prompt - I have a small plugin for the sole purpose of capturing the prompt using OnPluginPartialLine and don't have to worry about it anywhere else. However, if blocking events is just something that can be done optionally, then I suppose it wouldn't be such a big problem. I see it as something that would have few uses, since the possibility of screwing up later by blocking something now is likely to keep most people from using that option, but if it would provide something useful in at least one case of a few hundred then I suppose you might as well have it, actually - you probably should have it.
It is really a semantic issue. "Handled" means in this context "completely handled".
So, if a plugin wants to indicate that it is to be "the end of the chain", then it returns true.
Something like your "state change" would go to handlers that would return false, so the state change goes to other handlers. This is simply a convention you would expect them to follow, in much the same way that you are expecting the handler to be called in the first place.
This all follows on from the original idea that a "fireevent" calls gets told whether or not the event was handled. I think for this to have any real meaning you need to indicate that the event was handled, and I think in this context, "completely handled".
Umm. Well Nick, the only major issue with having it return anything at all is that some events you may 'want' to have all available plugins respond to. For instance, while the plugin counter one was silly, lets say I have a plugin that allows you to set a global 'theme' in all plugins I create. This would set the default colors to be used by errors and various types of output. I could have my master plugin send out an event FireEvent("ChangeErrorColor", "Gold"). All plugins that recognize this 'need' to handle it, not just the first one that happens to see it.
However, it is likely useless for saving execution time. For two plugins, there is no certainty as to which one Mushclient will call first, so only 1/2 of the time will it actually save time. For 3, 4, 10, 20.... The more plugins, the less often you can be sure that it will stop execution and save time. After all, there are no priority options for plugins, they get called more or less randomly and there is no guarentee that the right one to kill the event won't be the very last one called.
However.. After some thought, there may be some cases where it would be helpful. A notice about the plugin and who made it for instance.. You may want to generate an ANSI graphic when the first plugin loads, along with a notice of what it is called, but also fire an event to tell all following plugins to respond with their names. However, this would mean that the event must fire 'after' all autoloaded plugins are installed, but 'before' any other plugins have a chance to generate a timer delayed message of their own for it to work. But.. Another method could have all plugins respond to an event to inform the first one that it needs their names. Then its own timed delay to send the needed info to the screen, including the names. This still requires that the very first event only happen 'after' mushclient has completed startup procedures, since the event needs to be recieved by all other plugins, so they know to fire back their names and disable their own output of the notice.
So.. Ok, I agree, there is a valid use for it. Though it isn't really quite the same as if you where in an OS, where you have multiple priorities, several execution layers and something on a higher level has to respond 'before' the lowest one deals with it instead. Everything in plugins will be the same priority and layer, with no certain order in which the are called.
Quote: All plugins that recognize this 'need' to handle it, not just the first one that happens to see it.
No problem... you just don't mark it as handled. And if a plugin is 'gobbling' events, either that is intentional for the plugin or is a mistake.
Quote: Everything in plugins will be the same priority and layer, with no certain order in which the are called.
Just because YOU don't think you will have layers doesn't mean that other people will want layers. Besides, I suspect that if there weren't layers, then at some point you would start complaining about plugins not being able to intercept and halt events. :-)
Sure, only how about doing it as with OnPluginLineRecieved - you set it to false if you don't want the line displayed, and if you don't set it to anything it displays the line. That makes omitting the line an option, rather than an equal possibility to the one of displaying the line. Since plugins are likely to allow the events to "fall through" more often than block them, I think it would make sense to have the "fall through" behaviour as default, and "blocking" as optional for them.
Umm. Do you know something I don't Ksilyan? lol As they stand there is no priorities for plugins. And even if there was, it isn't like an OS, there can't be a layer lower than that of the plugins themselves, unless it is mushclient. Mushclient won't be responding to or interpretting such events. As for execution priority, that works for triggers within a plugin, where the order they execute can be predefined, but how exactly do you correctly set the priority for someone elses plugin? You can't. Therefor the idea of halting an event, unless all plugins involved know it and what they should do with it is total nonsense. It isn't unbelievable that someone may define a set of stadard events for plugins to respond to. If you tried to intercept and stop one of those, it would never work unless you as the plugin designer could be 100% certain that someone elses plugin wasn't assigned a higher (or even the highest) priority.
Frankly, it makes a lot more sense to work around the issue by not messing with interception in the first place, save in specific cases like my example, where it is not intercepted and stopped, but sent 'in the first place' as a "halt your normal task" type message. I see no practical way to layer plugins that won't break the feature. For instance, if you placed plugins in groups, then only allowed priority and the reception of events to that group, you could produce a layer system, but it breaks the ability to send out universal events based on a standard that *doesn't* require them to be in that group, but only using some accepted standard for the event. Letting someone add their plugin to the group then potentially breaks things too, since they may incorrectly choose the wrong priority and end up responding to the command, even though it was supposed to be intercepted and blocked.
Of course, a possible solution to this is to have both FireEvent and FireGroupEvent. Both types could still be recieved through the same sub, but the second one would allow the priority within the group itself to determine which one reieved it first. Priority in this case is irrelevant and unrealiable for FireEvent though, since the actually sequence they are expected to be checked in could be vitually anything in the global list. One person could set them to run from 0 to 5, someone else from 10-50, stepping up by tens. The result would be totally unpredictable, so I would suggest under such a design that returning True or False be totally ignored in the global FireEvent system, but halt farther attempts in the Group layer, where it serves a real purpose.
Unless you can describe some way Ksilyan, to make the behavior predictable in a global scope? I don't see how, since unlike triggers, you can't just pop into the editor and adjust the sequence number if the XML someone gave you breaks something. Plugins or groups of them should behave predictably the moment installed, without the need to tweak them. The most predictible behaviour is to ignore the True/False value in a global event imo. Anything else will break and require the poor guy that just downloaded it to edit the plugin file to fix it.
I just can't help but think that you're limiting something just because you personally can't see a use for it and therefore you don't think it should be there at all. I think that the examples Nick gave are good; you might want a 'controller plugin' that receives an event and stops everybody else from receiving it in certain cases.
Yes, people could set things up to work in a weird order, but that would sort of be their problem now wouldn't it? You can also incorrectly install your OS applications just as when you write code you can make mistakes that break things.
How are things predictable in Windows? It would suffice to emulate the way many operating systems handle prioritization in event handling, and there you go.
Regardless, if you don't like it, why not just return false? And if somebody gives you a plugin that DOES stop the event, then there is likely to be a reason they did that.. and if it bothers you, you can simply go change it to false.
I haven't really read about a quarter of the posts in the detail I should've, however I think the order thing is a valid concern.
As far as I'm aware, plugins are evaluated in a somewhat arbitrary order.
So one could never guarentee the same set of plugins would function the same on different computers, or possibly even on the same computer, at different times.
I'm not sure how, if this is the case, any sort of function like this would be viable. You could never really depend on it to stop all the other plugins.
Maybe I'm reading this all wrong, heck, I haven't payed enough attention to really know what this is all about.
If someone would like to explain to me both in a nutshell, but more importantly address the concern of order, It'd be much appreciated.
Like I said. It isn't practical or viable for cases of 'all' plugins. Even with an OS, there is a distinct 'service' level that only specific applications run in, because they 'require' the ability to respond before normal applications get a chance to recieve an event. Such a service isn't generally made into one unless there is some critical thing that it needs to do, where is has to tell the OS to wait, since simply closing down would cause a problem. There isn't any such critical event that you necessarilly need to top in the case of all plugins in general. Providing sequencing or priorities for all plugins just creates problems.
However, like I also said, I see no reason why someone can't create a collection of plugins, give them a group name *and then* set a sequence for which ones execute in that group in a particular order.
Example:
Plugins Hash -
Fred's group -
Fred plugin 1 = priority 1
Fred plugin 2 = priority 5
Fred plugin 3 = priority 5
Zeb's plugin
Jim's group -
Jim plugin 1 = priority 55
Jim plugin 2 = priotiry 3454
A FireEvent would hit each plugin, ignoring the return, since it is irrelevant. However, if one of Fred's plugins sent out a FireGroupEvent("Halt",""), then plugin 1 would catch it, set the return value to True, so all the others ignore it and do whatever the halt message was for. Maybe disabling all the plugins in the group or something.
This allows you to do exactly what you are suggesting, but isolates the plugins that 'require' this feature from those that might recieve it and misinterpret the result. It also solves the priority issue, since a global event *cannot* halt additional event functions from recieving it, precisely because at 'that' level you cannot control which plugin has what priority and will respond first. Even if you allowed someone to set a priority at that level, you can't predict what value they will use.
Why exactly is it a bad thing to make it only work in a situation where you can isolate and control what the plugin priorities will be? It seems to me to be the best solution. You can tell someone that to make their plugin work with your group, give it a priority higher than X and place it in group Y. In the global scope though, you might conceivably have 10 groups of plugins made by 10 different people, all with completely different priority settings. Even if by pure chance you use the same event name as their plugin, your asking for trouble. However, if yours and theirs are all distinctly seperated, you don't need to worry what Joe Smoe used for his priorities, or even what events he uses that could mess you up.
I think it would make sense to have the "fall through" behaviour as default, and "blocking" as optional for them.
I think in VB at least, we can make it so that not returning a value can default to be equivalent to a false, so the default behaviour would indeed to be fall through.
Quote:
As far as I'm aware, plugins are evaluated in a somewhat arbitrary order.
They will be evaluated in the order in which they are in your world file, as they are added to an internal list in that order. However, there is no really easy way of influencing that order, short of editing the world file.
It seems to me this is a separate issue. If the order of plugin evaluation is important, which it may be for other cases as well, then we need to discuss a "plugin sequence number" concept.
I tend to agree that if plugins are evaluated in an arbitrary, and not easily predicated order, then the idea of one plugin blocking an event may be a bit silly.
I think Shadowfyr's example of FireEvent "ChangeErrorColor", "Gold") would only apply to his plugins (who else would respond to "ChangeErrorColor"?) however I agree that if, say, the Chat plugin returned "true" even though it had not done anything with "ChangeErrorColor" that could be annoying.
However in this case you have to trust plugin authors to a certain extent, that they won't do something really silly. And, returning "handled" to an FireEvent when you didn't handle it, could be called silly.
For instance, a plugin might do 'DoCommand "quit" ', that would be annoying too.
However, like I also said, I see no reason why someone can't create a collection of plugins, give them a group name *and then* set a sequence for which ones execute in that group in a particular order.
This is an interesting idea. Initially plugins were supposed to be stand-alone things, however I can see that if they get more complex, especially with inter-plugin communication, then the issue arises that some plugins may happen to raise events that are misinterpreted, or mishandled, by other plugins by a different author.
One simple grouping approach would be "by author". So, if Shadowfyr's plugin raises an event, only Shadowfyr's plugins receive it.
Alternatively, you could specify a group name (or ID).
Another approach would be for plugins to "register" an event handler, eg. like this:
(Ked suggested something similar on Page 1 of this thread).
However this does not totally address the priority issue - what if two plugins register to handle "OnColourChanged"? Who gets it first? Do they both get it?
Also, what if two authors register "OnColourChanged" to handle different things (in different ways)?
So, you need:
Grouping - some events may only apply to a group
Sequencing - to do things in the right order
Stopping - maybe stop when an event is handled, maybe not
Uniqueness - to stop plugin authors inadvertently clashing (eg. by choosing the same group names)
I acknowledge that a lot of this is what Shadowfyr said.
I think I would prefer direct grouping over author. Someone else 'may' use my specifications for instance to desing a plugin that works with ones I made. Obviously in this case, the authors are going to be different, so a named group is imho probably better. As for uniqueness.. This is an issue, definitely. Though a bit less likely than collisions between events imho. Just using a group name like "Kagehi_Combat" would be enough to prevent a collision with "Combat" or "Magnum_Combat", etc.
Though, obviously using just "Combat" is a mistake in the first place. But adjusting one group name near the start of each plugin is a lot easier than fiddling with 50 different event names or tweaking priorities to fix a collection of someone's plugins, if grouping wasn't available. It is a lot easier to fix one simple error in judgement, than have to fix dozens of them (or more in really bad cases). ;)
What if I write a plug-in that is an extension to one of your plug-ins? Maybe I would want to be able to catch events even if you initially specified them to be your-group-only.
The best solution seems to be sequencing. Maybe, if one plug-in knew it needs to be ahead of a specific other plug-in (e.g. my extension to yours needs be evaluated first), then it could register itself with MUSHclient as being before. Then you simply give the user the power to manually modify sequencing numbers, and the problems seem to be solved.
Basically I have the impression that Shadowfyr's problem with a true/false intercepting event handler is that he's paranoid that somebody will gobble events they shouldn't. :-) In that case I would suggest that you simply modify the plug-in... or, Nick, you can create an option where you can disable a plug-in's ability to halt or even process events.
Quote: What if I write a plug-in that is an extension to one of your plug-ins? Maybe I would want to be able to catch events even if you initially specified them to be your-group-only.
Umm. Make yours load as part of my group? Duh! lol
And there is nothing preventing me from creating global events that I expect to be captured by 'all' plugins, not just those in my own group, as well as events you can send that mine know how to handle. I however probably don't want yours to have the option to completely override mine, even if you placed it in the same group. In which case, all you have to do is tweak maybe one plugin in my set to make yours have higher priority, not all half dozen or more I may have. As for any plugin knowing that it 'needs' to be ahead of others, that is likely only the case if it 'knows' what plugins it will be working with in the first place, in which case, why not have those in a group anyway?
I still don't get what your complaint is Ksilyan. ;)
I am not saying they are stupid, so much as impractical to impliment. And unless your plugin for some reason needs to halt some process that mine performs, why wouldn't the global, but non-interruptable events work to have them talk to each other?
How about this for a solution:
FireEvent(<type>, <data>)
Responder: Sub GlobalEvent(<ID>, <type>, <data>)
That way you can talk direct to another group, as though it was your own. You need yours to be informed of stuff mine is doing? Just add an extra line for that event to send to your group as well.
I can't really think of any case where I would even want someone else plugin to intercept and make my own plugins ignore their own events. It defeats the whole purpose of isolating them to prevent that in the first place. And if you really need it to do that, then you could always edit the plugins themselves to respond to an 'ignore this event' message of some sort. I can't see any practical way to have it both ways, allowing your plugins to interfer with the operation of mine (instead of just communicating) *and* also maintain the isolation needed to provide a level of safety between collections of them.
Quote: And unless your plugin for some reason needs to halt some process that mine performs, why wouldn't the global, but non-interruptable events work to have them talk to each other?
The 'and unless' part of your sentence answers the question. :-)
I sort of think you're being paranoid about security here. On Windows, when you install stuff, you're trusting that the software isn't going to do some really weird event hooking. The newer OSes fix this to some degree with better user security, but it still comes down to the simple fact that if things aren't done right, they crash.
Perhaps at the moment you might not want somebody messing with your plugin... otherwise, you probably would have made the feature to do whatever they want.
That being said, how many times have you and others complained about things in MUSHclient's event loop that you would have wanted? Nick at the time didn't feel they were necessary and so didn't expose the necessary interfaces. He may still not think they're necessary, but that's beside the point. What is relevant is that closing down your design like you're doing is somewhat of "selfish design" in that it prevents anybody in the future from talking with your script other than the - very possibly quite limited - interface you have created.
Let's say you have a plug-in that reacts to tells and changes their color and doesn't log them. Nothing more. I'm making it trivial on purpose for simplicity of example. Now let's say that I want to use your plug-in (on the assumption it does something else that's useful) but I want to prevent it from acting when the tell comes from Fred. So, assuming there is a 'tell' event (which there isn't, but whatever) I would intercept the tell event, and if it comes from Fred I'll kill it and deal with it myself; otherwise I will pass it on so that yours will deal with it.
I haven't spent the time to think up an exact and specific need for this in MUSHclient, but, this same event process has been used elsewhere and for a reason. I have the impression that you are saying we shouldn't have it in MUSHclient not because there's a *problem* with it, but simply because you don't see a use at the moment.
In the end the user has choice over what plug-ins are installed. If someone writes a plug-in that 'misbehaves', then the user simply shouldn't be using it or should complain to the plug-in author, not to the *event system* author.
Sigh.. Fine.. I would prefer something more stable and less "gee, lets hope things really do happen when they need to". Maybe, something like you suggested. In case where you 'absolutely must' have it, you could do:
ListenToEvents <group>
So events fired by that group would also be seen and reacted to by your own plugins GroupEvent function. You would have to intentionally bypass the security provided to seperate them that way, but as long as your own group had a sequence lower than mine, your plugin would recieve and process the event first. But I don't think the benefit of having every plugin global and interruptable is really as important as minimizing the problems that such a totally open system creates, with everything from general event collisions to actually making sure the right plugins execute in the right order.
FireEvent(<type>, <data>)
Responder: Sub GlobalEvent(<ID>, <type>, <data>)
OK, yes this sounds quite practical.
Assuming that the event cannot be cancelled then you *know* your other plugins, if installed, will get it. Also the ID (the ID of the sending plugin) will be supplied by MUSHclient, and thus cannot be spoofed.
So, your event handler might look like this:
Sub OnPluginEvent (id, type, data)
If id = "4aa4239d9a3d479fa6559ef5" Or _
id = "df84905195b4526b510d2fa5" Then
' process event
End If
End Sub
By testing for the IDs of your known plugins, you eliminate events (possibly with the same names) which are sent by unknown plugins.
The only possible downside is that the other plugins (ones you didn't write) might react in an unexpected way to your event, but hopefully that won't occur. If you are really worried about it you simply use CallPlugin to call them directly, and not use events.
As for groups, what is really achieved there? Someone can always claim to be in your group when they are not.
BTW, you can use the "Arrayxxxx" functions to pass multiple arguments by name, like this:
This passes the three arguments (name, language, client) to the event handler. It then extracts them like this:
Sub OnPluginEvent (id, type, data)
dim name, language, client
' extract args
ArrayCreate "args"
ArrayClear "args
ArrayImport "args", "data", ","
If type = "foo" Then
name = ArrayGet ("args", "name")
language = ArrayGet ("args", "language")
client = ArrayGet ("args", "client")
' process args here
End If ' end of event foo
End Sub
Well. The grouping was mainly a way to make sure that plugins really do execute, if only without your own group, in a known order. True, this may be the case with using the ID to make sure as well, but I was looking for a system that didn't explicitly need the related plugins to know what the ID's where, while still providing a similar level of protection. Frankly, I thought of passing the ID, not as a way to verify which plugins where valid, but to let them find out information about each other that you can't get without that, like which group they might have been in.
However, you have a point. As much as I dislike having to set the ID of each plugin that is valid, to avoid such collisions, it does provide some of the same degree of seperation and there isn't any reason why someone can't set up their plugin to accept an event that registers the sender as an exceptable source as well.
The only thing grouping would thus do then is allow you to disable several related plugins at once, but someone developing one could make that an event in them anyway.
So, we still need to be able to set 'sequence' or execution order, but anyway you try to manage that, someone can still mess it up anyway. ;) Managing such per plugin isn't that much worse than setting it once for a collection, then managing the groups order instead. Groups are probably not really necessary for anything, even mass disabling, which could be done through all the IDs the plugin doing the disabling knows. In fact, it is probably better, since it can disable the plugins it wants to, but not itself.
Ksilyan ought to be real unhappy about this though. ;) His whole arguement is that he might want to talk to my plugin, even though my plugin restricts what calls it will accept from him. Using the ID like this is even better at preventing such interference than groups. lol
----
BTW, as to the other type of events (objects), I am looking around now for info on IProvideClassInfo2. Apparently, it may be possible to have Mushclient get the IDispatch for the object, then pass the script the IProvideClassInfo2, which could, at least in theory, allow the scripts to use GetRef or the like to register an event handler for the object. This means COM events can be handled by the scripts, where currently you have to more or less create a wrapper for the control that can use CallPlugin or the like to 'inform' the script of what happened. This is impractical in the extreme. If there is some way to effectively register the object for proper use by the scripts own event manager, it solves the problem completely.
Quote: Ksilyan ought to be real unhappy about this though. ;) His whole arguement is that he might want to talk to my plugin, even though my plugin restricts what calls it will accept from him.
Actually... no. My argument was that I may wish to restrict your plugin's access to events, to be able to intercept/modify events, not necessarily talk to them. Regardless my main gripe was just that you seemed to be demanding specs that met your requirements and only yours. :)
Well, since the only reason I initially suggested grouping was to provide reliable priorities and everything else stemmed from arguements over the limitations of that, I figured something that could simplify plugin management might be a solution. I can't help it if we both have the tenacity of a bulldog when arguing our views. ;)