Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- /*
- Array is too dumb.
- */
- class Subscribers {
- constructor () {
- this.counter = 0
- this.mapping = new Map()
- }
- add(sink) {
- let index = this.counter++
- this.mapping.set(index, sink)
- return index
- }
- feed(x) {
- this.mapping.forEach(sink => sink(x))
- }
- }
- /*
- Event is a dispatcher. If its fed a thing, it retranslates it to all subscribers via connectors.
- Connector is a function that receives a thing and decides if it goes to the sinks.
- (See "resolve" in #transform() method)
- If you want some impure output from event, just call event#subscribe(callback).
- You can make some basic source events using
- - Event.timer(dt) -- sends Date.now() each "dt"
- - Event.clicks() -- document.onclick
- - Event.keyboard() -- document.onkeydown
- Then, you can transform these new-made events using:
- - event#map(f) -- applies function "f" to each thing coming through
- - event#filter(pred) -- only routes a thing to subscribers if it makes a "pred" succeed
- - event#reduce(acc, add) -- "add"s each thing coming in to "acc"umulator (which goes out)
- There also are high-level methods:
- - event#count() -- event that emits count of times "event" emitted anything
- -- (completely ignores what event emits)
- - event.throttle(dt) -- throws out emitted thing, if less than "dt" time passed
- - event#delay(dt) -- looks as "event", but all things come out after "dt" ms
- - event#case(split) -- see below, in the example section
- - Event.merge(...events) -- If any of the events fires, then merge-event fires, too.
- */
- class Event {
- constructor () {
- this.subscribers = new Subscribers()
- }
- /*
- Create an event and a feeder-function to actually pass things through.
- */
- static source() {
- let event = new Event()
- let feed = x => event.subscribers.feed(x)
- return {event, feed}
- }
- /*
- Create an event, pass its feeder to "init"ialiser function, then return the new event.
- */
- static setup(init) {
- let {event, feed} = Event.source()
- init(feed)
- return event
- }
- /*
- Add function to receive things coming through.
- */
- subscribe(sink) {
- this.subscribers.add(sink)
- return this
- }
- /*
- Pattern from map/filter/reduce.
- The "resolve" function argument decides if "x" ever goes to "sink"
- and what happens to it before.
- */
- transform(resolve) {
- return Event.setup(sink => this.subscribe(x => resolve(sink, x)))
- }
- /*
- Create event that is fed by some event on source object.
- */
- static globalEvent(eventName, source) {
- return Event.setup(sink => source[eventName] = sink)
- }
- /*
- Create an event that is fed by timer.
- */
- static timer(dt) {
- return Event.setup(sink => setInterval(() => sink(Date.now()), dt))
- }
- /*
- Some pretty standard events.
- */
- static clicks (source) { return Event.globalEvent("onclick", source) }
- static keyboard(source) { return Event.globalEvent("onkeydown", source) }
- /*
- Basic transformers.
- */
- map (f) { return this.transform((sink, x) => sink(f(x))) }
- filter(pred) { return this.transform((sink, x) => pred(x) ? sink(x) : 0) }
- /*
- If a thing comes in, add it to accumulator and give accumulator out.
- */
- reduce(seed, add) { return this.transform((sink, x) => sink(seed = add(seed, x))) }
- replaceEach(x) {
- return this.map(_ => x)
- }
- /*
- Turn things coming through into ones, then add them.
- */
- count() {
- return this.map(_ => 1).reduce(0, (x, y) => x + y)
- }
- /*
- Make an event that is fed by all "events".
- */
- static merge(...nodes) {
- return Event.setup(sink =>
- nodes.forEach(node => node.subscribe(sink))
- )
- }
- /*
- Given an object with predicates, return an object of events.
- See example.
- */
- case(split) {
- return Object
- .keys(split)
- .map(key => ({[key]: this.filter(split[key])}))
- .reduce(Object.assign, {})
- }
- /*
- Limit rate of events to 1 event per "dt" ms.
- */
- throttle(dt) {
- let last = -Infinity
- return this.filter(x => {
- let t = Date.now()
- if (t - last > dt) {
- last = t
- return true
- }
- })
- }
- /*
- Delay events by "dt" ms.
- */
- delay(dt) {
- return Event.setup(sink =>
- this.subscribe(x => setTimeout(() => sink(x), dt))
- )
- }
- log(msg) {
- return this.subscribe(x => console.log({[msg]: x}))
- }
- }
- /*
- A "last value" of event.
- */
- class Behaviour {
- static of(event, start) {
- return new Behaviour(event, start)
- }
- /*
- Make that each incoming event sets a value.
- */
- constructor (event, start) {
- this.value = start
- event.subscribe(x => this.value = x)
- }
- /*
- When an event arrives, ignore it and return current value.
- */
- sample(event) {
- event.map(_ => this.value)
- }
- combine(event, f) {
- event.map(x => f(x, this.value))
- }
- }
- // helper function
- let is = x => y => x == y
- // Parse out only WASD keys
- let wasd = Event.keyboard(window)
- .map (e => e.key)
- .filter(key => "wasd".includes(key))
- // Split them onto W, A, S and D key events
- let {w, a, s, d} = wasd.case({
- w: is('w'),
- a: is('a'),
- s: is('s'),
- d: is('d')
- })
- // Replace keys with concrete moves
- let w1 = w.replaceEach({dx: 1, dy: 0})
- let a1 = a.replaceEach({dx: 0, dy: 1})
- let s1 = s.replaceEach({dx: -1, dy: 0})
- let d1 = d.replaceEach({dx: 0, dy: -1})
- // Merge them into move stream
- // (You can add log to any point)
- let moves = Event.merge(w1, a1, s1, d1).log('move')
- // Starting with (0, 0), add moves to current coordinate
- let place = moves.reduce({x: 0, y: 0}, ({x, y}, {dx, dy}) => ({x: x + dx, y: y + dy}))
- // Turn current coordinate into a String
- let shown = place.map(JSON.stringify)
- // Noitfy if we found something
- let foundSomething = place.filter(({x, y}) => x == 5 && y == 3)
- // These events will be looged in console.
- shown.log("we are at")
- foundSomething.log("found something")
- Event.clicks(window)
- .map( e => ({x: e.screenX, y: e.screenY}))
- .filter(({x, y}) => x > 100 && x < 300 && y > 100 && y < 300)
- .map( e => JSON.stringify(e))
- .log("found me!")
Add Comment
Please, Sign In to add comment