From 5bef5fc95ab2fae5539f797f28db41981e1c2486 Mon Sep 17 00:00:00 2001 From: Greg Date: Sun, 18 Aug 2019 17:00:19 -0400 Subject: [PATCH] Add profiling code, and implementation using basic widgets instead of closures. --- cmd/hello/main.go | 321 +++++++++++++++++++++++++++++++++++++++------- main.go | 86 ++++++------- 2 files changed, 310 insertions(+), 97 deletions(-) diff --git a/cmd/hello/main.go b/cmd/hello/main.go index 9c6acaa..d5cf4b3 100644 --- a/cmd/hello/main.go +++ b/cmd/hello/main.go @@ -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() + } + } } diff --git a/main.go b/main.go index 83815cb..88baa69 100644 --- a/main.go +++ b/main.go @@ -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