aboutsummaryrefslogtreecommitdiffstats
path: root/alv/copilot.moon
blob: 7ef213482c664dd0295a6938f142a24ffadb49ff (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
----
-- File watcher and CLI entrypoint.
--
-- @classmod Copilot
lfs = require 'lfs'
import Scope from require 'alv.scope'
import Module from require 'alv.module'
import Error from require 'alv.error'
import Result from require 'alv.result'
import ValueStream from require 'alv.stream'

export COPILOT

class Copilot
--- static functions
-- @section static

  --- create a new Copilot.
  -- @classmethod
  -- @tparam string file name/path of the alive file to watch and execute
  new: (file) =>
    @T = 0
    @last_modification = 0
    @last_modules = {}
    @open file if file

--- members
-- @section members

  --- current tick
  -- @tfield number T

  --- change the running script.
  -- @tparam string file
  open: (file) =>
    if old = @last_modules.__root
      old\destroy!

    @last_modules.__root = Module file
    @active_module = @last_modules.__root

  --- require a module.
  -- @tparam string name
  -- @treturn Result result
  require: (name) =>
    Error.wrap "loading module '#{name}'", ->
      ok, lua = pcall require, "alv-lib.#{name}"
      if ok
        Result value: ValueStream.wrap lua
      else
        assert @modules, "no current eval cycle?"
        if mod = @modules[name]
          mod.root\make_ref!
        else
          last = @active_module
          prefix = @active_module.file\match('(.*)/[^/]*$') .. '/' or ''
          mod = @last_modules[name] or Module "#{prefix}#{name}.alv"
          L\trace "entering module #{mod}"
          @modules[name] = mod
          @active_module = mod
          ok, err = pcall mod\eval
          L\trace "returning to module #{mod}"
          @active_module = last
          if ok
            mod.root
          else
            error err

  --- poll for changes and tick.
  tick: =>
    assert not COPILOT, "another Copilot is already running!"
    return unless @last_modules.__root

    COPILOT = @
    @T += 1

    @poll!

    root = @last_modules.__root
    if root and root.root
      L\set_time 'run'
      ok, error = Error.try "updating", ->
        root.root\poll_io!
        root.root\tick!
      if not ok
        L\print error

    COPILOT = nil

  --- poll all loaded modules for changes.
  --
  -- Call `eval` if there are any, and write changed and newly added modules
  -- back to disk.
  poll: =>
    dirty = {}
    for name, mod in pairs @last_modules
      if mod\poll! > @last_modification
        table.insert dirty, mod

    return if #dirty == 0

    @last_modification = os.time!
    L\set_time 'eval'
    L\print "changes to files: #{table.concat [m.file for m in *dirty], ', '}"

    @modules = { __root: @last_modules.__root }
    ok, err = Error.try "processing changes", @modules.__root\eval

    if not ok
      for name, mod in pairs @modules
        mod\rollback!
      @modules = nil
      L\error err
      return

    for name, mod in pairs @last_modules
      if not @modules[name]
        mod\destroy!

    for name, mod in pairs @modules
      mod\finish!

    @last_modification = os.time!
    @last_modules, @modules = @modules, nil

{
  :Copilot
}