aboutsummaryrefslogtreecommitdiffstats
path: root/spec/internal/rtnode_spec.moon
blob: f757067f28fa9e57285b0e595a66670e1ac41b9d (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
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
import do_setup from require 'spec.test_setup'
import RTNode, Scope, SimpleRegistry from require 'alv'
import T, Input, Op, Constant from require 'alv.base'

class IOOp extends Op
  new: (...) =>
    super ...
    @is_dirty = true

  poll: => @is_dirty

make_op = (Class, inputs={}) ->
  with Class!
    \setup inputs

setup do_setup

describe 'RTNode', ->
  describe 'constructor', ->
    it 'takes result, children', ->
      result = Constant.num 3

      a = RTNode!
      b = RTNode!
      children = { a, b }

      node = RTNode :result, :children

      assert.is.equal result, node.result
      assert.is.same children, node.children

    it 'takes op and detects io_ops', ->
      op = make_op Op
      rtn = RTNode :op
      assert.is.equal op, rtn.op
      assert.is.same {}, rtn.io_ops

      op = make_op IOOp
      rtn = RTNode :op
      assert.is.equal op, rtn.op
      assert.is.same { op }, rtn.io_ops

    describe 'detects Op inputs', ->
      it '(hot -> side_input)', ->
        sig = T.num\mk_sig!
        inp = Input.hot sig
        rtn = RTNode op: make_op Op, { inp }
        assert.is.same { [sig]: inp }, rtn.side_inputs

      it '(cold -> discard)', ->
        rtn = RTNode op: make_op Op, { Input.cold T.num\mk_sig! }
        assert.is.same {}, rtn.side_inputs

      it "(childrens' results -> discard)", ->
        child = RTNode op: make_op(Op), result: T.num\mk_sig 123
        parent = RTNode children: { child }, op: make_op Op, { Input.hot child.result }
        assert.is.same {}, parent.side_inputs

    describe 'lifts up', ->
      it 'side_inputs from children', ->
        expected_inputs = {}
        children = {}

        for i=1,3
          result = T.num\mk_sig!
          input = Input.hot result
          table.insert children, RTNode op: make_op Op, { input }
          expected_inputs[result] = input

        mid_rtn = RTNode children: children
        out_rtn = RTNode children: { mid_rtn }

        assert.is.same expected_inputs, mid_rtn.side_inputs
        assert.is.same expected_inputs, out_rtn.side_inputs

      it 'io_ops from children', ->
        a_op = make_op IOOp
        a_rtn = RTNode op: a_op

        b_op = make_op IOOp
        b_rtn = RTNode op: b_op

        c_op = make_op Op
        c_rtn = RTNode op: c_op

        mid_rtn = RTNode children: { a_rtn, b_rtn, c_rtn }
        out_op = make_op IOOp
        out_rtn = RTNode children: { mid_rtn }, op: out_op

        assert.is.same { a_op, b_op }, mid_rtn.io_ops
        assert.is.same { a_op, b_op, out_op }, out_rtn.io_ops

  it ':type gets type and assets value', ->
    node = RTNode result: Constant.num 2
    assert.is.equal T.num, node\type!

    node = RTNode!
    assert.has.error -> node\type!

  it ':is_const', ->
    result = Constant.num 2
    pure = RTNode :result
    impure = RTNode op: make_op Op, { Input.hot T.num\mk_sig! }

    assert.is.true pure\is_const!
    assert.is.false impure\is_const!

    assert.is.equal result, pure\const!
    assert.has.error -> impure\const!
    assert.has.error (-> impure\const 'test'), 'test'

  it ':make_ref', ->
    result = T.num\mk_sig 2
    input = Input.hot result
    op = make_op IOOp, { input }
    thick = RTNode :result, :op, children: { RTNode!, RTNode! }
    ref = thick\make_ref!

    assert ref
    assert.is.equal thick.result, ref.result
    assert.is.same thick.side_inputs, ref.side_inputs
    assert.is.same {}, ref.children
    assert.is.same {}, ref.io_ops
    assert.is.nil ref.op

  describe ':poll_io', ->
    it 'polls all io_ops in tree', ->
      child = RTNode op: make_op IOOp
      node = RTNode children: { child }, op: make_op IOOp

      sc = spy.on child.op, 'poll'
      sn = spy.on node.op, 'poll'
      node\poll_io!

      assert.spy(sc).was_called_with match.ref child.op
      assert.spy(sn).was_called_with match.ref node.op

    it 'returns whether any ops were dirty', ->
      child = RTNode op: make_op IOOp
      node = RTNode children: { child }, op: make_op IOOp

      assert.is.true node\poll_io!

      child.op.is_dirty = false
      assert.is.true node\poll_io!

      node.op.is_dirty = false
      assert.is.false node\poll_io!

      child.op.is_dirty = true
      assert.is.true node\poll_io!

  describe ':tick', ->
    local a_value, a_child, a_input
    local b_value, b_child, b_input
    before_each ->
      a_value = T.num\mk_evt!
      a_input = Input.hot a_value
      a_child = RTNode op: make_op Op, { a_input }

      b_value = T.num\mk_evt!
      b_input = Input.hot b_value
      b_child = RTNode op: make_op Op, { b_input }

    it 'updates children when a side_input is dirty', ->
      a_value\set 1
      assert.is.true a_input\dirty!
      assert.is.false b_input\dirty!

      a = spy.on a_child, 'tick'
      b = spy.on b_child, 'tick'

      node = RTNode children: { a_child, b_child }
      node\tick!

      assert.spy(a).was_called_with match.ref a_child
      assert.spy(b).was_called_with match.ref b_child

    it 'early-outs when no side_inputs are dirty', ->
      assert.is.false a_input\dirty!
      assert.is.false b_input\dirty!

      a = spy.on a_child, 'tick'
      b = spy.on b_child, 'tick'

      node = RTNode children: { a_child, b_child }
      node\tick!

      assert.spy(a).was_not_called!
      assert.spy(b).was_not_called!

    it 'updates op when any op-inputs are dirty', ->
      a_value\set 1
      assert.is.true a_input\dirty!
      assert.is.false b_input\dirty!

      op = make_op Op, { a: Input.hot a_value }
      s = spy.on op, 'tick'

      node = RTNode :op, children: { a_child, b_child }
      node\tick!

      assert.spy(s).was_called_with match.ref op

    it 'early-outs when no op-inputs are dirty', ->
      a_value\set 1
      assert.is.true a_input\dirty!
      assert.is.false b_input\dirty!

      op = make_op Op, { Input.hot b_value }
      s = spy.on op, 'tick'

      node = RTNode :op, children: { a_child, b_child }
      node\tick!

      assert.spy(s).was_not_called!