Explorar o código

Benchmarking most things.

Steve Thielemann hai 1 ano
pai
achega
d7498959a7
Modificáronse 16 ficheiros con 450 adicións e 307 borrados
  1. 23 0
      door/NOTES.md
  2. 91 20
      door/bar.go
  3. 45 3
      door/bar_test.go
  4. 6 0
      door/benchbar.sh
  5. 26 3
      door/door.go
  6. 7 5
      door/line.go
  7. 67 11
      door/line_test.go
  8. 2 2
      door/menu.go
  9. 5 5
      door/menu_test.go
  10. 11 11
      door/nomoresecrets.go
  11. 13 6
      door/panel.go
  12. 11 2
      door/panel_test.go
  13. 4 0
      door/what-leaks
  14. 67 17
      door/wopr.go
  15. 70 23
      door/write.go
  16. 2 199
      door/write_linux.go

+ 23 - 0
door/NOTES.md

@@ -0,0 +1,23 @@
+
+# What leaks from stack to heap?
+
+go build -gcflags=-m=2 . 2>&1 | less
+
+# Changes:
+
+* RenderF
+    This doesn't need to return .Bytes(), we can use the output (*bytes.Buffer)
+    This is Redundant.
+
+* Panel
+    Change []Line to []*Line
+    Otherwise adding to Lines is a Copy/Makes more garbage. (!)
+
+* Line
+    I'd like NewLine to take (narg... any), to init:
+        Text
+        RenderF
+        UpdateF
+        Width
+        DefaultColor (the 2nd "text" item)
+    And have NewLine return *Line.

+ 91 - 20
door/bar.go

@@ -1,11 +1,12 @@
 package door
 
 import (
+	"bytes"
 	"fmt"
 	"strings"
 )
 
-// TODO:  Break out the progress bar characters into stuctures.
+// TODO:  Break out the progress bar characters into structures.
 // Write tests to verify that CP437 matches Unicode.
 // See box_test.  ;)
 
@@ -73,8 +74,13 @@ func (bl *BarLine) CheckRange() {
 	}
 }
 
