Guest User

Untitled

a guest
Dec 15th, 2018
112
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 6.53 KB | None | 0 0
  1. /*
  2. Array is too dumb.
  3. */
  4. class Subscribers {
  5. constructor () {
  6. this.counter = 0
  7. this.mapping = new Map()
  8. }
  9.  
  10. add(sink) {
  11. let index = this.counter++
  12. this.mapping.set(index, sink)
  13. return index
  14. }
  15.  
  16. feed(x) {
  17. this.mapping.forEach(sink => sink(x))
  18. }
  19. }
  20.  
  21. /*
  22. Event is a dispatcher. If its fed a thing, it retranslates it to all subscribers via connectors.
  23.  
  24. Connector is a function that receives a thing and decides if it goes to the sinks.
  25. (See "resolve" in #transform() method)
  26.  
  27. If you want some impure output from event, just call event#subscribe(callback).
  28.  
  29. You can make some basic source events using
  30. - Event.timer(dt) -- sends Date.now() each "dt"
  31. - Event.clicks() -- document.onclick
  32. - Event.keyboard() -- document.onkeydown
  33.  
  34. Then, you can transform these new-made events using:
  35. - event#map(f) -- applies function "f" to each thing coming through
  36. - event#filter(pred) -- only routes a thing to subscribers if it makes a "pred" succeed
  37. - event#reduce(acc, add) -- "add"s each thing coming in to "acc"umulator (which goes out)
  38.  
  39. There also are high-level methods:
  40. - event#count() -- event that emits count of times "event" emitted anything
  41. -- (completely ignores what event emits)
  42.  
  43. - event.throttle(dt) -- throws out emitted thing, if less than "dt" time passed
  44. - event#delay(dt) -- looks as "event", but all things come out after "dt" ms
  45. - event#case(split) -- see below, in the example section
  46. - Event.merge(...events) -- If any of the events fires, then merge-event fires, too.
  47. */
  48. class Event {
  49. constructor () {
  50. this.subscribers = new Subscribers()
  51. }
  52.  
  53. /*
  54. Create an event and a feeder-function to actually pass things through.
  55. */
  56. static source() {
  57. let event = new Event()
  58. let feed = x => event.subscribers.feed(x)
  59. return {event, feed}
  60. }
  61.  
  62. /*
  63. Create an event, pass its feeder to "init"ialiser function, then return the new event.
  64. */
  65. static setup(init) {
  66. let {event, feed} = Event.source()
  67. init(feed)
  68. return event
  69. }
  70.  
  71. /*
  72. Add function to receive things coming through.
  73. */
  74. subscribe(sink) {
  75. this.subscribers.add(sink)
  76. return this
  77. }
  78.  
  79. /*
  80. Pattern from map/filter/reduce.
  81. The "resolve" function argument decides if "x" ever goes to "sink"
  82. and what happens to it before.
  83. */
  84. transform(resolve) {
  85. return Event.setup(sink => this.subscribe(x => resolve(sink, x)))
  86. }
  87.  
  88. /*
  89. Create event that is fed by some event on source object.
  90. */
  91. static globalEvent(eventName, source) {
  92. return Event.setup(sink => source[eventName] = sink)
  93. }
  94.  
  95. /*
  96. Create an event that is fed by timer.
  97. */
  98. static timer(dt) {
  99. return Event.setup(sink => setInterval(() => sink(Date.now()), dt))
  100. }
  101.  
  102. /*
  103. Some pretty standard events.
  104. */
  105. static clicks (source) { return Event.globalEvent("onclick", source) }
  106. static keyboard(source) { return Event.globalEvent("onkeydown", source) }
  107.  
  108. /*
  109. Basic transformers.
  110. */
  111. map (f) { return this.transform((sink, x) => sink(f(x))) }
  112. filter(pred) { return this.transform((sink, x) => pred(x) ? sink(x) : 0) }
  113.  
  114. /*
  115. If a thing comes in, add it to accumulator and give accumulator out.
  116. */
  117. reduce(seed, add) { return this.transform((sink, x) => sink(seed = add(seed, x))) }
  118.  
  119. replaceEach(x) {
  120. return this.map(_ => x)
  121. }
  122.  
  123. /*
  124. Turn things coming through into ones, then add them.
  125. */
  126. count() {
  127. return this.map(_ => 1).reduce(0, (x, y) => x + y)
  128. }
  129.  
  130. /*
  131. Make an event that is fed by all "events".
  132. */
  133. static merge(...nodes) {
  134. return Event.setup(sink =>
  135. nodes.forEach(node => node.subscribe(sink))
  136. )
  137. }
  138.  
  139. /*
  140. Given an object with predicates, return an object of events.
  141. See example.
  142. */
  143. case(split) {
  144. return Object
  145. .keys(split)
  146. .map(key => ({[key]: this.filter(split[key])}))
  147. .reduce(Object.assign, {})
  148. }
  149.  
  150. /*
  151. Limit rate of events to 1 event per "dt" ms.
  152. */
  153. throttle(dt) {
  154. let last = -Infinity
  155. return this.filter(x => {
  156. let t = Date.now()
  157. if (t - last > dt) {
  158. last = t
  159. return true
  160. }
  161. })
  162. }
  163.  
  164. /*
  165. Delay events by "dt" ms.
  166. */
  167. delay(dt) {
  168. return Event.setup(sink =>
  169. this.subscribe(x => setTimeout(() => sink(x), dt))
  170. )
  171. }
  172.  
  173. log(msg) {
  174. return this.subscribe(x => console.log({[msg]: x}))
  175. }
  176. }
  177.  
  178. /*
  179. A "last value" of event.
  180. */
  181. class Behaviour {
  182. static of(event, start) {
  183. return new Behaviour(event, start)
  184. }
  185.  
  186. /*
  187. Make that each incoming event sets a value.
  188. */
  189. constructor (event, start) {
  190. this.value = start
  191. event.subscribe(x => this.value = x)
  192. }
  193.  
  194. /*
  195. When an event arrives, ignore it and return current value.
  196. */
  197. sample(event) {
  198. event.map(_ => this.value)
  199. }
  200.  
  201. combine(event, f) {
  202. event.map(x => f(x, this.value))
  203. }
  204. }
  205.  
  206. // helper function
  207. let is = x => y => x == y
  208.  
  209. // Parse out only WASD keys
  210. let wasd = Event.keyboard(window)
  211. .map (e => e.key)
  212. .filter(key => "wasd".includes(key))
  213.  
  214. // Split them onto W, A, S and D key events
  215. let {w, a, s, d} = wasd.case({
  216. w: is('w'),
  217. a: is('a'),
  218. s: is('s'),
  219. d: is('d')
  220. })
  221.  
  222. // Replace keys with concrete moves
  223. let w1 = w.replaceEach({dx: 1, dy: 0})
  224. let a1 = a.replaceEach({dx: 0, dy: 1})
  225. let s1 = s.replaceEach({dx: -1, dy: 0})
  226. let d1 = d.replaceEach({dx: 0, dy: -1})
  227.  
  228. // Merge them into move stream
  229. // (You can add log to any point)
  230. let moves = Event.merge(w1, a1, s1, d1).log('move')
  231.  
  232. // Starting with (0, 0), add moves to current coordinate
  233. let place = moves.reduce({x: 0, y: 0}, ({x, y}, {dx, dy}) => ({x: x + dx, y: y + dy}))
  234.  
  235. // Turn current coordinate into a String
  236. let shown = place.map(JSON.stringify)
  237.  
  238. // Noitfy if we found something
  239. let foundSomething = place.filter(({x, y}) => x == 5 && y == 3)
  240.  
  241. // These events will be looged in console.
  242. shown.log("we are at")
  243. foundSomething.log("found something")
  244.  
  245. Event.clicks(window)
  246. .map( e => ({x: e.screenX, y: e.screenY}))
  247. .filter(({x, y}) => x > 100 && x < 300 && y > 100 && y < 300)
  248. .map( e => JSON.stringify(e))
  249. .log("found me!")
Add Comment
Please, Sign In to add comment