package conbuf import ( "errors" "fmt" "io" "gordenko.dev/dima/diploma/bin" ) const chunkSize = 512 var ( ErrOutOfRange = errors.New("out of range") ) type ContinuousBuffer struct { chunks [][]byte } func NewFromBuffer(buf []byte) *ContinuousBuffer { var ( chunks [][]byte copied = 0 ) for copied < len(buf) { chunk := make([]byte, chunkSize) end := min(copied+chunkSize, len(buf)) copy(chunk, buf[copied:end]) chunks = append(chunks, chunk) copied += end - copied } return &ContinuousBuffer{ chunks: chunks, } } func New(chunks [][]byte) *ContinuousBuffer { for i, chunk := range chunks { if len(chunk) != chunkSize { panic(fmt.Sprintf("wrong chunk #%d size %d", i, len(chunk))) } } return &ContinuousBuffer{ chunks: chunks, } } func (s *ContinuousBuffer) Chunks() [][]byte { return s.chunks } // [0, pos) func (s *ContinuousBuffer) GetByte(idx int) byte { chunkIdx := idx / chunkSize if chunkIdx >= len(s.chunks) { panic(ErrOutOfRange) } byteIdx := idx % chunkSize return s.chunks[chunkIdx][byteIdx] } func (s *ContinuousBuffer) SetByte(idx int, b byte) { chunkIdx := idx / chunkSize if chunkIdx > len(s.chunks) { panic(ErrOutOfRange) } if chunkIdx == len(s.chunks) { s.chunks = append(s.chunks, make([]byte, chunkSize)) } byteIdx := idx % chunkSize s.chunks[chunkIdx][byteIdx] = b } func (s *ContinuousBuffer) SetFlag(idx int, flag byte) { chunkIdx := idx / chunkSize if chunkIdx > len(s.chunks) { panic(ErrOutOfRange) } if chunkIdx == len(s.chunks) { s.chunks = append(s.chunks, make([]byte, chunkSize)) } byteIdx := idx % chunkSize s.chunks[chunkIdx][byteIdx] |= flag } func (s *ContinuousBuffer) UnsetFlag(idx int, flag byte) { chunkIdx := idx / chunkSize if chunkIdx > len(s.chunks) { panic(ErrOutOfRange) } if chunkIdx == len(s.chunks) { s.chunks = append(s.chunks, make([]byte, chunkSize)) } byteIdx := idx % chunkSize s.chunks[chunkIdx][byteIdx] &^= flag } // [since, until) func (s *ContinuousBuffer) ShiftOnePosToRight(since int, until int) { if since < 0 { panic("since < 0") } if since >= until { panic("since >= until") } chunkIdx := until / chunkSize byteIdx := until % chunkSize if chunkIdx > len(s.chunks) { panic(ErrOutOfRange) } if chunkIdx == len(s.chunks) { if byteIdx == 0 { s.chunks = append(s.chunks, make([]byte, chunkSize)) } else { panic(ErrOutOfRange) } } var ( qty = until - since prevChunkIdx int prevByteIdx int ) for range qty { prevChunkIdx = chunkIdx prevByteIdx = byteIdx - 1 if prevByteIdx < 0 { prevChunkIdx = chunkIdx - 1 prevByteIdx = chunkSize - 1 } s.chunks[chunkIdx][byteIdx] = s.chunks[prevChunkIdx][prevByteIdx] if byteIdx > 0 { byteIdx-- } else { chunkIdx-- byteIdx = chunkSize - 1 } } } // [since, until) func (s *ContinuousBuffer) ShiftOnePosToLeft(since int, until int) { if since <= 0 { panic("since <= 0") } if since >= until { panic("since >= until") } chunkIdx := since / chunkSize byteIdx := since % chunkSize if until > len(s.chunks)*chunkSize { panic(ErrOutOfRange) } var ( qty = until - since prevChunkIdx int prevByteIdx int ) for range qty { prevChunkIdx = chunkIdx prevByteIdx = byteIdx - 1 if prevByteIdx < 0 { prevChunkIdx = chunkIdx - 1 prevByteIdx = chunkSize - 1 } s.chunks[prevChunkIdx][prevByteIdx] = s.chunks[chunkIdx][byteIdx] byteIdx++ if byteIdx == chunkSize { chunkIdx++ byteIdx = 0 } } } func (s *ContinuousBuffer) PutUint32(pos int, num uint32) { s.CopyTo(pos, []byte{ byte(num), byte(num >> 8), byte(num >> 16), byte(num >> 24), }) } func (s *ContinuousBuffer) GetUint32(pos int) uint32 { arr := s.Slice(pos, pos+4) return uint32(arr[0]) | (uint32(arr[1]) << 8) | (uint32(arr[2]) << 16) | (uint32(arr[3]) << 24) } func (s *ContinuousBuffer) ReversePutVarUint64(pos int, num uint64) int { var tmp [9]byte for i := range 8 { tmp[i] = byte(num & 127) num >>= 7 if num == 0 { tmp[i] |= 128 n := i + 1 s.ReverseCopyTo(pos, tmp[:n]) return n } } tmp[8] = byte(num) n := 9 s.ReverseCopyTo(pos, tmp[:]) return n } func (s *ContinuousBuffer) ReverseGetVarUint64(idx int) (num uint64, n int, err error) { chunkIdx := idx / chunkSize if chunkIdx >= len(s.chunks) { panic(ErrOutOfRange) } var ( byteIdx = idx % chunkSize chunk = s.chunks[chunkIdx] b byte ) for i := range 8 { b = chunk[byteIdx] if b >= 128 { num |= uint64(b&127) << uint(i*7) return num, i + 1, nil } num |= uint64(b) << uint(i*7) if byteIdx > 0 { byteIdx-- } else { if chunkIdx == 0 { return 0, 0, io.EOF } else { chunkIdx-- chunk = s.chunks[chunkIdx] byteIdx = chunkSize - 1 } } } return num | uint64(chunk[byteIdx])<<56, 9, nil } func (s *ContinuousBuffer) ReversePutVarInt64(pos int, x int64) int { return s.ReversePutVarUint64(pos, bin.EncodeZigZag(x)) } func (s *ContinuousBuffer) ReverseGetVarInt64(idx int) (int64, int, error) { u64, n, err := s.ReverseGetVarUint64(idx) if err != nil { return 0, 0, err } return bin.DecodeZigZag(u64), n, nil } func (s *ContinuousBuffer) PutVarUint64(pos int, num uint64) int { var tmp [9]byte for i := range 8 { tmp[i] = byte(num & 127) num >>= 7 if num == 0 { tmp[i] |= 128 n := i + 1 s.CopyTo(pos, tmp[:n]) return n } } tmp[8] = byte(num) s.CopyTo(pos, tmp[:]) return 9 } func (s *ContinuousBuffer) GetVarUint64(idx int) (num uint64, n int, err error) { chunkIdx := idx / chunkSize if chunkIdx >= len(s.chunks) { panic(ErrOutOfRange) } var ( byteIdx = idx % chunkSize chunk = s.chunks[chunkIdx] b byte ) for i := range 8 { b = chunk[byteIdx] if b >= 128 { num |= uint64(b&127) << uint(i*7) return num, i + 1, nil } num |= uint64(b) << uint(i*7) byteIdx++ if byteIdx == chunkSize { chunkIdx++ if chunkIdx == len(s.chunks) { return 0, 0, io.EOF } chunk = s.chunks[chunkIdx] byteIdx = 0 } } return num | uint64(chunk[byteIdx])<<56, 9, nil } func (s *ContinuousBuffer) PutVarInt64(idx int, x int64) int { return s.PutVarUint64(idx, bin.EncodeZigZag(x)) } func (s *ContinuousBuffer) GetVarInt64(idx int) (int64, int, error) { u64, n, err := s.GetVarUint64(idx) if err != nil { return 0, 0, err } return bin.DecodeZigZag(u64), n, nil } func (s *ContinuousBuffer) CopyTo(idx int, data []byte) { chunkIdx := idx / chunkSize if chunkIdx > len(s.chunks) { panic(ErrOutOfRange) } if chunkIdx == len(s.chunks) { s.chunks = append(s.chunks, make([]byte, chunkSize)) } byteIdx := idx % chunkSize chunk := s.chunks[chunkIdx] copied := 0 for _, b := range data { chunk[byteIdx] = b copied++ byteIdx++ if byteIdx == chunkSize { byteIdx = 0 chunkIdx++ if chunkIdx == len(s.chunks) { if copied == len(data) { return } s.chunks = append(s.chunks, make([]byte, chunkSize)) } chunk = s.chunks[chunkIdx] } } } func (s *ContinuousBuffer) ReverseCopyTo(idx int, data []byte) { chunkIdx := idx / chunkSize if chunkIdx > len(s.chunks) { panic(ErrOutOfRange) } if chunkIdx == len(s.chunks) { s.chunks = append(s.chunks, make([]byte, chunkSize)) } byteIdx := idx % chunkSize chunk := s.chunks[chunkIdx] copied := 0 for i := len(data) - 1; i >= 0; i-- { chunk[byteIdx] = data[i] copied++ byteIdx++ if byteIdx == chunkSize { byteIdx = 0 chunkIdx++ if chunkIdx == len(s.chunks) { if copied == len(data) { return } s.chunks = append(s.chunks, make([]byte, chunkSize)) } chunk = s.chunks[chunkIdx] } } } // [since, until) func (s *ContinuousBuffer) Slice(since int, until int) []byte { if since >= until { return nil } size := len(s.chunks) * chunkSize if until >= size { panic(ErrOutOfRange) } data := make([]byte, until-since) chunkIdx := since / chunkSize byteIdx := since % chunkSize chunk := s.chunks[chunkIdx] for i := range len(data) { data[i] = chunk[byteIdx] byteIdx++ if byteIdx == chunkSize { byteIdx = 0 chunkIdx++ chunk = s.chunks[chunkIdx] } } return data } func (s *ContinuousBuffer) DecodeRunLength(pos int) (length uint16, n int) { b1 := s.GetByte(pos) pos-- if b1 < 128 { length = uint16(b1) n = 1 } else { b2 := s.GetByte(pos) length = uint16(b1&127) | (uint16(b2) << 7) n = 2 } length += 2 return } func (s *ContinuousBuffer) Copy() *ContinuousBuffer { var copies [][]byte for _, chunk := range s.chunks { buf := make([]byte, chunkSize) copy(buf, chunk) copies = append(copies, buf) } return New(copies) } // size to copy func (s *ContinuousBuffer) CopyChunksToOneBuffer(dst []byte, size int) { pos := 0 for _, chunk := range s.chunks { if size >= len(chunk) { copy(dst[pos:], chunk) size -= len(chunk) pos += len(chunk) } else { copy(dst[pos:], chunk[:size]) return } } }