pixel-examples/community/amidakuji/glossary/fpschk.go

135 lines
3.4 KiB
Go

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)
}