Files
lneto/internal/zoneprinter.go

124 lines
2.9 KiB
Go

package internal
import (
"errors"
"fmt"
"strconv"
)
type ZonePrinter struct {
zbuf []BufferZone
aux []byte
}
type BufferZone struct {
Name string
Start, End int
}
func (bp *ZonePrinter) AppendPrintZones(dst []byte, bufSize int, zones ...BufferZone) ([]byte, error) {
n := bufSize
if n == 0 {
return dst, errors.New("empty buffer")
}
if len(bp.zbuf) != 0 {
panic("race condition detected: zbuf not zero lengthed- ZonePrinter not to be used concurrently")
}
// Zeroed zone buffer. Will be used to detect collisions.
// TODO(soypat): Really wasteful, there's an obvious way to just have len(zones)+1 zbuf, but I've already spent too much time here. This also makes collision detection really easy so whatever.
bp.zbuf = append(bp.zbuf[:0], make([]BufferZone, n)...)
labels := bp.zbuf
// helper to paint an interval on the ring
paint := func(z BufferZone) error {
if z.End == 0 { // 0 == empty
return nil
}
if z.Start < z.End {
for i := z.Start; i < z.End; i++ {
if labels[i].Name != "" {
return fmt.Errorf("paint collision: %q/%q @%d size=%d", labels[i].Name, z.Name, i, bufSize)
}
labels[i].Name = z.Name
}
} else { // wrap
for i := z.Start; i < n; i++ {
if labels[i].Name != "" {
return fmt.Errorf("wrap(start) paint collision: %q/%q @%d size=%d", labels[i].Name, z.Name, i, bufSize)
}
labels[i].Name = z.Name
}
for i := 0; i < z.End; i++ {
if labels[i].Name != "" {
return fmt.Errorf("wrap(end) paint collision: %q/%q @%d size=%d", labels[i].Name, z.Name, i, bufSize)
}
labels[i].Name = z.Name
}
}
return nil
}
// paint
for _, zone := range zones {
err := paint(zone)
if err != nil {
return dst, err
}
}
// unpainted sections are free.
for i := range labels {
if labels[i].Name == "" {
labels[i].Name = "free"
}
}
// Compress to segments.
icurrent := 0
for i := 1; i < n; i++ {
current := labels[icurrent]
next := labels[i]
if current.Name != next.Name {
labels[icurrent].End = i
icurrent++
labels[icurrent].Start = i
labels[icurrent].Name = next.Name
}
}
segs := labels[:icurrent+1]
segs[icurrent].End = n
var l1 []byte = dst
var l2 []byte = bp.aux[:0]
for _, s := range segs {
l2start := len(l2)
if s.Name == "free" {
l2 = append(l2, "| "...)
} else {
l2 = append(l2, "|--"...)
}
l2 = append(l2, s.Name...)
l2 = append(l2, '(')
l2 = strconv.AppendInt(l2, int64(s.End-s.Start), 10)
if s.Name == "free" {
l2 = append(l2, ") "...)
} else {
l2 = append(l2, ")--"...)
}
fraglen := len(l2) - l2start
l1start := len(l1)
l1 = strconv.AppendInt(l1, int64(s.Start), 10)
paddingNeeded := fraglen - (len(l1) - l1start)
for range paddingNeeded {
l1 = append(l1, ' ')
}
}
l1 = strconv.AppendInt(l1, int64(bufSize), 10)
l1 = append(l1, '\n')
l2 = append(l2, '|')
l1 = append(l1, l2...)
bp.zbuf = bp.zbuf[:0] // zero out, used to detect concurrent usage of ZonePrinter.
return l1, nil
}