package org.yamcs.parameterarchive; import java.nio.BufferOverflowException; import java.nio.ByteBuffer; import java.util.List; import org.yamcs.parameter.Value; import org.yamcs.utils.DecodingException; import org.yamcs.utils.IntArray; import org.yamcs.utils.ValueUtility; import org.yamcs.utils.VarIntUtil; import me.lemire.integercompression.FastPFOR128; import me.lemire.integercompression.IntWrapper; /** * 32 bit integers * encoded as deltas of deltas (good if the values are relatively constant or in increasing order) * * @author nm * */ public class IntValueSegment extends BaseSegment implements ValueSegment { final static int SUBFORMAT_ID_RAW = 0; //uncompresed final static int SUBFORMAT_ID_DELTAZG_FPF128_VB = 1; //compressed with DeltaZigzag and then FastPFOR128 plus VariableByte for remaining final static int SUBFORMAT_ID_DELTAZG_VB = 2; //compressed with DeltaZigzag plus VariableByte private boolean signed; IntArray values; IntValueSegment(boolean signed) { super(FORMAT_ID_IntValueSegment); values = new IntArray(); this.signed = signed; } private IntValueSegment() { super(FORMAT_ID_IntValueSegment); } @Override public void writeTo(ByteBuffer bb) { int position = bb.position(); //try first to write compressed, if we fail (for random data we may exceed the buffer) then write in raw format try { writeCompressed(bb); } catch (IndexOutOfBoundsException|BufferOverflowException e) { bb.position(position); writeRaw(bb); } } public void writeCompressed(ByteBuffer bb) { int[] ddz = VarIntUtil.encodeDeltaDeltaZigZag(values); FastPFOR128 fastpfor = FastPFORFactory.get(); int size = ddz.length; IntWrapper inputoffset = new IntWrapper(0); IntWrapper outputoffset = new IntWrapper(0); int[] xc = new int[size]; fastpfor.compress(ddz, inputoffset, size, xc, outputoffset); if (outputoffset.get() == 0) { //fastpfor didn't compress anything, probably there were too few datapoints writeHeader(SUBFORMAT_ID_DELTAZG_VB, bb); } else { writeHeader(SUBFORMAT_ID_DELTAZG_FPF128_VB, bb); int length = outputoffset.get(); for(int i=0; i<length; i++) { bb.putInt(xc[i]); } } //write the remaining bytes varint compressed for(int i = inputoffset.get(); i<size; i++) { VarIntUtil.writeVarInt32(bb, ddz[i]); } } private void writeRaw(ByteBuffer bb) { writeHeader(SUBFORMAT_ID_RAW, bb); int n = values.size(); for(int i=0; i<n; i++) { bb.putInt(values.get(i)); } } //write header: // 1st byte: spare signed/unsigned subformatid // 3 bits 1 bit 4 bits // 2nd+ bytes: varint of n private void writeHeader(int subFormatId, ByteBuffer bb) { int x = signed?1:0; x=(x<<4)|subFormatId; bb.put((byte)x); VarIntUtil.writeVarInt32(bb, values.size()); } static public IntValueSegment parseFrom(ByteBuffer bb) throws DecodingException { IntValueSegment r = new IntValueSegment(); r.parse(bb); return r; } private void parse(ByteBuffer bb) throws DecodingException { byte x = bb.get(); int subFormatId = x&0xF; signed = (((x>>4)&1)==1); int n = VarIntUtil.readVarInt32(bb); switch(subFormatId) { case SUBFORMAT_ID_RAW: parseRaw(bb, n); break; case SUBFORMAT_ID_DELTAZG_FPF128_VB: //intentional fall through case SUBFORMAT_ID_DELTAZG_VB: parseCompressed(bb, n, subFormatId); break; default: throw new DecodingException("Unknown subformatId: "+subFormatId); } } private void parseRaw(ByteBuffer bb, int n) { values = new IntArray(n); for(int i =0;i<n; i++) { values.add(bb.getInt()); } } private void parseCompressed(ByteBuffer bb, int n, int subFormatId) throws DecodingException { int[] ddz = new int[n]; IntWrapper inputoffset = new IntWrapper(0); IntWrapper outputoffset = new IntWrapper(0); int position = bb.position(); if(subFormatId==SUBFORMAT_ID_DELTAZG_FPF128_VB) { int[] x = new int[(bb.limit()-bb.position())/4]; for(int i=0; i<x.length;i++) { x[i] = bb.getInt(); } FastPFOR128 fastpfor = FastPFORFactory.get(); fastpfor.uncompress(x, inputoffset, x.length, ddz, outputoffset); bb.position(position+inputoffset.get()*4); } for(int i = outputoffset.get(); i<n; i++) { ddz[i] = VarIntUtil.readVarInt32(bb); } values = IntArray.wrap(VarIntUtil.decodeDeltaDeltaZigZag(ddz)); } public static IntValueSegment consolidate(List<Value> values, boolean signed) { IntValueSegment segment = new IntValueSegment(signed); int n = values.size(); segment.signed = signed; if(signed) { for(int i =0;i<n; i++) { segment.add(values.get(i).getSint32Value()); } } else { for(int i =0;i<n; i++) { segment.add(values.get(i).getUint32Value()); } } return segment; } private void add(int v) { values.add(v); } @Override public int getMaxSerializedSize() { return 5 + 4*values.size(); //1+for format id + 4 for the size plus 4 for each element } @Override public Value getValue(int index) { if(signed) { return ValueUtility.getSint32Value(values.get(index)); } else { return ValueUtility.getUint32Value(values.get(index)); } } @Override public int[] getRange(int posStart, int posStop, boolean ascending) { int[] r = new int[posStop-posStart]; if(ascending) { for(int i = posStart; i<posStop; i++) { r[i-posStart] = values.get(i); } } else { for(int i = posStop; i>posStart; i--) { r[posStop-i] = values.get(i); } } return r; } @Override public int size() { return values.size(); } @Override public void add(int pos, Value v) { if(signed) { values.add(pos, v.getSint32Value()); } else { values.add(pos, v.getUint32Value()); } } @Override public IntValueSegment consolidate() { return this; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; IntValueSegment other = (IntValueSegment) obj; if (signed != other.signed) return false; if (values == null) { if (other.values != null) return false; } else if (!values.equals(other.values)) return false; return true; } }