Files
qb/enc/insdelta.go
2026-06-10 06:18:45 +03:00

527 lines
13 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package enc
import (
"io"
"log"
"math"
bin "gordenko.dev/dima/bin/little"
"gordenko.dev/dima/qb"
)
type InstantDeltaCompressor struct {
buf []byte
coef float64
pos int
baseValue float64
lastDelta int64
h byte
state *InstantDeltaCapturedState
}
func NewInstantDeltaCompressor(fracDigits byte, buf []byte, payloadSize int) *InstantDeltaCompressor {
var coef float64 = 1
if fracDigits > 0 {
coef = math.Pow(10, float64(fracDigits))
}
s := &InstantDeltaCompressor{
buf: buf,
coef: coef,
}
if payloadSize > 0 {
s.pos = payloadSize - 1 // завжди показує на h
// base value на початку
u64, _, err := bin.GetVarUint64(s.buf)
if err != nil {
log.Fatalf("bug: get base value: %s", err)
}
s.baseValue = float64(u64) / s.coef
s.h = s.buf[s.pos]
s.lastDelta, _, err = bin.ReverseGetVarInt64(s.buf[:s.pos-1])
if err != nil {
log.Fatalf("bug: get last delta: %s", err)
}
}
return s
}
// func (s *InstantDeltaCompressor) RestoreState() {
// }
// func (s *InstantDeltaCompressor) Size() int {
// return s.pos
// }
func (s *InstantDeltaCompressor) Evaluate(value float64) (compressionWay int, requiredSpace int) {
tmp := (value - s.baseValue) * s.coef
if tmp > 0 {
tmp += eps
} else {
tmp -= eps
}
delta := int64(tmp)
if s.pos > 0 {
if s.h < 128 {
// run
if delta == s.lastDelta && s.h < 127 {
compressionWay = incrementRun
requiredSpace += bin.CountVarInt64(s.lastDelta) + // current delta
hSize
} else {
compressionWay = endSeries
requiredSpace += bin.CountVarInt64(s.lastDelta) + // previous delta
hSize + // h - end of run
bin.CountVarInt64(delta) + // new literal
hSize // new h
}
} else {
// literal
if delta != s.lastDelta {
if s.h < 255 {
compressionWay = incrementLiteral
requiredSpace += bin.CountVarInt64(s.lastDelta) + // previous delta
bin.CountVarInt64(delta) + // new delta
hSize
} else {
compressionWay = endSeries
requiredSpace += bin.CountVarInt64(s.lastDelta) + // previous delta
hSize + // h - end of literal
bin.CountVarInt64(delta) + // new literal
hSize // new h
}
} else {
compressionWay = startRun
if s.h > 128 {
requiredSpace += hSize // h - end of literal
}
requiredSpace += bin.CountVarInt64(delta) + // new delta
hSize // new h
}
}
} else {
// encode base value
compressionWay = addBaseValue
requiredSpace = bin.CountVarInt64(int64(value*s.coef)) + // base value
bin.CountVarInt64(delta) + // new delta
+hSize // new h
}
return
}
func (s *InstantDeltaCompressor) Compress(compressionWay int, value float64) {
tmp := (value - s.baseValue) * s.coef
if tmp > 0 {
tmp += eps
} else {
tmp -= eps
}
delta := int64(tmp)
switch compressionWay {
case incrementRun:
s.h++
case incrementLiteral:
// write previous delta and increment counter
n, _ := bin.ReversePutVarInt64(s.buf[s.pos:], s.lastDelta)
s.pos += n
s.lastDelta = delta
s.h++
case endSeries:
n, _ := bin.ReversePutVarInt64(s.buf[s.pos:], s.lastDelta)
s.pos += n
// write h - end of run
s.buf[s.pos] = s.h
s.pos++
// start new literal (length=1)
s.lastDelta = delta
s.h = 128
case startRun:
if s.h > 128 {
// write h - end of literal (because length > 1)
s.h--
s.buf[s.pos] = s.h
s.pos++
}
// start new run (length=2)
s.h = 0
case addBaseValue:
// start new literal (length=1)
n, _ := bin.PutVarInt64(s.buf[s.pos:], int64(value*s.coef))
s.pos += n
s.baseValue = value
// start new literal (length=1)
s.lastDelta = 0
s.h = 128
}
}
func (s *InstantDeltaCompressor) DeleteLast() {}
type InstantDeltaCapturedState struct {
H byte
LastDelta int64
Payload []byte
}
func (s *InstantDeltaCompressor) CaptureState() {
if s.state != nil {
qb.Abort(qb.RepeatableLock, nil)
}
// позиція посувається вліво, отже може перескочити на попередній chunk
s.state = &InstantDeltaCapturedState{
H: s.h,
LastDelta: s.lastDelta,
Payload: s.buf[:s.pos],
}
}
// fix - повернути в Pool буфери
func (s *InstantDeltaCompressor) ForgetCapturedState() {
s.state = nil
}
func (s *InstantDeltaCompressor) Offset() int {
// if s.state != nil {
// return s.state.Pos
// }
return 0
}
func (s *InstantDeltaCompressor) Size() int {
if s.state == nil {
return s.pos
} else {
return bin.CountVarInt64(s.state.LastDelta) + hSize + len(s.state.Payload)
}
}
// Snapshot - для створення снапшота.
func (s *InstantDeltaCompressor) Payload() []byte {
if s.state == nil {
return s.buf[:s.pos]
} else {
return s.state.Payload
}
}
func (s *InstantDeltaCompressor) WritePayloadTo(w io.Writer) (err error) {
if s.state == nil {
_, err = w.Write(s.buf[:s.pos])
return
} else {
_, err = w.Write(s.state.Payload)
if err != nil {
return
}
_, err = bin.WriteVarInt64(w, s.state.LastDelta)
if err != nil {
return
}
_, err = w.Write([]byte{
s.state.H,
})
return
}
}
// func (s *InstantDeltaCompressor) encodeTail(lastDelta int64, h byte) []byte {
// tail := make([]byte, 10) // max var uint64 + h
// n, _ := bin.PutVarInt64(tail, lastDelta)
// tail[n] = h
// return tail[:n+1]
// }
func (s *InstantDeltaCompressor) Rotate(newbuf []byte) {
// УВАГА!
// state не чіпаємо
s.buf = newbuf
s.pos = 0
s.baseValue = 0
s.lastDelta = 0
s.h = 0
}
func (s *InstantDeltaCompressor) LastValue() float64 {
if s.state == nil {
return s.baseValue + float64(s.lastDelta)*s.coef
} else {
return s.baseValue + float64(s.state.LastDelta)*s.coef
}
}
func (s *InstantDeltaCompressor) CreateDecompressor() qb.ValueDecompressor {
var (
h = s.h
lastDelta = s.lastDelta
payload = s.buf[:s.pos]
)
if s.state != nil {
h = s.state.H
lastDelta = s.state.LastDelta
payload = s.state.Payload
}
return NewInstantDeltaDecompressorFromState(InstantDeltaDecompressorFromStateOptions{
Coef: s.coef,
H: h,
LastDelta: lastDelta,
Payload: payload,
})
}
func (s *InstantDeltaCompressor) Chunks() []byte {
return s.buf
}
// DECOMPRESSOR
type InstantDeltaDecompressor struct {
buf []byte
coef float64
pos int
bound int
baseValue float64
lastValue float64
isRun bool
pending int
done bool
}
func NewInstantDeltaDecompressor(buf []byte, fracDigits byte) *InstantDeltaDecompressor {
var coef float64 = 1
if fracDigits > 0 {
coef = math.Pow(10, float64(fracDigits))
}
i64, n, err := bin.GetVarInt64(buf)
if err != nil {
log.Fatalf("bug: get base value: %s", err)
}
//fmt.Printf("baseValue: %.2f, bound: %d, pos: %d\n", float64(u64)/coef, n, size)
s := &InstantDeltaDecompressor{
buf: buf,
coef: coef,
pos: len(buf), // first free
baseValue: float64(i64) / coef,
bound: n,
}
s.readHeader()
s.readValue()
return s
}
type InstantDeltaDecompressorFromStateOptions struct {
Coef float64
H byte
LastDelta int64
Payload []byte
}
func NewInstantDeltaDecompressorFromState(opt InstantDeltaDecompressorFromStateOptions) *InstantDeltaDecompressor {
i64, n, err := bin.GetVarInt64(opt.Payload)
if err != nil {
log.Fatalf("bug: get base value: %s", err)
}
//fmt.Printf("baseValue: %.2f, bound: %d, pos: %d\n", float64(u64)/coef, n, size)
s := &InstantDeltaDecompressor{
buf: opt.Payload,
coef: opt.Coef,
pos: len(opt.Payload), // first free
baseValue: float64(i64) / opt.Coef,
bound: n,
}
s.lastValue = s.baseValue + float64(opt.LastDelta)/s.coef
s.decodeHeaderByte(opt.H)
//fmt.Printf("restore from bound: isRun=%t, pending=%d\n", s.isRun, s.pending)
return s
}
func (s *InstantDeltaDecompressor) NextValue() (value float64, done bool) {
//fmt.Printf("NextValue(): bound: %d, pos: %d, pending: %d\n", s.bound, s.pos, s.pending)
if s.done {
return 0, true
}
// метод працює як do while - спочатку значення, а потім перевірка умови
value = s.lastValue
s.pending--
if s.pending > 0 {
// якщо в серії залишаються елементи
if !s.isRun {
s.readValue()
}
} else if s.pos > s.bound {
// в серії більше немає елементів, отже перевіряє чи є ще дані в буфері.
// дані є - читаю заголовок наступної серії
s.readHeader()
s.readValue()
} else {
s.done = true
}
return value, false
}
func (s *InstantDeltaDecompressor) readHeader() {
s.pos--
h := s.buf[s.pos]
s.decodeHeaderByte(h)
// fmt.Println("h:", h)
// fmt.Println("isRun:", s.isRun)
// fmt.Println("pending:", s.pending)
}
func (s *InstantDeltaDecompressor) readValue() {
i64, n, err := bin.ReverseGetVarInt64(s.buf[:s.pos])
if err != nil {
log.Fatalln(err)
}
// fmt.Println()
// fmt.Println("read from pos:", s.pos)
// fmt.Println("read delta:", u64)
// fmt.Println("read delta n:", n)
// fmt.Println()
s.pos -= n
s.lastValue = s.baseValue + float64(i64)/s.coef
}
func (s *InstantDeltaDecompressor) decodeHeaderByte(h byte) {
s.isRun = h < 128
if s.isRun {
s.pending = int(h&127) + 2
} else {
s.pending = int(h&127) + 1
}
}
// DECOMPRESSOR
// type ReverseInstantDeltaDecompressor struct {
// buf *conbuf.ContinuousBuffer
// pos int
// bound int
// baseValue float64
// lastValue float64
// coef float64
// isRun bool
// pending int
// }
// func NewReverseInstantDeltaDecompressor(buf *conbuf.ContinuousBuffer, size int, fracDigits byte) *ReverseInstantDeltaDecompressor {
// var coef float64 = 1
// if fracDigits > 0 {
// coef = math.Pow(10, float64(fracDigits))
// }
// u64, n, err := buf.GetVarInt64(0)
// if err != nil {
// log.Fatalf("bug: get base value: %s", err)
// }
// //fmt.Printf("baseValue: %.2f, bound: %d, pos: %d\n", float64(u64)/coef, n, size)
// return &ReverseInstantDeltaDecompressor{
// buf: buf,
// coef: coef,
// pos: size - 1, // last elem
// baseValue: float64(u64) / coef,
// bound: n,
// }
// }
// func (s *ReverseInstantDeltaDecompressor) NextValue() (value float64, done bool) {
// //fmt.Printf("NextValue(): bound: %d, pos: %d, pending: %d\n", s.bound, s.pos, s.pending)
// if s.pending > 0 {
// // якщо в серії залишаються елементи
// if !s.isRun {
// s.readValue()
// }
// s.pending--
// return s.lastValue, false
// }
// if s.pos > s.bound {
// // читаю заголовок наступної серії
// s.readHeader()
// s.readValue()
// s.pending--
// return s.lastValue, false
// }
// // серія завершена - перевіряю чи є ще серії
// return 0, true
// }
// func (s *ReverseInstantDeltaDecompressor) readHeader() {
// h := s.buf.GetByte(s.pos)
// s.pos--
// s.isRun = h < 128
// if s.isRun {
// s.pending = int(h&127) + 2
// } else {
// s.pending = int(h&127) + 1
// }
// // fmt.Println("h:", h)
// // fmt.Println("isRun:", s.isRun)
// // fmt.Println("pending:", s.pending)
// }
// func (s *ReverseInstantDeltaDecompressor) readValue() {
// i64, n, err := s.buf.ReverseGetVarInt64(s.pos)
// if err != nil {
// log.Fatalln(err)
// }
// // fmt.Println()
// // fmt.Println("read from pos:", s.pos)
// // fmt.Println("read delta:", u64)
// // fmt.Println("read delta n:", n)
// // fmt.Println()
// s.pos -= n
// s.lastValue = s.baseValue + float64(i64)/s.coef
// }
// func (s *InstantDeltaCompressor) Append(value float64) {
// if s.pos == 0 {
// // base value
// n, _ := bin.PutVarInt64(s.buf[s.pos:], int64(value*s.coef))
// s.pos += n
// s.baseValue = value
// s.appendNewLiteral(0)
// } else {
// tmp := (value - s.baseValue) * s.coef
// if tmp > 0 {
// tmp += eps
// } else {
// tmp -= eps
// }
// delta := int64(tmp)
// if delta == s.lastDelta {
// if s.h < 128 {
// // run блок - отже треба збільшити лічильник
// if s.h < 127 {
// // increase counter
// s.h++
// s.buf[s.pos-1] = s.h
// } else {
// // не можу збільшити - буде переповнення. Додаю новий literal блок
// // counter overflow
// // fix encode delta
// s.appendNewLiteral(delta)
// }
// } else {
// // literal блок.
// // Якщо в ньому лише одне значення - перетворюю його на run блок.
// // Інакше забираю останнє значення із literal блока і додаю новий run блок.
// q := s.h & 127
// if q == 0 { // 1 кодується як 0
// s.convertLiteralToRun()
// } else {
// s.convertLastFromLiteralToRun()
// }
// }
// } else {
// if s.h < 127 {
// // end of run
// s.appendNewLiteral(delta)
// } else {
// if s.h < 255 {
// // encode value from pos - 1, then append h byte
// s.appendDeltaToLiteral(delta)
// } else {
// // overflowed - encode new
// s.appendNewLiteral(delta)
// }
// }
// }
// }
// }