Roll20 uses cookies to improve your experience on our site. Cookies enable you to enjoy certain features, social sharing functionality, and tailor message and display ads to your interests on our site and others. They also help us understand how our site is being used. By continuing to use our site, you consent to our use of cookies. Update your cookie preferences .
×
Create a free account

[Idea] Cross-script event management

1456819901
Lucian
Pro
API Scripter
Hey All, I was talking to Ziechael about his plans for my DynamicLightRecorder yesterday, and he was vaguely hoping that he could have it triggered when he updated a token using TokenMod. Now obviously this won't work out of the box, because the API doesn't trigger change events, so I said I'd talk to Aaron about putting a listener framework onto TokenMod - I believe that he did something similar with GroupInitiative and TurnMarker, or maybe it was Mark. But then as I thought about it some more, it occurred to me that maybe we could do better: how about we create a centralised event manager for all scripts to use? Here's how it would work: Install the EM as the first script in the sandbox The EM provides wrapper methods for all the Roll20 methods that return objects i.e. Campaign(), getObj(), getAllObjs(), findObjs(), filterObjs() These wrappers return wrapped objects that delegate to the underlying objects. But when you call .set() it also fires an appropriate change event All scripts that want to participate just need to global replace the calls to the above methods with EM.method, and then register their change handlers to EM.registerListener(eventName, listener) To make life easier we can have a 'noSelf' parameter on the register method so that you don't get millions of change events for your own set calls If we wanted to be really radical, we could have the EM also register *all* the available listeners on Roll20, and then have scripts shift over to registering everything with the EM. This would allow the EM to do helpful things like evening out the currently rather inconsistent event firing in response to API actions (e.g. createObj fires token:change but not token:add, you could choose not to receive on:destroy for your own remove calls etc, perhaps we could even debounce the annoying add/change combo for things that get grid-snapped) What do you think? It's actually a pretty easy script to write, it's just a case of getting agreement that it's worth doing/using from the main script authors here, otherwise it'd be a waste of time... Cheers, Lucian
1456839660

