1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
|
----
-- Persistent expression Operator.
--
-- @classmod Op
deepcopy = (val) ->
switch type val
when 'number', 'string', 'boolean', 'nil'
val
when 'table'
assert (not getmetatable {}), "state should only contain simple tables!"
{(deepcopy k), (deepcopy v) for k,v in pairs val}
else
error "state cannot contain values of type '#{type val}'"
class Op
--- members
-- @section members
do_yield = (table) ->
for k, v in pairs table
if v.__class
coroutine.yield v
else
do_yield v
--- yield all `Input`s from the (potentially nested) `inputs` table
--
-- @treturn iterator iterator over `inputs`
all_inputs: => coroutine.wrap -> do_yield @inputs
--- create a mutable copy of this Op.
--
-- Used to wrap insulate eval-cycles from each other. The copy does not have
-- `inputs` set, since it is expected that this is (re)set in `setup`.
--
-- @treturn Op
fork: =>
out = if @out then @out\fork!
state = if @state then deepcopy @state
@@ out, state
--- internal state of this Op.
--
-- This may be any simple Lua value, including Lua tables, as long as it has
-- no metatables, multiple references/loops, userdata etc.
--
-- @tfield table state
--- `Stream` instance representing this Op's computed output value.
--
-- Must be set to a `Stream` instance once `setup` finishes. Must not change
-- type, be removed or replaced outside of `new` and `setup`. If it is a
-- `ValueStream`, it should have a value assigned via `set` or the
-- constructor once `tick` is called the first time. If `out`'s value is not
-- initialized in `new` or `setup`, the implementation must make sure
-- `tick``(true)` is called at least on the first eval-cycle the Op goes
-- through, e.g. by using an `Input.hot` with a `ValueStream`.
--
-- @tfield Stream out
--- table containing `Input`s to this Op.
--
-- The `inputs` table can be nested with string or integer keys,
-- but all leaf-entries must be `Input` instances. It must not contain loops
-- or instances of other classes.
--
-- @tfield {Input,...} inputs
--- Op interface.
--
-- methods that have to be implemented by `Op` implementations.
-- @section interface
--- construct a new instance.
--
-- The optional parameters `out` and `state` are used by `fork` to duplicate
-- an instance. If the constructor is overriden, these parameters must be
-- forwarded to the superconstructor unchanged.
--
-- @function new
-- @classmethod
-- @tparam ?Stream out `out`
-- @tparam ?table state `state`
--- parse arguments and patch self.
--
-- Called once every eval-cycle. `inputs` is a list of `Result`s that are the
-- argument to this op. The `inputs` have to be wrapped in `Input` instances
-- to define update behaviour. Use `base.match` to parse them, then delegate to
-- `super:setup` to patch the `Input` instances.
--
-- @function setup
-- @tparam {Result,...} inputs a sequence of `Result`s
-- @tparam Scope scope the active scope
--- handle incoming events and update `out` (optional).
--
-- Called once per frame if any `Input`s are dirty. Some `Input`s may have
-- special behaviour immediately after `setup` that can cause them to become
-- dirty at eval-time. In this case, an eval-time tick is executed. You can
-- detect this using the `setup` parameter.
--
-- `tick` is called after `setup`. `tick` is not called immediately after
-- `setup` if no `inputs` are dirty. Update `out` here.
--
-- @tparam bool setup whether this is an eval-time tick
tick: =>
--- called when the Op is destroyed (optional).
destroy: =>
--- implementation utilities.
--
-- super-methods and utilities for use by implementations.
-- @section super
--- if `type` is passed, an output stream is instantiated.
-- if `init` is passed, the stream is initialized to that Lua value.
-- it is okay not to use this and create the output stream in :setup() if the
-- type is not known at this time.
--
-- @classmethod
-- @tparam ?Stream out `out`
-- @tparam ?table state `state`
new: (@out, @state) =>
do_setup = (old, cur) ->
for k, cur_val in pairs cur
old_val = old and old[k]
-- are these inputs or nested tables?
cur_plain = cur_val and not cur_val.__class
old_plain = old_val and not old_val.__class
if cur_plain and old_plain
-- both are tables, recurse
do_setup old_val, cur_val
elseif not (cur_plain or old_plain)
-- both are streams (or nil), setup them
cur_val\setup old_val
--- setup previous `inputs`, if any, with the new inputs, and write them to
-- `inputs`. The `inputs` table can be nested with string or integer keys,
-- but all leaf-entries must be `Input` instances. It must not contain loops
-- or instances of other classes.
--
-- @tparam table inputs table of `Input`s
setup: (inputs) =>
old_inputs = @inputs
@inputs = inputs
do_setup old_inputs, @inputs
do_unwrap = (value) ->
if value.__class
value\unwrap!
else
{k, do_unwrap v for k,v in pairs value}
--- `\unwrap` all `Input`s in `@inputs` and return a table with the same
-- shape.
--
-- @treturn table the values of all `Input`s
unwrap_all: => do_unwrap @inputs
__tostring: => "<op: #{@@__name}>"
__inherited: (cls) => cls.__base.__tostring = @__tostring
{
:Op
}
|