aboutsummaryrefslogtreecommitdiffstats
path: root/spec/rtnode_spec.moon
blob: 6ac7d1552d6794e933360dd9980cc12526b4ce07 (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
import do_setup from require 'spec.test_setup'
import RTNode, Scope, SimpleRegistry from require 'alv'
import T, Input, Op, Constant, IOStream from require 'alv.base'

setup do_setup

class DirtyIO extends IOStream
  new: => super T.dirty_io
  dirty: => true

op_with_inputs = (inputs) ->
  with Op!
    \setup inputs if inputs

dirty_op = ->
  result = DirtyIO!
  input = Input.hot result
  result, input, op_with_inputs { input }

node_with_sideinput = (result, input) ->
  RTNode :result, side_inputs: { [result]: input }

describe 'RTNode', ->
  it 'wraps 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 ':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 = node_with_sideinput result, {}

    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 = op_with_inputs { 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.nil ref.op

  it 'lifts up inputs from op', ->
    event = T.bang\mk_evt!
    event_input = Input.hot event

    value = T.num\mk_sig 4
    value_input = Input.hot value

    op = op_with_inputs { event_input, value_input }
    node = RTNode :op, result: value

    assert.is.equal op, node.op
    assert.is.same { [event]: event_input, [value]: value_input },
                   node.side_inputs

  it 'does not lift up op inputs that are also children', ->
    child_result, child_input, child_op = dirty_op!
    result = T.num\mk_sig 4
    child = RTNode op: child_op, :result

    event = T.bang\mk_evt!
    event_input = Input.hot event
    input = Input.hot child

    op = op_with_inputs { event_input, input }
    node = RTNode :op, children: { child }

    assert.is.same { [event]: event_input, [child_result]: child_input },
                   node.side_inputs

  it 'does not lift up op inputs that are cold', ->
    bang = T.bang\mk_const true
    bang_input = Input.hot bang

    num = T.num\mk_const 2
    num_input = Input.cold num

    sym = T.sym\mk_sig 'hello'
    sym_input = Input.hot sym

    op = op_with_inputs { bang_input, num_input, sym_input }
    node = RTNode :op

    assert.is.same { [sym]: sym_input }, node.side_inputs

  it 'lifts up side_inputs from children', ->
    event_value = T.bang\mk_evt!
    event_input = Input.hot event_value
    event = RTNode op: op_with_inputs { event_input }
    assert.is.same { [event_value]: event_input }, event.side_inputs

    value_value = T.num\mk_sig 4
    value_input = Input.hot value_value
    value = RTNode op: op_with_inputs { value_input }
    assert.is.same { [value_value]: value_input }, value.side_inputs

    node = RTNode children: { event, value }
    assert.is.same { [event_value]: event_input, [value_value]: value_input },
                   node.side_inputs

  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 = node_with_sideinput a_value, a_input

      b_value = T.num\mk_evt!
      b_input = Input.hot b_value
      b_child = node_with_sideinput b_value, 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 = op_with_inputs 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 = op_with_inputs { 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!

  describe ':poll_io', ->
    it 'polls IOs referenced in side_inputs', ->
      io, input, op = dirty_op!
      node = RTNode :op

      s = spy.on io, 'poll'
      assert.is.same { [io]: input }, node.side_inputs
      node\poll_io!

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