github.com/rajveermalviya/gamen@v0.1.2-0.20220930195403-9be15877c1aa/internal/web/display.go (about) 1 //go:build js 2 3 package web 4 5 import ( 6 "syscall/js" 7 "time" 8 9 "github.com/rajveermalviya/gamen/internal/common/atomicx" 10 ) 11 12 type Display struct { 13 destroyRequested atomicx.Bool 14 destroyed atomicx.Bool 15 16 windows map[uint64]*Window 17 18 eventCallbacksChan chan func() 19 } 20 21 func NewDisplay() (*Display, error) { 22 return &Display{ 23 windows: map[uint64]*Window{}, 24 eventCallbacksChan: make(chan func()), 25 }, nil 26 } 27 28 func (d *Display) Poll() bool { 29 wait := make(chan struct{}) 30 cb := js.FuncOf(func(this js.Value, args []js.Value) any { 31 if d.destroyed.Load() { 32 return nil 33 } 34 35 wait <- struct{}{} 36 37 return nil 38 }) 39 defer cb.Release() 40 js.Global().Call("requestAnimationFrame", cb) 41 42 outerloop: 43 for { 44 select { 45 case cb := <-d.eventCallbacksChan: 46 cb() 47 48 innerloop: 49 for { 50 select { 51 case cb := <-d.eventCallbacksChan: 52 cb() 53 default: 54 break innerloop 55 } 56 } 57 58 case <-wait: 59 break outerloop 60 } 61 } 62 63 if d.destroyRequested.Load() && !d.destroyed.Load() { 64 d.destroy() 65 return false 66 } 67 68 return !d.destroyed.Load() 69 } 70 71 func (d *Display) Wait() bool { 72 // wait for first event 73 cb := <-d.eventCallbacksChan 74 cb() 75 76 // then poll all pending events 77 return d.Poll() 78 } 79 80 func (d *Display) WaitTimeout(timeout time.Duration) bool { 81 timer := time.NewTimer(timeout) 82 83 select { 84 case <-timer.C: 85 return !d.destroyed.Load() 86 87 case cb := <-d.eventCallbacksChan: 88 if !timer.Stop() { 89 <-timer.C 90 } 91 92 cb() 93 94 // then poll all pending events 95 return d.Poll() 96 } 97 } 98 99 func (d *Display) Destroy() { 100 d.destroyRequested.Store(true) 101 } 102 103 func (d *Display) destroy() { 104 for id, w := range d.windows { 105 w.Destroy() 106 107 d.windows[id] = nil 108 delete(d.windows, id) 109 } 110 111 close(d.eventCallbacksChan) 112 113 d.destroyed.Store(true) 114 }