github.com/e154/smart-home@v0.17.2-0.20240311175135-e530a6e5cd45/system/scripts/eventloop/eventloop.go (about) 1 // This file is part of the Smart Home 2 // Program complex distribution https://github.com/e154/smart-home 3 // Copyright (C) 2016-2023, Filippov Alex 4 // 5 // This library is free software: you can redistribute it and/or 6 // modify it under the terms of the GNU Lesser General Public 7 // License as published by the Free Software Foundation; either 8 // version 3 of the License, or (at your option) any later version. 9 // 10 // This library is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 // Library General Public License for more details. 14 // 15 // You should have received a copy of the GNU Lesser General Public 16 // License along with this library. If not, see 17 // <https://www.gnu.org/licenses/>. 18 19 package eventloop 20 21 import ( 22 "time" 23 24 "github.com/dop251/goja" 25 "github.com/dop251/goja_nodejs/console" 26 "github.com/dop251/goja_nodejs/require" 27 ) 28 29 type job struct { 30 goja.Callable 31 args []goja.Value 32 cancelled bool 33 } 34 35 type timer struct { 36 job 37 timer *time.Timer 38 } 39 40 type interval struct { 41 job 42 ticker *time.Ticker 43 stopChan chan struct{} 44 } 45 46 // EventLoop ... 47 type EventLoop struct { 48 vm *goja.Runtime 49 jobChan chan func() 50 jobCount int32 51 running bool 52 } 53 54 // NewEventLoop ... 55 func NewEventLoop(vm *goja.Runtime) *EventLoop { 56 57 loop := &EventLoop{ 58 vm: vm, 59 jobChan: make(chan func()), 60 } 61 62 new(require.Registry).Enable(vm) 63 console.Enable(vm) 64 _ = vm.Set("setTimeout", loop.setTimeout) 65 _ = vm.Set("setInterval", loop.setInterval) 66 _ = vm.Set("clearTimeout", loop.clearTimeout) 67 _ = vm.Set("clearInterval", loop.clearInterval) 68 69 return loop 70 } 71 72 func (loop *EventLoop) schedule(call goja.FunctionCall, repeating bool) goja.Value { 73 if fn, ok := goja.AssertFunction(call.Argument(0)); ok { 74 delay := call.Argument(1).ToInteger() 75 var args []goja.Value 76 if len(call.Arguments) > 2 { 77 args = call.Arguments[2:] 78 } 79 if repeating { 80 return loop.vm.ToValue(loop.addInterval(fn, time.Duration(delay)*time.Millisecond, args)) 81 } else { 82 return loop.vm.ToValue(loop.addTimeout(fn, time.Duration(delay)*time.Millisecond, args)) 83 } 84 } 85 return nil 86 } 87 88 func (loop *EventLoop) setTimeout(call goja.FunctionCall) goja.Value { 89 return loop.schedule(call, false) 90 } 91 92 func (loop *EventLoop) setInterval(call goja.FunctionCall) goja.Value { 93 return loop.schedule(call, true) 94 } 95 96 // Run calls the specified function, starts the event loop and waits until there are no more delayed jobs to run 97 // after which it stops the loop and returns. 98 // The instance of goja.Runtime that is passed to the function and any Values derived from it must not be used outside 99 // of the function. 100 // Do NOT use this function while the loop is already running. Use RunOnLoop() instead. 101 func (loop *EventLoop) Run(fn func(*goja.Runtime)) { 102 fn(loop.vm) 103 loop.run() 104 } 105 106 // Start the event loop in the background. The loop continues to run until Stop() is called. 107 func (loop *EventLoop) Start() { 108 go loop.runInBackground() 109 } 110 111 // Stop the loop that was started with Start(). After this function returns there will be no more jobs executed 112 // by the loop. It is possible to call Start() or Run() again after this to resume the execution. 113 // Note, it does not cancel active timeouts. 114 func (loop *EventLoop) Stop() { 115 ch := make(chan struct{}) 116 117 loop.jobChan <- func() { 118 loop.running = false 119 ch <- struct{}{} 120 } 121 122 <-ch 123 } 124 125 // RunOnLoop schedules to run the specified function in the context of the loop as soon as possible. 126 // The order of the runs is preserved (i.e. the functions will be called in the same order as calls to RunOnLoop()) 127 // The instance of goja.Runtime that is passed to the function and any Values derived from it must not be used outside 128 // of the function. 129 func (loop *EventLoop) RunOnLoop(fn func(*goja.Runtime)) { 130 loop.jobChan <- func() { 131 fn(loop.vm) 132 } 133 } 134 135 func (loop *EventLoop) run() { 136 loop.running = true 137 for loop.running && loop.jobCount > 0 { 138 job, ok := <-loop.jobChan 139 if !ok { 140 break 141 } 142 job() 143 } 144 } 145 146 func (loop *EventLoop) runInBackground() { 147 loop.running = true 148 for job := range loop.jobChan { 149 job() 150 if !loop.running { 151 break 152 } 153 } 154 } 155 156 func (loop *EventLoop) addTimeout(f goja.Callable, timeout time.Duration, args []goja.Value) *timer { 157 t := &timer{ 158 job: job{Callable: f, args: args}, 159 } 160 161 t.timer = time.AfterFunc(timeout, func() { 162 loop.jobChan <- func() { 163 loop.doTimeout(t) 164 } 165 }) 166 167 loop.jobCount++ 168 return t 169 } 170 171 func (loop *EventLoop) addInterval(f goja.Callable, timeout time.Duration, args []goja.Value) *interval { 172 i := &interval{ 173 job: job{Callable: f, args: args}, 174 ticker: time.NewTicker(timeout), 175 stopChan: make(chan struct{}), 176 } 177 178 go i.run(loop) 179 loop.jobCount++ 180 return i 181 } 182 183 func (loop *EventLoop) doTimeout(t *timer) { 184 if !t.cancelled { 185 _, _ = t.Callable(nil, t.args...) 186 t.cancelled = true 187 loop.jobCount-- 188 } 189 } 190 191 func (loop *EventLoop) doInterval(i *interval) { 192 if !i.cancelled { 193 _, _ = i.Callable(nil, i.args...) 194 } 195 } 196 197 func (loop *EventLoop) clearTimeout(t *timer) { 198 if !t.cancelled { 199 t.timer.Stop() 200 t.cancelled = true 201 loop.jobCount-- 202 } 203 } 204 205 func (loop *EventLoop) clearInterval(i *interval) { 206 if !i.cancelled { 207 i.cancelled = true 208 i.stopChan <- struct{}{} 209 loop.jobCount-- 210 } 211 } 212 213 func (i *interval) run(loop *EventLoop) { 214 for { 215 select { 216 case <-i.stopChan: 217 i.ticker.Stop() 218 break 219 case <-i.ticker.C: 220 loop.jobChan <- func() { 221 loop.doInterval(i) 222 } 223 } 224 } 225 }