WatcherLib

by Ter13
Easy, minimal impact event watchers
ID:2316683
 
event_watching var (datum)

See also:
  event_watchers var (datum)
  AddEventWatcher() (datum)
  RemoveEventWatcher() (datum)
  TriggerEvent() (datum)
  Del() (datum)

Default value:
  null

The event_watching list is a multi-dimensional list (when in use) of datums that this object is currently watching. If nothing is currently watching this object, it will be null rather than a list.

The structure of this list is as follows:

{ "event1_name" = {object1, object2}, "event2_name" = {object3, object 4} }

This list is primarily used to ensure that circular references between watchers are maintained so that deletion of objects can be sped up by removing known references to this one. You should never have to worry about this list, and can basically ignore that it exists.




event_watchers var (datum)

See also:
  event_watching var (datum)
  AddEventWatcher() (datum)
  RemoveEventWatcher() (datum)
  TriggerEvent() (datum)
  Del() (datum)

Default value:
  null

The event_watchers list is a multi-dimensional list of datums that are currently watching events generated by this object.

The structure of this list is as follows:

{ "event1_name" = {object1, object2}, "event2_name" = {object3, object 4} }

This list is used by TriggerEvent() to loop over watchers of a specified event.




AddEventWatcher proc (datum)

See also:
  event_watchers var (datum)
  event_watching var (datum)
  RemoveEventWatcher() (datum)
  TriggerEvent() (datum)
  Del() (datum)

Format:
  AddEventWatcher(datum reference, event name)

This function adds an object as an event watcher of this object. When the specified event is triggered on this object, the object's event hook will be called.

See TriggerEvent() for more information on how all of this works.




RemoveEventWatcher proc (datum)

See also:
  event_watchers var (datum)
  event_watching var (datum)
  AddEventWatcher() (datum)
  TriggerEvent() (datum)
  Del() (datum)

Format:
  RemoveEventWatcher(datum reference, event name)

This function will remove the referenced object from this object's stored list of objects listening to the supplied event.




TriggerEvent proc (datum)

See also:
  event_watchers var (datum)
  event_watching var (datum)
  AddEventWatcher() (datum)
  RemoveEventWatcher() (datum)
  Del() (datum)

Format:
  TriggerEvent(event name, arguments (list))
  TriggerEvent(event name, arguments (list), cancelable)

Calling TriggerEvent() will call an event proc on all objects listening to this object for that event.

The optional cancelable arg, if supplied (any true value) will actually only call the response hooks until one returns a non-true value. If all objects return a true value, this proc returns 1. If any hook returns non-true, 0 is returned. This is a way to ask multiple objects dynamically for permission to do something rather than telling a series of dynamic objects something has been done.

If cancelable is not supplied, or supplied as 0 or null, all watching objects' response hooks are called regardless of what any of them return.


How to use WatcherLib:

First, define the event hooks. It's probably best to define all hooks on datum even though it's not totally necessary.

datum
proc
canMove(atom/movable/mover,atom/NewLoc,Dir,Step_x,Step_y)
return 1

onMoved(atom/movable/mover,atom/OldLoc,OldDir,OldStep_x,OldStep_y)


Next, we need to create the situations in which the hooks can be called. In our case, it's going to be before and after a movement:

atom/movable
Move(atom/NewLoc,Dir=0,Step_x=0,Step_y=0)
if(!TriggerEvent("canMove",list(src,NewLoc,Dir,Step_x,Step_y),1)) //this is a cancelable event
return 0

var/OldLoc = loc
var/OldDir = dir
var/OldStep_x = step_x
var/OldStep_y = step_y

. = ..()

if(. || dir!=OldDir)
TriggerEvent("onMoved",list(src,OldLoc,OldDir,OldStep_x,OldStep_y)) //this is not a cancelable event


Notice that the string that's passed to TriggerEvent() matches the name of the proc we defined on datum. The arguments we're passing to the trigger event proc is arbitrary. You can declare whatever argument structure you want.

Last, we just need to add a watcher to an object and declare response behavior:

move_test
onMoved(atom/movable/mover,atom/OldLoc,OldDir,OldStep_x,OldStep_y)
world << "[mover] moved! [OldLoc.x]:[OldStep_x],[OldLoc.y]:[OldStep_y],[OldLoc.z] -> [mover.x]:[mover.step_x],[mover.y]:[mover.step_y],[mover.z]"

mob
var
obj/move_test/watchertest
Login()
..()
watchertest = new()
AddEventWatcher(watchertest,"onMoved")

Logout()
RemoveEventWatcher(watchertest,"onMoved")
watchertest = null
..()


You can link up any object to any other, and declare your own dynamic hook behavior to standardize dynamic inter-connectivity between objects at will.




Del proc (datum)

Del()'s behavior has been overridden for datums to clean up the circular references stored in event_watchers and event_watching. This will prevent worst-case deletion behavior from happening. However, any object listening to another, or being listened to will not be garbage collected. You either need to remove all listeners yourself, or delete the object manually.