package enc import ( "io" "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 h byte state *CumulativeDeltaCapturedState } // Після відновлення із снапшота func NewCumulativeDeltaCompressor(buf []byte, fracDigits byte) *CumulativeDeltaCompressor { var coef float64 = 1 if fracDigits > 0 { coef = math.Pow(10, float64(fracDigits)) } return &CumulativeDeltaCompressor{ buf: buf, coef: coef, } } func (s *CumulativeDeltaCompressor) RestoreState(payloadSize int) { 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.ReverseGetVarUint64(s.buf[:s.pos-1]) if err != nil { log.Fatalf("bug: get last delta: %s", err) } } } // func (s *CumulativeDeltaCompressor) Size() int { // return s.pos // } func (s *CumulativeDeltaCompressor) Evaluate(value float64) (compressionWay int, requiredSpace int) { delta := uint64((value-s.baseValue)*s.coef + eps) // fix - if delta 0, no eps requiredSpace = s.pos // fix - requiredSpace - all space if s.pos > 0 { if s.h < 128 { // run if delta == s.lastDelta && s.h < 127 { compressionWay = incrementRun } else { compressionWay = endSeries requiredSpace += bin.CountVarUint64(uint64(delta)) + hSize } } else { // literal if delta != s.lastDelta { if s.h < 255 { compressionWay = incrementLiteral requiredSpace += bin.CountVarUint64(uint64(delta)) } else { compressionWay = endSeries requiredSpace += bin.CountVarUint64(uint64(delta)) + hSize } } else { compressionWay = startRun if s.h > 128 { requiredSpace += hSize // h - end of run } } } } else { // encode base value compressionWay = addBaseValue requiredSpace = bin.CountVarUint64(uint64(value*s.coef)) + // base value bin.CountVarUint64(0) + // new delta hSize // new h } return } // pos завжди вказує на h func (s *CumulativeDeltaCompressor) Compress(compressionWay int, value float64) { delta := uint64((value-s.baseValue)*s.coef + eps) switch compressionWay { case incrementRun: s.h++ case incrementLiteral: s.lastDelta = delta s.h++ // write previous delta and increment counter n, _ := bin.ReversePutVarUint64(s.buf[s.pos:], delta) s.pos += n case endSeries: s.lastDelta = delta n, _ := bin.ReversePutVarUint64(s.buf[s.pos:], delta) s.pos += n s.h = 128 // start new literal (length=1) case startRun: if s.h > 128 { // write h - end of literal (because length > 1) s.pos -= 1 + bin.CountVarUint64(s.lastDelta) s.h-- s.buf[s.pos] = s.h s.pos++ // run delta n, _ := bin.ReversePutVarUint64(s.buf[s.pos:], delta) s.pos += n } else { } // start new run (length=2) s.h = 0 case addBaseValue: // start new literal (length=1) n, _ := bin.PutVarUint64(s.buf[s.pos:], uint64(value*s.coef)) s.pos += n s.baseValue = value s.lastDelta = 0 n, _ = bin.ReversePutVarUint64(s.buf[s.pos:], s.lastDelta) s.pos += n // start new literal (length=1) s.h = 128 } s.buf[s.pos] = s.h } func (s *CumulativeDeltaCompressor) DeleteLast() { } type CumulativeDeltaCapturedState struct { H byte LastDelta uint64 Payload []byte } func (s *CumulativeDeltaCompressor) CaptureState() { if s.state != nil { qb.Abort(qb.RepeatableLock, nil) } // позиція посувається вліво, отже може перескочити на попередній chunk pos := s.pos - hSize - bin.CountVarUint64(s.lastDelta) s.state = &CumulativeDeltaCapturedState{ H: s.h, LastDelta: s.lastDelta, Payload: s.buf[:pos], } } // fix - повернути в Pool буфери func (s *CumulativeDeltaCompressor) ForgetCapturedState() { s.state = nil } func (s *CumulativeDeltaCompressor) Offset() int { // if s.state != nil { // return s.state.Pos // } return 0 } func (s *CumulativeDeltaCompressor) Size() int { if s.state == nil { return s.pos } else { return bin.CountVarUint64(s.state.LastDelta) + hSize + len(s.state.Payload) } } // Snapshot - для створення снапшота. func (s *CumulativeDeltaCompressor) Payload() []byte { if s.state == nil { return s.buf[:s.pos] } else { return s.state.Payload } } func (s *CumulativeDeltaCompressor) 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.WriteVarUint64(w, s.state.LastDelta) if err != nil { return } _, err = w.Write([]byte{ s.state.H, }) return } } // func (s *CumulativeDeltaCompressor) encodeTail(lastDelta uint64, h byte) []byte { // tail := make([]byte, 10) // max var uint64 + h // n, _ := bin.PutVarUint64(tail, lastDelta) // tail[n] = h // return tail[:n+1] // } func (s *CumulativeDeltaCompressor) Rotate(newbuf []byte) { // n, _ := bin.PutVarUint64(s.buf[s.pos:], s.lastDelta) // s.pos += n // s.buf[s.pos] = s.h // s.pos++ // payloadSize = s.pos // УВАГА! // state не чіпаємо s.buf = newbuf s.pos = 0 s.baseValue = 0 s.lastDelta = 0 s.h = 0 } func (s *CumulativeDeltaCompressor) 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 *CumulativeDeltaCompressor) 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 NewCumulativeDeltaDecompressorFromState(CumulativeDeltaDecompressorFromStateOptions{ Coef: s.coef, H: h, LastDelta: lastDelta, Payload: payload, }) } // 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, 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) s := &CumulativeDeltaDecompressor{ buf: buf, coef: coef, pos: len(buf), // first free baseValue: float64(u64) / coef, bound: n, } // читаю заголовок наступної серії s.readHeader() s.readValue() return s } type CumulativeDeltaDecompressorFromStateOptions struct { Coef float64 H byte LastDelta uint64 Payload []byte } func NewCumulativeDeltaDecompressorFromState(opt CumulativeDeltaDecompressorFromStateOptions) *CumulativeDeltaDecompressor { u64, n, err := bin.GetVarUint64(opt.Payload) if err != nil { log.Fatalf("bug: get base value: %s", err) } //fmt.Printf("baseValue: %.2f, bound: %d, pos: %d\n", float64(u64)/opt.Coef, n, size) s := &CumulativeDeltaDecompressor{ buf: opt.Payload, coef: opt.Coef, pos: len(opt.Payload), // first free baseValue: float64(u64) / opt.Coef, bound: n, } s.lastValue = s.baseValue + float64(opt.LastDelta)/s.coef s.decodeHeaderByte(opt.H) //fmt.Printf("payload: %d\n", opt.Payload) //fmt.Printf("restore from state: bound=%d, isRun=%t, pending=%d, baseValue=%v, lastValue=%v\n", // n, s.isRun, s.pending, s.baseValue, s.lastValue) return s } 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 } // метод працює як 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 *CumulativeDeltaDecompressor) readHeader() { //fmt.Println("read from pos:", s.pos) 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 *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 //fmt.Println(s.baseValue, float64(u64)/s.coef) } func (s *CumulativeDeltaDecompressor) decodeHeaderByte(h byte) { s.isRun = h < 128 if s.isRun { s.pending = int(h) + 2 } else { s.pending = int(h&127) + 1 } } // // 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) // } // } // } // } // }