package chunkenc import ( "fmt" "log" "math" "gordenko.dev/dima/qb" "gordenko.dev/dima/qb/bin" ) type InstantDeltaCompressor struct { buf []byte coef float64 pos int baseValue float64 lastDelta int64 lastDeltaSize int h byte state *InstantDeltaBound } func NewInstantDeltaCompressor(buf []byte, size int, fracDigits byte) *InstantDeltaCompressor { var coef float64 = 1 if fracDigits > 0 { coef = math.Pow(10, float64(fracDigits)) } s := &InstantDeltaCompressor{ buf: buf, pos: size, coef: coef, } if size > 0 { i64, _, err := bin.GetVarInt64(s.buf) if err != nil { log.Fatalf("bug: get base value: %s", err) } s.baseValue = float64(i64) / s.coef s.h = s.buf[s.pos-1] s.lastDelta, s.lastDeltaSize, err = bin.ReverseGetVarInt64(s.buf[:s.pos-2]) if err != nil { log.Fatalf("bug: get last delta: %s", err) } } return s } func (s *InstantDeltaCompressor) Size() int { return s.pos } 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) } } } } } func (s *InstantDeltaCompressor) convertLastFromLiteralToRun() { // Зменшую кількість елементів в literal блоці s.h-- s.pos -= 1 + s.lastDeltaSize s.buf[s.pos] = s.h // закриваю literal блок s.pos++ s.lastDeltaSize, _ = bin.ReversePutVarInt64(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 *InstantDeltaCompressor) convertLiteralToRun() { // Знімаю flagLiteral, а лічильник 0 дорівнює 2 елементам в серії. s.h = 0 s.buf[s.pos-1] = s.h } func (s *InstantDeltaCompressor) appendDeltaToLiteral(delta int64) { s.h++ // збільшую к-сть дельт s.lastDelta = delta s.pos-- s.lastDeltaSize, _ = bin.ReversePutVarInt64(s.buf[s.pos:], delta) s.pos += s.lastDeltaSize s.buf[s.pos] = s.h s.pos++ } func (s *InstantDeltaCompressor) appendNewLiteral(delta int64) { s.h = flagLiteral s.lastDelta = delta s.lastDeltaSize, _ = bin.ReversePutVarInt64(s.buf[s.pos:], delta) s.pos += s.lastDeltaSize s.buf[s.pos] = flagLiteral // literal, length = 1 s.pos++ } func (s *InstantDeltaCompressor) DeleteLast() {} type InstantDeltaBound struct { Pos int H byte LastDelta int64 Chunks []byte } // delta h func (s *InstantDeltaCompressor) Lock() { if s.state != nil { qb.Abort(qb.RepeatableLock, nil) } // позиція посувається вліво, отже може перескочити на попередній chunk pos := s.pos - 1 - s.lastDeltaSize s.state = &InstantDeltaBound{ Pos: pos, H: s.h, LastDelta: s.lastDelta, Chunks: s.buf[:s.pos], // fix check ? } } // fix - повернути в Pool буфери func (s *InstantDeltaCompressor) Unlock() { s.state = nil } func (s *InstantDeltaCompressor) Offset() int { if s.state != nil { return s.state.Pos } return 0 } func (s *InstantDeltaCompressor) 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.ReversePutVarInt64(pos, s.state.LastDelta) // buf.SetByte(pos, s.state.H) // pos++ // return chunks, pos return nil, 0 } func (s *InstantDeltaCompressor) CreateDecompressor(fracDigits byte) qb.ValueDecompressor { if s.state == nil { d := NewInstantDeltaDecompressor(s.buf, s.Size(), fracDigits) d.RestoreFromEnd() return d } d := NewInstantDeltaDecompressor(s.buf, s.Size(), fracDigits) d.RestoreFromBound(*s.state) return d } func (s *InstantDeltaCompressor) Renew() { // УВАГА! // state не чіпаємо s.buf = make([]byte, minBufferSize) s.pos = 0 // s.baseValue = 0 s.lastDelta = 0 s.lastDeltaSize = 0 s.h = 0 } func (s *InstantDeltaCompressor) CalcRequiredSpace(value float64) int { return 0 } 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, size int, 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) return &InstantDeltaDecompressor{ buf: buf, coef: coef, pos: size - 1, // last elem baseValue: float64(i64) / coef, bound: n, } } func (s *InstantDeltaDecompressor) RestoreFromEnd() { if s.pos > s.bound { // читаю заголовок наступної серії s.readHeader() s.readValue() } else { s.done = true } } func (s *InstantDeltaDecompressor) RestoreFromBound(bound InstantDeltaBound) { 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 *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 } // повертаю значення, що було прочитано в методі 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 *InstantDeltaDecompressor) 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 *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 // }