Memory profiling code. Updates to reduce memory footprint.

This commit is contained in:
Greg 2019-08-28 11:48:22 -04:00
parent 635655e768
commit 83c9d40500
8 changed files with 547 additions and 174 deletions

1
.gitignore vendored
View File

@ -1,4 +1,5 @@
cmd/hello/hello cmd/hello/hello
cmd/cal/cal cmd/cal/cal
cmd/scroll/scroll cmd/scroll/scroll
cmd/grid/grid
*.apk *.apk

View File

@ -152,7 +152,7 @@ func eventloop() {
case app.DestroyEvent: case app.DestroyEvent:
return return
case app.UpdateEvent: case app.UpdateEvent:
ctx.Reset(e) ctx.Reset(&e)
mth.SetText(fmt.Sprintf("%s %d", sm.Month.String(), sm.Year)) mth.SetText(fmt.Sprintf("%s %d", sm.Month.String(), sm.Year))

View File

@ -3,6 +3,7 @@ package main
import ( import (
"git.wow.st/gmp/giowrap" "git.wow.st/gmp/giowrap"
"fmt"
"image" "image"
"image/color" "image/color"
"log" "log"
@ -27,12 +28,13 @@ import (
) )
var ( var (
FPS int = 100 FPS int = 100
frames int64 frames int64
frametime int64 // nanoseconds frametime int64 // nanoseconds
maxframetime int64 maxframetime int64
mux sync.Mutex mux sync.Mutex
Profile bool = false Profile bool = true
RecordMallocs bool = false
) )
func NewButton(face text.Face, t string, c color.RGBA) giowrap.Clickable { func NewButton(face text.Face, t string, c color.RGBA) giowrap.Clickable {
@ -69,7 +71,7 @@ func main() {
fallthrough fallthrough
default: default:
log.Fatal(`Usage: log.Fatal(`Usage:
hello [main1|main2] hello [main1|main2|main3|main4]
`) `)
case os.Args[1] == "main1": case os.Args[1] == "main1":
log.Print("main1()") log.Print("main1()")
@ -77,10 +79,17 @@ hello [main1|main2]
case os.Args[1] == "main2": case os.Args[1] == "main2":
log.Print("main2()") log.Print("main2()")
go 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() app.Main()
} }
// Example with functional layout
func main1() { func main1() {
w := app.NewWindow() w := app.NewWindow()
regular, err := sfnt.Parse(goregular.TTF) regular, err := sfnt.Parse(goregular.TTF)
@ -108,17 +117,156 @@ func main1() {
profiled := false profiled := false
startTime := time.Now() 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 { for {
select { select {
case <-t.C: case <-t.C:
w.Invalidate() w.Invalidate()
case e := <-w.Events(): case e := <-w.Events():
mux.Lock()
stime := time.Now() stime := time.Now()
switch e := e.(type) { switch e := e.(type) {
case app.DestroyEvent: case app.DestroyEvent:
return return
case app.UpdateEvent: 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( OuterInset(
f1( f1(
e1, e1,
@ -127,9 +275,107 @@ func main1() {
giowrap.Flexible(0.67), giowrap.Flexible(0.67),
f2( f2(
InnerInset(btn1), InnerInset(btn1),
giowrap.Flexible(0.5), giowrap.Flexible(0.50),
InnerInset(btn2), 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) )).Layout(ctx)
ctx.Update() ctx.Update()
if btn1.Clicked(ctx) { if btn1.Clicked(ctx) {
@ -138,6 +384,20 @@ func main1() {
if btn2.Clicked(ctx) { if btn2.Clicked(ctx) {
log.Print("Clicked: " + e2.Text()) 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() dur := time.Since(stime).Nanoseconds()
mux.Lock() mux.Lock()
@ -148,28 +408,11 @@ func main1() {
} }
mux.Unlock() 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() w := app.NewWindow()
q := w.Queue() q := w.Queue()
ops := new(ui.Ops) ops := new(ui.Ops)
@ -215,6 +458,18 @@ func main2() {
case app.DestroyEvent: case app.DestroyEvent:
return return
case app.UpdateEvent: 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 c := &e.Config
ops.Reset() ops.Reset()
faces.Reset(c) faces.Reset(c)
@ -257,6 +512,16 @@ func main2() {
dims = ins.End(f1.Layout(c1, c2, c3)) dims = ins.End(f1.Layout(c1, c2, c3))
} }
w.Update(ops) 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() dur := time.Since(stime).Nanoseconds()
mux.Lock() mux.Lock()
@ -267,24 +532,6 @@ func main2() {
} }
mux.Unlock() 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()
}
} }
} }

View File

@ -4,6 +4,8 @@ import (
"fmt" "fmt"
"image/color" "image/color"
"log" "log"
"runtime"
"time"
gio "git.wow.st/gmp/giowrap" gio "git.wow.st/gmp/giowrap"
@ -99,18 +101,21 @@ func eventloop() {
lbar := gio.NewLabel(" ", gio.Face(face)) lbar := gio.NewLabel(" ", gio.Face(face))
f2 := gio.NewFlex(gio.Axis(layout.Vertical)) f2 := gio.NewFlex(gio.Axis(layout.Vertical))
topbar := gio.NewLabel("Scroll X and Y. Click to select", gio.Face(face)) 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))) sh := gio.HScroll(gio.NewFlex(gio.Axis(layout.Horizontal)))
sv := gio.VScroll(gio.NewFlex(gio.Axis(layout.Vertical))) sv := gio.VScroll(gio.NewFlex(gio.Axis(layout.Vertical)))
numlabs := 50 numrows := 16
labs := make([][]gio.Widget, numlabs) numcols := 50
sels := make([][]bool, numlabs) labs := make([][]gio.Widget, numrows)
for i := 0; i < 16; i++ { sels := make([][]bool, numrows)
labs[i] = make([]gio.Widget, numlabs) for i := 0; i < numrows; i++ {
sels[i] = make([]bool, numlabs) labs[i] = make([]gio.Widget, numcols)
for j := 0; j < numlabs; j++ { sels[i] = make([]bool, numcols)
labs[i][j] = NewSLabel(fmt.Sprintf("%03d", i*16+j), &sels[i][j]) 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)) 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 var oldInsets app.Insets
stime := time.Now()
sm := runtime.MemStats{}
for { for {
select { select {
case e := <-w.Events(): case e := <-w.Events():
@ -129,32 +156,30 @@ func eventloop() {
case app.DestroyEvent: case app.DestroyEvent:
return return
case app.UpdateEvent: 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) { if diffInsets(e.Insets, oldInsets) {
oldInsets = e.Insets oldInsets = e.Insets
resetSysinset(e.Insets) resetSysinset(e.Insets)
} }
page.Layout(ctx)
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)
ctx.Update() 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)
}
} }
} }
} }

40
grid.go
View File

@ -124,24 +124,34 @@ type HeightOpt struct{ height int }
func Height(x int) HeightOpt { return HeightOpt{x} } func Height(x int) HeightOpt { return HeightOpt{x} }
func (x HeightOpt) DoGridOption(g *Grid) { g.Height = x.height } 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 { func NewGrid(cols int, gops ...GridOption) WidgetCombinator {
g := &Grid{Cols: cols} ret := &gridWidget{}
ret.g = Grid{Cols: cols}
for _, gop := range gops { 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 func(ws ...Widget) Widget {
return NewfWidget(func(ctx *Context) { ret.ws = ws
cs := g.Init(ctx.ops, ctx.cs) return ret
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]
})
} }
} }
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]
}

222
main.go
View File

@ -42,12 +42,13 @@ func (e *Extra) New() int {
type Context struct { type Context struct {
Faces measure.Faces Faces measure.Faces
w *app.Window w *app.Window
c *app.Config c *app.Config
q input.Queue q input.Queue
ops *ui.Ops ops *ui.Ops
cs layout.Constraints cs layout.Constraints
dims layout.Dimens dims layout.Dimens
container Widget
} }
func NewContext(w *app.Window) *Context { 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.c = &e.Config
ctx.ops.Reset() ctx.ops.Reset()
ctx.cs = layout.RigidConstraints(e.Size) ctx.cs = layout.RigidConstraints(e.Size)
ctx.Faces.Reset(ctx.c) ctx.Faces.Reset(ctx.c)
Mallocs("ctx.Reset() done")
} }
func (ctx *Context) Update() { func (ctx *Context) Update() {
@ -121,6 +124,7 @@ func NewLabel(t string, lops ...LabelOption) *Label {
} }
func (l *Label) Layout(ctx *Context) { func (l *Label) Layout(ctx *Context) {
Mallocs("Label.Layout()")
ctx.dims = l.l.Layout(ctx.ops, ctx.cs) 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) { func (e *Editor) Layout(ctx *Context) {
Mallocs("Editor.Layout()")
ctx.dims = e.e.Layout(ctx.c, ctx.q, ctx.ops, ctx.cs) 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) SetText(s string) { e.e.SetText(s) }
func (e *Editor) Focus() { e.e.Focus() } 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 Stack WidgetCombinator
type stackWidget struct {
s layout.Stack
scs []layout.StackChild
ws []Widget
}
func NewStack() Stack { func NewStack() Stack {
s := layout.Stack{Alignment: layout.Center} ret := &stackWidget{}
scs := make([]layout.StackChild, 0) ret.s = layout.Stack{Alignment: layout.Center}
ret.scs = make([]layout.StackChild, 0)
return func(ws ...Widget) Widget { return func(ws ...Widget) Widget {
return NewfWidget(func(ctx *Context) { ret.ws = ws
s.Init(ctx.ops, ctx.cs) return ret
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]
})
} }
} }
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 List = WidgetCombinator
type AxisOpt struct{ axis layout.Axis } 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 } func (x AxisOpt) DoListOption(o *ListOpts) { o.axis = x.axis }
type listWidget struct {
l layout.List
ws []Widget
}
func NewList(los ...ListOption) List { func NewList(los ...ListOption) List {
ret := &listWidget{}
opts := &ListOpts{} opts := &ListOpts{}
for _, o := range los { for _, o := range los {
o.DoListOption(opts) o.DoListOption(opts)
} }
l := layout.List{Axis: opts.axis} ret.l = layout.List{Axis: opts.axis}
return func(ws ...Widget) Widget { return func(ws ...Widget) Widget {
return NewfWidget(func(ctx *Context) { ret.ws = ws
for l.Init(ctx.c, ctx.q, ctx.ops, ctx.cs, len(ws)); l.More(); l.Next() { return ret
ctx.cs = l.Constraints()
ws[l.Index()].Layout(ctx)
l.End(ctx.dims)
}
ctx.dims = l.Layout()
})
} }
} }
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 { func HScroll(c WidgetCombinator) WidgetCombinator {
return NewList(Axis(layout.Horizontal)) return NewList(Axis(layout.Horizontal))
} }
@ -237,19 +250,6 @@ func VScroll(c WidgetCombinator) WidgetCombinator {
type Flex = 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 } type AlignmentOpt struct{ alignment layout.Alignment }
func Alignment(x layout.Alignment) AlignmentOpt { return AlignmentOpt{x} } 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 AxisOpt) DoFlexOption(o *FlexOpts) { o.axis = x.axis }
func (x AlignmentOpt) DoFlexOption(o *FlexOpts) { o.alignment = x.alignment } 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. // NewFlex returns a WidgetCombinator that wraps the layout.Flex element.
func (fw *flexWidget) Layout(ctx *Context) {
fw.f(ctx)
}
func NewFlex(fos ...FlexOption) Flex { func NewFlex(fos ...FlexOption) Flex {
opts := &FlexOpts{} opts := &FlexOpts{}
for _, o := range fos { for _, o := range fos {
@ -275,29 +303,23 @@ func NewFlex(fos ...FlexOption) Flex {
Axis: opts.axis, Axis: opts.axis,
Alignment: opts.alignment, Alignment: opts.alignment,
} }
index := extra.New()
extra.data[index] = opts
// do not call "make" inside the Layout function
fcs := make([]layout.FlexChild, 0) fcs := make([]layout.FlexChild, 0)
return func(ws ...Widget) Widget { return func(ws ...Widget) Widget {
return NewfWidget(func(ctx *Context) { ret := &flexWidget{}
// ensure child widgets write options to the right place ret.f = func(ctx *Context) {
extra.cur = index oldContainer := ctx.container
opts := extra.data[index].(*FlexOpts) ctx.container = ret
f.Init(ctx.ops, ctx.cs) f.Init(ctx.ops, ctx.cs)
for _, w := range ws { for _, w := range ws {
if opts.flexible != 0 { ctx.cs = f.Rigid()
ctx.cs = f.Flexible(opts.flexible)
} else {
ctx.cs = f.Rigid()
}
w.Layout(ctx) w.Layout(ctx)
fcs = append(fcs, f.End(ctx.dims)) fcs = append(fcs, f.End(ctx.dims))
} }
ctx.dims = f.Layout(fcs...) 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 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. //NewInset returns a WidgetCombinator that wraps the layout.Inset element.
func NewInset(insos ...InsetOption) WidgetCombinator { func NewInset(insos ...InsetOption) func(...Widget) Widget {
opts := &InsetOpts{} opts := InsetOpts{}
for _, o := range insos { 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} ins := layout.Inset{Top: opts.top, Right: opts.right, Bottom: opts.bottom, Left: opts.left}
return func(ws ...Widget) Widget { 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) ctx.cs = ins.Begin(ctx.c, ctx.ops, ctx.cs)
for _, w := range ws { for _, w := range ws {
w.Layout(ctx) w.Layout(ctx)
} }
ctx.dims = ins.End(ctx.dims) ctx.dims = ins.End(ctx.dims)
}) ctx.container = oldContainer
}
return ret
} }
} }
@ -369,14 +404,24 @@ type Enclosure interface {
End(*Context) End(*Context)
} }
type encloseWidget struct {
enc Enclosure
ws []Widget
}
func Enclose(e Enclosure, ws ...Widget) Widget { func Enclose(e Enclosure, ws ...Widget) Widget {
return NewfWidget(func(ctx *Context) { return &encloseWidget{e, ws}
e.Begin(ctx) }
for _, w := range ws {
w.Layout(ctx) func (e *encloseWidget) Layout(ctx *Context) {
} oldContainer := ctx.container
e.End(ctx) 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 { type BackgroundOpts struct {
@ -412,11 +457,14 @@ func NewBackground(bos ...BackgroundOption) WidgetCombinator {
} }
func (bg *Background) Begin(ctx *Context) { func (bg *Background) Begin(ctx *Context) {
Mallocs("Background.Begin()")
bg.macro.Record(ctx.ops) bg.macro.Record(ctx.ops)
ctx.cs = bg.Inset.Begin(ctx.c, ctx.ops, ctx.cs) ctx.cs = bg.Inset.Begin(ctx.c, ctx.ops, ctx.cs)
Mallocs("Background.Begin() done")
} }
func (bg *Background) End(ctx *Context) { func (bg *Background) End(ctx *Context) {
Mallocs("Background.End()")
ctx.dims = bg.Inset.End(ctx.dims) ctx.dims = bg.Inset.End(ctx.dims)
bg.macro.Stop() bg.macro.Stop()
var stack ui.StackOp 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) paint.PaintOp{Rect: f32.Rectangle{Max: f32.Point{X: w, Y: h}}}.Add(ctx.ops)
bg.macro.Add(ctx.ops) bg.macro.Add(ctx.ops)
stack.Pop() stack.Pop()
Mallocs("Background.End() done")
} }
// https://pomax.github.io/bezierinfo/#circles_cubic. // 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) {
Mallocs("Rrect() start")
w, h := float32(width), float32(height) w, h := float32(width), float32(height)
Mallocs("Rrect() const")
const c = 0.55228475 // 4*(sqrt(2)-1)/3 const c = 0.55228475 // 4*(sqrt(2)-1)/3
var b paint.PathBuilder var b paint.PathBuilder
Mallocs("Rrect() init")
b.Init(ops) b.Init(ops)
Mallocs("Rrect() move")
b.Move(f32.Point{X: w, Y: h - se}) 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.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}) 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.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.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() b.End()
Mallocs("Rrect() end")
} }
type Clickable interface { type Clickable interface {
@ -465,13 +519,15 @@ type cWidget struct {
click *gesture.Click click *gesture.Click
} }
func (w cWidget) Layout(ctx *Context) { func (w *cWidget) Layout(ctx *Context) {
Mallocs("cWidget.Layout()")
w.w.Layout(ctx) w.w.Layout(ctx)
pointer.RectAreaOp{image.Rect(0, 0, ctx.dims.Size.X, ctx.dims.Size.Y)}.Add(ctx.ops) pointer.RectAreaOp{image.Rect(0, 0, ctx.dims.Size.X, ctx.dims.Size.Y)}.Add(ctx.ops)
w.click.Add(ctx.ops) w.click.Add(ctx.ops)
} }
func (w cWidget) Clicked(ctx *Context) bool { 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) { for e, ok := w.click.Next(ctx.q); ok; e, ok = w.click.Next(ctx.q) {
if e.Type == gesture.TypeClick { if e.Type == gesture.TypeClick {
ctx.w.Invalidate() ctx.w.Invalidate()
@ -482,6 +538,6 @@ func (w cWidget) Clicked(ctx *Context) bool {
} }
//Clickable converts any Widget into a clickable Widget. //Clickable converts any Widget into a clickable Widget.
func AsClickable(w Widget) cWidget { func AsClickable(w Widget) *cWidget {
return cWidget{w: w, click: new(gesture.Click)} return &cWidget{w: w, click: new(gesture.Click)}
} }

34
prof.go Normal file
View File

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