aboutsummaryrefslogtreecommitdiffstats
path: root/alv/base/op.moon
blob: 8cd28545ad0106880ad6adc05ba38249e9e00a14 (plain)
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
169
170
171
172
173
174
175
176
177
----
-- Persistent expression Operator.
--
-- @classmod Op
import deep_copy, deep_iter, deep_map from require 'alv.util'
import T from require 'alv.type'

class Op
--- members
-- @section members

  --- yield all `Input`s from the (potentially nested) `inputs` table
  --
  -- @treturn iterator iterator over `inputs`
  all_inputs: => coroutine.wrap -> deep_iter @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 deep_copy @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

  --- `Result` instance representing this Op's computed output value.
  --
  -- Must be set to a `Result` 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 Result 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 ?Result out `out`
  -- @tparam ?table state `state`

  --- parse arguments and patch self.
  --
  -- Called once every eval-cycle. `inputs` is a list of `RTNode`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 {RTNode,...} inputs a sequence of `RTNode`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: =>

  --- collect visualisation data (optional).
  --
  -- This may return any simple Lua value, including Lua tables, as long as it
  -- has no metatables, multiple references/loops, userdata etc.
  --
  -- This value is exposed to alv editors in order to render realtime
  -- visualisations overlaid onto the program text.
  --
  -- @treturn table vis
  vis: =>
    if @out and @out.metatype == '!'
      { type: 'event' }
    elseif @out and @out.type == T.bool
      { type: 'bool' }
    else
      {}

  --- poll for external changes (optional).
  --
  -- If implemented, this method will be called at a high frequency and should
  -- return `true` whenever processing is required due to an external event or
  -- condition. After polling all such IO Ops a new tick will be executed if any
  -- returned true. The implementation of `poll` is responsible for triggering
  -- the `tick` method by writing to an internally allocated `Result` that has
  -- been inserted into `inputs`.
  --
  -- @function poll
  -- @treturn ?boolean dirty whether processing is required

--- 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 ?Result out `out`
  -- @tparam ?table state `state`
  new: (@out, @state) =>

  do_setup = (old, cur) ->
    -- are these inputs or nested tables?
    old_plain = old and not old.__class
    cur_plain = cur and not cur.__class

    if cur_plain
      -- both are tables, recurse
      for k, cur_nest in pairs cur
        do_setup (old and old[k]), cur_nest
    elseif cur and not (cur_plain or old_plain)
      -- both are streams (or nil), setup them
      cur\setup old

  --- 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

  --- `\unwrap` all `Input`s in `@inputs` and return a table with the same
  -- shape.
  --
  -- @treturn table the values of all `Input`s
  unwrap_all: => deep_map @inputs, (i) -> i\unwrap!

  __tostring: => "<op: #{@@__name}>"
  __inherited: (cls) => cls.__base.__tostring = @__tostring

{
  :Op
}