aboutsummaryrefslogtreecommitdiffstats
path: root/alv/rtnode.moon
blob: 5fa4898f9aa6fee29d61c64e07e8742b8a6dd33e (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
----
-- RTNode of evaluating an expression.
--
-- `RTNode`s form a tree that controls execution order and message
-- between `Op`s.
--
-- @classmod RTNode
import Error from require 'alv.error'

class RTNode
--- members
-- @section members

  --- return whether this RTNode's result is const.
  is_const: => not next @side_inputs

  --- assert result-constness and return the result.
  -- @tparam[opt] string msg the error message to throw
  -- @treturn any
  const: (msg) =>
    assert not (next @side_inputs), msg or "eval-time const expected"
    @result

  --- assert this result has a result, return its type.
  -- @treturn string
  type: =>
    assert @result, "RTNode with result expected"
    @result.type

  --- assert this result has a result, returns its metatype.
  -- @treturn string `"result"` or `"event"`
  metatype: =>
    assert @result, "RTNode with result expected"
    @result.metatype

  --- create a copy of this result with result-copy semantics.
  --
  -- the copy has the same @result and @side_inputs, but will not (doubly)update
  -- anything on `tick` or `poll`.
  make_ref: =>
    RTNode result: @result, side_inputs: @side_inputs

  --- poll all IO Ops effecting this (sub)tree for changes.
  --
  -- should be called once per frame on the root, right before tick.
  --
  -- @treturn ?boolean whether any IO Op marked itself dirty.
  poll_io: =>
    dirty = false

    for op in *@io_ops
      dirty = op\poll! or dirty

    dirty

  --- in depth-first order, tick all Ops which have dirty Inputs.
  --
  -- short-circuits if there are no dirty Inputs in the entire subtree
  tick: =>
    if #@io_ops == 0
      any_dirty = false
      for result, input in pairs @side_inputs
        if input\dirty!
          any_dirty = true
          break

      -- early-out if no Inputs are dirty in this whole subtree
      return unless any_dirty

    for child in *@children
      child\tick!

    if @op
      -- we have to check self_dirty here, because Inputs from children may
      -- have become dirty due to \tick
      self_dirty = false
      for input in @op\all_inputs!
        if input\dirty!
          self_dirty = true

      return unless self_dirty

      Error.wrap "ticking #{@op}", @op\tick

  __tostring: =>
    buf = "<RT=#{@result}"
    buf ..= " #{@op}" if @op
    buf ..= " (#{#@children} children)" if #@children > 0
    buf ..= ">"
    buf

  --- the `Result` result.
  -- @tfield ?Result result

  --- an Op.
  -- @tfield ?Op op

  --- list of child `RTNode`s from subexpressions.
  -- @tfield {RTNode,...} children

  --- cached list of all IO Ops inside this RTNode and children.
  -- @tfield {Op,...} io_ops

  --- cached mapping of all `Result`/`Input` pairs affecting this RTNode.
  --
  -- This is the union of all `children`s `side_inputs` and all `Input`s from
  -- `op` that are not the `result` of any child.
  --
  -- @tfield {[Result]=Input,...} side_inputs

--- static functions
-- @section static

  --- create a new RTNode.
  -- @classmethod
  -- @param params table with optional keys op, result, children. default: {}
  new: (params={}) =>
    @result = params.result
    @op = params.op
    @children = params.children or {}
    @io_ops = params.io_ops or {}

    if params.side_inputs
      @side_inputs = params.side_inputs
      return

    @side_inputs = {}
    is_child = {}

    for child in *@children
      -- collect child side_inputs
      for result, input in pairs child.side_inputs
        @side_inputs[result] = input

      -- collect child io_ops
      for op in *child.io_ops
        table.insert @io_ops, op

      if child.result
        is_child[child.result] = true

    if @op
      if @op.poll
        table.insert @io_ops, @op

      -- find inputs outside this tree
      for i in @op\all_inputs!
        if i.mode == 'hot' and not is_child[i.result]
          @side_inputs[i.result] = i

    -- "freeze" ~-streams if there are no IO Ops around
    return unless @result and @result.metatype == '~'
    return if #@io_ops > 0

    all_const = true
    for result, input in pairs @side_inputs
      if result.metatype != '='
        all_const = false
        break
    return unless all_const

    @result = @result.type\mk_const @result\unwrap!

{
  :RTNode
}