Category talk:Wren-event

From Rosetta Code
(Redirected from Talk:Wren-event)

Event-driven programming

Unlike many other object-oriented languages, Wren has no built-in support for event-driven programming. Nevertheless, occasions arise where this is a natural paradigm to use and the purpose of this module is to facilitate it in a simple way using the observer pattern.

There is only one class, Event, which provides a bridge between an actual event and the function(s) which handle that event.

The basic idea is that functions register to receive automatic notifications when an event occurs. When an event arises, the code which detects it notifies the corresponding Event object which in turn notifies the event handlers by calling them with the appropriate arguments. The first argument is always the Event object itself (since a function may register for several different events) and the second argument is a map (which may be empty) of any additional data which the function may need to handle the event.

The actual events can be triggered and detected in various ways such as by user input or by code running in a loop within a host application which calls back Wren.

Note that handlers must be functions, which are objects in Wren, and not methods. However, one can still use a method by simply wrapping it in a function.

An Event object keeps track of the currently 'active' handlers by storing them in a list. Once added handlers are never removed from this list but can be set to 'inactive' if no longer needed and changed back to 'active' if needed again. They are usually identified by their index in the list (zero-based) which is returned when the handler is first registered.

All Event objects are stored in a library represented by a static field of the Event class. This enables one to look up an object using its name (which must be a unique string) if the original reference is no longer available or difficult to access.

The Event class can be sub-classed if one needs to provide additional functionality or customize notifications in some way. However, apart from the 'notify' and 'toString' methods, all other methods should not normally be overridden as they provide the basic infrastructure for implementing the paradigm.

The first line of the sub-class's constructor should always be:

super(name)

to ensure the subclass object is initialized properly. Note that, as static methods are not inherited in Wren, sub-class objects are stored in the Event class's library. One can still determine the actual type of the event using its 'type' property which all objects inherit from the Object class.

Source code

/* Module "event.wren" */

import "./check" for Check

/*
    An Event object represents a delegate for an underlying event based on the observer pattern. 
    Functions may register to receive automatic notifications when the object itself is notified
    that the underlying event has occurred by code which detects each such occurrence.

    A library of Event objects is maintained (in a static field) so that references to such objects
    are always easily available.
*/

class Event {
    // Private method which initializes the event library.
    static init_() {
        if (__library == null) __library  = {}
    }

    // Returns whether or not an event called 'name' exists in the library.
    static exists(name) {
        Check.str("name", name, 1)
        return __library.containsKey(name)
    }

    // Returns the Event object for an event called 'name' or null if it doesn't exist.
    static [name] {
        Check.str("name", name, 1)
        return __library[name]
    }

    // Private method to add an Event object to the library using its name as the key.
    // Should only be called from this class or a sub-class.
    // Aborts the fiber if an event with this name already exists.
    static add_(event) {
        Check.type("event", event, "Event")
        var name = event.name
        if (exists(name)) {
            Fiber.abort("An event called %(name) already exists in the library.")
        }
        __library[name] = event
    }

    // Removes an Event called 'name' from the library.
    // Does nothing if 'name' doesn't exist.
    static remove(name) {
        Check.str("name", name, 1)
        if (exists(name)) __library.remove(name)
    }

    // Returns a list of all event names currently in the library.
    static names { __library.keys.toList }

    // Constructs a new Event object from a name which must be unique within the library.
    // The _handlers field is a list each entry of which is a tuple containing a reference to the handler
    // function and whether or not its registration is currently active.
    construct new(name) {
        Check.str("name", name, 1) 
        _name = name
        _handlers = []
        __library[name] = this
    }

    // Returns the name of the current instance.
    name { _name }

    // Private method (for use by this class or sub-classes) which returns the handlers list.
    handlers_ { _handlers }

    // Public method which returns a copy of the handlers list.
    handlers { _handlers.toList }

    // Returns a reference to a handler function from its handler index.
    [index] {
        Check.int("handler index", index, 0, _handlers.count - 1)
        return _handlers[index][0]
    }

    // Returns whether or not a given handler index is currently active.
    isActive(index) {
        Check.int("handler index", index, 0, _handlers.count - 1)
        return _handlers[index][1]
    }

    // Returns the count of currently active handlers for this instance.
    activeCount { _handlers.count { |h| h[1] } }

    // Returns the handler index for a given handler function or -1 if it doesn't exist.
    findHandler(func) {
        Check.func("handler", func, 2)
        for (i in 0..._handlers.count) {
            if (Object.same(_handlers[i][0], func)) return i
        }
        return -1
    }

    // Registers a function to receive automatic notifications when the underlying event is triggered.
    // The function must take two arguments : the event name and an argument map (which can be empty).
    // Returns the index of the handler in the list (i.e. starts from 0).
    register(handler) {
        Check.func("handler", handler, 2)
        _handlers.add([handler, true])
        return _handlers.count - 1
    }

    // De-registers a function from receiving notifications using its handler index
    // but does not actually remove it from the _handlers list.
    deregister(index) {
        Check.int("handler index", index, 0, _handlers.count - 1)
        _handlers[index][1] = false
    }

    // Re-registers a previously registered function to receive notifications using its handler index.
    // Has no effect if its registration is already active.
    reregister(index) {
        Check.int("handler index", index, 0, _handlers.count - 1)
        _handlers[index][1] = true
    }

    // Notifies all handlers with an active registration that the underlying event has been triggered
    // by calling them with the Event object and the argument map (possibly empty) passed in to it.
    // Handlers are called in handler number order.
    // This method should be called by code which detects when the underlying event occurs.
    notify(argMap) {
        Check.map("argMap", argMap)
        for (h in _handlers) {
            if (h[1]) h[0].call(this, argMap)
        }
    }

    // Returns a string representation of the current instance.
    toString { "Event: %(_name)" }
}

// Initialize the event library.
Event.init_()