Edited 1456839954
Lithl
Pro
Sheet Author
API Scripter
Here, I've taken a swing at it:&nbsp; <a href="https://github.com/Lithl/roll20-api-scripts/blob/o" rel="nofollow">https://github.com/Lithl/roll20-api-scripts/blob/o</a>... (Note: this version does not respond to VTT events, only to API client code.) Events are always named in the form type : action [: property ]. type &nbsp;is any Roll20 object type.&nbsp; action &nbsp;can be one of 'get', 'set', 'remove', or 'create'. property &nbsp;is only used with 'get' and 'set' actions, can be any appropriate property of the appropriate object type, and is optional. addEventListener(event, callback) Must be called before any events you wish to capture. The callback function will receive the parameters observable , next , and event . observable &nbsp;will be the current state of the object, or a dummy object for 'create' events. next &nbsp;will be the property values that the observed object will have &nbsp;after the event is processed ( next &nbsp;is fully defined for 'set' events, and is equal to the properties being used to create the object plus the _type property for 'create' events). event &nbsp;will be the event being listened to. removeEventListener(event, callback) The event and callback must exactly match parameters you have previously passed to addEventListener in order for this function to do anything. observe(obj) Creates an observable object which is capable of notifying listeners of events. The obj &nbsp;parameter must be a Roll20 object; the script will throw a TypeError otherwise. createObservable(type, props) Creates a Roll20 Object and an observer for it. This function is the only way to trigger 'create' events. Observable.id, Observable.set, Observable.get, Observable.remove These functions (and property, for id )&nbsp;operate essentially the same as their normal Roll20 object counterparts, except that they can trigger registered events. Observable.remove &nbsp;does not exist at all if the object type is not one that can be removed. Observable.base The base &nbsp;property gives direct access to the Roll20 object being observed. This can allow you to do things like modify the object without triggering events, if desired. Observable.preventDefault() If you call preventDefault &nbsp;from within an event callback function, the default behavior of that event will not happen! &nbsp;The Roll20 object will not be created, it won't be deleted, its values won't change, etc. Of particular note, if you prevent the default behavior of a 'get' event (which normally returns a value), your event listener will now be responsible for returning a value; if you don't, client code will get a value of undefined. Note that if there are multiple listeners reacting to the same 'get' event and &nbsp;any of those listeners prevents the default behavior, the return value will be an array of all return values from all listeners that fired, as opposed to a single value. (If the default action isn't prevented, a single value will be returned, and if the default is prevented but there is only one listener responding to the event, a single value will be returned.)
1456844763
The Aaron
Pro
API Scripter
This is a good idea and something I've actually been working (I call my version Emcee =D). &nbsp; I'm happy to add bindings into TokenMod and such, though I'll likely still develop my own library for it. &nbsp;Would that I had more time to do all the things I want to do!
1456845542
Ziechael
Forum Champion
Sheet Author
API Scripter
The Aaron said: Would that I had more time to do all the things I want to do! And all the things we &nbsp;want you &nbsp;to do...
1456849230
Lucian
Pro
API Scripter
@Brian - Wow, that's pretty sophisticated for such a quick response. I have to say it's probably a bit cleverer then I would go for in the circumstances; for something that is intended to be used by a wide range of script developers as a common framework, I think there's a bit too much potential for people shooting themselves in the foot/causing mayhem with others' scripts. I thought about the idea of cancellable events myself, and I know it has good pedigree in the DOM but 1) it's kind of futile when you can't cancel normal Roll20 events as well and b) I think this is going to cause all kinds of crazy if people go to town with it. The get behaviour in particular could really throw people! Also, I think providing the ability to ignore self-generated events is going to be super-important for adoption - a lot of scripts will have to change very substantially if they suddenly get events fired at them for every set call they make... That said, your code is very elegant and it looks like a nice framework - I don't want you to think I'm being disparaging - I just think perhaps&nbsp;I'd vote for sticking to a simple, read-only framework that just provides notification, much as the Roll20 API itself does. Apart from anything else, this thing will only be useful if we can persuade most people to use it, and for that I think brutal simplicity and the principle of least surprise are probably the order of the day.... @Aaron - Somehow I knew you'd already have this in your sights! How does what you were thinking of compare to what Brian has put together?
1456860258
The Aaron
Pro
API Scripter
A simpler interface and a more complex backend. &nbsp;I've considered wrapping the objects which could get a little complicated (if they are changing 20 different things, you probably don't want to send 20 different notifications), but largely I'm interested in more general events similar to what I've got in GroupInitiative, Bump, TurnMarker, etc.&nbsp; I agree that the interface needs to be pretty simple. &nbsp;I had planned on supporting .on( eventList , callback ) for registration and probably .notify( event , context ). &nbsp;However, this fits into a wider framework I've been working on called TheAaronBase . Likely I'd wrap all the native events so that all event registration could go through one system, and then I can support some other things...
1456912083
Lithl
Pro
Sheet Author
API Scripter
Lucian H. said: I thought about the idea of cancellable events myself, and I know it has good pedigree in the DOM but 1) it's kind of futile when you can't cancel normal Roll20 events as well Well, the code I posted doesn't react to user actions in the VTT at all at the moment, so the fact that it's impossible to cancel those events doesn't matter as much. =P The way I figure it, the primary goal of this is to be able to hook multiple scripts together. Both scripts can clearly &nbsp;react the way they intend to when a user does something in the VTT, they just can't (generally) react to each other. So, including VTT event support isn't nearly as important. And, IMO, having the ability &nbsp;to cancel an event is useful, even if it's rarely used. The Aaron said: A simpler interface and a more complex backend. &nbsp;I've considered wrapping the objects which could get a little complicated (if they are changing 20 different things, you probably don't want to send 20 different notifications), but largely I'm interested in more general events similar to what I've got in GroupInitiative, Bump, TurnMarker, etc. The way my script above handles a set &nbsp;call with multiple properties is somewhat similar to how Roll20 handles a VTT event that would trigger multiple events registered with on : Get a list of properties being changed Get all listeners for 'set' events of the appropriate object type From those, reject all listeners that are looking for a specific property For each property being changed, get all listeners from the list in #2 which care about that property, and run the callback for all of those listeners For each listener in the list from #3, run the callback Thus, no single listener will be called for a single event (even if that event is multiple properties being changed simultaneously) more than once (unless you've registered it multiple times), listeners on specific properties will run before general listeners, and beyond that listeners will run in the order they're registered.