Add profiling code, and implementation using basic widgets instead of closures.
This commit is contained in:
parent
a463c073ed
commit
5bef5fc95a
|
@ -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,13 +42,50 @@ func NewButton(face text.Face, t string, c color.RGBA) giowrap.Clickable {
|
|||
|
||||
func main() {
|
||||
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)
|
||||
regular, err := sfnt.Parse(goregular.TTF)
|
||||
if err != nil {
|
||||
log.Fatal("Cannot parse font.")
|
||||
}
|
||||
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.Focus()
|
||||
|
||||
|
@ -47,17 +102,21 @@ func main() {
|
|||
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 = ctx.Reset(e)
|
||||
ctx = OuterInset(
|
||||
ctx.Reset(e)
|
||||
OuterInset(
|
||||
f1(
|
||||
e1,
|
||||
giowrap.Flexible(5),
|
||||
|
@ -65,7 +124,7 @@ func main() {
|
|||
giowrap.Flexible(10),
|
||||
f2(
|
||||
InnerInset(btn1),
|
||||
giowrap.Flexible(1),
|
||||
giowrap.Flexible(0.5),
|
||||
InnerInset(btn2),
|
||||
),
|
||||
)).Layout(ctx)
|
||||
|
@ -77,9 +136,173 @@ func main() {
|
|||
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
86
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
|
||||
|
|
Loading…
Reference in New Issue
Block a user