diff --git a/Makefile b/Makefile index a7ee97ecabeae8..27ce567d234742 100644 --- a/Makefile +++ b/Makefile @@ -4,6 +4,7 @@ TS_FILES = \ msg.pb.js \ os.ts \ runtime.ts \ + timers.ts \ util.ts deno: assets.go msg.pb.go main.go diff --git a/main.go b/main.go index be7ad541f21b78..39ba9cb87ed6f1 100644 --- a/main.go +++ b/main.go @@ -9,8 +9,13 @@ import ( "os" "path" "runtime" + "sync" + "time" ) +var wg sync.WaitGroup +var resChan chan *Msg + func SourceCodeHash(filename string, sourceCodeBuf []byte) string { h := md5.New() h.Write([]byte(filename)) @@ -70,6 +75,22 @@ func HandleSourceCodeCache(filename string, sourceCode string, return out } +func HandleTimerStart(id int32, interval bool, duration int32) []byte { + wg.Add(1) + go func() { + defer wg.Done() + time.Sleep(time.Duration(duration) * time.Millisecond) + resChan <- &Msg{ + Payload: &Msg_TimerReady{ + TimerReady: &TimerReadyMsg{ + Id: id, + }, + }, + } + }() + return nil +} + func UserHomeDir() string { if runtime.GOOS == "windows" { home := os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH") @@ -121,7 +142,11 @@ func recv(buf []byte) []byte { return HandleSourceCodeFetch(payload.Filename) case *Msg_SourceCodeCache: payload := msg.GetSourceCodeCache() - return HandleSourceCodeCache(payload.Filename, payload.SourceCode, payload.OutputCode) + return HandleSourceCodeCache(payload.Filename, payload.SourceCode, + payload.OutputCode) + case *Msg_TimerStart: + payload := msg.GetTimerStart() + return HandleTimerStart(payload.Id, payload.Interval, payload.Duration) default: panic("Unexpected message") } @@ -137,6 +162,9 @@ func main() { cwd, err := os.Getwd() check(err) + resChan = make(chan *Msg) + doneChan := make(chan bool) + out, err := proto.Marshal(&Msg{ Payload: &Msg_Start{ Start: &StartMsg{ @@ -151,4 +179,23 @@ func main() { os.Stderr.WriteString(err.Error()) os.Exit(1) } + + // In a goroutine, we wait on for all goroutines to complete (for example + // timers). We use this to signal to the main thread to exit. + go func() { + wg.Wait() + doneChan <- true + }() + + for { + select { + case msg := <-resChan: + out, err := proto.Marshal(msg) + err = worker.SendBytes(out) + check(err) + case <-doneChan: + // All goroutines have completed. Now we can exit main(). + return + } + } } diff --git a/main.ts b/main.ts index e840e5b5a91999..d853cc2b12e08d 100644 --- a/main.ts +++ b/main.ts @@ -1,6 +1,7 @@ import { main as pb } from "./msg.pb"; import "./util"; import * as runtime from "./runtime"; +import * as timers from "./timers"; import * as path from "path"; function start(cwd: string, argv: string[]): void { @@ -17,6 +18,9 @@ V8Worker2.recv((ab: ArrayBuffer) => { case "start": start(msg.start.cwd, msg.start.argv); break; + case "timerReady": + timers.timerReady(msg.timerReady.id, msg.timerReady.done); + break; default: console.log("Unknown message", msg); break; diff --git a/msg.proto b/msg.proto index 181dcc551e9d7a..1ab78b6906f0df 100644 --- a/msg.proto +++ b/msg.proto @@ -10,6 +10,8 @@ message Msg { SourceCodeFetchResMsg source_code_fetch_res = 12; SourceCodeCacheMsg source_code_cache = 13; ExitMsg exit = 14; + TimerStartMsg timer_start = 15; + TimerReadyMsg timer_ready = 16; } } @@ -33,3 +35,14 @@ message SourceCodeCacheMsg { } message ExitMsg { int32 code = 1; } + +message TimerStartMsg { + int32 id = 1; + bool interval = 2; + int32 duration = 3; // In milliseconds. +} + +message TimerReadyMsg { + int32 id = 1; + bool done = 2; +} diff --git a/os.ts b/os.ts index d09026c6ccd9ed..44c3ab50882411 100644 --- a/os.ts +++ b/os.ts @@ -35,7 +35,7 @@ function typedArrayToArrayBuffer(ta: TypedArray): ArrayBuffer { return ab as ArrayBuffer; } -function sendMsgFromObject(obj: pb.IMsg): null | pb.Msg { +export function sendMsgFromObject(obj: pb.IMsg): null | pb.Msg { const msg = pb.Msg.fromObject(obj); const ui8 = pb.Msg.encode(msg).finish(); const ab = typedArrayToArrayBuffer(ui8); diff --git a/testdata/004_set_timeout.ts b/testdata/004_set_timeout.ts new file mode 100644 index 00000000000000..fe55e31ba02f9f --- /dev/null +++ b/testdata/004_set_timeout.ts @@ -0,0 +1,5 @@ +setTimeout(function() { + console.log("World"); +}, 10); + +console.log("Hello"); diff --git a/testdata/004_set_timeout.ts.out b/testdata/004_set_timeout.ts.out new file mode 100644 index 00000000000000..f9264f7fbd31ae --- /dev/null +++ b/testdata/004_set_timeout.ts.out @@ -0,0 +1,2 @@ +Hello +World diff --git a/timers.ts b/timers.ts new file mode 100644 index 00000000000000..ed84c00e9df9d7 --- /dev/null +++ b/timers.ts @@ -0,0 +1,43 @@ +import { _global } from "./util"; +import { sendMsgFromObject } from "./os"; + +let nextTimerId = 1; + +// tslint:disable-next-line:no-any +type TimerCallback = (...args: any[]) => void; + +interface Timer { + id: number; + cb: TimerCallback; + interval: boolean; + duration: number; // milliseconds +} + +const timers = new Map(); + +export function setTimeout(cb: TimerCallback, duration: number): number { + const timer = { + id: nextTimerId++, + interval: false, + duration, + cb + }; + timers.set(timer.id, timer); + sendMsgFromObject({ + timerStart: { + id: timer.id, + interval: false, + duration + } + }); + return timer.id; +} +_global["setTimeout"] = setTimeout; + +export function timerReady(id: number, done: boolean): void { + const timer = timers.get(id); + timer.cb(); + if (done) { + timers.delete(id); + } +}