alive / 29a466d alv / rtnode.moon

Tree @29a466d (Download .tar.gz)

rtnode.moon @29a466draw · history · blame

-- 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"

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

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

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

  --- poll all IOStream instances that are effecting this (sub)tree.
  -- should be called once per frame on the root, right before tick.
  poll_io: =>
    for result, input in pairs @side_inputs
      result\poll! if input.mode == 'io'

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

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

    for child in *@children

    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 ..= ">"

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

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

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

  --- 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 {}

    if params.side_inputs
      @side_inputs = params.side_inputs

    @side_inputs, is_child = {}, {}
    for child in *@children
      for result, input in pairs child.side_inputs
        @side_inputs[result] = input
      if child.result
        is_child[child.result] = true

    if @op
      for i in @op\all_inputs!
        if i.mode == 'io' or (i.mode == 'hot' and not is_child[i.result])
          @side_inputs[i.result] = i

    if @result
      if next @side_inputs
        assert @result.metatype != '=', "Const result #{@result} has side_inputs"
      elseif @result.metatype == '~'
        @result = @result.type\mk_const @result\unwrap!