package chunkenc import ( "fmt" "log" "math" bin "gordenko.dev/dima/bin/little" "gordenko.dev/dima/qb" ) // COMPRESSOR REVERSE /* Формат: base value (var u64) delta (var u64) q (qty of previous deltas; msb=0 - series, msb=1 - non series, low 7 bits - qty 1..128) delta delta delta q Дельта рахується від base value. Декодування у зворотньому порядку. Після base value слідують run або literal блоки. Run блок - це delta + header byte в кінці. Literal блок - це від одної до N дельт + header byte в кінці. Спочатку створюється literal блок. Якщо для останної дельти додається дублікат, literal блок модифікується - лічильник зменшується до 1. А остання дельта переміщюється в новий run блок. Причому лічильник 0 - означає 2 елементи. Приклад: До: v1 v2 v3 h-byte(literal, 3) <- v3 Після: v1 v2 h-byte(literal, 2) v3 h-byte(run, 2) */ type CumulativeDeltaCompressor struct { buf []byte coef float64 pos int baseValue float64 lastDelta uint64 lastDeltaSize int h byte state *CumulativeDeltaBound } func NewCumulativeDeltaCompressor(buf []byte, size int, fracDigits byte) *CumulativeDeltaCompressor { var coef float64 = 1 if fracDigits > 0 { coef = math.Pow(10, float64(fracDigits)) } s := &CumulativeDeltaCompressor{ buf: buf, pos: size, // перший вільний байт coef: coef, } if size > 0 { 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-1] s.lastDelta, s.lastDeltaSize, err = bin.ReverseGetVarUint64(s.buf[:s.pos-2]) if err != nil { log.Fatalf("bug: get last delta: %s", err) } } return s } func (s *CumulativeDeltaCompressor) Size() int { return s.pos } func (s *CumulativeDeltaCompressor) CalcRequiredSpace(value float64) int { return 0 } func (s *CumulativeDeltaCompressor) Append(value float64) { if s.pos == 0 { // base value n, _ := bin.PutVarUint64(s.buf[s.pos:], uint64(value*s.coef)) s.pos += n s.baseValue = value s.appendNewLiteral(0) } else { delta := uint64((value-s.baseValue)*s.coef + eps) 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 s.appendNewLiteral(delta) } } else { // literal блок. // Якщо в ньому лише одне значення - перетворюю його на run блок. // Інакше забираю останнє значення із literal блока і додаю новий run блок. q := s.h & 127 if q == 0 { // 1 кодується як 0 s.convertLiteralToRun() } else { // забираю останнє значення із literal блоку щоб зробити run блок s.convertLastFromLiteralToRun() } } } else { if s.h < 127 { // fix 128? // 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) } } } } } func (s *CumulativeDeltaCompressor) convertLastFromLiteralToRun() { // Зменшую кількість елементів в literal блоці s.h-- s.pos -= 1 + s.lastDeltaSize s.buf[s.pos] = s.h // закриваю literal блок s.pos++ s.lastDeltaSize, _ = bin.ReversePutVarUint64(s.buf[s.pos:], s.lastDelta) s.pos += s.lastDeltaSize s.h = 0 // run блок, довжини 2 s.buf[s.pos] = s.h s.pos++ } func (s *CumulativeDeltaCompressor) convertLiteralToRun() { // Знімаю flagLiteral, а лічильник 0 дорівнює 2 елементам в серії. s.h = 0 s.buf[s.pos-1] = s.h } func (s *CumulativeDeltaCompressor) appendDeltaToLiteral(delta uint64) { s.h++ // збільшую к-сть дельт s.lastDelta = delta s.pos-- s.lastDeltaSize, _ = bin.ReversePutVarUint64(s.buf[s.pos:], delta) s.pos += s.lastDeltaSize s.buf[s.pos] = s.h s.pos++ } func (s *CumulativeDeltaCompressor) appendNewLiteral(delta uint64) { s.h = flagLiteral s.lastDelta = delta s.lastDeltaSize, _ = bin.ReversePutVarUint64(s.buf[s.pos:], delta) s.pos += s.lastDeltaSize s.buf[s.pos] = flagLiteral // literal, length = 1 s.pos++ } func (s *CumulativeDeltaCompressor) DeleteLast() { } type CumulativeDeltaBound struct { Pos int H byte LastDelta uint64 Chunks []byte } // delta h func (s *CumulativeDeltaCompressor) Lock() { if s.state != nil { qb.Abort(qb.RepeatableLock, nil) } // позиція посувається вліво, отже може перескочити на попередній chunk pos := s.pos - 1 - s.lastDeltaSize s.state = &CumulativeDeltaBound{ Pos: pos, H: s.h, LastDelta: s.lastDelta, Chunks: s.buf[:s.pos], // fix check ? } } // fix - повернути в Pool буфери func (s *CumulativeDeltaCompressor) Unlock() { s.state = nil } func (s *CumulativeDeltaCompressor) Offset() int { if s.state != nil { return s.state.Pos } return 0 } func (s *CumulativeDeltaCompressor) Snapshot() ([]byte, int) { // if s.state == nil { // return s.buf, s.Size() // } // // ВАЖЛИВО! // // Треба відтворити стан останнього чанка // var ( // pos = s.state.Pos // chunk = make([]byte, conbuf.ChunkSize) // lastChunkIdx = len(s.state.Chunks) - 1 // qtyToCopy = pos % conbuf.ChunkSize // ) // copy(chunk, s.state.Chunks[lastChunkIdx][:qtyToCopy]) // chunks := append(s.state.Chunks[:lastChunkIdx], chunk) // buf := conbuf.New(chunks) // pos += buf.ReversePutVarUint64(pos, s.state.LastDelta) // buf.SetByte(pos, s.state.H) // pos++ // return chunks, pos return nil, 0 } func (s *CumulativeDeltaCompressor) CreateDecompressor(fracDigits byte) qb.ValueDecompressor { if s.state == nil { d := NewCumulativeDeltaDecompressor(s.buf, s.Size(), fracDigits) d.RestoreFromEnd() return d } d := NewCumulativeDeltaDecompressor(s.buf, s.Size(), fracDigits) d.RestoreFromBound(*s.state) return d } func (s *CumulativeDeltaCompressor) Renew() { // УВАГА! // state не чіпаємо s.buf = make([]byte, minBufferSize) s.pos = 0 // s.baseValue = 0 s.lastDelta = 0 s.lastDeltaSize = 0 s.h = 0 } func (s *CumulativeDeltaCompressor) Chunks() []byte { return s.buf } // DECOMPRESSOR type CumulativeDeltaDecompressor struct { buf []byte coef float64 pos int bound int baseValue float64 lastValue float64 isRun bool pending int done bool } func NewCumulativeDeltaDecompressor(buf []byte, size int, fracDigits byte) *CumulativeDeltaDecompressor { var coef float64 = 1 if fracDigits > 0 { coef = math.Pow(10, float64(fracDigits)) } u64, n, err := bin.GetVarUint64(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) return &CumulativeDeltaDecompressor{ buf: buf, coef: coef, pos: size - 1, // last elem baseValue: float64(u64) / coef, bound: n, } } func (s *CumulativeDeltaDecompressor) RestoreFromEnd() { if s.pos > s.bound { // читаю заголовок наступної серії s.readHeader() s.readValue() } else { s.done = true } } func (s *CumulativeDeltaDecompressor) RestoreFromBound(bound CumulativeDeltaBound) { s.pos = bound.Pos - 1 s.lastValue = s.baseValue + float64(bound.LastDelta) s.decodeHeaderByte(bound.H) fmt.Printf("restore from bound: isRun=%t, pending=%d\n", s.isRun, s.pending) } func (s *CumulativeDeltaDecompressor) 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 } // повертаю значення, що було прочитано в методі RestoreFromBound/RestoreFromEnd 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 *CumulativeDeltaDecompressor) readHeader() { h := s.buf[s.pos] s.pos-- s.decodeHeaderByte(h) // fmt.Println("h:", h) // fmt.Println("isRun:", s.isRun) // fmt.Println("pending:", s.pending) } func (s *CumulativeDeltaDecompressor) readValue() { u64, n, err := bin.ReverseGetVarUint64(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(u64)/s.coef } func (s *CumulativeDeltaDecompressor) decodeHeaderByte(h byte) { s.isRun = h < 128 if s.isRun { s.pending = int(h&127) + 2 } else { s.pending = int(h&127) + 1 } } //