Axxord.st 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. Smalltalk createPackage: 'Axxord'!
  2. Object subclass: #Axes
  3. instanceVariableNames: ''
  4. package: 'Axxord'!
  5. !Axes class methodsFor: 'parsing'!
  6. parse: message
  7. | result stack anArray |
  8. anArray := message tokenize: ' '.
  9. result := #().
  10. stack := { result }.
  11. anArray do: [ :each |
  12. | asNum inner close |
  13. close := 0.
  14. inner := each.
  15. [ inner notEmpty and: [ inner first = '(' ]] whileTrue: [ inner := inner allButFirst. stack add: (stack last add: #()) ].
  16. [ inner notEmpty and: [ inner last = ')' ]] whileTrue: [ inner := inner allButLast. close := close + 1 ].
  17. (inner notEmpty and: [ inner first = '~' ]) ifTrue: [ inner := { inner allButFirst } ].
  18. asNum := inner isString ifTrue: [ (inner ifEmpty: [ 'NaN' ]) asNumber ] ifFalse: [ inner ].
  19. asNum = asNum ifTrue: [ stack last add: asNum ] ifFalse: [
  20. inner ifNotEmpty: [ stack last add: inner ] ].
  21. close timesRepeat: [ stack removeLast ] ].
  22. ^ result
  23. ! !
  24. Object subclass: #Axon
  25. instanceVariableNames: 'factory'
  26. package: 'Axxord'!
  27. !Axon commentStamp!
  28. I represent a pub-sub based on a key (called 'aspect').
  29. I manage aspect-block subscriptions (called 'interests') as well as run blocks of dirtied interests.
  30. The interest objects are responsible of decision if the change of an aspect is relevant for them.
  31. Interest object must be subclasses of `AxonInterest`.
  32. My subclasses must provide implementation for:
  33. - add:
  34. - do:
  35. - clean!
  36. !Axon methodsFor: 'action'!
  37. addInterest: anInterest
  38. self
  39. add: (anInterest flag; yourself);
  40. dirty: true
  41. !
  42. changed: anAspect
  43. | needsToRun |
  44. needsToRun := false.
  45. self do: [ :each |
  46. (each accepts: anAspect) ifTrue: [
  47. each flag.
  48. needsToRun := true ]].
  49. self dirty: needsToRun
  50. !
  51. changedAll
  52. | needsToRun |
  53. needsToRun := false.
  54. self do: [ :each |
  55. each flag.
  56. needsToRun := true ].
  57. self dirty: needsToRun
  58. !
  59. dirty: aBoolean
  60. aBoolean ifTrue: [[ self run ] fork]
  61. !
  62. run
  63. [
  64. | needsClean |
  65. needsClean := false.
  66. self do: [ :each |
  67. each isFlagged ifTrue: [ each run ].
  68. each isEnabled ifFalse: [ needsClean := true ]
  69. ].
  70. needsClean ifTrue: [ self clean ]
  71. ] on: Error do: [ self dirty: true ]
  72. ! !
  73. !Axon methodsFor: 'injecting'!
  74. registerIn: anObject
  75. <inlineJS: 'anObject.$axon$=self'>
  76. ! !
  77. Axon subclass: #DumbAxon
  78. instanceVariableNames: ''
  79. package: 'Axxord'!
  80. !DumbAxon commentStamp!
  81. I am an axon that does nothing.!
  82. !DumbAxon methodsFor: 'as yet unclassified'!
  83. add: anInterest
  84. "pass"
  85. !
  86. clean
  87. "pass"
  88. !
  89. do: aBlock
  90. "pass"
  91. ! !
  92. Axon subclass: #SimpleAxon
  93. instanceVariableNames: 'queue'
  94. package: 'Axxord'!
  95. !SimpleAxon methodsFor: 'accessing'!
  96. add: aSubscription
  97. queue add: aSubscription.
  98. ! !
  99. !SimpleAxon methodsFor: 'bookkeeping'!
  100. clean
  101. queue := queue select: [ :each | each isEnabled ]
  102. ! !
  103. !SimpleAxon methodsFor: 'enumeration'!
  104. do: aBlock
  105. queue do: aBlock
  106. ! !
  107. !SimpleAxon methodsFor: 'initialization'!
  108. initialize
  109. super initialize.
  110. queue := OrderedCollection new
  111. ! !
  112. Object subclass: #AxonInterest
  113. instanceVariableNames: 'aspect actionBlock flagged'
  114. package: 'Axxord'!
  115. !AxonInterest methodsFor: 'accessing'!
  116. aspect: anAspect block: aBlock
  117. aspect := anAspect.
  118. actionBlock := aBlock
  119. !
  120. flag
  121. flagged := true
  122. ! !
  123. !AxonInterest methodsFor: 'action'!
  124. run
  125. [ flagged := false. actionBlock value ]
  126. on: AxonOff do: [ actionBlock := nil ]
  127. ! !
  128. !AxonInterest methodsFor: 'initialization'!
  129. initialize
  130. super initialize.
  131. aspect := nil.
  132. actionBlock := nil.
  133. flagged := false.
  134. ! !
  135. !AxonInterest methodsFor: 'testing'!
  136. accepts: anAspect
  137. "Should return true if change for anAspect is relevant for this AxonInterest"
  138. self subclassResponsibility
  139. !
  140. isEnabled
  141. ^actionBlock notNil
  142. !
  143. isFlagged
  144. ^flagged
  145. ! !
  146. AxonInterest subclass: #InterestedInEqual
  147. instanceVariableNames: ''
  148. package: 'Axxord'!
  149. !InterestedInEqual methodsFor: 'testing'!
  150. accepts: anAspect
  151. ^ anAspect = aspect
  152. ! !
  153. AxonInterest subclass: #InterestedThruAxes
  154. instanceVariableNames: ''
  155. package: 'Axxord'!
  156. !InterestedThruAxes methodsFor: 'testing'!
  157. accepts: anAspect
  158. ^anAspect size <= aspect size
  159. ifTrue: [anAspect = (aspect copyFrom: 1 to: anAspect size)]
  160. ifFalse: [aspect = (anAspect copyFrom: 1 to: aspect size)]
  161. ! !
  162. AxonInterest subclass: #InterestedUpToAxes
  163. instanceVariableNames: ''
  164. package: 'Axxord'!
  165. !InterestedUpToAxes methodsFor: 'testing'!
  166. accepts: anAspect
  167. ^anAspect size <= aspect size and: [anAspect = (aspect copyFrom: 1 to: anAspect size)]
  168. ! !
  169. Error subclass: #AxonOff
  170. instanceVariableNames: ''
  171. package: 'Axxord'!
  172. !AxonOff commentStamp!
  173. Signal me from the subscription block to unsubscribe it.!
  174. !Array methodsFor: '*Axxord'!
  175. asAxisIn: anObject ifAbsent: aBlock
  176. | receiver selector result |
  177. selector := self first.
  178. receiver := anObject yourself. "JSObjectProxy hack"
  179. [ result := receiver perform: selector ]
  180. on: MessageNotUnderstood do: [ :mnu |
  181. ((mnu message selector = selector
  182. and: [ mnu receiver == receiver ])
  183. and: [ mnu message arguments isEmpty ])
  184. ifFalse: [ mnu resignal ].
  185. ^ aBlock value ].
  186. ^ result
  187. !
  188. asAxisIn: anObject ifAbsent: aBlock put: anotherObject
  189. | receiver selector arguments result |
  190. selector := self first asMutator.
  191. receiver := anObject yourself. "JSObjectProxy hack"
  192. arguments := { anotherObject }.
  193. [ result := receiver perform: selector withArguments: arguments ]
  194. on: MessageNotUnderstood do: [ :mnu |
  195. ((mnu message selector = selector
  196. and: [ mnu receiver == receiver ])
  197. and: [ mnu message arguments = arguments ])
  198. ifFalse: [ mnu resignal ].
  199. ^ aBlock value ].
  200. ^ result
  201. ! !
  202. !Number methodsFor: '*Axxord'!
  203. asAxisIn: anObject ifAbsent: aBlock
  204. (anObject respondsTo: #at:ifAbsent:)
  205. ifTrue: [ ^ anObject at: self ifAbsent: aBlock ]
  206. ifFalse: aBlock
  207. !
  208. asAxisIn: anObject ifAbsent: aBlock put: anotherObject
  209. (anObject respondsTo: #at:put:)
  210. ifTrue: [ ^ anObject at: self put: anotherObject ]
  211. ifFalse: aBlock
  212. ! !
  213. !Object methodsFor: '*Axxord'!
  214. asAxisIn: anObject ifAbsent: aBlock
  215. ^ aBlock value
  216. !
  217. asAxisIn: anObject ifAbsent: aBlock put: anotherObject
  218. ^ aBlock value
  219. !
  220. atAxes: aCollection ifAbsent: aBlock
  221. ^ aCollection inject: self into: [ :soFar :segment |
  222. segment asAxisIn: soFar ifAbsent: [ ^ aBlock value ]]
  223. !
  224. atAxes: aCollection ifAbsent: aBlock put: value
  225. | penultimate |
  226. penultimate := self atAxes: aCollection allButLast ifAbsent: aBlock.
  227. ^ aCollection last asAxisIn: penultimate ifAbsent: aBlock put: value
  228. !
  229. axes: aCollection consume: aBlock
  230. | value |
  231. value := self atAxes: aCollection ifAbsent: [ ^self ].
  232. ^ aBlock value: value
  233. !
  234. axes: aCollection transform: aBlock
  235. | value newValue |
  236. value := self atAxes: aCollection ifAbsent: [ ^self ].
  237. newValue := aBlock value: value.
  238. value == newValue ifFalse: [ self atAxes: aCollection ifAbsent: [ ^self ] put: newValue ].
  239. self registeredAxon ifNotNil: [:axon | axon changed: aCollection]
  240. !
  241. registeredAxon
  242. <inlineJS: 'return self.$axon$'>
  243. ! !
  244. !String methodsFor: '*Axxord'!
  245. asAxisIn: anObject ifAbsent: aBlock
  246. (anObject respondsTo: #at:ifAbsent:)
  247. ifTrue: [ ^ anObject at: self ifAbsent: aBlock ]
  248. ifFalse: aBlock
  249. !
  250. asAxisIn: anObject ifAbsent: aBlock put: anotherObject
  251. (anObject respondsTo: #at:put:)
  252. ifTrue: [ ^ anObject at: self put: anotherObject ]
  253. ifFalse: aBlock
  254. ! !