Add profiling code, and implementation using basic widgets instead of closures.

This commit is contained in:
Greg 2019-08-18 17:00:19 -04:00
parent a463c073ed
commit 5bef5fc95a
2 changed files with 310 additions and 97 deletions

View File

@ -5,17 +5,35 @@ import (
"image/color"
"log"
"os"
"runtime"
"runtime/pprof"
"sync"
"time"
"gioui.org/ui"
"gioui.org/ui/app"
gdraw "gioui.org/ui/draw"
"gioui.org/ui/f32"
"gioui.org/ui/gesture"
"gioui.org/ui/layout"
"gioui.org/ui/measure"
"gioui.org/ui/pointer"
"gioui.org/ui/text"
"golang.org/x/image/font/sfnt"
"golang.org/x/image/font/gofont/goregular"
)
var (
FPS int = 100
frames int64
frametime int64 // nanoseconds
maxframetime int64
mux sync.Mutex
Profile bool = false
)
func NewButton(face text.Face, t string, c color.RGBA) giowrap.Clickable {
lbl := giowrap.NewLabel(t, giowrap.LabelFace(face), giowrap.LabelAlignment(text.Center))
bg := giowrap.NewBackground(giowrap.BgColor(c), giowrap.BgRadius(ui.Dp(4)))
@ -24,62 +42,267 @@ func NewButton(face text.Face, t string, c color.RGBA) giowrap.Clickable {
func main() {
go func() {
w := app.NewWindow(nil)
regular, err := sfnt.Parse(goregular.TTF)
if err != nil {
log.Fatal("Cannot parse font.")
}
ctx := giowrap.NewContext(w)
t := time.NewTicker(time.Second/30)
e1 := giowrap.NewEditor("text 1",giowrap.EditorFace(ctx.Faces.For(regular, ui.Sp(24))))
e1.Focus()
e2 := giowrap.NewEditor("text 2",giowrap.EditorFace(ctx.Faces.For(regular, ui.Sp(24))))
f1 := giowrap.NewFlex(giowrap.FlexAxis(layout.Vertical))
OuterInset := giowrap.NewInset(giowrap.InsetSize(ui.Dp(10)))
InnerInset := giowrap.NewInset(giowrap.InsetSize(ui.Dp(10)))
f2 := giowrap.NewFlex(giowrap.FlexAxis(layout.Horizontal))
btn1 := NewButton(ctx.Faces.For(regular, ui.Sp(24)),
"push1", color.RGBA{A: 0xff, R: 0x3c, G: 0x98, B: 0xc6})
btn2 := NewButton(ctx.Faces.For(regular, ui.Sp(24)),
"push2", color.RGBA{A: 0xff, R: 0x3c, G: 0x98, B: 0xc6})
ms := &runtime.MemStats{}
var a1 uint64
for {
select {
case <-t.C:
w.Invalidate()
case e := <-w.Events():
switch e := e.(type) {
case app.DestroyEvent:
return
case app.DrawEvent:
ctx = ctx.Reset(e)
ctx = OuterInset(
f1(
e1,
giowrap.Flexible(5),
InnerInset(e2),
giowrap.Flexible(10),
f2(
InnerInset(btn1),
giowrap.Flexible(1),
InnerInset(btn2),
),
)).Layout(ctx)
ctx.Draw()
if btn1.Clicked(ctx) {
time.Sleep(time.Second * 3)
mux.Lock()
if frames == 0 {
mux.Unlock()
continue
}
runtime.ReadMemStats(ms)
a1 = ms.Alloc
runtime.GC()
runtime.ReadMemStats(ms)
log.Printf("Alloc: %d - %d = %d (%d per frame)", a1, ms.Alloc, a1 - ms.Alloc, (a1 - ms.Alloc) / (3 * uint64(FPS)))
log.Printf("Frametime: %d max Frametime: %d us\n", frametime / (frames * 1000), maxframetime / 1000)
frames = 0
frametime = 0
maxframetime = 0
mux.Unlock()
}
}()
switch os.Args[1] {
case "", "main1":
log.Print("main1()")
go main1()
case "main2":
log.Print("main2()")
go main2()
default:
log.Fatal(`Usage:
hello [main1|main2]
`)
}
app.Main()
}
func main1() {
w := app.NewWindow(nil)
regular, err := sfnt.Parse(goregular.TTF)
if err != nil {
log.Fatal("Cannot parse font.")
}
ctx := giowrap.NewContext(w)
t := time.NewTicker(time.Second/time.Duration(FPS))
e1 := giowrap.NewEditor("text 1",giowrap.EditorFace(ctx.Faces.For(regular, ui.Sp(24))))
e1.Focus()
e2 := giowrap.NewEditor("text 2",giowrap.EditorFace(ctx.Faces.For(regular, ui.Sp(24))))
f1 := giowrap.NewFlex(giowrap.FlexAxis(layout.Vertical))
OuterInset := giowrap.NewInset(giowrap.InsetSize(ui.Dp(10)))
InnerInset := giowrap.NewInset(giowrap.InsetSize(ui.Dp(10)))
f2 := giowrap.NewFlex(giowrap.FlexAxis(layout.Horizontal))
btn1 := NewButton(ctx.Faces.For(regular, ui.Sp(24)),
"push1", color.RGBA{A: 0xff, R: 0x3c, G: 0x98, B: 0xc6})
btn2 := NewButton(ctx.Faces.For(regular, ui.Sp(24)),
"push2", color.RGBA{A: 0xff, R: 0x3c, G: 0x98, B: 0xc6})
profiled := false
startTime := time.Now()
for {
select {
case <-t.C:
w.Invalidate()
case e := <-w.Events():
stime := time.Now()
switch e := e.(type) {
case app.DestroyEvent:
return
case app.DrawEvent:
ctx.Reset(e)
OuterInset(
f1(
e1,
giowrap.Flexible(5),
InnerInset(e2),
giowrap.Flexible(10),
f2(
InnerInset(btn1),
giowrap.Flexible(0.5),
InnerInset(btn2),
),
)).Layout(ctx)
ctx.Draw()
if btn1.Clicked(ctx) {
log.Print("Clicked: " + e1.Text() )
}
if btn2.Clicked(ctx) {
log.Print("Clicked: " + e2.Text() )
}
}
dur := time.Since(stime).Nanoseconds()
mux.Lock()
frames = frames + 1
frametime = frametime + dur
if dur > maxframetime { maxframetime = dur }
mux.Unlock()
}
if profiled { continue }
if time.Since(startTime) < time.Second * 10 { continue }
if Profile {
profiled = true
f, err := os.Create("memprofile.pprof")
if err != nil {
log.Fatal("could not create memory profile: ", err)
}
if err := pprof.WriteHeapProfile(f); err != nil {
log.Fatal("could not write memory profile: ", err)
}
log.Print("Memory profile written")
f.Close()
}
}
}
func main2() {
w := app.NewWindow(nil)
q := w.Queue()
ops := new(ui.Ops)
var faces measure.Faces
regular, err := sfnt.Parse(goregular.TTF)
if err != nil {
log.Fatal("Cannot parse font.")
}
t := time.NewTicker(time.Second/time.Duration(FPS))
e1 := &text.Editor{
Face: faces.For(regular, ui.Sp(24)),
SingleLine: true,
}
e1.Focus()
e1.SetText("text 1")
e2 := &text.Editor{
Face: faces.For(regular, ui.Sp(24)),
SingleLine: true,
}
e2.Focus()
e2.SetText("text 2")
f1 := layout.Flex{ Axis: layout.Vertical }
OuterInset := layout.UniformInset(ui.Dp(10))
InnerInset := layout.UniformInset(ui.Dp(10))
f2 := layout.Flex{ Axis: layout.Horizontal }
btn1 := text.Label{
Face: faces.For(regular, ui.Sp(24)),
Text: "push1",
}
b1ins := layout.UniformInset(ui.Dp(4))
btn2 := text.Label{
Face: faces.For(regular, ui.Sp(24)),
Text: "push2",
}
b2ins := layout.UniformInset(ui.Dp(4))
var bg1, bg2 ui.MacroOp
click1 := new(gesture.Click)
click2 := new(gesture.Click)
profiled := false
startTime := time.Now()
for {
select {
case <-t.C:
w.Invalidate()
case e := <-w.Events():
stime := time.Now()
switch e := e.(type) {
case app.DestroyEvent:
return
case app.DrawEvent:
c := &e.Config
ops.Reset()
faces.Reset(c)
cs := layout.RigidConstraints(e.Size)
cs = OuterInset.Begin(c, ops, cs)
f1.Init(ops, cs)
cs = f1.Rigid()
dims := e1.Layout(c, q, ops, cs)
f1c1 := f1.End(dims)
cs = f1.Flexible(5)
cs = InnerInset.Begin(c, ops, cs)
dims = e2.Layout(c, q, ops, cs)
dims = InnerInset.End(dims)
f1c2 := f1.End(dims)
cs = f1.Flexible(10)
f2.Init(ops, cs)
cs = f2.Rigid()
cs = InnerInset.Begin(c, ops, cs)
bg1.Record(ops)
cs = b1ins.Begin(c, ops, cs)
dims = btn1.Layout(ops, cs)
dims = b1ins.End(dims)
pointer.RectAreaOp{Size: dims.Size}.Add(ops)
click1.Add(ops)
bg1.Stop()
wi, h := float32(dims.Size.X), float32(dims.Size.Y)
r := float32(c.Px(ui.Dp(4)))
giowrap.Rrect(ops, wi, h, r, r, r, r)
gdraw.ColorOp{Color: color.RGBA{A: 0xff, R: 0x3c, G: 0x98, B: 0xc6}}.Add(ops)
gdraw.DrawOp{Rect: f32.Rectangle{Max: f32.Point{X: wi, Y: h}}}.Add(ops)
bg1.Add(ops)
dims = InnerInset.End(dims)
f2c1 := f2.End(dims)
cs = f2.Flexible(0.5)
cs = InnerInset.Begin(c, ops, cs)
bg2.Record(ops)
cs = b2ins.Begin(c, ops, cs)
dims = btn2.Layout(ops, cs)
dims = b2ins.End(dims)
pointer.RectAreaOp{Size: dims.Size}.Add(ops)
click2.Add(ops)
bg2.Stop()
wi, h = float32(dims.Size.X), float32(dims.Size.Y)
giowrap.Rrect(ops, wi, h, r, r, r, r)
gdraw.ColorOp{Color: color.RGBA{A: 0xff, R: 0x3c, G: 0x98, B: 0xc6}}.Add(ops)
gdraw.DrawOp{Rect: f32.Rectangle{Max: f32.Point{X: wi, Y: h}}}.Add(ops)
bg2.Add(ops)
dims = InnerInset.End(dims)
f2c2 := f2.End(dims)
dims = f2.Layout(f2c1, f2c2)
f1c3 := f1.End(dims)
dims = f1.Layout(f1c1, f1c2, f1c3)
dims = OuterInset.End(dims)
w.Draw(ops)
for _,ev := range click1.Events(q) {
if ev.Type == gesture.TypeClick {
log.Print("Clicked: " + e1.Text() )
}
if btn2.Clicked(ctx) {
}
for _, ev := range click2.Events(q) {
if ev.Type == gesture.TypeClick {
log.Print("Clicked: " + e2.Text() )
}
}
}
dur := time.Since(stime).Nanoseconds()
mux.Lock()
frames = frames + 1
frametime = frametime + dur
if dur > maxframetime { maxframetime = dur }
mux.Unlock()
}
}()
app.Main()
if profiled { continue }
if time.Since(startTime) < time.Second * 10 { continue }
if Profile {
profiled = true
f, err := os.Create("memprofile.pprof")
if err != nil {
log.Fatal("could not create memory profile: ", err)
}
if err := pprof.WriteHeapProfile(f); err != nil {
log.Fatal("could not write memory profile: ", err)
}
log.Print("Memory profile written")
f.Close()
}
}
}

86
main.go
View File

@ -47,31 +47,29 @@ type Context struct {
dims layout.Dimens
}
func NewContext(w *app.Window) Context {
ret := Context{
func NewContext(w *app.Window) *Context {
return &Context{
w: w,
ops: new(ui.Ops),
q: w.Queue(),
}
return ret
}
func (ctx Context) Reset(e app.DrawEvent) Context {
func (ctx *Context) Reset(e app.DrawEvent) {
ctx.c = &e.Config
ctx.ops.Reset()
ctx.cs = layout.RigidConstraints(e.Size)
ctx.Faces.Reset(ctx.c)
return ctx
}
func (ctx Context) Draw() {
func (ctx *Context) Draw() {
ctx.w.Draw(ctx.ops)
}
type Layout func(Context) Context
type Layout func(*Context)
type Widget interface {
Layout(Context) Context
Layout(*Context)
}
type WidgetCombinator func(...Widget) Widget
@ -104,9 +102,8 @@ func NewLabel(t string, lops ...LabelOption) *Label {
return ret
}
func (l *Label) Layout(ctx Context) Context {
func (l *Label) Layout(ctx *Context) {
ctx.dims = l.l.Layout(ctx.ops, ctx.cs)
return ctx
}
type Editor struct {
@ -134,9 +131,8 @@ func NewEditor(t string, eops ...EditorOption) *Editor {
return ret
}
func (e *Editor) Layout(ctx Context) Context {
func (e *Editor) Layout(ctx *Context) {
ctx.dims = e.e.Layout(ctx.c, ctx.q, ctx.ops, ctx.cs)
return ctx
}
func (e *Editor) Text() string { return e.e.Text() }
@ -151,8 +147,8 @@ func NewfWidget(l Layout) fWidget {
return fWidget{ l: l }
}
func (fw fWidget) Layout(ctx Context) Context {
return fw.l(ctx)
func (fw fWidget) Layout(ctx *Context) {
fw.l(ctx)
}
type Flex WidgetCombinator
@ -160,9 +156,8 @@ type Flex WidgetCombinator
// This "Widget" does nothing except set the Flexible field of a FlexOpts
// struct within the Extra data structure
func Flexible(v float32) Widget {
return NewfWidget(func(ctx Context) Context {
return NewfWidget(func(ctx *Context) {
extra.data[extra.cur].(*FlexOpts).flexible = v
return ctx
})
}
@ -197,25 +192,26 @@ func NewFlex(fos ...FlexOption) Flex {
}
index := extra.New()
extra.data[index] = opts
// do not call "make" inside the Layout function
fcs := make([]layout.FlexChild,0)
return func(ws ...Widget) Widget {
return NewfWidget(func(ctx Context) Context {
return NewfWidget(func(ctx *Context) {
// ensure child widgets write options to the right place
extra.cur = index
opts := extra.data[index].(*FlexOpts)
f.Init(ctx.ops, ctx.cs)
fcs := make([]layout.FlexChild,len(ws))
for i, w := range ws {
if v := opts.flexible; v != 0 {
ctx.cs = f.Flexible(v)
for _, w := range ws {
if opts.flexible != 0 {
ctx.cs = f.Flexible(opts.flexible)
} else {
ctx.cs = f.Rigid()
}
ctx = w.Layout(ctx)
fcs[i] = f.End(ctx.dims)
w.Layout(ctx)
fcs = append(fcs, f.End(ctx.dims))
}
ctx.dims = f.Layout(fcs...)
return ctx
f.Layout(fcs...)
fcs = fcs[0:0] // truncate
})
}
}
@ -253,14 +249,12 @@ func NewInset(insos ...InsetOption) WidgetCombinator {
for _,o := range insos { o(opts) }
ins := layout.Inset{ Top: opts.top, Right: opts.right, Bottom: opts.bottom, Left: opts.left }
return func(ws ...Widget) Widget {
return NewfWidget(func(ctx Context) Context {
return NewfWidget(func(ctx *Context) {
ctx.cs = ins.Begin(ctx.c, ctx.ops, ctx.cs)
var dims layout.Dimens
for _, w := range ws {
ctx = w.Layout(ctx)
w.Layout(ctx)
}
ctx.dims = ins.End(dims)
return ctx
ctx.dims = ins.End(ctx.dims)
})
}
}
@ -274,18 +268,17 @@ type Background struct {
}
type Enclosure interface {
Begin(Context) Context
End(Context) Context
Begin(*Context)
End(*Context)
}
func Enclose(e Enclosure, ws ...Widget) Widget {
return NewfWidget(func(ctx Context) Context {
ctx = e.Begin(ctx)
return NewfWidget(func(ctx *Context) {
e.Begin(ctx)
for _,w := range ws {
ctx = w.Layout(ctx)
w.Layout(ctx)
}
ctx = e.End(ctx)
return ctx
e.End(ctx)
})
}
@ -315,13 +308,12 @@ func NewBackground(bos ...BackgroundOption) WidgetCombinator {
}
}
func (bg *Background) Begin(ctx Context) Context {
func (bg *Background) Begin(ctx *Context) {
bg.macro.Record(ctx.ops)
ctx.cs = bg.Inset.Begin(ctx.c, ctx.ops, ctx.cs)
return ctx
}
func (bg *Background) End(ctx Context) Context {
func (bg *Background) End(ctx *Context) {
ctx.dims = bg.Inset.End(ctx.dims)
bg.macro.Stop()
//var stack ui.StackOp
@ -333,16 +325,15 @@ func (bg *Background) End(ctx Context) Context {
if r > h / 2 {
r = h / 2
}
rrect(ctx.ops, w, h, r, r, r, r)
Rrect(ctx.ops, w, h, r, r, r, r)
}
gdraw.ColorOp{Color: bg.Color}.Add(ctx.ops)
gdraw.DrawOp{Rect: f32.Rectangle{Max: f32.Point{X: w, Y: h}}}.Add(ctx.ops)
bg.macro.Add(ctx.ops)
return ctx
}
// https://pomax.github.io/bezierinfo/#circles_cubic.
func rrect(ops *ui.Ops, width, height, se, sw, nw, ne float32) {
func Rrect(ops *ui.Ops, width, height, se, sw, nw, ne float32) {
w, h := float32(width), float32(height)
const c = 0.55228475 // 4*(sqrt(2)-1)/3
var b gdraw.PathBuilder
@ -360,7 +351,7 @@ func rrect(ops *ui.Ops, width, height, se, sw, nw, ne float32) {
type Clickable interface {
Widget
Clicked(Context) bool
Clicked(*Context) bool
}
//cWidget is a clickable Widget that provides the Clicked() method.
@ -369,14 +360,13 @@ type cWidget struct {
click *gesture.Click
}
func (w cWidget) Layout(ctx Context) Context {
ctx = w.w.Layout(ctx)
func (w cWidget) Layout(ctx *Context) {
w.w.Layout(ctx)
pointer.RectAreaOp{Size: ctx.dims.Size}.Add(ctx.ops)
w.click.Add(ctx.ops)
return ctx
}
func (w cWidget) Clicked(ctx Context) bool {
func (w cWidget) Clicked(ctx *Context) bool {
for _,e := range w.click.Events(ctx.q) {
if e.Type == gesture.TypeClick {
return true