package glossary import ( "fmt" "image/color" "sync" "time" "github.com/faiface/pixel" "github.com/faiface/pixel/imdraw" "github.com/faiface/pixel/text" "golang.org/x/image/colornames" ) // FPSWatch measures the real-time frame rates and displays it on a target canvas. type FPSWatch struct { txt *text.Text // shared variable atlas *text.Atlas // borrowed atlas for txt imd *imdraw.IMDraw // shared variable mutex sync.Mutex // synchronize // fps int // The FPS evaluated every second. frames int // Frames count before the FPS update. seccer <-chan time.Time // Ticks time every second. // desc string pos pixel.Vec anchorX AnchorX anchorY AnchorY colorBg color.Color colorTxt color.Color } // NewFPSWatch is a constructor. func NewFPSWatch( additionalCaption string, _pos pixel.Vec, _anchorY AnchorY, _anchorX AnchorX, // This is because the order is usually Y then X in spoken language. _colorBg, _colorTxt color.Color, ) (watch *FPSWatch) { return &FPSWatch{ atlas: AtlasASCII(), fps: 0, frames: 0, seccer: nil, desc: additionalCaption, pos: _pos, anchorX: _anchorX, anchorY: _anchorY, colorBg: _colorBg, colorTxt: _colorTxt, } } // NewFPSWatchSimple is a constructor. func NewFPSWatchSimple(_pos pixel.Vec, _anchorY AnchorY, _anchorX AnchorX) *FPSWatch { return NewFPSWatch("", _pos, _anchorY, _anchorX, colornames.Black, colornames.White) } // Start ticking every second. func (watch *FPSWatch) Start() { watch.seccer = time.Tick(time.Second) } // Poll () should be called only once and in every single frame. (Obligatory) // This is an extended behavior of Update() like funcs. func (watch *FPSWatch) Poll() { watch.frames++ select { case <-watch.seccer: watch.fps = watch.frames watch.frames = 0 go watch._Update() default: } } // SetPos to a position in screen coords. func (watch *FPSWatch) SetPos(pos pixel.Vec, anchorY AnchorY, anchorX AnchorX) { watch.pos = pos watch.anchorX = anchorX watch.anchorY = anchorY } // GetFPS returns the most recent FPS recorded. // A non-ptr FPSWatch as a read only argument passes lock by value within itself but that seems totally fine. func (watch FPSWatch) GetFPS() int { return watch.fps } // Draw FPSWatch. func (watch *FPSWatch) Draw(t pixel.Target) { // lock before accessing txt & imdraw watch.mutex.Lock() defer watch.mutex.Unlock() if watch.imd == nil && watch.txt == nil { // isInvisible set to true. return // An empty image is drawn. } watch.imd.Draw(t) watch.txt.Draw(t, pixel.IM) } // unexported func (watch *FPSWatch) _Update() { // lock before txt & imdraw update watch.mutex.Lock() defer watch.mutex.Unlock() // text label (a state machine) if watch.txt == nil { // lazy creation watch.txt = text.New(pixel.ZV, watch.atlas) } txt := watch.txt txt.Clear() str := fmt.Sprint("FPS: ", watch.fps, " ", watch.desc) AnchorTxt(txt, watch.pos, watch.anchorX, watch.anchorY, str) txt.Color = watch.colorTxt txt.Dot.X -= 1.0 txt.Dot.Y += 5.0 txt.WriteString(str) // imdraw (a state machine) if watch.imd == nil { // lazy creation watch.imd = imdraw.New(nil) } imd := watch.imd imd.Clear() imd.Color = watch.colorBg imd.Push(VerticesOfRect(txt.Bounds())...) imd.Polygon(0) }