From 83c9d4050013e037e59025defa99627ff7c6175c Mon Sep 17 00:00:00 2001 From: Greg Date: Wed, 28 Aug 2019 11:48:22 -0400 Subject: [PATCH] Memory profiling code. Updates to reduce memory footprint. --- .gitignore | 1 + cmd/cal/main.go | 2 +- cmd/grid/{grid.go => main.go} | 0 cmd/hello/main.go | 339 +++++++++++++++++++++++++++++----- cmd/scroll/main.go | 83 ++++++--- grid.go | 40 ++-- main.go | 222 +++++++++++++--------- prof.go | 34 ++++ 8 files changed, 547 insertions(+), 174 deletions(-) rename cmd/grid/{grid.go => main.go} (100%) create mode 100644 prof.go diff --git a/.gitignore b/.gitignore index 00de0bb..dd43ffa 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ cmd/hello/hello cmd/cal/cal cmd/scroll/scroll +cmd/grid/grid *.apk diff --git a/cmd/cal/main.go b/cmd/cal/main.go index 1579f0e..cf6a78b 100644 --- a/cmd/cal/main.go +++ b/cmd/cal/main.go @@ -152,7 +152,7 @@ func eventloop() { case app.DestroyEvent: return case app.UpdateEvent: - ctx.Reset(e) + ctx.Reset(&e) mth.SetText(fmt.Sprintf("%s %d", sm.Month.String(), sm.Year)) diff --git a/cmd/grid/grid.go b/cmd/grid/main.go similarity index 100% rename from cmd/grid/grid.go rename to cmd/grid/main.go diff --git a/cmd/hello/main.go b/cmd/hello/main.go index a74a2a8..07d5099 100644 --- a/cmd/hello/main.go +++ b/cmd/hello/main.go @@ -3,6 +3,7 @@ package main import ( "git.wow.st/gmp/giowrap" + "fmt" "image" "image/color" "log" @@ -27,12 +28,13 @@ import ( ) var ( - FPS int = 100 - frames int64 - frametime int64 // nanoseconds - maxframetime int64 - mux sync.Mutex - Profile bool = false + FPS int = 100 + frames int64 + frametime int64 // nanoseconds + maxframetime int64 + mux sync.Mutex + Profile bool = true + RecordMallocs bool = false ) func NewButton(face text.Face, t string, c color.RGBA) giowrap.Clickable { @@ -69,7 +71,7 @@ func main() { fallthrough default: log.Fatal(`Usage: -hello [main1|main2] +hello [main1|main2|main3|main4] `) case os.Args[1] == "main1": log.Print("main1()") @@ -77,10 +79,17 @@ hello [main1|main2] case os.Args[1] == "main2": log.Print("main2()") go main2() + case os.Args[1] == "main3": + log.Print("main3()") + go main3() + case os.Args[1] == "main4": + log.Print("main4()") + go main4() } app.Main() } +// Example with functional layout func main1() { w := app.NewWindow() regular, err := sfnt.Parse(goregular.TTF) @@ -108,17 +117,156 @@ func main1() { profiled := false startTime := time.Now() + page := OuterInset( + f1( + e1, + giowrap.Flexible(0.33), + InnerInset(e2), + giowrap.Flexible(0.67), + f2( + InnerInset(btn1), + giowrap.Flexible(0.50), + InnerInset(btn2), + ), + ), + ) + + for { + select { + case <-t.C: + w.Invalidate() + case e := <-w.Events(): + mux.Lock() + stime := time.Now() + switch e := e.(type) { + case app.DestroyEvent: + return + case app.UpdateEvent: + profileNow := false + var ms runtime.MemStats + if Profile && time.Since(startTime) > time.Second*2 { + startTime = time.Now() + profileNow = true + } + if profileNow { + if !profiled { + prof(0) + } + runtime.ReadMemStats(&ms) + if RecordMallocs { + giowrap.RecordMallocs() + } + } + giowrap.Mallocs("pre-reset") + ctx.Reset(&e) + giowrap.Mallocs("pre-layout") + page.Layout(ctx) + giowrap.Mallocs("post-layout") + ctx.Update() + giowrap.Mallocs("post-update") + if btn1.Clicked(ctx) { + log.Print("Clicked: " + e1.Text()) + } + if btn2.Clicked(ctx) { + log.Print("Clicked: " + e2.Text()) + } + giowrap.Mallocs("post-clicked()") + if profileNow { + for _, i := range giowrap.AllMallocs { + fmt.Printf("mallocs: %d (%s)\n", i.Value, i.Text) + } + giowrap.AllMallocs = nil + mallocs := ms.Mallocs + runtime.ReadMemStats(&ms) + mallocs = ms.Mallocs - mallocs + log.Printf("Mallocs = %d\n", mallocs) + if !profiled { + prof(1) + profiled = true + } + } + } + dur := time.Since(stime).Nanoseconds() + frames = frames + 1 + frametime = frametime + dur + if dur > maxframetime { + maxframetime = dur + } + mux.Unlock() + } + } +} + +func prof(x int) { + names := []string{"memprofile-1.pprof", "memprofile-2.pprof"} + f1, err := os.Create(names[x]) + if err != nil { + log.Fatal("could not create memory profile: ", err) + } + if err := pprof.WriteHeapProfile(f1); err != nil { + log.Fatal("could not write memory profile: ", err) + } + f1.Close() + log.Print("Memory profile written") +} + +// Example showing use of WidgetCombinators in the main loop. +func main2() { + w := app.NewWindow() + 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.Face(ctx.Faces.For(regular, ui.Sp(24)))) + e1.Focus() + + e2 := giowrap.NewEditor("text 2", giowrap.Face(ctx.Faces.For(regular, ui.Sp(24)))) + + f1 := giowrap.NewFlex(giowrap.Axis(layout.Vertical)) + OuterInset := giowrap.NewInset(giowrap.Size(ui.Dp(10))) + InnerInset := giowrap.NewInset(giowrap.Size(ui.Dp(10))) + + f2 := giowrap.NewFlex(giowrap.Axis(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(): + mux.Lock() stime := time.Now() switch e := e.(type) { case app.DestroyEvent: return case app.UpdateEvent: - ctx.Reset(e) + profileNow := false + var ms runtime.MemStats + if Profile && time.Since(startTime) > time.Second*2 { + startTime = time.Now() + profileNow = true + } + if profileNow { + if !profiled { + prof(0) + } + runtime.ReadMemStats(&ms) + if RecordMallocs { + giowrap.RecordMallocs() + } + } + giowrap.Mallocs("pre-reset") + ctx.Reset(&e) + giowrap.Mallocs("pre-layout") OuterInset( f1( e1, @@ -127,9 +275,107 @@ func main1() { giowrap.Flexible(0.67), f2( InnerInset(btn1), - giowrap.Flexible(0.5), + giowrap.Flexible(0.50), InnerInset(btn2), ), + ), + ).Layout(ctx) + giowrap.Mallocs("post-layout") + ctx.Update() + giowrap.Mallocs("post-update") + if btn1.Clicked(ctx) { + log.Print("Clicked: " + e1.Text()) + } + if btn2.Clicked(ctx) { + log.Print("Clicked: " + e2.Text()) + } + giowrap.Mallocs("post-clicked()") + if profileNow { + for _, i := range giowrap.AllMallocs { + fmt.Printf("mallocs: %d (%s)\n", i.Value, i.Text) + } + giowrap.AllMallocs = nil + mallocs := ms.Mallocs + runtime.ReadMemStats(&ms) + mallocs = ms.Mallocs - mallocs + log.Printf("Mallocs = %d\n", mallocs) + if !profiled { + prof(1) + profiled = true + } + } + } + dur := time.Since(stime).Nanoseconds() + frames = frames + 1 + frametime = frametime + dur + if dur > maxframetime { + maxframetime = dur + } + mux.Unlock() + } + } +} + +// Example showing widget allocation within the main loop (causes unnecessary +// memory allocations) +func main3() { + w := app.NewWindow() + 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.Face(ctx.Faces.For(regular, ui.Sp(24)))) + e1.Focus() + + e2 := giowrap.NewEditor("text 2", giowrap.Face(ctx.Faces.For(regular, ui.Sp(24)))) + + 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.UpdateEvent: + profileNow := false + var ms runtime.MemStats + if Profile && time.Since(startTime) > time.Second*2 { + profileNow = true + startTime = time.Now() + } + if profileNow { + if !profiled { + prof(0) + } + runtime.ReadMemStats(&ms) + if RecordMallocs { + giowrap.RecordMallocs() + } + } + ctx.Reset(&e) + giowrap.NewInset(giowrap.Size(ui.Dp(10)))( + giowrap.NewFlex(giowrap.Axis(layout.Vertical))( + e1, + //giowrap.Flexible(0.33), + giowrap.NewInset(giowrap.Size(ui.Dp(10)))(e2), + //giowrap.Flexible(0.67), + giowrap.NewFlex(giowrap.Axis(layout.Horizontal))( + giowrap.NewInset(giowrap.Size(ui.Dp(10)))(btn1), + //giowrap.Flexible(0.50), + giowrap.NewInset(giowrap.Size(ui.Dp(10)))(btn2), + ), )).Layout(ctx) ctx.Update() if btn1.Clicked(ctx) { @@ -138,6 +384,20 @@ func main1() { if btn2.Clicked(ctx) { log.Print("Clicked: " + e2.Text()) } + if profileNow { + for _, i := range giowrap.AllMallocs { + fmt.Printf("mallocs: %d (%s)\n", i.Value, i.Text) + } + giowrap.AllMallocs = nil + mallocs := ms.Mallocs + runtime.ReadMemStats(&ms) + mallocs = ms.Mallocs - mallocs + log.Printf("Mallocs = %d\n", mallocs) + if !profiled { + prof(1) + profiled = true + } + } } dur := time.Since(stime).Nanoseconds() mux.Lock() @@ -148,28 +408,11 @@ func main1() { } 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() { +// Example using the base Gio API. +func main4() { w := app.NewWindow() q := w.Queue() ops := new(ui.Ops) @@ -215,6 +458,18 @@ func main2() { case app.DestroyEvent: return case app.UpdateEvent: + profileNow := false + var ms runtime.MemStats + if Profile && time.Since(startTime) > time.Second*2 { + profileNow = true + startTime = time.Now() + } + if profileNow { + if !profiled { + prof(0) + } + runtime.ReadMemStats(&ms) + } c := &e.Config ops.Reset() faces.Reset(c) @@ -257,6 +512,16 @@ func main2() { dims = ins.End(f1.Layout(c1, c2, c3)) } w.Update(ops) + if profileNow { + mallocs := ms.Mallocs + runtime.ReadMemStats(&ms) + mallocs = ms.Mallocs - mallocs + log.Printf("Mallocs = %d\n", mallocs) + if !profiled { + prof(1) + profiled = true + } + } } dur := time.Since(stime).Nanoseconds() mux.Lock() @@ -267,24 +532,6 @@ func main2() { } 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() - } } } diff --git a/cmd/scroll/main.go b/cmd/scroll/main.go index ddfbf69..535f727 100644 --- a/cmd/scroll/main.go +++ b/cmd/scroll/main.go @@ -4,6 +4,8 @@ import ( "fmt" "image/color" "log" + "runtime" + "time" gio "git.wow.st/gmp/giowrap" @@ -99,18 +101,21 @@ func eventloop() { lbar := gio.NewLabel(" ", gio.Face(face)) f2 := gio.NewFlex(gio.Axis(layout.Vertical)) topbar := gio.NewLabel("Scroll X and Y. Click to select", gio.Face(face)) + f3 := gio.NewFlex(gio.Axis(layout.Horizontal)) + f4 := gio.NewFlex(gio.Axis(layout.Vertical)) sh := gio.HScroll(gio.NewFlex(gio.Axis(layout.Horizontal))) sv := gio.VScroll(gio.NewFlex(gio.Axis(layout.Vertical))) - numlabs := 50 - labs := make([][]gio.Widget, numlabs) - sels := make([][]bool, numlabs) - for i := 0; i < 16; i++ { - labs[i] = make([]gio.Widget, numlabs) - sels[i] = make([]bool, numlabs) - for j := 0; j < numlabs; j++ { - labs[i][j] = NewSLabel(fmt.Sprintf("%03d", i*16+j), &sels[i][j]) + numrows := 16 + numcols := 50 + labs := make([][]gio.Widget, numrows) + sels := make([][]bool, numrows) + for i := 0; i < numrows; i++ { + labs[i] = make([]gio.Widget, numcols) + sels[i] = make([]bool, numcols) + for j := 0; j < numcols; j++ { + labs[i][j] = NewSLabel(fmt.Sprintf("%03d", i+j*numrows), &sels[i][j]) } } @@ -120,8 +125,30 @@ func eventloop() { gio.Left(x.Left), gio.Right(x.Right)) } + page := sysbg(sysinset(bg(margin( + f1(lbar, f2(topbar, sh(sv(f3( + f4(labs[0]...), + f4(labs[1]...), + f4(labs[2]...), + f4(labs[3]...), + f4(labs[4]...), + f4(labs[5]...), + f4(labs[6]...), + f4(labs[7]...), + f4(labs[8]...), + f4(labs[9]...), + f4(labs[10]...), + f4(labs[11]...), + f4(labs[12]...), + f4(labs[13]...), + f4(labs[14]...), + f4(labs[15]...), + ))))))))) + var oldInsets app.Insets + stime := time.Now() + sm := runtime.MemStats{} for { select { case e := <-w.Events(): @@ -129,32 +156,30 @@ func eventloop() { case app.DestroyEvent: return case app.UpdateEvent: - ctx.Reset(e) + profileNow := time.Since(stime) > time.Second*2 + if profileNow { + stime = time.Now() + //gio.RecordMallocs() + runtime.ReadMemStats(&sm) + } + gio.Mallocs("start") + ctx.Reset(&e) if diffInsets(e.Insets, oldInsets) { oldInsets = e.Insets resetSysinset(e.Insets) } - - sysbg(sysinset(bg(margin( - f1(lbar, f2(topbar, sh( - sv(labs[0]...), - sv(labs[1]...), - sv(labs[2]...), - sv(labs[3]...), - sv(labs[4]...), - sv(labs[5]...), - sv(labs[6]...), - sv(labs[7]...), - sv(labs[8]...), - sv(labs[9]...), - sv(labs[10]...), - sv(labs[11]...), - sv(labs[12]...), - sv(labs[13]...), - sv(labs[14]...), - sv(labs[15]...), - ))))))).Layout(ctx) + page.Layout(ctx) ctx.Update() + gio.Mallocs("end") + if profileNow { + for _, m := range gio.AllMallocs { + fmt.Printf("Mallocs: %d (%s)\n", m.Value, m.Text) + } + gio.AllMallocs = nil + oldMallocs := sm.Mallocs + runtime.ReadMemStats(&sm) + fmt.Printf("Mallocs = %d\n", sm.Mallocs-oldMallocs) + } } } } diff --git a/grid.go b/grid.go index 7783c5e..05f8cd2 100644 --- a/grid.go +++ b/grid.go @@ -124,24 +124,34 @@ type HeightOpt struct{ height int } func Height(x int) HeightOpt { return HeightOpt{x} } func (x HeightOpt) DoGridOption(g *Grid) { g.Height = x.height } +type gridWidget struct { + g Grid + gcs []GridChild + ws []Widget +} + func NewGrid(cols int, gops ...GridOption) WidgetCombinator { - g := &Grid{Cols: cols} + ret := &gridWidget{} + ret.g = Grid{Cols: cols} for _, gop := range gops { - gop.DoGridOption(g) + gop.DoGridOption(&ret.g) } - gcs := make([]GridChild, 0) + ret.gcs = make([]GridChild, 0) return func(ws ...Widget) Widget { - return NewfWidget(func(ctx *Context) { - cs := g.Init(ctx.ops, ctx.cs) - ctx.cs = cs - for _, w := range ws { - g.Begin() - w.Layout(ctx) - ctx.cs = cs // widget layout can modify constraints... - gcs = append(gcs, g.End(ctx.dims)) - } - ctx.dims = g.Layout(gcs...) - gcs = gcs[0:0] - }) + ret.ws = ws + return ret } } + +func (gw *gridWidget) Layout(ctx *Context) { + cs := gw.g.Init(ctx.ops, ctx.cs) + ctx.cs = cs + for _, w := range gw.ws { + gw.g.Begin() + w.Layout(ctx) + ctx.cs = cs // widget layout can modify constraints... + gw.gcs = append(gw.gcs, gw.g.End(ctx.dims)) + } + ctx.dims = gw.g.Layout(gw.gcs...) + gw.gcs = gw.gcs[0:0] +} diff --git a/main.go b/main.go index 329c90e..a644c3c 100644 --- a/main.go +++ b/main.go @@ -42,12 +42,13 @@ func (e *Extra) New() int { type Context struct { Faces measure.Faces - w *app.Window - c *app.Config - q input.Queue - ops *ui.Ops - cs layout.Constraints - dims layout.Dimens + w *app.Window + c *app.Config + q input.Queue + ops *ui.Ops + cs layout.Constraints + dims layout.Dimens + container Widget } func NewContext(w *app.Window) *Context { @@ -58,11 +59,13 @@ func NewContext(w *app.Window) *Context { } } -func (ctx *Context) Reset(e app.UpdateEvent) { +func (ctx *Context) Reset(e *app.UpdateEvent) { + Mallocs("ctx.Reset() ctx.c") ctx.c = &e.Config ctx.ops.Reset() ctx.cs = layout.RigidConstraints(e.Size) ctx.Faces.Reset(ctx.c) + Mallocs("ctx.Reset() done") } func (ctx *Context) Update() { @@ -121,6 +124,7 @@ func NewLabel(t string, lops ...LabelOption) *Label { } func (l *Label) Layout(ctx *Context) { + Mallocs("Label.Layout()") ctx.dims = l.l.Layout(ctx.ops, ctx.cs) } @@ -157,6 +161,7 @@ func NewEditor(t string, eops ...EditorOption) *Editor { } func (e *Editor) Layout(ctx *Context) { + Mallocs("Editor.Layout()") ctx.dims = e.e.Layout(ctx.c, ctx.q, ctx.ops, ctx.cs) } @@ -164,38 +169,36 @@ func (e *Editor) Text() string { return e.e.Text() } func (e *Editor) SetText(s string) { e.e.SetText(s) } func (e *Editor) Focus() { e.e.Focus() } -type fWidget struct { - l Layout -} - -func NewfWidget(l Layout) fWidget { - return fWidget{l: l} -} - -func (fw fWidget) Layout(ctx *Context) { - fw.l(ctx) -} - type Stack WidgetCombinator +type stackWidget struct { + s layout.Stack + scs []layout.StackChild + ws []Widget +} + func NewStack() Stack { - s := layout.Stack{Alignment: layout.Center} - scs := make([]layout.StackChild, 0) + ret := &stackWidget{} + ret.s = layout.Stack{Alignment: layout.Center} + ret.scs = make([]layout.StackChild, 0) return func(ws ...Widget) Widget { - return NewfWidget(func(ctx *Context) { - s.Init(ctx.ops, ctx.cs) - for _, w := range ws { - ctx.cs = s.Rigid() - w.Layout(ctx) - scs = append(scs, s.End(ctx.dims)) - } - ctx.dims = s.Layout(scs...) - scs = scs[0:0] - }) + ret.ws = ws + return ret } } +func (sw *stackWidget) Layout(ctx *Context) { + sw.s.Init(ctx.ops, ctx.cs) + for _, w := range sw.ws { + ctx.cs = sw.s.Rigid() + w.Layout(ctx) + sw.scs = append(sw.scs, sw.s.End(ctx.dims)) + } + ctx.dims = sw.s.Layout(sw.scs...) + sw.scs = sw.scs[0:0] +} + type List = WidgetCombinator type AxisOpt struct{ axis layout.Axis } @@ -209,24 +212,34 @@ type ListOption interface{ DoListOption(*ListOpts) } func (x AxisOpt) DoListOption(o *ListOpts) { o.axis = x.axis } +type listWidget struct { + l layout.List + ws []Widget +} + func NewList(los ...ListOption) List { + ret := &listWidget{} opts := &ListOpts{} for _, o := range los { o.DoListOption(opts) } - l := layout.List{Axis: opts.axis} + ret.l = layout.List{Axis: opts.axis} + return func(ws ...Widget) Widget { - return NewfWidget(func(ctx *Context) { - for l.Init(ctx.c, ctx.q, ctx.ops, ctx.cs, len(ws)); l.More(); l.Next() { - ctx.cs = l.Constraints() - ws[l.Index()].Layout(ctx) - l.End(ctx.dims) - } - ctx.dims = l.Layout() - }) + ret.ws = ws + return ret } } +func (lw *listWidget) Layout(ctx *Context) { + for lw.l.Init(ctx.c, ctx.q, ctx.ops, ctx.cs, len(lw.ws)); lw.l.More(); lw.l.Next() { + ctx.cs = lw.l.Constraints() + lw.ws[lw.l.Index()].Layout(ctx) + lw.l.End(ctx.dims) + } + ctx.dims = lw.l.Layout() +} + func HScroll(c WidgetCombinator) WidgetCombinator { return NewList(Axis(layout.Horizontal)) } @@ -237,19 +250,6 @@ func VScroll(c WidgetCombinator) WidgetCombinator { 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) { - extra.data[extra.cur].(*FlexOpts).flexible = v - }) -} -func Rigid() Widget { - return NewfWidget(func(ctx *Context) { - extra.data[extra.cur].(*FlexOpts).flexible = 0 - }) -} - type AlignmentOpt struct{ alignment layout.Alignment } func Alignment(x layout.Alignment) AlignmentOpt { return AlignmentOpt{x} } @@ -265,7 +265,35 @@ type FlexOption interface{ DoFlexOption(*FlexOpts) } func (x AxisOpt) DoFlexOption(o *FlexOpts) { o.axis = x.axis } func (x AlignmentOpt) DoFlexOption(o *FlexOpts) { o.alignment = x.alignment } +type flexWidget struct { + f func(*Context) + flexible float32 +} + +type flexOptWidget struct { + flexible float32 +} + +func (fo flexOptWidget) Layout(ctx *Context) { + Mallocs("flexOptWidget.Layout()") + ctx.container.(*flexWidget).flexible = fo.flexible +} + +// This "Widget" does nothing except set the Flexible field of a FlexOpts +// struct within the Extra data structure +func Flexible(v float32) Widget { + return flexOptWidget{v} +} +func Rigid() Widget { + return flexOptWidget{0} +} + // NewFlex returns a WidgetCombinator that wraps the layout.Flex element. + +func (fw *flexWidget) Layout(ctx *Context) { + fw.f(ctx) +} + func NewFlex(fos ...FlexOption) Flex { opts := &FlexOpts{} for _, o := range fos { @@ -275,29 +303,23 @@ func NewFlex(fos ...FlexOption) Flex { Axis: opts.axis, Alignment: opts.alignment, } - 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) { - // ensure child widgets write options to the right place - extra.cur = index - opts := extra.data[index].(*FlexOpts) + ret := &flexWidget{} + ret.f = func(ctx *Context) { + oldContainer := ctx.container + ctx.container = ret f.Init(ctx.ops, ctx.cs) for _, w := range ws { - if opts.flexible != 0 { - ctx.cs = f.Flexible(opts.flexible) - } else { - ctx.cs = f.Rigid() - } + ctx.cs = f.Rigid() w.Layout(ctx) fcs = append(fcs, f.End(ctx.dims)) } ctx.dims = f.Layout(fcs...) - fcs = fcs[0:0] // truncate - }) + fcs = fcs[0:0] + ctx.container = oldContainer + } + return ret } } @@ -338,21 +360,34 @@ func (x SizeOpt) DoInsetOption(o *InsetOpts) { o.left = x.size } +type insetWidget func(*Context) + +func (iw insetWidget) Layout(ctx *Context) { + iw(ctx) +} + //NewInset returns a WidgetCombinator that wraps the layout.Inset element. -func NewInset(insos ...InsetOption) WidgetCombinator { - opts := &InsetOpts{} +func NewInset(insos ...InsetOption) func(...Widget) Widget { + opts := InsetOpts{} for _, o := range insos { - o.DoInsetOption(opts) + o.DoInsetOption(&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) { + Mallocs("NewInset() return") + var ret insetWidget + ret = func(ctx *Context) { + oldContainer := ctx.container + ctx.container = ret ctx.cs = ins.Begin(ctx.c, ctx.ops, ctx.cs) for _, w := range ws { w.Layout(ctx) } ctx.dims = ins.End(ctx.dims) - }) + ctx.container = oldContainer + } + return ret } } @@ -369,14 +404,24 @@ type Enclosure interface { End(*Context) } +type encloseWidget struct { + enc Enclosure + ws []Widget +} + func Enclose(e Enclosure, ws ...Widget) Widget { - return NewfWidget(func(ctx *Context) { - e.Begin(ctx) - for _, w := range ws { - w.Layout(ctx) - } - e.End(ctx) - }) + return &encloseWidget{e, ws} +} + +func (e *encloseWidget) Layout(ctx *Context) { + oldContainer := ctx.container + ctx.container = e + e.enc.Begin(ctx) + for _, w := range e.ws { + w.Layout(ctx) + } + e.enc.End(ctx) + ctx.container = oldContainer } type BackgroundOpts struct { @@ -412,11 +457,14 @@ func NewBackground(bos ...BackgroundOption) WidgetCombinator { } func (bg *Background) Begin(ctx *Context) { + Mallocs("Background.Begin()") bg.macro.Record(ctx.ops) ctx.cs = bg.Inset.Begin(ctx.c, ctx.ops, ctx.cs) + Mallocs("Background.Begin() done") } func (bg *Background) End(ctx *Context) { + Mallocs("Background.End()") ctx.dims = bg.Inset.End(ctx.dims) bg.macro.Stop() var stack ui.StackOp @@ -435,14 +483,19 @@ func (bg *Background) End(ctx *Context) { paint.PaintOp{Rect: f32.Rectangle{Max: f32.Point{X: w, Y: h}}}.Add(ctx.ops) bg.macro.Add(ctx.ops) stack.Pop() + Mallocs("Background.End() done") } // https://pomax.github.io/bezierinfo/#circles_cubic. func Rrect(ops *ui.Ops, width, height, se, sw, nw, ne float32) { + Mallocs("Rrect() start") w, h := float32(width), float32(height) + Mallocs("Rrect() const") const c = 0.55228475 // 4*(sqrt(2)-1)/3 var b paint.PathBuilder + Mallocs("Rrect() init") b.Init(ops) + Mallocs("Rrect() move") b.Move(f32.Point{X: w, Y: h - se}) b.Cube(f32.Point{X: 0, Y: se * c}, f32.Point{X: -se + se*c, Y: se}, f32.Point{X: -se, Y: se}) // SE b.Line(f32.Point{X: sw - w + se, Y: 0}) @@ -452,6 +505,7 @@ func Rrect(ops *ui.Ops, width, height, se, sw, nw, ne float32) { b.Line(f32.Point{X: w - ne - nw, Y: 0}) b.Cube(f32.Point{X: ne * c, Y: 0}, f32.Point{X: ne, Y: ne - ne*c}, f32.Point{X: ne, Y: ne}) // NE b.End() + Mallocs("Rrect() end") } type Clickable interface { @@ -465,13 +519,15 @@ type cWidget struct { click *gesture.Click } -func (w cWidget) Layout(ctx *Context) { +func (w *cWidget) Layout(ctx *Context) { + Mallocs("cWidget.Layout()") w.w.Layout(ctx) pointer.RectAreaOp{image.Rect(0, 0, ctx.dims.Size.X, ctx.dims.Size.Y)}.Add(ctx.ops) w.click.Add(ctx.ops) } func (w cWidget) Clicked(ctx *Context) bool { + Mallocs("cWidget.Clicked()") for e, ok := w.click.Next(ctx.q); ok; e, ok = w.click.Next(ctx.q) { if e.Type == gesture.TypeClick { ctx.w.Invalidate() @@ -482,6 +538,6 @@ func (w cWidget) Clicked(ctx *Context) bool { } //Clickable converts any Widget into a clickable Widget. -func AsClickable(w Widget) cWidget { - return cWidget{w: w, click: new(gesture.Click)} +func AsClickable(w Widget) *cWidget { + return &cWidget{w: w, click: new(gesture.Click)} } diff --git a/prof.go b/prof.go new file mode 100644 index 0000000..2a331da --- /dev/null +++ b/prof.go @@ -0,0 +1,34 @@ +package giowrap + +import ( + "runtime" +) + +type AllocEntry struct { + Value uint64 + Text string +} + +var ( + memstats runtime.MemStats + oldMallocs uint64 + AllMallocs []AllocEntry +) + +func RecordMallocs() { + if AllMallocs == nil { + AllMallocs = make([]AllocEntry, 0, 64) + } else { + AllMallocs = AllMallocs[0:0] + } + runtime.ReadMemStats(&memstats) + oldMallocs = memstats.Mallocs +} + +func Mallocs(s string) { + if AllMallocs == nil { + return + } + runtime.ReadMemStats(&memstats) + AllMallocs = append(AllMallocs, AllocEntry{memstats.Mallocs - oldMallocs, s}) +}