diff options
| author | s-ol <s-ol@users.noreply.github.com> | 2020-03-02 19:08:48 +0000 |
|---|---|---|
| committer | s-ol <s-ol@users.noreply.github.com> | 2020-03-02 19:09:56 +0000 |
| commit | c26b90317cb67ddcf71237e1d2f5fad87a5a3191 (patch) | |
| tree | 3577416ff058cbd5e955c33693684a2b78c0c364 /core | |
| parent | find IO via Result tree, not Registry (diff) | |
| download | alive-c26b90317cb67ddcf71237e1d2f5fad87a5a3191.tar.gz alive-c26b90317cb67ddcf71237e1d2f5fad87a5a3191.zip | |
document more interfaces
Diffstat (limited to 'core')
| -rw-r--r-- | core/base.moon | 225 | ||||
| -rw-r--r-- | core/cell.moon | 4 | ||||
| -rw-r--r-- | core/init.moon | 8 |
3 files changed, 151 insertions, 86 deletions
diff --git a/core/base.moon b/core/base.moon index 41ddd4c..8bd6271 100644 --- a/core/base.moon +++ b/core/base.moon @@ -1,58 +1,29 @@ -- base definitions for extensions import Value, Result from require 'core.value' +import match from require 'core.pattern' unpack or= table.unpack -class Input - new: (value) => - assert value, "nil passed to Input: #{value}" - @stream = switch value.__class - when Result - assert value.value, "Input from result without value!" - when Value - value - else - error "Input from unknown value: #{value}" - - merge: (previous) => - - finish_setup: => - dirty: => @stream\dirty! - unwrap: => @stream\unwrap! - type: => @stream.type - - __call: => @stream\unwrap! - __tostring: => "#{@@__name}:#{@stream}" - __inherited: (cls) => - cls.__base.__call = @__call - cls.__base.__tostring = @__tostring - --- ColdInput scheduling policy +-- an incoming side-effect adapter, polled by the main event loop to pump +-- events into the dataflow graph. -- --- never marked dirty -class ColdInput extends Input - dirty: => false - --- ValueInput scheduling policy +-- subclasses must implement this interface: -- --- during setup, only marked dirty if old and new stream differ in value -class ValueInput extends Input - merge: (old) => @dirty_setup = not old or @stream != old.stream - finish_setup: => @dirty_setup = false - dirty: => @dirty_setup or @stream\dirty! - --- EventInput scheduling policy +-- :new() - construct a new instance -- --- only marked dirty if the input stream itself is dirty -class EventInput extends Input - --- IOInput scheduling policy +-- must prepare the instance for :dirty(). +-- +-- :tick() - poll for changes +-- +-- called every frame by the event loop to update internal state. +-- +-- :dirty() - whether this adapter requires processing +-- +-- must return a boolean indicating whether `Op`s that refer to this instance +-- via `IOInput` should be notified (via `Op:tick()`). May be called multiple +-- multiple times. May be called before :tick() on the first frame after +-- construction. -- --- lifts streams of IO objects to events -class IOInput extends Input - impure: true - dirty: => @stream\unwrap!\dirty! - class IO -- called in the main event loop tick: => @@ -62,20 +33,55 @@ class IO -- a persistent expression Operator -- --- accepts Const or Stream inputs and produces a Stream output +-- subclasses must implement this interface: +-- +-- :new() - construct a new instance +-- +-- the super-constructor can be used to construct a `Value` instance in @out. +-- +-- :setup(inputs, scope) - parse arguments and patch self +-- +-- called once every eval-cycle. `inputs` is a list of `Result`s that are the +-- argument to this op. The `inputs` have to be wrapped in `Input` instances +-- to define update behaviour. Use `match` to parse them, then delegate to +-- super to patch the `Input` instances. +-- +-- :tick(setup) - handle incoming events and update @out +-- +-- called once per frame if any inputs are dirty. Some `Input`s (like +-- `ValueInput`) have special behaviour immediately after :setup(). You can +-- detect this using the `setup` parameter, which is true the first time +-- :tick() is called after :setup(). :tick() is not called immediately after +-- :setup() if no `@inputs` are dirty. Update @out here. +-- +-- :destroy() - called when the Op is destroyed +-- +-- .out - a `Value` instance representing this Op's computed output value. +-- +-- @out must be set to a `Value` instance once :setup() finishes. @out must +-- not change type, be removed or replaced outside of :new() and :setup(). +-- @out should have a value assigned via :set() or the `Value` constructor +-- once :tick(true) is called. If @out's value is not initialized in :new() +-- or :setup(), the implementation must make sure :tick(true) is called at +-- least on the first eval-cycle the Op goes through, e.g. by using a +-- `ValueInput`. +-- class Op +-- super-implementations for extensions + -- if `type` is passed, an output stream is instantiated. + -- if `init` is passed, the stream is initialized to that Lua value. + -- it is okay not to use this and create the output stream in :setup() if the + -- type is not known at this time. new: (type, init) => - @impulses = {} - if type @out = Value type, init - -- (re)-initialize this Op with the given inputs - -- after this method finishes, :tick(true) is called once, after which - -- @impulses and @out have to be set and may not change until :setup() - -- is called again. + -- setups previous @inputs, if any, with the new inputs, and writes them to + -- `@inputs`. The `inputs` table can be nested with string or integer keys, + -- but all leaf-entries must be `Input` instances. It must not contain loops + -- or instances of other classes. setup: do - do_merge = (old, cur) -> + do_setup = (old, cur) -> for k, cur_val in pairs cur old_val = old and old[k] @@ -85,16 +91,20 @@ class Op if cur_plain and old_plain -- both are tables, recurse - do_merge old_val, cur_val + do_setup old_val, cur_val elseif not (cur_plain or old_plain) - -- both are streams (or nil), merge them - cur_val\merge old_val + -- both are streams (or nil), setup them + cur_val\setup old_val (inputs) => old_inputs = @inputs @inputs = inputs - do_merge old_inputs, @inputs + do_setup old_inputs, @inputs + + tick: => + destroy: => +-- utilities -- iterate over the (potentially nested) inputs table all_inputs: do do_yield = (table) -> @@ -106,15 +116,6 @@ class Op => coroutine.wrap -> do_yield @inputs - -- called once per frame if any inputs or impulses are dirty, and once - -- immediately after :setup(). 'first' will be true in the latter case. - -- Should update @out. - tick: (first) => - - -- called once when the Op is destroyed - destroy: => - --- utilities unwrap_all: do do_unwrap = (value) -> if value.__class @@ -139,19 +140,17 @@ class Op __tostring: => "<op: #{@@__name}>" __inherited: (cls) => cls.__base.__tostring = @__tostring --- a builtin / special form / cell-evaluation strategy +-- a builtin / special form / cell-evaluation strategy. -- --- responsible for quoting/evaluating subexpressions, --- instantiating and patching Ops, --- updating the current Scope, --- etc. +-- responsible for quoting/evaluating subexpressions, instantiating and patching +-- Ops updating the current Scope, etc. See core.builtin and core.invoke for +-- many examples. class Action -- head: the (:eval'd) head of the Cell to evaluate (a Const) -- tag: the Tag of the expression to evaluate new: (head, @tag) => @patch head --- AST interface -- * eval args -- * perform scope effects -- * patch nested exprs @@ -202,23 +201,97 @@ class Action __tostring: => "<#{@@__name} #{@head}>" __inherited: (cls) => cls.__base.__tostring = @__tostring --- a ALV function definition +-- an ALV function definition -- -- when called, expands its body with params bound to the fn arguments -- (see core.invoke.fn-invoke) class FnDef -- params: sequence of (:quote'd) symbols, each naming a function parameter -- body: (:quote'd) expression the function evaluates to - -- scoe: the lexical scope the function was defined in (closure) + -- scope: the lexical scope the function was defined in (closure) new: (@params, @body, @scope) => __tostring: => "(fn (#{table.concat [p\stringify! for p in *@params], ' '}) ...)" +-- an update scheduling policy for `Op`. +-- +-- subclasses must implement this interface: +-- +-- :new(value) - create an instance +-- +-- `value` is either a `Value` or a `Result` instance and should be unwrapped. +-- +-- :setup(prev) - copy state from old instance +-- +-- called by `Op:setup()` with another `Input` instance or `nil` once this instance is +-- registered. Must prepare this instance for :dirty(). +-- +--- :dirty() - whether this input requires processing +-- +-- must return a boolean indicating whether `Op`s that refer to this instance +-- should be notified (via `Op:tick()`). +-- +-- :finish_setup() - leave setup state +-- +-- called after the Op has completed (or skipped) its first `Op:tick()` after +-- `Op:setup()`. Must prepare this instance for dataflow operation. +-- +class Input + new: (value) => + assert value, "nil passed to Input: #{value}" + @stream = switch value.__class + when Result + assert value.value, "Input from result without value!" + when Value + value + else + error "Input from unknown value: #{value}" + + setup: (previous) => + + finish_setup: => + dirty: => @stream\dirty! + unwrap: => @stream\unwrap! + type: => @stream.type + + __call: => @stream\unwrap! + __tostring: => "#{@@__name}:#{@stream}" + __inherited: (cls) => + cls.__base.__call = @__call + cls.__base.__tostring = @__tostring + +-- Never marked dirty. Use this for input streams that are only read when +-- another input fires. +class ColdInput extends Input + dirty: => false + +-- Marked dirty for the setup-tick if old and new stream differ in current +-- value. This is the most common `Input` strategy. Should be used whenever a +-- value denotes state. +class ValueInput extends Input + setup: (old) => @dirty_setup = not old or @stream != old.stream + finish_setup: => @dirty_setup = false + dirty: => @dirty_setup or @stream\dirty! + +-- Only marked dirty if the input stream itself is dirty. Should be used +-- whenever a value denotes a momentary event or impulse. +class EventInput extends Input + +-- Marked dirty when an IO object is dirty. Must be used for IO values. +class IOInput extends Input + impure: true + dirty: => @stream\unwrap!\dirty! + { - :ValueInput, :EventInput, :IOInput, :ColdInput :IO :Op :Action :FnDef + + :ValueInput, :EventInput, :IOInput, :ColdInput + + -- redundant exports, to keep anything an extension might need in one import + :Value, :Result + :match } diff --git a/core/cell.moon b/core/cell.moon index c052d46..724b74e 100644 --- a/core/cell.moon +++ b/core/cell.moon @@ -23,8 +23,8 @@ class Cell Action\eval_cell scope, @tag, head, @tail! - -- quoting a Cell recursively quotes children, but preserves identity this - -- means that a quoted Cell may only be 'used' once. use :clone() otherwise. + -- quoting a Cell recursively quotes children, but preserves identity. This + -- means that a quoted Cell may only be 'used' once. Use :clone() otherwise. quote: (scope) => children = [child\quote scope for child in *@children] Cell @tag, children, @white diff --git a/core/init.moon b/core/init.moon index 6a8c5d4..73f63f9 100644 --- a/core/init.moon +++ b/core/init.moon @@ -1,9 +1,5 @@ L or= setmetatable {}, __index: => -> -import Op, IO, Action, FnDef, EventInput, ValueInput, IOInput, ColdInput - from require 'core.base' -import match from require 'core.pattern' - import Value, Result, load_ from require 'core.value' import Scope from require 'core.scope' load_! @@ -19,10 +15,6 @@ globals = Scope.from_table require 'core.builtin' { :Value, :Result :Cell, :RootCell - - :Op, :IO, :Action, :FnDef - :EventInput, :ValueInput, :IOInput, :ColdInput, - :match :Scope :Registry, :Tag |
