Herbert Vojčík 392d117439 0.3.1 | 7 vuotta sitten | |
---|---|---|
src | 7 vuotta sitten | |
.gitignore | 9 vuotta sitten | |
Gruntfile.js | 7 vuotta sitten | |
LICENSE-MIT | 9 vuotta sitten | |
README.md | 7 vuotta sitten | |
bower.json | 7 vuotta sitten | |
deploy.js | 7 vuotta sitten | |
devel.js | 7 vuotta sitten | |
index.html | 7 vuotta sitten | |
local.amd.json | 7 vuotta sitten | |
package.json | 7 vuotta sitten | |
testing.js | 7 vuotta sitten |
Small blackboard system for Amber Smalltalk.
Axon is a pub-sub system used in Axxord. It is fully generic, so it can be used for other purposes as well, without using the rest of the library.
It consists of two components: Axon
abstract base class
(and its implemenetation SimpleAxon
), which are subscription managers;
and AxonInterest
abstract base class (and its pluggable implementation
PluggableInterest
) that represent the actual subscription.
You can create an axon, like in axon := SimpleAxon new
. Then, you will add
some interests in an aspect, as in axon addInterest: (PluggableInterest new
accept: [:aspect | aspect = #amount] enact: [Transcript show: 'Amount is ';
show: aThing amount; cr])
. It is not prescribed what an aspect can be,
or an aspect of what it is. It is used only to distinguish whether the change
is related to the interest or not (as in previous example, one is interested
in changes of #amount
).
Whenever the change in some aspect happens for the object axon is watching,
axon should be let known, using axon changed: #amount
. You can also
tell an axon that everything has changed, using axon changedAll
.
Axon then makes related interests enact, eventually (it may or may not happen
immediately in the same thread, so enact should actually check if it is still
interesting when it takes its turn).
There is no removeInterest:
. The interest removes itself by signalling,
as in PluggableInterest accept: [:aspect | aspect = #enabled]
enact: [aThing isEnabled ifTrue: [Transcript show: 'still alive'; cr]
ifFalse: [AxonOff signal]]
.
Axon and friends are all in their dedicated Axxord-Axon
category / package.
Axes is hierarchical index used to access blackboard data.
Axes is an array of elements: either strings, numbers or a sub-arrays. These are used to denote the (relative) location of a piece of data in a hierarchical object, and is used to read or write from / to this position.
Elements of a path are equivalent to elements of paths in classic file systems: each elements is one step deeper in a tree hierarchy. Thus, to read a data denoted by a path, Axes starts from actual position, reads the contents denoted by first element, use the result to read the contents denoted by second elements etc. until the end. To write the data, the algorithm is similar to reading one, byt the last element is used to write the data instead.
foo at: aString
is performed;foo at: aString put: value
is performed;foo at: aNumber
is performed;foo at: aNumber put: value
is performed;#(bar)
is read from foo, foo bar
is performed;#(bar)
is written to foo, foo bar: value
is performed.For example reading container
at axes #((todos) 1 done)
essentially does
| x |
x := container todos at: 1.
^ x at: 'done'
But, whenever:
container
fails to perform todos
, orcontainer todos
fails to perform at:ifAbsent:
, orcontainer todos
does not contain index 1, orcontainer todos at: 1
fails to perform at:ifAbsent:
, orcontainer todos at: 1
does not contain index 'done',the "failed to get" fallback is taken.
Similarly, putting true
into container
at axes: #((todos) 1 done)
essentially does
| x |
x := container todos at: 1.
^ x at: 'done' put: true
But, whenever:
container
fails to perform todos
, orcontainer todos
fails to perform at:ifAbsent:
, orcontainer todos
does not contain index 1, orcontainer todos at: 1
fails to do at:put:
,the "failed to put" fallback is taken.
Axes
class can parse a string representation of axes. The syntax
of the string representation is resembling Smalltalk literal array syntax
very closely. For example Axes parse: '(value)'
and Axes parse:
'(todos) 1 done'
produce #((value))
and #((todos) 1 done)
as results.
Syntactic sugar: as (foo)
happens often, to denote unary selector,
it can be written equivalently as ~foo
, to improve readability.
So above Axes' parseable string representation
would likely be written '~value'
and '~todos 1 done'
instead.
Axxord is a blackboard-like system using Axon
and friends to observe
the object of interest, using axes as an aspect, and coming with a few helpers
included.
You create the model you want to observe as plain Smalltalk object
(wrapped JavaScript object should work, too). Then create an axon:
axon := SimpleAxon new
and assign it to the object of interest:
model axxord: axon
.
The clients of the model should access it via the axxord extension methods
axes:consume:
and axes:transform:
. To read an aspect and enact on
the result, use model axes: #((value)) consume: [:counter | ...]
; to change
the aspect, use model axes: #((todos) 1 done) transform: [:done | done not]
.
The latter automatically informs the axon after the change is made.
There are convenience methods to create the axes-related interests.
To create an interest on an aspect only, but not its sub-contents,
use Axes newInterestUpTo: #(left (amount)) doing: [...]
. This would react to
axon changed: #(left)
as well as axon changed: #(left (amount))
,
but not to axon changed :#(left (amount) (description))
. On the contrary,
an interest created by Axes newInterestThru: #(left (amount)) doing: [...]
would react to axon changed :#(left (amount) (description))
as well,
but none of them would react to unrelated axon changed: #(left (name))
;
as shown in the following table
axon changed: |
newInterestUpTo: #(left (amount)) |
newInterestThru: #(left (amount)) |
---|---|---|
#(left) |
enacts | enacts |
#(left (amount)) |
enacts | enacts |
#(left (amount) (desc)) |
passes | enacts |
#(left (name)) |
passes | passes |
There is a helper class that allows you to isolate reads/writes done by
axes:consume:
and axes:transform:
from each other. Instead of using
model := MyModel new
instance directly, use model := Axolator on:
MyModel new
instead. It wraps the instance you pass to on:
so that
axes:consume:
always gets deepCopy
of the value (so you it can be modified
but version in the blackboard stays unmodified) and the result written back
to blackboard by axes:transform:
is sent deepCopy
as well
(so if value passed to axes:transform:
is saved and modified later,
it does not alter the blackboard).