aboutsummaryrefslogtreecommitdiffstats
path: root/alv/base/input.moon
blob: fe01f8820696850ef3ddce5a3f561bc32d18ecb5 (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
----
-- Update scheduling policy for `Op` arguments.
--
-- @classmod Input
import ValueStream, EventStream, IOStream from require 'alv.stream'
import Result from require 'alv.result'

inherits = (klass, frm) ->
  assert klass, "cant find the ancestor of nil"
  return true if klass == frm
  while klass.__parent
    return true if klass.__parent == frm
    klass = klass.__parent
  false

match_parent = (inst, map) ->
  klass = assert inst and inst.__class, "not an instance"
  if key = map[klass]
    return key

  while klass.__parent
    if key = map[klass.__parent]
      return key

    klass = klass.__parent

local ColdInput, ValueInput, IOInput, mapping

class Input
--- Input interface.
--
-- Methods that have to be implemented by `Input` implementations.
-- @section interface

  --- create a new Input.
  --
  -- @classmethod
  -- @tparam Stream stream
  new: (@stream) =>
    assert @stream, "nil passed to Input: #{value}"

  --- copy state from old instance (optional).
  --
  -- called by `Op:setup` with another `Input` instance or `nil` once this instance is
  -- registered. Must prepare this instance for `dirty`.
  --
  -- May enter a 'setup state' that is exited using `finish_setup`.
  --
  -- @tparam ?Input prev previous `Input` intance or nil
  setup: (prev) =>

  --- whether this input requires processing (optional).
  --
  -- must return a boolean indicating whether `Op`s that refer to this instance
  -- should be notified (via `Op:tick`). If not overwritten, delegates to
  -- `stream`:@{ValueStream:dirty|dirty}.
  --
  -- @treturn bool whether processing is necessary
  dirty: => @stream\dirty!

  --- leave setup state (optional).
  --
  -- called after the `Op` has completed (or skipped) its first `Op:tick` after
  -- `Op:setup`. Must prepare this instance for dataflow operation.
  finish_setup: =>

  --- unwrap to Lua value (optional).
  --
  -- @treturn any the raw Lua value
  unwrap: => @stream\unwrap!

  --- return the type name of this `Input` (optional).
  type: => @stream.type

  --- return the metatype name of this `Input` (optional).
  metatype: => @stream.metatype

  --- the current value
  --
  -- @tfield ValueStream stream

--- members
-- @section members

  --- alias for `unwrap`.
  __call: => @stream\unwrap!

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

--- static functions
-- @section static

  --- Create a `cold` `Input`.
  --
  -- Never marked dirty. Use this for input streams that are only read when
  -- another `Input` is dirty.
  --
  -- @tparam Stream|Result value
  @cold: (value) ->
    if value.__class == Result
      value = assert value.value, "Input from result without value!"
    ColdInput value

  --- Create a `hot` `Input`.
  --
  -- Behaviour depends on what kind of `Stream` `value` is:
  --
  -- - `ValueStream`: Marked dirty only if old and new `ValueStream` differ.
  -- - `EventStream` and `IOStream`: Marked dirty only if the current
  --   `EventStream` is dirty.
  --
  -- This is the most common `Input` strategy.
  --
  -- @tparam Stream|Result value
  @hot: (value) ->
    if value.__class == Result
      value = assert value.value, "Input from result without value!"

    InputType = match_parent value, mapping
    assert InputType, "Input from unknown value: #{value}"
    InputType value

class ColdInput extends Input
  dirty: => false

class ValueInput extends Input
  setup: (old) => @dirty_setup = not old or @stream != old.stream
  finish_setup: => @dirty_setup = nil
  dirty: =>
    return @dirty_setup if @dirty_setup != nil
    @stream\dirty!

class IOInput extends Input
  io: true

mapping = {
  [ValueStream]: ValueInput
  [EventStream]: Input
  [IOStream]: IOInput
}

{
  :Input
}