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" "image/color"
"log" "log"
"os"
"runtime"
"runtime/pprof"
"sync"
"time" "time"
"gioui.org/ui" "gioui.org/ui"
"gioui.org/ui/app" "gioui.org/ui/app"
gdraw "gioui.org/ui/draw"
"gioui.org/ui/f32"
"gioui.org/ui/gesture"
"gioui.org/ui/layout" "gioui.org/ui/layout"
"gioui.org/ui/measure"
"gioui.org/ui/pointer"
"gioui.org/ui/text" "gioui.org/ui/text"
"golang.org/x/image/font/sfnt" "golang.org/x/image/font/sfnt"
"golang.org/x/image/font/gofont/goregular" "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 { func NewButton(face text.Face, t string, c color.RGBA) giowrap.Clickable {
lbl := giowrap.NewLabel(t, giowrap.LabelFace(face), giowrap.LabelAlignment(text.Center)) lbl := giowrap.NewLabel(t, giowrap.LabelFace(face), giowrap.LabelAlignment(text.Center))
bg := giowrap.NewBackground(giowrap.BgColor(c), giowrap.BgRadius(ui.Dp(4))) bg := giowrap.NewBackground(giowrap.BgColor(c), giowrap.BgRadius(ui.Dp(4)))
@ -24,13 +42,50 @@ func NewButton(face text.Face, t string, c color.RGBA) giowrap.Clickable {
func main() { func main() {
go func() { go func() {
ms := &runtime.MemStats{}
var a1 uint64
for {
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) w := app.NewWindow(nil)
regular, err := sfnt.Parse(goregular.TTF) regular, err := sfnt.Parse(goregular.TTF)
if err != nil { if err != nil {
log.Fatal("Cannot parse font.") log.Fatal("Cannot parse font.")
} }
ctx := giowrap.NewContext(w) ctx := giowrap.NewContext(w)
t := time.NewTicker(time.Second/30) t := time.NewTicker(time.Second/time.Duration(FPS))
e1 := giowrap.NewEditor("text 1",giowrap.EditorFace(ctx.Faces.For(regular, ui.Sp(24)))) e1 := giowrap.NewEditor("text 1",giowrap.EditorFace(ctx.Faces.For(regular, ui.Sp(24))))
e1.Focus() e1.Focus()
@ -47,17 +102,21 @@ func main() {
btn2 := NewButton(ctx.Faces.For(regular, ui.Sp(24)), btn2 := NewButton(ctx.Faces.For(regular, ui.Sp(24)),
"push2", color.RGBA{A: 0xff, R: 0x3c, G: 0x98, B: 0xc6}) "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():
stime := time.Now()
switch e := e.(type) { switch e := e.(type) {
case app.DestroyEvent: case app.DestroyEvent:
return return
case app.DrawEvent: case app.DrawEvent:
ctx = ctx.Reset(e) ctx.Reset(e)
ctx = OuterInset( OuterInset(
f1( f1(
e1, e1,
giowrap.Flexible(5), giowrap.Flexible(5),
@ -65,7 +124,7 @@ func main() {
giowrap.Flexible(10), giowrap.Flexible(10),
f2( f2(
InnerInset(btn1), InnerInset(btn1),
giowrap.Flexible(1), giowrap.Flexible(0.5),
InnerInset(btn2), InnerInset(btn2),
), ),
)).Layout(ctx) )).Layout(ctx)
@ -77,9 +136,173 @@ func main() {
log.Print("Clicked: " + e2.Text() ) 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() )
}
}
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()
}
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()
} }
} }
}()
app.Main()
} }

86
main.go
View File

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