aboutsummaryrefslogtreecommitdiffstats
path: root/core/base/op.moon
blob: a8da100369eb7e1edf2c72b2d78eba306d837135 (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
----
-- Persistent expression Operator.
--
-- @classmod Op
import Value from require 'core.value'

ident = (tbl) ->
  mt = getmetatable tbl
  setmetatable tbl, nil

  id = tostring tbl

  setmetatable tbl, mt
  id\sub #'table: 0x'

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 Value
  fork: =>
    with setmetatable {}, getmetatable @
      .state = {k,v for k,v in pairs @state} if @state
      .out = @out\fork! if @out

  --- `Value` instance representing this Op's computed output value.
  --
  -- Must be set to a `Value` instance once `setup` finishes. Must not change
  -- type, be removed or replaced outside of `new` and `setup`. Should have a
  -- value assigned via `set` or the `Value` 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.value`.
  --
  -- @tfield Value 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 super-constructor can be used to construct a `Value` instance in `out`.
  --
  -- @function new
  -- @classmethod

  --- 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 (like
  -- `Input.value`) 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[opt] string type the type-name for `out`
  -- @tparam[optchain] any init the initial value for `out`
  new: (type, init) =>
    @state = {}
    if type
      @out = Value type, init

  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
}