-func (bl *BarLine) Output() string {
-	var output string
+func (bl *BarLine) Output() []byte {
+	// bl.Text & bl.render & bl.update
+	if bl.render == nil {
+		bl.render = &bytes.Buffer{}
+	}
+	bl.render.Reset()
+
 	var step_width int64
 
 	if bl.UpdateP != nil {
@@ -82,57 +88,73 @@ func (bl *BarLine) Output() string {
 	}
 
 	bl.CheckRange()
+	// CheckRange can change the DefaultColor.
+	bl.render.WriteString(bl.DefaultColor)
+
+	if bl.update == nil {
+		bl.update = &bytes.Buffer{}
+	}
+	bl.update.Reset()
 
 	switch bl.Style {
 	case SOLID:
 		step_width = int64(100 * 100 / bl.Width)
 		var steps int = int(bl.Percent / step_width)
 
-		output += strings.Repeat(BARS.Solid, steps)
+		bl.update.WriteString(strings.Repeat(BARS.Solid, steps))
 		// This will work, because we aren't trying to len(output) with unicode.
-		output += strings.Repeat(" ", int(bl.Width-steps))
+		bl.update.WriteString(strings.Repeat(" ", int(bl.Width-steps)))
 
 	case HALF_STEP:
 		step_width = int64(100 * 100 / bl.Width)
 		var steps int = int(bl.Percent * 2 / step_width)
 
-		output += strings.Repeat(BARS.Half[0], steps/2)
+		bl.update.WriteString(strings.Repeat(BARS.Half[0], steps/2))
 		if steps%2 == 1 {
-			output += BARS.Half[1]
+			bl.update.WriteString(BARS.Half[1])
 			steps++
 		}
-		output += strings.Repeat(" ", bl.Width-(steps/2))
+		bl.update.WriteString(strings.Repeat(" ", bl.Width-(steps/2)))
 
 	case GRADIENT:
 		step_width = int64(100 * 100 / bl.Width)
 		var steps int = int(bl.Percent * 4 / step_width)
 
-		output += strings.Repeat(BARS.Gradient[0], steps/4)
+		bl.update.WriteString(strings.Repeat(BARS.Gradient[0], steps/4))
 		if steps%4 != 0 {
 			switch steps % 4 {
 			case 1, 2, 3:
-				output += BARS.Gradient[steps%4]
+				bl.update.WriteString(BARS.Gradient[steps%4])
 			}
 			for steps%4 != 0 {
 				steps++
 			}
 		}
-		output += strings.Repeat(" ", bl.Width-(steps/4))
+		bl.update.WriteString(strings.Repeat(" ", bl.Width-(steps/4)))
 	}
 
 	if bl.PercentStyle != PERCENT_NONE {
-		percent := fmt.Sprintf("%d", bl.Percent/100)
+		var pctbuff [10]byte
+		var percent = pctbuff[0:0]
+		percent = fmt.Appendf(percent, "%d", bl.Percent/100)
 
 		var pos int = bl.Width/2 - 1
-		if percent != "100" {
-			percent += "%"
+		if bytes.Compare(percent, []byte("100")) != 0 {
+			percent = append(percent, '%')
 			if len(percent) < 3 {
-				percent = " " + percent
+				percent = append(percent, ' ')
+				copy(percent[1:], percent[0:])
+				percent[0] = ' '
+				// percent = " " + percent
 			}
 		}
 
 		if bl.PercentStyle == PERCENT_SPACE {
-			percent = " " + percent + " "
+			percent = append(percent, ' ')
+			copy(percent[1:], percent[0:])
+			percent = append(percent, ' ')
+			percent[0] = ' '
+			// percent = " " + percent + " "
 			pos--
 		}
 
@@ -143,13 +165,62 @@ func (bl *BarLine) Output() string {
 		// newString := string(sliceable[:5]) + " new content " + (sliceable[10:])
 		// fmt.Printf("%d %d [%s] %d [%s]\n", bl.Width, pos, percent, len(output), output)
 
+		// The % part -- isn't working at the moment.
+		// I need to insert the percent into the buffer. (but how)?
+		// and unicode?  hmm!
+
+		// Note: This takes ownership of update.
+		bl.Text = bytes.NewBuffer(bl.update.Bytes())
+
+		var idx int = 0
 		if Unicode {
-			runes := []rune(output)
-			output = string(runes[:pos]) + percent + string(runes[pos+len(percent):])
+			for {
+				rune, _, err := bl.Text.ReadRune()
+				if err != nil {
+					break
+				}
+				if (idx >= pos) && (idx < len(percent)+pos) {
+					bl.render.WriteByte(percent[idx-pos])
+				} else {
+					bl.render.WriteRune(rune)
+				}
+				idx++
+			}
+			/*
+				runes := []rune(output)
+				for idx, b := range percent {
+					runes[pos+idx] = rune(b)
+				}
+				output = string(runes[:pos]) + percent + string(runes[pos+len(percent):])
+			*/
 		} else {
-			output = output[:pos] + percent + output[pos+len(percent):]
+
+			for {
+				b, err := bl.Text.ReadByte()
+				if err != nil {
+					break
+				}
+				if (idx >= pos) && (idx < len(percent)+pos) {
+					bl.render.WriteByte(percent[idx-pos])
+				} else {
+					bl.render.WriteByte(b)
+				}
+				idx++
+
+			}
+			/*
+				for idx, b := range percent {
+					render[pos+idx] = b
+				}
+			*/
+			// output = output[:pos] + percent + output[pos+len(percent):]
 		}
+		_ = idx
+
+	} else {
+		bl.render.Write(bl.update.Bytes())
 	}
 
-	return bl.DefaultColor + output
+	// return bl.DefaultColor + output
+	return bl.render.Bytes()
 }

+ 45 - 3
door/bar_test.go

@@ -92,7 +92,7 @@ func testBars(t *testing.T) {
 
 	for pct, text := range BarSolid {
 		bar.Percent = int64(pct * 100)
-		got := bar.Output()
+		got := string(bar.Output())
 		if got != text {
 			t.Errorf("BarSolidRange: Expected %#v (%d%%), got %#v", text, pct, got)
 		}
@@ -115,7 +115,7 @@ func testBars(t *testing.T) {
 
 	for pct, text := range BarHalf {
 		bar.Percent = int64(pct * 100)
-		got := bar.Output()
+		got := string(bar.Output())
 		if got != text {
 			t.Errorf("BarHalf: Expected %#v (%d%%), got %#v", text, pct, got)
 		}
@@ -140,10 +140,52 @@ func testBars(t *testing.T) {
 
 	for pct, text := range BarGrad {
 		percent = int64(pct * 100)
-		got := bar.Output()
+		got := string(bar.Output())
 		if got != text {
 			t.Errorf("BarGradient: Expected %#v (%d%%), got %#v", text, pct, got)
 		}
 	}
 
 }
+
+func BenchmarkBar(b *testing.B) {
+	Unicode = false
+
+	BarColor := ColorText("BLUE")
+	Bar25 := ColorText("RED")
+	Bar50 := ColorText("BROWN")
+	Bar75 := ColorText("BOLD YEL")
+	Bar95 := ColorText("GREEN")
+	Bar100 := ColorText("BRI GREEN")
+
+	bar := BarLine{Line: Line{DefaultColor: BarColor}, Width: 10, Style: SOLID}
+	bar.ColorRange = []BarRange{
+		{2500, Bar25},
+		{5000, Bar50},
+		{7500, Bar75},
+		{9500, Bar95},
+		{10100, Bar100}}
+
+	BarColor2 := ColorText("BLA ON WHI")
+	bar2 := BarLine{Line: Line{DefaultColor: BarColor2}, Width: 10, Style: HALF_STEP, PercentStyle: PERCENT_SPACE}
+
+	BarColor3 := ColorText("RED")
+	bar3 := BarLine{Line: Line{DefaultColor: BarColor3},
+		Width: 10, Style: GRADIENT}
+
+	var percent int64
+	var upFunc = func() int64 {
+		return percent
+	}
+
+	bar.UpdateP = upFunc
+	bar2.UpdateP = upFunc
+	bar3.UpdateP = upFunc
+
+	for n := 0; n < b.N; n++ {
+		percent = (int64(n) * 100) / int64(b.N)
+		bar.Output()
+		bar2.Output()
+		bar3.Output()
+	}
+}

+ 6 - 0
door/benchbar.sh

@@ -0,0 +1,6 @@
+#!/bin/bash
+
+go test -bench=BenchmarkBar -benchmem -memprofile memory.out -cpuprofile cpu.out
+echo go tool pprof memory.out
+echo go tool pprof cpu.out
+

+ 26 - 3
door/door.go

@@ -23,6 +23,7 @@ import (
 	"bytes"
 	"flag"
 	"fmt"
+	"io"
 	"log"
 	"os"
 	"path/filepath"
@@ -69,7 +70,7 @@ var Height int                                                  // Screen height
 var Width int                                                   // Screen width detected
 var Inactivity time.Duration = time.Duration(120) * time.Second // Inactivity timeout
 
-type ColorRender func(*bytes.Buffer, []byte) []byte
+type ColorRender func(*bytes.Buffer, []byte)
 type Updater func(*bytes.Buffer)
 
 type UpdaterI interface {
@@ -117,9 +118,7 @@ func (*noCopy) Unlock() {}
 
 type BaseWriter struct {
 	Closed             bool
-	TranslateNL        bool
 	TranslateToUnicode bool
-	nlBuffer           *bytes.Buffer
 	uniBuffer          *bytes.Buffer
 	ansiCSI            bool
 	ansiCode           []byte
@@ -132,6 +131,8 @@ type Door struct {
 	WRITEFD       int
 	Writer        OSWriter         // OS specific writer
 	writeMutex    sync.Mutex       // Writer lock
+	nlBuffer      *bytes.Buffer    // NewLines Translate buffer
+	TranslateNL   bool             // Translate NewLines?
 	ansiCSI       bool             // ANSI CSI
 	ansiCode      []byte           // ANSI CSI Codes
 	Disconnected  bool             // Has User disconnected/Hung up?
@@ -446,12 +447,34 @@ func (d *Door) Close() {
 // Example:
 //
 //	d.Write(door.Goto(1, 5) + "Now at X=1, Y=5.")
+
 func Goto(x int, y int) []byte {
 	var output []byte
 	output = fmt.Appendf(output, "\x1b[%d;%dH", y, x)
 	return output
 }
 
+// No change in benchmarks using this ...
+
+func GotoW(x, y int, w io.Writer) {
+	fmt.Fprintf(w, "\x1b[%d;%dH", y, x)
+}
+
+// Why doesn't func Goto2 work?  I get wrong answers / tests fail.
+// If this is called from a go routine .. while another goto is being
+// done... hell might brake loose.
+
+var gotobuff *bytes.Buffer
+
+func Goto2(x int, y int) []byte {
+	if gotobuff == nil {
+		gotobuff = &bytes.Buffer{}
+	}
+	gotobuff.Reset()
+	fmt.Fprintf(gotobuff, "\x1b[%d;%dH", y, x)
+	return gotobuff.Bytes()
+}
+
 func GotoS(x int, y int) string {
 	return fmt.Sprintf("\x1b[%d;%dH", y, x)
 }

+ 7 - 5
door/line.go

@@ -111,7 +111,9 @@ func (l *Line) Update() bool {
 	l.UpdateF(l.update)
 
 	l.LineLength(l.update)
+	// Has the line changed (update)?
 	if bytes.Compare(l.update.Bytes(), l.Text.Bytes()) != 0 {
+		// Yes, copy into Text.
 		l.Text.Reset()
 		l.Text.Write(l.update.Bytes())
 		return true
@@ -165,6 +167,7 @@ func (l *Line) Output() []byte {
 	if l.render == nil {
 		l.render = &bytes.Buffer{}
 	}
+
 	if l.RenderF == nil {
 		l.render.Reset()
 		l.render.WriteString(l.DefaultColor)
@@ -172,7 +175,8 @@ func (l *Line) Output() []byte {
 		return l.render.Bytes()
 		// return l.DefaultColor + l.Text
 	} else {
-		return l.RenderF(l.render, l.Text.Bytes())
+		l.RenderF(l.render, l.Text.Bytes())
+		return l.render.Bytes()
 	}
 }
 
@@ -190,7 +194,7 @@ func RenderUppercase(Upper string, NonUpper string) ColorRender {
 		NonUpperColor = []byte(ColorText(NonUpper))
 	}
 
-	return func(output *bytes.Buffer, text []byte) []byte {
+	return func(output *bytes.Buffer, text []byte) {
 		var lastColor *[]byte
 		output.Reset()
 		for _, letter := range text {
@@ -207,11 +211,10 @@ func RenderUppercase(Upper string, NonUpper string) ColorRender {
 			}
 			output.WriteByte(letter)
 		}
-		return output.Bytes()
 	}
 }
 
-func RenderBlueYellow(output *bytes.Buffer, text []byte) []byte {
+func RenderBlueYellow(output *bytes.Buffer, text []byte) {
 	output.Reset()
 
 	// var output = RenderPool.Get().(*strings.Builder)
@@ -239,5 +242,4 @@ func RenderBlueYellow(output *bytes.Buffer, text []byte) []byte {
 	// var result = output.String()
 	// RenderPool.Put(output)
 	// return result
-	return output.Bytes()
 }

+ 67 - 11
door/line_test.go

@@ -133,8 +133,18 @@ func BenchmarkLine(b *testing.B) {
 		var lineBuff *bytes.Buffer = &bytes.Buffer{}
 		lineBuff.WriteString(fmt.Sprintf("Line %d of %d", n, b.N))
 		var line Line = Line{Text: lineBuff}
-		var output []byte = line.Output()
-		_ = output
+		line.Output()
+	}
+}
+func BenchmarkLineColor(b *testing.B) {
+	Unicode = false
+	color := ColorText("BRI WHI ON BLUE")
+
+	for n := 0; n < b.N; n++ {
+		var lineBuff *bytes.Buffer = &bytes.Buffer{}
+		lineBuff.WriteString(fmt.Sprintf("Line %d of %d", n, b.N))
+		var line Line = Line{Text: lineBuff, DefaultColor: color}
+		line.Output()
 	}
 }
 
@@ -142,16 +152,62 @@ func BenchmarkLine(b *testing.B) {
 // BenchmarkLineRender-4 739462 1709 ns/op 376 B/op 13 allocs/op
 // BenchmarkLineRender-4 862102 1434 ns/op 648 B/op 9 allocs/op
 
+/*
+// This actually made BenchmarkLineUpdate worse.
+func printd(value int64, write io.Writer) {
+	var buffer [32]byte
+	var pos int
+
+	if value == 0 {
+		write.Write([]byte{'0'})
+		return
+	}
+
+	for value > 0 {
+		buffer[pos] = byte(int64('0') + value%10)
+		pos++
+		value = value / 10
+	}
+
+	for pos != 0 {
+		pos--
+		write.Write(buffer[pos : pos+1])
+	}
+}
+*/
+
+func BenchmarkLineUpdate(b *testing.B) {
+	Unicode = false
+	var line Line = Line{}
+	var n int
+
+	line.UpdateF = func(u *bytes.Buffer) {
+		u.Reset()
+		/*
+			u.WriteString("Line ")
+			printd(int64(n), u)
+			u.WriteString(" of ")
+			printd(int64(b.N), u)
+		*/
+		fmt.Fprintf(u, "Line %d of %d", n, b.N)
+	}
+	line.Update()
+
+	for n = 0; n < b.N; n++ {
+		line.Update()
+		line.Output()
+	}
+}
+
 func BenchmarkLineRender(b *testing.B) {
 	Unicode = false
 	var rf ColorRender = RenderUppercase("RED", "GREEN")
+	var line Line = NewLine("ThIs Is CrAzY TeXt HeRe")
+	line.RenderF = rf
+	var n int
 
-	for n := 0; n < b.N; n++ {
-		var lineBuff *bytes.Buffer = &bytes.Buffer{}
-		lineBuff.WriteString(fmt.Sprintf("Line %d of %d", n, b.N))
-		var line Line = Line{Text: lineBuff, RenderF: rf} // RenderBlueYellow}
-		var output []byte = line.Output()
-		_ = output
+	for n = 0; n < b.N; n++ {
+		line.Output()
 	}
 }
 
@@ -161,14 +217,14 @@ func BenchmarkLineRender(b *testing.B) {
 // BenchmarkLineColor-4 2944704 400.0 ns/op 8 B/op 0 allocs/op
 // No change making Color strings to []byte. (reverted change)
 
-func BenchmarkLineColor(b *testing.B) {
+func BenchmarkLineRenderUpdate(b *testing.B) {
 
 	var Up = ColorText("BLUE")
 	var Down = ColorText("BOLD BLUE")
 	var Num = ColorText("BRI GREEN")
 	var Sym = ColorText("CYAN")
 
-	var render = func(output *bytes.Buffer, text []byte) []byte {
+	var render = func(output *bytes.Buffer, text []byte) {
 		output.Reset()
 		var last *string
 		// var r Render = Render{Line: text}
@@ -202,7 +258,7 @@ func BenchmarkLineColor(b *testing.B) {
 			output.WriteByte(letter)
 			// output.WriteString(string(letter))
 		}
-		return output.Bytes()
+		// return output.Bytes()
 		// return r.Result
 	}
 

+ 2 - 2
door/menu.go

@@ -25,7 +25,7 @@ type Menu struct {
 }
 
 func MakeMenuRender(bracketColor, optionColor, upperColor, lowerColor string) ColorRender {
-	f := func(output *bytes.Buffer, text []byte) []byte {
+	f := func(output *bytes.Buffer, text []byte) {
 		output.Reset()
 		var lastColor *string
 		option := true
@@ -61,7 +61,7 @@ func MakeMenuRender(bracketColor, optionColor, upperColor, lowerColor string) Co
 				}
 			}
 		}
-		return output.Bytes()
+		// return output.Bytes()
 	}
 	return f
 }

+ 5 - 5
door/menu_test.go

@@ -23,7 +23,9 @@ func TestMenuRender(t *testing.T) {
 	// Fake menu line
 	var input string = "[X] BUGZ test"
 	var buff *bytes.Buffer = &bytes.Buffer{}
-	var got string = string(render(buff, []byte(input)))
+	// var got string = string(render(buff, []byte(input)))
+	render(buff, []byte(input))
+	var got string = buff.String()
 	var expected string = bracket + "[" + option + "X" + bracket + "]"
 	expected += lower + " " + upper + "BUGZ" + lower + " test"
 
@@ -153,17 +155,15 @@ func TestMenuConnection(t *testing.T) {
 	}}
 
 	// Use simple renders for testing
-	m.SelectedR = func(output *bytes.Buffer, text []byte) []byte {
+	m.SelectedR = func(output *bytes.Buffer, text []byte) {
 		output.Reset()
 		output.WriteString(ColorText("BLACK ON WHITE"))
 		output.Write(text)
-		return output.Bytes()
 	}
-	m.UnselectedR = func(output *bytes.Buffer, text []byte) []byte {
+	m.UnselectedR = func(output *bytes.Buffer, text []byte) {
 		output.Reset()
 		output.WriteString(ColorText("WHI ON BLA"))
 		output.Write(text)
-		return output.Bytes()
 	}
 
 	m.AddSelection("A", "ART")

+ 11 - 11
door/nomoresecrets.go

@@ -139,17 +139,17 @@ func NoMoreSecrets(output []byte, Door *Door, config *NoMoreSecretsConfig) {
 	rand.Seed(time.Now().UnixNano())
 
 	if Unicode {
-		var original []rune = []rune(string(output)) // original / master
-		var work []rune = make([]rune, 0)            // work copy we modify
-		var workpos int = 0                          // where we are in the work copy
-		var charpos []int                            // where characters are we can change
-		var chartime []int                           // character time / reveal timeout
-		var revealpos map[int]int                    // workpos to original position
-		var colormap map[int]string                  // index to color end position (workpos)
-		var coloridx []int                           // index of positions (map isn't ordered)
-		var lastcolor []int                          // LastColor tracking for keeping colors proper
-		var currentANSI string                       // ANSI Escape code we've extracted
-		var ANSIchar rune                            // Last character of the ANSI Escape code
+		var original []rune = []rune(string(output))       // original / master
+		var work []rune = make([]rune, 0, len(original))   // work copy we modify
+		var workpos int = 0                                // where we are in the work copy
+		var charpos []int = make([]int, 0, len(original))  // where characters are we can change
+		var chartime []int = make([]int, 0, len(original)) // character time / reveal timeout
+		var revealpos map[int]int                          // workpos to original position
+		var colormap map[int]string                        // index to color end position (workpos)
+		var coloridx []int                                 // index of positions (map isn't ordered)
+		var lastcolor []int                                // LastColor tracking for keeping colors proper
+		var currentANSI string                             // ANSI Escape code we've extracted
+		var ANSIchar rune                                  // Last character of the ANSI Escape code
 
 		colormap = make(map[int]string)
 		revealpos = make(map[int]int)

+ 13 - 6
door/panel.go

@@ -154,7 +154,8 @@ func (p *Panel) Output() []byte {
 	var row int = p.Y
 	if p.HasBorder() {
 		// Top line / border
-		p.output.Write(Goto(p.X, row))
+		// p.output.Write(Goto(p.X, row))
+		GotoW(p.X, row, p.output)
 		p.output.WriteString(p.BorderColor)
 		p.output.WriteString(box_style.Top_Left)
 		if p.Title != "" {
@@ -170,13 +171,15 @@ func (p *Panel) Output() []byte {
 	}
 
 	for _, line := range p.Lines {
-		p.output.Write(Goto(p.X, row))
+		// p.output.Write(Goto(p.X, row))
+		GotoW(p.X, row, p.output)
 		line.LineLength(line.Text)
 
 		var joined bool = false
 
 		if p.HasBorder() {
 			top := box_style.Top
+			// This only works in non-unicode mode
 			if bytes.Compare(line.Text.Bytes()[0:len(top)], []byte(top)) == 0 {
 				// Yes, this line needs to be joined...
 				p.output.WriteString(p.BorderColor + box_style.Middle_Left)
@@ -188,11 +191,13 @@ func (p *Panel) Output() []byte {
 
 		if !joined {
 			if p.HasBorder() {
-				p.output.WriteString(p.BorderColor + box_style.Side)
+				p.output.WriteString(p.BorderColor)
+				p.output.WriteString(box_style.Side)
 			}
 			p.output.Write(line.Output())
 			if style > 0 {
-				p.output.WriteString(p.BorderColor + box_style.Side)
+				p.output.WriteString(p.BorderColor)
+				p.output.WriteString(box_style.Side)
 			}
 		}
 
@@ -201,8 +206,10 @@ func (p *Panel) Output() []byte {
 
 	if p.HasBorder() {
 		// Bottom / border
-		p.output.Write(Goto(p.X, row))
-		p.output.WriteString(p.BorderColor + box_style.Bottom_Left)
+		// p.output.Write(Goto(p.X, row))
+		GotoW(p.X, row, p.output)
+		p.output.WriteString(p.BorderColor)
+		p.output.WriteString(box_style.Bottom_Left)
 		p.output.WriteString(strings.Repeat(box_style.Top, p.Width) + box_style.Bottom_Right)
 	}
 	return p.output.Bytes()

+ 11 - 2
door/panel_test.go

@@ -120,6 +120,8 @@ func BenchmarkPanel(b *testing.B) {
 	var l Line = Line{UpdateF: updater}
 	l.Update()
 	p.Lines = append(p.Lines, l)
+	p.Lines = append(p.Lines, l)
+	p.Lines = append(p.Lines, l)
 
 	for n := 0; n < b.N; n++ {
 		x = n
@@ -130,15 +132,22 @@ func BenchmarkPanelRender(b *testing.B) {
 	Unicode = false
 	var TestX int = 2
 	var TestY int = 2
-	var p Panel = Panel{X: TestX, Y: TestY, Width: 3, Style: DOUBLE_SINGLE}
+	var p Panel = Panel{X: TestX, Y: TestY, Width: 8, Style: DOUBLE_SINGLE}
 	var x int = 0
 	var updater Updater = func(u *bytes.Buffer) {
 		u.Reset()
-		fmt.Fprintf(u, "%3d", x)
+		// Give this text that it will have to do something with (MiXeD CaSe)
+		if x%2 == 0 {
+			fmt.Fprintf(u, "TeSt %3d", x)
+		} else {
+			fmt.Fprintf(u, "tEsT %3d", x)
+		}
 	}
 	var l Line = Line{UpdateF: updater, RenderF: RenderUppercase("BRI WHI ON BLUE", "GREEN")}
 	l.Update()
 	p.Lines = append(p.Lines, l)
+	p.Lines = append(p.Lines, l)
+	p.Lines = append(p.Lines, l)
 
 	for n := 0; n < b.N; n++ {
 		x = n

+ 4 - 0
door/what-leaks

@@ -0,0 +1,4 @@
+#!/bin/bash
+
+go build -gcflags=-m=2 . 2>&1
+

+ 67 - 17
door/wopr.go

@@ -4,7 +4,6 @@ import (
 	"bytes"
 	"fmt"
 	"log"
-	"strings"
 	"time"
 )
 
@@ -74,6 +73,13 @@ func (w *WOPR) Init(elapsed time.Time, remaining time.Time, color string) {
 	ehour.UpdateF = func(u *bytes.Buffer) {
 		var hours int = int(w.ElapsedD.Hours())
 		u.Reset()
+		/*
+			// This does not change anything in the benchmark.
+			u.WriteString("     ")
+			u.WriteByte(byte(int('0') + (hours%100)/10))
+			u.WriteByte(byte(int('0') + (hours%100)%10))
+			u.WriteString(" HRS     ")
+		*/
 		fmt.Fprintf(u, "     %02d HRS     ", hours%100)
 	}
 	ehour.Update()
@@ -95,13 +101,31 @@ func (w *WOPR) Init(elapsed time.Time, remaining time.Time, color string) {
 		u.Reset()
 		// Left to Right
 		if Unicode {
-			var buffer []rune = []rune(strings.Repeat(" ", 16))
-			buffer[w.Index] = '\u25a0'
-			u.WriteString(string(buffer))
+			for i := 0; i < 16; i++ {
+				if i == w.Index {
+					u.WriteRune('\u25a0')
+				} else {
+					u.WriteByte(' ')
+				}
+			}
+			/*
+				var buffer []rune = []rune(strings.Repeat(" ", 16))
+				buffer[w.Index] = '\u25a0'
+				u.WriteString(string(buffer))
+			*/
 		} else {
-			var buffer []byte = bytes.Repeat([]byte(" "), 16)
-			buffer[w.Index] = '\xfe'
-			u.WriteString(string(buffer))
+			for i := 0; i < 16; i++ {
+				if i == w.Index {
+					u.WriteRune('\xfe')
+				} else {
+					u.WriteByte(' ')
+				}
+			}
+			/*
+				var buffer []byte = bytes.Repeat([]byte(" "), 16)
+				buffer[w.Index] = '\xfe'
+				u.WriteString(string(buffer))
+			*/
 		}
 	}
 	eanimate.Update() // Text.Write(eanimate.UpdateF())
@@ -117,6 +141,12 @@ func (w *WOPR) Init(elapsed time.Time, remaining time.Time, color string) {
 	rhour.UpdateF = func(u *bytes.Buffer) {
 		var hours int = int(w.RemainingD.Hours())
 		u.Reset()
+		/*
+			u.WriteString("     ")
+			u.WriteByte(byte(int('0') + (hours%100)/10))
+			u.WriteByte(byte(int('0') + (hours%100)%10))
+			u.WriteString(" HRS     ")
+		*/
 		fmt.Fprintf(u, "     %02d HRS     ", hours%100)
 	}
 	rhour.Update() // Text.Write(rhour.UpdateF())
@@ -138,11 +168,29 @@ func (w *WOPR) Init(elapsed time.Time, remaining time.Time, color string) {
 		u.Reset()
 		// Left to Right
 		if Unicode {
-			var buffer []rune = []rune(strings.Repeat(" ", 16))
-			buffer[15-w.Index] = '\u25a0'
-			u.WriteString(string(buffer))
+			// Changing to for loop from buffers did 0.
+			for i := 0; i < 16; i++ {
+				if i == 15-w.Index {
+					u.WriteRune('\u25a0')
+				} else {
+					u.WriteByte(' ')
+				}
+			}
+			/*
+				var buffer []rune = []rune(strings.Repeat(" ", 16))
+				buffer[15-w.Index] = '\u25a0'
+				u.WriteString(string(buffer))
+			*/
 		} else {
-			var buffer []byte = bytes.Repeat([]byte{' '}, 16)
+			for i := 0; i < 16; i++ {
+				if i == 15-w.Index {
+					u.WriteByte('\xfe')
+				} else {
+					u.WriteByte(' ')
+				}
+			}
+
+			// var buffer []byte = bytes.Repeat([]byte{' '}, 16)
 			// No change here.
 			/*
 				var buffer [16]byte = [16]byte{' ', ' ', ' ', ' ',
@@ -151,9 +199,11 @@ func (w *WOPR) Init(elapsed time.Time, remaining time.Time, color string) {
 					' ', ' ', ' ', ' '}
 			*/
 			// bytes.Repeat([]byte(" "), 16)
-			buffer[15-w.Index] = '\xfe'
-			u.Write(buffer[:])
-			// u.WriteString(string(buffer))
+			/*
+				buffer[15-w.Index] = '\xfe'
+				u.Write(buffer[:])
+				// u.WriteString(string(buffer))
+			*/
 		}
 	}
 	ranimate.Update() // Text.Write(ranimate.UpdateF())
@@ -169,7 +219,6 @@ func (w *WOPR) Inc() {
 
 		w.RemainingD = time.Duration(w.RemainingD.Seconds()-1) * time.Second
 		w.ElapsedD = time.Duration(w.ElapsedD.Seconds()+1) * time.Second
-
 	}
 }
 
@@ -234,9 +283,10 @@ func (w *WOPR) Animate(d *Door) {
 			case <-w.Ticker.C:
 				w.ElapsedPanel.Update()
 				w.RemainingPanel.Update()
-				var output []byte = w.Output()
+				//var output []byte = w.Output()
 				if !d.Writer.IsClosed() {
-					d.Write(output)
+					// d.Write(output)
+					d.Write(w.Output())
 				} else {
 					w.Ticker.Stop()
 					return

+ 70 - 23
door/write.go

@@ -11,6 +11,8 @@ import (
 
 var FindANSIColor *regexp.Regexp
 
+const WRITE_DEBUG bool = false
+
 func init() {
 	FindANSIColor = regexp.MustCompile("\x1b\\[([0-9;]*)m")
 }
@@ -140,13 +142,17 @@ Have the Write manage itself.
 
 // For now, update is SavePos + output + RestorePos.
 // FUTURE: Access the screen struct to "save" Color+Attr+Pos.
+
 func (d *Door) Update(output []byte) {
 	// sync.Pool ?
-	var buffer bytes.Buffer
-	buffer.WriteString(SAVE_POS)
-	buffer.Write(output)
-	buffer.WriteString(RESTORE_POS)
-	d.Writer.Write(buffer.Bytes())
+	d.WriteA(SAVE_POS, output, RESTORE_POS)
+	/*
+		var buffer bytes.Buffer
+		buffer.WriteString(SAVE_POS)
+		buffer.Write(output)
+		buffer.WriteString(RESTORE_POS)
+		d.Writer.Write(buffer.Bytes())
+	*/
 	// d.Write(SAVE_POS + output + RESTORE_POS)
 }
 
@@ -172,6 +178,7 @@ func (d *Door) WriteA(a ...interface{}) {
 	}
 }
 
+// Is this byte the end of the ANSI CSI?
 func EndCSI(c byte) bool {
 	return (c >= 0x40) && (c <= 0x7f)
 }
@@ -189,7 +196,10 @@ func (d *Door) ANSIProcess() {
 			d.Writer.LastSavedColor = d.Writer.LastSavedColor[0:slen]
 		}
 		copy(d.Writer.LastSavedColor, d.LastColor)
-		log.Printf("ColorSave: %d\n", d.Writer.LastSavedColor)
+		if WRITE_DEBUG {
+			log.Printf("ColorSave: %d\n", d.Writer.LastSavedColor)
+		}
+
 	case 'u':
 		// Restore Color
 		var slen int = len(d.Writer.LastSavedColor)
@@ -199,7 +209,9 @@ func (d *Door) ANSIProcess() {
 			d.LastColor = d.LastColor[0:slen]
 		}
 		copy(d.LastColor, d.Writer.LastSavedColor)
-		log.Printf("ColorRestore: %d\n", d.LastColor)
+		if WRITE_DEBUG {
+			log.Printf("ColorRestore: %d\n", d.LastColor)
+		}
 	case 'm':
 		// Process color code
 		var color [5]int
@@ -231,7 +243,9 @@ func (d *Door) ANSIProcess() {
 			cpos++
 		}
 
-		log.Printf("color: [%d]", color[0:cpos])
+		if WRITE_DEBUG {
+			log.Printf("color: [%d]", color[0:cpos])
+		}
 		var newColor = ParseColorArray(d.LastColor)
 		for _, c := range color[0:cpos] {
 			switch c {
@@ -266,14 +280,20 @@ func (d *Door) ANSIProcess() {
 		if newColor.BG != -1 {
 			d.LastColor = append(d.LastColor, newColor.BG+40)
 		}
-		log.Printf("LastColor: [%d]", d.LastColor)
+		if WRITE_DEBUG {
+			log.Printf("LastColor: [%d]", d.LastColor)
+		}
 	}
 
-	var outputByte [20]byte
-	var output []byte = outputByte[0:0]
+	if WRITE_DEBUG {
+		var outputByte [20]byte
+		var output []byte = outputByte[0:0]
+
+		output = fmt.Appendf(output, "ANSI: [%q]\n", d.ansiCode)
+		log.Printf("%s", output)
+	}
 
-	output = fmt.Appendf(output, "ANSI: [%q]\n", d.ansiCode)
-	log.Printf("%s", output)
+	// Reset
 	d.ansiCode = d.ansiCode[0:0]
 	d.ansiCSI = false
 }
@@ -326,17 +346,36 @@ func (d *Door) ANSIScan(output []byte) {
 	}
 }
 
+func (d *Door) NewLinesTranslate(output []byte) []byte {
+	var pos, nextpos int
+	if d.nlBuffer == nil {
+		d.nlBuffer = &bytes.Buffer{}
+	}
+	d.nlBuffer.Reset()
+
+	for pos != -1 {
+		nextpos = bytes.Index(output[pos:], []byte{'\n'})
+		if nextpos != -1 {
+			nextpos += pos
+			// Something to do
+			d.nlBuffer.Write(output[pos:nextpos])
+			nextpos++
+			pos = nextpos
+			d.nlBuffer.Write([]byte("\r\n"))
+		} else {
+			d.nlBuffer.Write(output[pos:])
+			pos = nextpos // -1
+		}
+	}
+	// log.Printf(">> %q\n", ow.nlBuffer.Bytes())
+	return d.nlBuffer.Bytes()
+}
+
 func (d *Door) LockedWrite(output []byte) {
 	if d.writeMutex.TryLock() {
 		log.Panic("LockedWrite: mutex was NOT locked.")
 	}
 
-	// var lastColorBytes [32]byte
-	var lastColor string // []byte = lastColorBytes[0:0]
-	/*
-		if (bytes.HasPrefix(output, []byte(SavePos))) &&
-			(bytes.HasSuffix(output, []byte(RestorePos))) {
-	*/
 	/*
 		if bytes.HasSuffix(output, []byte(RestorePos)) {
 			// Write the current color
@@ -346,16 +385,24 @@ func (d *Door) LockedWrite(output []byte) {
 		}
 	*/
 
-	log.Printf(">> %q\n", output)
+	if true {
+		output = d.NewLinesTranslate(output)
+	}
+
+	if WRITE_DEBUG {
+		log.Printf(">> %q\n", output)
+	}
 	d.Writer.Write(output)
 	d.ANSIScan(output)
 
 	if bytes.HasSuffix(output, []byte(RestorePos)) {
-		lastColor = Color(d.LastColor)
+		lastColor := Color(d.LastColor)
 		//fmt.Append(lastColor, []byte(Color(d.LastColor)))
-		log.Printf("Restore LastColor: %d => %q", d.LastColor, lastColor)
+		if WRITE_DEBUG {
+			log.Printf("Restore LastColor: %d => %q", d.LastColor, lastColor)
+		}
 		d.Writer.Write([]byte(lastColor))
-		d.ANSIScan([]byte(lastColor))
+		//d.ANSIScan([]byte(lastColor))
 	}
 	/*
 		if len(lastColor) != 0 {

+ 2 - 199
door/write_linux.go

@@ -1,7 +1,6 @@
 package door
 
 import (
-	"bytes"
 	"syscall"
 )
 
@@ -16,156 +15,13 @@ type OSWriter struct {
 func (ow *OSWriter) Init(d *Door) {
 	ow.Closed = false
 	ow.Handle = d.Config.Comm_handle
-	ow.TranslateNL = true // Yes, translate NL => CR+NL
-	ow.nlBuffer = &bytes.Buffer{}
-	// ow.restoreBuff = &bytes.Buffer{}
 	ow.ansiCode = make([]byte, 0, 32)
 }
 
-func (ow *OSWriter) CP437toUnicode(output []byte) []byte {
-	if ow.uniBuffer == nil {
-		ow.uniBuffer = &bytes.Buffer{}
-	}
-	for _, ch := range output {
-		// Perform translation here
-		ow.uniBuffer.WriteByte(ch)
-	}
-
-	return ow.uniBuffer.Bytes()
-}
-
-func (ow *OSWriter) NewLines(output []byte) []byte {
-	var pos, nextpos int
-	ow.nlBuffer.Reset()
-
-	for pos != -1 {
-		nextpos = bytes.Index(output[pos:], []byte{'\n'})
-		if nextpos != -1 {
-			nextpos += pos
-			// Something to do
-			ow.nlBuffer.Write(output[pos:nextpos])
-			nextpos++
-			pos = nextpos
-			ow.nlBuffer.Write([]byte("\r\n"))
-		} else {
-			ow.nlBuffer.Write(output[pos:])
-			pos = nextpos // -1
-		}
-	}
-	// log.Printf(">> %q\n", ow.nlBuffer.Bytes())
-	return ow.nlBuffer.Bytes()
-}
-
-/*
-// Read byte slice, add color restore (after restorepos).
-func (ow *OSWriter) HandleRestoreColor(output []byte) []byte {
-	// Parse out ANSI codes:
-	// Look for m (color), s (savepos), u (restorepos)
-	// Possibly H (goto)
-	ow.restoreBuff.Reset()
-	var changed bool
-	var pos, nextpos int
-	var outputLen int = len(output)
-
-	// Handle the case where the CSI code continues...
-	if ow.ansiCSI {
-		// Continue processing CSI code
-		var c byte
-		for pos != outputLen {
-			c = output[pos]
-			pos++
-			ow.ansiCode = append(ow.ansiCode, c)
-			if (c >= 0x40) && (c <= 0x7f) {
-				// Found it
-				ow.ansiCSI = false
-				break
-			}
-		}
-		ow.restoreBuff.Write(output[0:pos])
-		pos++
-		if !ow.ansiCSI {
-			log.Printf("CSI: %c [%q]\n", c, ow.ansiCode)
-			// Process this code (if of interest)
-			// Reset the buffer
-			ow.ansiCode = ow.ansiCode[0:0]
-		}
-	}
-
-	// To verify that we're keeping the buffer in the proper state
-	changed = true
-
-	log.Printf("raw: [%q]\n", output)
-
-	for pos != -1 {
-		nextpos = bytes.Index(output[pos:], []byte{'\x1b'})
-		if nextpos != -1 {
-			nextpos += pos
-			// Output everything up to the \x1b
-			ow.restoreBuff.Write(output[pos:nextpos])
-
-			// Found ESC
-			if output[nextpos+1] == '[' {
-				// Found CSI start
-				// https://en.wikipedia.org/wiki/ANSI_escape_code#CSI_(Control_Sequence_Introducer)_sequences
-				ow.ansiCSI = true
-				// While not the end, and not end of CSI, append to ansiCode
-				var csiPos int = nextpos + 2
-				var c byte
-				for csiPos != outputLen {
-					c = output[csiPos]
-					csiPos++
-					ow.ansiCode = append(ow.ansiCode, c)
-					if (c >= 0x40) && (c <= 0x7f) {
-						// FOUND IT!
-						ow.ansiCSI = false
-						break
-					}
-				}
-
-				// Write out the CSI code (or what we have of it)
-				ow.restoreBuff.Write(output[nextpos:csiPos])
-				nextpos = csiPos
-
-				if !ow.ansiCSI {
-					log.Printf("CSI: %c [%q]\n", c, ow.ansiCode)
-					// Process this code (if of interest)
-					// Reset the buffer
-					ow.ansiCode = ow.ansiCode[0:0]
-				}
-			} else {
-				ow.restoreBuff.WriteByte('\x1b')
-				nextpos++
-			}
-
-			pos = nextpos
-		} else {
-			ow.restoreBuff.Write(output[pos:])
-			pos = nextpos // -1 (end)
-		}
-	}
-
-	// log.Printf("<< [%q]\n>> [%q]\n", output, ow.restoreBuff.Bytes())
-
-	if changed {
-		return ow.restoreBuff.Bytes()
-	} else {
-		return output
-	}
-}
-*/
-
 // The low-lever writer function
 func (ow *OSWriter) OSWrite(buffer []byte) (int, error) {
-	var buff []byte = buffer
-
-	// Filters (!)
-
-	if ow.TranslateNL {
-		buff = ow.NewLines(buff)
-	}
-
-	n, err := syscall.Write(ow.Handle, buff)
-	if (err != nil) || (n != len(buff)) {
+	n, err := syscall.Write(ow.Handle, buffer)
+	if (err != nil) || (n != len(buffer)) {
 		if !ow.Closed {
 			ow.Closed = true
 			// Don't need to close reader, it will close itself.
@@ -191,56 +47,3 @@ func (ow *OSWriter) Stop() {
 func (ow *OSWriter) IsClosed() bool {
 	return ow.Closed
 }
-
-// deprecated
-/*
-func (d *Door) OSWrite(buffer []byte) {
-	if d.WriterClosed {
-		return
-	}
-
-	if DEBUG_DOOR {
-		if d.writerMutex.TryLock() {
-			log.Panicln("OSWrite: writerMutex was NOT locked.")
-		}
-	}
-
-
-	n, err := syscall.Write(d.Config.Comm_handle, buffer)
-	if (err != nil) || (n != len(buffer)) {
-		if !d.WriterClosed {
-			d.WriterClosed = true
-		}
-	}
-}
-*/
-
-// Deprecated
-// This is the writer go routine.
-
-/*
-// The parts of interest that I'm holding off on implementing for right now.
-
-	if strings.HasSuffix(output, RestorePos) {
-		output += Color(d.LastColor)
-
-	} else {
-		d.UpdateLastColor(output, &d.LastColor)
-	}
-
-	buffer := []byte(output)
-
-	// n, err := low_write(handle, buffer)
-
-	n, err := syscall.Write(handle, buffer)
-	if (err != nil) || (n != len(buffer)) {
-		log.Println("closeChannel")
-		Closed = true
-		d.writerMutex.Lock()
-		if !d.WriterClosed {
-			d.WriterClosed = true
-			// close(d.writerChannel)
-		}
-		d.writerMutex.Unlock()
-	}
-*/