Added support for value delta tracking

Added support for delta serialization
Tweaked Serializer class to be more robust when handling edge cases regarding typing issues
Added support for boxed field types to PrimitiveSerializers
This commit is contained in:
Gabriel Tofvesson 2018-07-25 08:39:07 +02:00
parent 04dd29ad56
commit 60cebfb966
10 changed files with 391 additions and 121 deletions

View File

@ -1,5 +1,4 @@
import net.tofvesson.networking.SyncHandler; import net.tofvesson.networking.*;
import net.tofvesson.networking.SyncedVar;
public class Main { public class Main {
@SyncedVar("NonNegative") @SyncedVar("NonNegative")
@ -20,8 +19,16 @@ public class Main {
@SyncedVar @SyncedVar
public static boolean[] test = {true, false}; public static boolean[] test = {true, false};
@SyncedVar
public static DiffTracked<Integer> tracker = new DiffTracked<>(5, Integer.class);
@SyncedVar
public static DiffTrackedArray<Long> tracker2 = new DiffTrackedArray<>(Long.class, 8, i -> (long)i);
public static void main(String[] args){ public static void main(String[] args){
Main testObject = new Main(); Main testObject = new Main();
SyncHandler.Companion.registerSerializer(DiffTrackedSerializer.Companion.getSingleton());
SyncHandler sync = new SyncHandler(); SyncHandler sync = new SyncHandler();
sync.registerSyncObject(testObject); sync.registerSyncObject(testObject);
sync.registerSyncObject(Main.class); sync.registerSyncObject(Main.class);
@ -29,18 +36,27 @@ public class Main {
// Generate mismatch check // Generate mismatch check
byte[] mismatchCheck = sync.generateMismatchCheck(); byte[] mismatchCheck = sync.generateMismatchCheck();
// Trigger change flags
tracker.setValue(9);
tracker2.set(3L, 2);
tracker2.set(5L, 0);
// Generate snapshot of values to serialize // Generate snapshot of values to serialize
byte[] ser = sync.serialize(); byte[] ser = sync.serialize();
System.out.println("Created and serialized snapshot of field values:\n\t"+ System.out.print("Created and serialized snapshot of field values:\n\t"+
testObject.syncTest+"\n\t"+ testObject.syncTest+"\n\t"+
staticTest+"\n\t"+ staticTest+"\n\t"+
value+"\n\t"+ value+"\n\t"+
testObject.testbool+"\n\t"+ testObject.testbool+"\n\t"+
testbool1+"\n\t"+ testbool1+"\n\t"+
test[0]+"\n\t"+ test[0]+"\n\t"+
test[1]+"\n" test[1]+"\n\t"+
tracker
); );
for(Long value : tracker2.getValues())
System.out.print("\n\t"+value);
System.out.println('\n');
// Modify all the values // Modify all the values
testObject.syncTest = 20; testObject.syncTest = 20;
@ -52,8 +68,11 @@ public class Main {
test[0] = false; test[0] = false;
test[1] = true; test[1] = true;
test[2] = true; test[2] = true;
tracker.setValue(400);
tracker2.set(8L, 2);
tracker2.set(100L, 0);
System.out.println("Set a new state of test values:\n\t"+ System.out.print("Set a new state of test values:\n\t"+
testObject.syncTest+"\n\t"+ testObject.syncTest+"\n\t"+
staticTest+"\n\t"+ staticTest+"\n\t"+
value+"\n\t"+ value+"\n\t"+
@ -61,8 +80,12 @@ public class Main {
testbool1+"\n\t"+ testbool1+"\n\t"+
test[0]+"\n\t"+ test[0]+"\n\t"+
test[1]+"\n\t"+ test[1]+"\n\t"+
test[2]+"\n" test[2]+"\n\t"+
tracker
); );
for(Long value : tracker2.getValues())
System.out.print("\n\t"+value);
System.out.println('\n');
// Do mismatch check // Do mismatch check
if(!sync.doMismatchCheck(mismatchCheck)) throw new RuntimeException("Target sync mismatch"); if(!sync.doMismatchCheck(mismatchCheck)) throw new RuntimeException("Target sync mismatch");
@ -70,15 +93,18 @@ public class Main {
// Load snapshot values back // Load snapshot values back
sync.deserialize(ser); sync.deserialize(ser);
System.out.println("Deserialized snapshot values:\n\t"+ System.out.print("Deserialized snapshot values:\n\t"+
testObject.syncTest+"\n\t"+ testObject.syncTest+"\n\t"+
staticTest+"\n\t"+ staticTest+"\n\t"+
value+"\n\t"+ value+"\n\t"+
testObject.testbool+"\n\t"+ testObject.testbool+"\n\t"+
testbool1+"\n\t"+ testbool1+"\n\t"+
test[0]+"\n\t"+ test[0]+"\n\t"+
test[1]+"\n\n" + test[1]+"\n\t"+
"Snapshot size: \"+ser.length+\" bytes" tracker);
); for(Long value : tracker2.getValues())
System.out.print("\n\t"+value);
System.out.println("\n\nSnapshot size: "+ser.length+" bytes");
} }
} }

View File

@ -0,0 +1,17 @@
package net.tofvesson.networking
import java.util.*
class DiffTracked<T>(initial: T, val valueType: Class<T>) {
private var changed = false
private var _value = initial
var value: T
get() = _value
set(value) {
changed = changed or (value != this._value)
_value = value
}
fun hasChanged() = changed
fun clearChangeState() { changed = false }
override fun toString() = Objects.toString(_value)!!
}

View File

@ -0,0 +1,31 @@
package net.tofvesson.networking
class DiffTrackedArray<T>(val elementType: Class<T>, val size: Int, gen: (Int) -> T) {
val values: Array<T> = java.lang.reflect.Array.newInstance(elementType, size) as Array<T>
val changeMap = Array(size) {false}
init{
for(index in 0 until size)
values[index] = gen(index)
}
operator fun get(index: Int) = values[index]
operator fun set(value: T, index: Int) {
changeMap[index] = changeMap[index] or (values[index] != value)
values[index] = value
}
fun clearChangeState(){
for (index in changeMap.indices)
changeMap[index] = false
}
fun hasChanged(): Boolean {
for(value in changeMap)
if(value)
return true
return false
}
override fun toString() = values.toString()
}

View File

@ -0,0 +1,129 @@
package net.tofvesson.networking
import java.lang.reflect.Field
import java.nio.ByteBuffer
class DiffTrackedSerializer private constructor(): Serializer(arrayOf(
DiffTracked::class.java,
DiffTrackedArray::class.java
)) {
companion object {
private val trackedField = DiffTracked::class.java.getDeclaredField("_value")
private val holderValue = Holder::class.java.getDeclaredField("value")
val singleton = DiffTrackedSerializer()
}
override fun computeSizeExplicit(field: Field, flags: Array<out SyncFlag>, owner: Any?, fieldType: Class<*>): Pair<Int, Int> =
when(fieldType) {
DiffTracked::class.java -> {
val tracker = field.get(owner) as DiffTracked<*>
val serializer = SyncHandler.getCompatibleSerializer(tracker.valueType)
if(tracker.hasChanged()){
val result = serializer.computeSizeExplicit(trackedField, flags, tracker, tracker.valueType)
Pair(result.first, result.second+1)
}else Pair(0, 1)
}
DiffTrackedArray::class.java -> {
val tracker = field.get(owner) as DiffTrackedArray<*>
val serializer = SyncHandler.getCompatibleSerializer(tracker.elementType)
if(tracker.hasChanged()){
var bits = 0
var bytes = 0
val holder = Holder(null)
for(index in tracker.changeMap.indices)
if(tracker.changeMap[index]){
holder.value = tracker[index]
val result = serializer.computeSizeExplicit(holderValue, flags, holder, tracker.elementType)
bytes += result.first
bits += result.second
}
Pair(bytes, bits+tracker.size)
}else Pair(0, tracker.size)
}
else -> Pair(0, 0)
}
override fun serializeExplicit(field: Field, flags: Array<out SyncFlag>, owner: Any?, byteBuffer: ByteBuffer, offset: Int, bitFieldOffset: Int, fieldType: Class<*>): Pair<Int, Int> =
when(fieldType) {
DiffTracked::class.java -> {
val tracker = field.get(owner) as DiffTracked<*>
val serializer = SyncHandler.getCompatibleSerializer(tracker.valueType)
writeBit(tracker.hasChanged(), byteBuffer, bitFieldOffset)
if(tracker.hasChanged()){
val result = serializer.serializeExplicit(trackedField, flags, tracker, byteBuffer, offset, bitFieldOffset+1, tracker.valueType)
tracker.clearChangeState()
Pair(result.first, result.second)
}else{
tracker.clearChangeState()
Pair(offset, bitFieldOffset+1)
}
}
DiffTrackedArray::class.java -> {
val tracker = field.get(owner) as DiffTrackedArray<*>
val serializer = SyncHandler.getCompatibleSerializer(tracker.elementType)
if(tracker.hasChanged()){
var bits = bitFieldOffset
var bytes = offset
val holder = Holder(null)
for(index in tracker.changeMap.indices) {
writeBit(tracker.changeMap[index], byteBuffer, bits++)
if (tracker.changeMap[index]) {
holder.value = tracker[index]
val result = serializer.serializeExplicit(holderValue, flags, holder, byteBuffer, bytes, bits, tracker.elementType)
bytes = result.first
bits = result.second
}
}
tracker.clearChangeState()
Pair(bytes, bits)
}else{
tracker.clearChangeState()
Pair(offset, bitFieldOffset+tracker.size)
}
}
else -> Pair(0, 0)
}
override fun deserializeExplicit(field: Field, flags: Array<out SyncFlag>, owner: Any?, byteBuffer: ByteBuffer, offset: Int, bitFieldOffset: Int, fieldType: Class<*>): Pair<Int, Int> =
when(fieldType) {
DiffTracked::class.java -> {
val tracker = field.get(owner) as DiffTracked<*>
val serializer = SyncHandler.getCompatibleSerializer(tracker.valueType)
var bytes = offset
var bits = bitFieldOffset
if(readBit(byteBuffer, bits++)){
val result = serializer.deserializeExplicit(trackedField, flags, tracker, byteBuffer, bytes, bits, tracker.valueType)
bytes = result.first
bits = result.second
}
tracker.clearChangeState()
Pair(bytes, bits)
}
DiffTrackedArray::class.java -> {
val tracker = field.get(owner) as DiffTrackedArray<*>
val serializer = SyncHandler.getCompatibleSerializer(tracker.elementType)
var bits = bitFieldOffset
var bytes = offset
val holder = Holder(null)
val array = tracker.values as Array<Any?>
for(index in tracker.changeMap.indices){
if(readBit(byteBuffer, bits++)){
holder.value = tracker[index]
val result = serializer.deserializeExplicit(holderValue, flags, holder, byteBuffer, bytes, bits, tracker.elementType)
bytes = result.first
bits = result.second
array[index] = holder.value
}
}
tracker.clearChangeState()
Pair(bytes, bits)
}
else -> Pair(0, 0)
}
private data class Holder(var value: Any?)
}

View File

@ -18,11 +18,11 @@ class PrimitiveArraySerializer private constructor(): Serializer(arrayOf(
val singleton = PrimitiveArraySerializer() val singleton = PrimitiveArraySerializer()
} }
override fun computeSize(field: Field, flags: Array<out SyncFlag>, owner: Any?): Pair<Int, Int> { override fun computeSizeExplicit(field: Field, flags: Array<out SyncFlag>, owner: Any?, fieldType: Class<*>): Pair<Int, Int> {
val arrayLength = java.lang.reflect.Array.getLength(field.get(owner)) val arrayLength = java.lang.reflect.Array.getLength(field.get(owner))
var byteSize = if(flags.contains(knownSize)) 0 else varIntSize(arrayLength.toLong()) var byteSize = if(flags.contains(knownSize)) 0 else varIntSize(arrayLength.toLong())
var bitSize = 0 var bitSize = 0
when (field.type) { when (fieldType) {
BooleanArray::class.java -> bitSize = arrayLength BooleanArray::class.java -> bitSize = arrayLength
ByteArray::class.java -> byteSize += arrayLength ByteArray::class.java -> byteSize += arrayLength
ShortArray::class.java -> ShortArray::class.java ->
@ -74,7 +74,7 @@ class PrimitiveArraySerializer private constructor(): Serializer(arrayOf(
return Pair(byteSize, bitSize) return Pair(byteSize, bitSize)
} }
override fun serialize(field: Field, flags: Array<out SyncFlag>, owner: Any?, byteBuffer: ByteBuffer, offset: Int, bitFieldOffset: Int): Pair<Int, Int> { override fun serializeExplicit(field: Field, flags: Array<out SyncFlag>, owner: Any?, byteBuffer: ByteBuffer, offset: Int, bitFieldOffset: Int, fieldType: Class<*>): Pair<Int, Int> {
val arrayLength = java.lang.reflect.Array.getLength(field.get(owner)) val arrayLength = java.lang.reflect.Array.getLength(field.get(owner))
var localByteOffset = offset var localByteOffset = offset
var localBitOffset = bitFieldOffset var localBitOffset = bitFieldOffset
@ -82,7 +82,7 @@ class PrimitiveArraySerializer private constructor(): Serializer(arrayOf(
writeVarInt(byteBuffer, offset, arrayLength.toLong()) writeVarInt(byteBuffer, offset, arrayLength.toLong())
localByteOffset += varIntSize(arrayLength.toLong()) localByteOffset += varIntSize(arrayLength.toLong())
} }
when (field.type) { when (fieldType) {
BooleanArray::class.java -> BooleanArray::class.java ->
for(value in field.get(owner) as BooleanArray) for(value in field.get(owner) as BooleanArray)
writeBit(value, byteBuffer, localBitOffset++) writeBit(value, byteBuffer, localBitOffset++)
@ -167,7 +167,7 @@ class PrimitiveArraySerializer private constructor(): Serializer(arrayOf(
return Pair(localByteOffset, localBitOffset) return Pair(localByteOffset, localBitOffset)
} }
override fun deserialize(field: Field, flags: Array<out SyncFlag>, owner: Any?, byteBuffer: ByteBuffer, offset: Int, bitFieldOffset: Int): Pair<Int, Int> { override fun deserializeExplicit(field: Field, flags: Array<out SyncFlag>, owner: Any?, byteBuffer: ByteBuffer, offset: Int, bitFieldOffset: Int, fieldType: Class<*>): Pair<Int, Int> {
var localByteOffset = offset var localByteOffset = offset
var localBitOffset = bitFieldOffset var localBitOffset = bitFieldOffset
val localLength = java.lang.reflect.Array.getLength(field.get(owner)) val localLength = java.lang.reflect.Array.getLength(field.get(owner))
@ -183,7 +183,7 @@ class PrimitiveArraySerializer private constructor(): Serializer(arrayOf(
if(arrayLength!=localLength) java.lang.reflect.Array.newInstance(field.type.componentType, arrayLength) if(arrayLength!=localLength) java.lang.reflect.Array.newInstance(field.type.componentType, arrayLength)
else field.get(owner) else field.get(owner)
when (field.type) { when (fieldType) {
BooleanArray::class.java -> { BooleanArray::class.java -> {
val booleanTarget = target as BooleanArray val booleanTarget = target as BooleanArray
for (index in 0 until arrayLength) for (index in 0 until arrayLength)

View File

@ -1,196 +1,207 @@
package net.tofvesson.networking package net.tofvesson.networking
import net.tofvesson.reflect.*
import java.lang.reflect.Field import java.lang.reflect.Field
import java.nio.ByteBuffer import java.nio.ByteBuffer
class PrimitiveSerializer private constructor() : Serializer(arrayOf( class PrimitiveSerializer private constructor() : Serializer(arrayOf(
Boolean::class.java, Boolean::class.java,
java.lang.Boolean::class.java,
Byte::class.java, Byte::class.java,
java.lang.Byte::class.java,
Short::class.java, Short::class.java,
java.lang.Short::class.java,
Int::class.java, Int::class.java,
java.lang.Integer::class.java,
Long::class.java, Long::class.java,
java.lang.Long::class.java,
Float::class.java, Float::class.java,
Double::class.java java.lang.Float::class.java,
Double::class.java,
java.lang.Double::class.java
)) { )) {
companion object { val singleton = PrimitiveSerializer() } companion object { val singleton = PrimitiveSerializer() }
override fun computeSize( override fun computeSizeExplicit(
field: Field, field: Field,
flags: Array<out SyncFlag>, flags: Array<out SyncFlag>,
owner: Any? owner: Any?,
fieldType: Class<*>
): Pair<Int, Int> = ): Pair<Int, Int> =
when(field.type){ when(fieldType){
Boolean::class.java -> Pair(0, 1) java.lang.Boolean::class.java, Boolean::class.java -> Pair(0, 1)
Byte::class.java -> Pair(1, 0) java.lang.Byte::class.java, Byte::class.java -> Pair(1, 0)
Short::class.java -> java.lang.Short::class.java, Short::class.java ->
if(flags.contains(SyncFlag.NoCompress)) Pair(2, 0) if(flags.contains(SyncFlag.NoCompress)) Pair(2, 0)
else Pair(varIntSize( else Pair(varIntSize(
if(flags.contains(SyncFlag.NonNegative)) field.getShort(owner).toLong() if(flags.contains(SyncFlag.NonNegative)) field.getShortAdaptive(owner).toLong()
else zigZagEncode(field.getShort(owner).toLong()) else zigZagEncode(field.getShortAdaptive(owner).toLong())
), 0) ), 0)
Int::class.java -> java.lang.Integer::class.java, Int::class.java ->
if(flags.contains(SyncFlag.NoCompress)) Pair(4, 0) if(flags.contains(SyncFlag.NoCompress)) Pair(4, 0)
else Pair(varIntSize( else Pair(varIntSize(
if(flags.contains(SyncFlag.NonNegative)) field.getInt(owner).toLong() if(flags.contains(SyncFlag.NonNegative)) field.getIntAdaptive(owner).toLong()
else zigZagEncode(field.getInt(owner).toLong()) else zigZagEncode(field.getIntAdaptive(owner).toLong())
), 0) ), 0)
Long::class.java -> java.lang.Long::class.java, Long::class.java ->
if(flags.contains(SyncFlag.NoCompress)) Pair(8, 0) if(flags.contains(SyncFlag.NoCompress)) Pair(8, 0)
else Pair(varIntSize( else Pair(varIntSize(
if(flags.contains(SyncFlag.NonNegative)) field.getLong(owner) if(flags.contains(SyncFlag.NonNegative)) field.getLongAdaptive(owner)
else zigZagEncode(field.getLong(owner)) else zigZagEncode(field.getLongAdaptive(owner))
), 0) ), 0)
Float::class.java -> java.lang.Float::class.java, Float::class.java ->
if(flags.contains(SyncFlag.NoCompress)) Pair(4, 0) if(flags.contains(SyncFlag.NoCompress)) Pair(4, 0)
else Pair(varIntSize( else Pair(varIntSize(
if(flags.contains(SyncFlag.FloatEndianSwap)) bitConvert(swapEndian(floatToInt(field.getFloat(owner)))) if(flags.contains(SyncFlag.FloatEndianSwap)) bitConvert(swapEndian(floatToInt(field.getFloatAdaptive(owner))))
else bitConvert(floatToInt(field.getFloat(owner))) else bitConvert(floatToInt(field.getFloatAdaptive(owner)))
), 0) ), 0)
Double::class.java -> java.lang.Double::class.java, Double::class.java ->
if(flags.contains(SyncFlag.NoCompress)) Pair(8, 0) if(flags.contains(SyncFlag.NoCompress)) Pair(8, 0)
else Pair(varIntSize( else Pair(varIntSize(
if(flags.contains(SyncFlag.FloatEndianSwap)) swapEndian(doubleToLong(field.getDouble(owner))) if(flags.contains(SyncFlag.FloatEndianSwap)) swapEndian(doubleToLong(field.getDoubleAdaptive(owner)))
else doubleToLong(field.getDouble(owner)) else doubleToLong(field.getDoubleAdaptive(owner))
), 0) ), 0)
else -> Pair(0, 0) else -> Pair(0, 0)
} }
override fun serialize( override fun serializeExplicit(
field: Field, field: Field,
flags: Array<out SyncFlag>, flags: Array<out SyncFlag>,
owner: Any?, owner: Any?,
byteBuffer: ByteBuffer, byteBuffer: ByteBuffer,
offset: Int, offset: Int,
bitFieldOffset: Int bitFieldOffset: Int,
fieldType: Class<*>
): Pair<Int, Int> = ): Pair<Int, Int> =
when(field.type){ when(fieldType){
Boolean::class.java -> { java.lang.Boolean::class.java, Boolean::class.java -> {
writeBit(field.getBoolean(owner), byteBuffer, bitFieldOffset) writeBit(field.getBooleanAdaptive(owner), byteBuffer, bitFieldOffset)
Pair(offset, bitFieldOffset+1) Pair(offset, bitFieldOffset+1)
} }
Byte::class.java -> { java.lang.Byte::class.java, Byte::class.java -> {
byteBuffer.put(offset, field.getByte(owner)) byteBuffer.put(offset, field.getByteAdaptive(owner))
Pair(offset+1, bitFieldOffset) Pair(offset+1, bitFieldOffset)
} }
Short::class.java -> java.lang.Short::class.java, Short::class.java ->
if(flags.contains(SyncFlag.NoCompress)){ if(flags.contains(SyncFlag.NoCompress)){
byteBuffer.putShort(offset, field.getShort(owner)) byteBuffer.putShort(offset, field.getShortAdaptive(owner))
Pair(offset+2, bitFieldOffset) Pair(offset+2, bitFieldOffset)
} }
else { else {
val rawValue = val rawValue =
if(flags.contains(SyncFlag.NonNegative)) field.getShort(owner).toLong() if(flags.contains(SyncFlag.NonNegative)) field.getShortAdaptive(owner).toLong()
else zigZagEncode(field.getShort(owner).toLong()) else zigZagEncode(field.getShortAdaptive(owner).toLong())
writeVarInt(byteBuffer, offset, rawValue) writeVarInt(byteBuffer, offset, rawValue)
Pair(offset+varIntSize(rawValue), bitFieldOffset) Pair(offset+varIntSize(rawValue), bitFieldOffset)
} }
Int::class.java -> java.lang.Integer::class.java, Int::class.java ->
if(flags.contains(SyncFlag.NoCompress)){ if(flags.contains(SyncFlag.NoCompress)){
byteBuffer.putInt(offset, field.getInt(owner)) byteBuffer.putInt(offset, field.getIntAdaptive(owner))
Pair(offset+4, bitFieldOffset) Pair(offset+4, bitFieldOffset)
} }
else { else {
val rawValue = val rawValue =
if(flags.contains(SyncFlag.NonNegative)) field.getInt(owner).toLong() if(flags.contains(SyncFlag.NonNegative)) field.getIntAdaptive(owner).toLong()
else zigZagEncode(field.getInt(owner).toLong()) else zigZagEncode(field.getIntAdaptive(owner).toLong())
writeVarInt(byteBuffer, offset, rawValue) writeVarInt(byteBuffer, offset, rawValue)
Pair(offset+varIntSize(rawValue), bitFieldOffset) Pair(offset+varIntSize(rawValue), bitFieldOffset)
} }
Long::class.java -> java.lang.Long::class.java, Long::class.java ->
if(flags.contains(SyncFlag.NoCompress)){ if(flags.contains(SyncFlag.NoCompress)){
byteBuffer.putLong(offset, field.getLong(owner)) byteBuffer.putLong(offset, field.getLongAdaptive(owner))
Pair(offset+8, bitFieldOffset) Pair(offset+8, bitFieldOffset)
} }
else { else {
val rawValue = val rawValue =
if(flags.contains(SyncFlag.NonNegative)) field.getLong(owner) if(flags.contains(SyncFlag.NonNegative)) field.getLongAdaptive(owner)
else zigZagEncode(field.getLong(owner)) else zigZagEncode(field.getLongAdaptive(owner))
writeVarInt(byteBuffer, offset, rawValue) writeVarInt(byteBuffer, offset, rawValue)
Pair(offset+varIntSize(rawValue), bitFieldOffset) Pair(offset+varIntSize(rawValue), bitFieldOffset)
} }
Float::class.java -> java.lang.Float::class.java, Float::class.java ->
if(flags.contains(SyncFlag.NoCompress)){ if(flags.contains(SyncFlag.NoCompress)){
byteBuffer.putFloat(offset, field.getFloat(owner)) byteBuffer.putFloat(offset, field.getFloatAdaptive(owner))
Pair(offset+4, bitFieldOffset) Pair(offset+4, bitFieldOffset)
} }
else{ else{
val rawValue = val rawValue =
if(flags.contains(SyncFlag.FloatEndianSwap)) bitConvert(swapEndian(floatToInt(field.getFloat(owner)))) if(flags.contains(SyncFlag.FloatEndianSwap)) bitConvert(swapEndian(floatToInt(field.getFloatAdaptive(owner))))
else bitConvert(floatToInt(field.getFloat(owner))) else bitConvert(floatToInt(field.getFloatAdaptive(owner)))
writeVarInt(byteBuffer, offset, rawValue) writeVarInt(byteBuffer, offset, rawValue)
Pair(offset+varIntSize(rawValue), bitFieldOffset) Pair(offset+varIntSize(rawValue), bitFieldOffset)
} }
Double::class.java -> java.lang.Double::class.java, Double::class.java ->
if(flags.contains(SyncFlag.NoCompress)){ if(flags.contains(SyncFlag.NoCompress)){
byteBuffer.putDouble(offset, field.getDouble(owner)) byteBuffer.putDouble(offset, field.getDoubleAdaptive(owner))
Pair(offset+8, bitFieldOffset) Pair(offset+8, bitFieldOffset)
} }
else{ else{
val rawValue = val rawValue =
if(flags.contains(SyncFlag.FloatEndianSwap)) swapEndian(doubleToLong(field.getDouble(owner))) if(flags.contains(SyncFlag.FloatEndianSwap)) swapEndian(doubleToLong(field.getDoubleAdaptive(owner)))
else doubleToLong(field.getDouble(owner)) else doubleToLong(field.getDoubleAdaptive(owner))
writeVarInt(byteBuffer, offset, rawValue) writeVarInt(byteBuffer, offset, rawValue)
Pair(offset+varIntSize(rawValue), bitFieldOffset) Pair(offset+varIntSize(rawValue), bitFieldOffset)
} }
else -> Pair(offset, bitFieldOffset) else -> Pair(offset, bitFieldOffset)
} }
override fun deserialize( override fun deserializeExplicit(
field: Field, field: Field,
flags: Array<out SyncFlag>, flags: Array<out SyncFlag>,
owner: Any?, owner: Any?,
byteBuffer: ByteBuffer, byteBuffer: ByteBuffer,
offset: Int, offset: Int,
bitFieldOffset: Int bitFieldOffset: Int,
fieldType: Class<*>
): Pair<Int, Int> = ): Pair<Int, Int> =
when(field.type){ when(fieldType){
Boolean::class.java -> { java.lang.Boolean::class.java, Boolean::class.java -> {
field.setBoolean(owner, readBit(byteBuffer, bitFieldOffset)) field.setBooleanAdaptive(owner, readBit(byteBuffer, bitFieldOffset))
Pair(offset, bitFieldOffset+1) Pair(offset, bitFieldOffset+1)
} }
Byte::class.java -> { java.lang.Byte::class.java, Byte::class.java -> {
field.setByte(owner, byteBuffer.get(offset)) field.setByteAdaptive(owner, byteBuffer.get(offset))
Pair(offset+1, bitFieldOffset) Pair(offset+1, bitFieldOffset)
} }
Short::class.java -> java.lang.Short::class.java, Short::class.java ->
if(flags.contains(SyncFlag.NoCompress)){ if(flags.contains(SyncFlag.NoCompress)){
field.setShort(owner, byteBuffer.getShort(offset)) field.setShortAdaptive(owner, byteBuffer.getShort(offset))
Pair(offset+2, bitFieldOffset) Pair(offset+2, bitFieldOffset)
} }
else { else {
val rawValue = val rawValue =
if(flags.contains(SyncFlag.NonNegative)) readVarInt(byteBuffer, offset) if(flags.contains(SyncFlag.NonNegative)) readVarInt(byteBuffer, offset)
else zigZagDecode(readVarInt(byteBuffer, offset)) else zigZagDecode(readVarInt(byteBuffer, offset))
field.setShort(owner, rawValue.toShort()) field.setShortAdaptive(owner, rawValue.toShort())
Pair(offset+varIntSize(rawValue), bitFieldOffset) Pair(offset+varIntSize(rawValue), bitFieldOffset)
} }
Int::class.java -> java.lang.Integer::class.java, Int::class.java ->
if(flags.contains(SyncFlag.NoCompress)){ if(flags.contains(SyncFlag.NoCompress)){
field.setInt(owner, byteBuffer.getInt(offset)) field.setIntAdaptive(owner, byteBuffer.getInt(offset))
Pair(offset+4, bitFieldOffset) Pair(offset+4, bitFieldOffset)
} }
else { else {
val rawValue = val rawValue =
if(flags.contains(SyncFlag.NonNegative)) readVarInt(byteBuffer, offset) if(flags.contains(SyncFlag.NonNegative)) readVarInt(byteBuffer, offset)
else zigZagDecode(readVarInt(byteBuffer, offset)) else zigZagDecode(readVarInt(byteBuffer, offset))
field.setInt(owner, rawValue.toInt()) field.setIntAdaptive(owner, rawValue.toInt())
Pair(offset+varIntSize(rawValue), bitFieldOffset) Pair(offset+varIntSize(rawValue), bitFieldOffset)
} }
Long::class.java -> java.lang.Long::class.java, Long::class.java ->
if(flags.contains(SyncFlag.NoCompress)){ if(flags.contains(SyncFlag.NoCompress)){
field.setLong(owner, byteBuffer.getLong(offset)) field.setLongAdaptive(owner, byteBuffer.getLong(offset))
Pair(offset+8, bitFieldOffset) Pair(offset+8, bitFieldOffset)
} }
else { else {
val rawValue = val rawValue =
if(flags.contains(SyncFlag.NonNegative)) readVarInt(byteBuffer, offset) if(flags.contains(SyncFlag.NonNegative)) readVarInt(byteBuffer, offset)
else zigZagDecode(readVarInt(byteBuffer, offset)) else zigZagDecode(readVarInt(byteBuffer, offset))
field.setLong(owner, rawValue) field.setLongAdaptive(owner, rawValue)
Pair(offset+varIntSize(rawValue), bitFieldOffset) Pair(offset+varIntSize(rawValue), bitFieldOffset)
} }
Float::class.java -> java.lang.Float::class.java, Float::class.java ->
if(flags.contains(SyncFlag.NoCompress)){ if(flags.contains(SyncFlag.NoCompress)){
field.setFloat(owner, byteBuffer.getFloat(offset)) field.setFloatAdaptive(owner, byteBuffer.getFloat(offset))
Pair(offset+4, bitFieldOffset) Pair(offset+4, bitFieldOffset)
} }
else{ else{
@ -198,12 +209,12 @@ class PrimitiveSerializer private constructor() : Serializer(arrayOf(
val rawValue = val rawValue =
if(flags.contains(SyncFlag.FloatEndianSwap)) intToFloat(swapEndian(readVal.toInt())) if(flags.contains(SyncFlag.FloatEndianSwap)) intToFloat(swapEndian(readVal.toInt()))
else intToFloat(readVal.toInt()) else intToFloat(readVal.toInt())
field.setFloat(owner, rawValue) field.setFloatAdaptive(owner, rawValue)
Pair(offset+varIntSize(readVal), bitFieldOffset) Pair(offset+varIntSize(readVal), bitFieldOffset)
} }
Double::class.java -> java.lang.Double::class.java, Double::class.java ->
if(flags.contains(SyncFlag.NoCompress)){ if(flags.contains(SyncFlag.NoCompress)){
field.setDouble(owner, byteBuffer.getDouble(offset)) field.setDoubleAdaptive(owner, byteBuffer.getDouble(offset))
Pair(offset+8, bitFieldOffset) Pair(offset+8, bitFieldOffset)
} }
else{ else{
@ -211,7 +222,7 @@ class PrimitiveSerializer private constructor() : Serializer(arrayOf(
val rawValue = val rawValue =
if(flags.contains(SyncFlag.FloatEndianSwap)) longToDouble(swapEndian(readVal)) if(flags.contains(SyncFlag.FloatEndianSwap)) longToDouble(swapEndian(readVal))
else longToDouble(readVal) else longToDouble(readVal)
field.setDouble(owner, rawValue) field.setDoubleAdaptive(owner, rawValue)
Pair(offset+varIntSize(readVal), bitFieldOffset) Pair(offset+varIntSize(readVal), bitFieldOffset)
} }
else -> Pair(offset, bitFieldOffset) else -> Pair(offset, bitFieldOffset)

View File

@ -11,36 +11,63 @@ abstract class Serializer(registeredTypes: Array<Class<*>>) {
* Compute the size in bits that a field will occupy * Compute the size in bits that a field will occupy
* @return Size in the byteField (first) and bitField (second) that need to be allocated * @return Size in the byteField (first) and bitField (second) that need to be allocated
*/ */
abstract fun computeSize( fun computeSize(
field: Field, field: Field,
flags: Array<out SyncFlag>, flags: Array<out SyncFlag>,
owner: Any? owner: Any?
): Pair<Int, Int> = computeSizeExplicit(field, flags, owner, field.type)
abstract fun computeSizeExplicit(
field: Field,
flags: Array<out SyncFlag>,
owner: Any?,
fieldType: Class<*>
): Pair<Int, Int> ): Pair<Int, Int>
/** /**
* Serialize a field to the buffer * Serialize a field to the buffer
* @return The new offset (first) and bitFieldOffset (second) * @return The new offset (first) and bitFieldOffset (second)
*/ */
abstract fun serialize( fun serialize(
field: Field, field: Field,
flags: Array<out SyncFlag>, flags: Array<out SyncFlag>,
owner: Any?, owner: Any?,
byteBuffer: ByteBuffer, byteBuffer: ByteBuffer,
offset: Int, offset: Int,
bitFieldOffset: Int bitFieldOffset: Int
): Pair<Int, Int> = serializeExplicit(field, flags, owner, byteBuffer, offset, bitFieldOffset, field.type)
abstract fun serializeExplicit(
field: Field,
flags: Array<out SyncFlag>,
owner: Any?,
byteBuffer: ByteBuffer,
offset: Int,
bitFieldOffset: Int,
fieldType: Class<*>
): Pair<Int, Int> ): Pair<Int, Int>
/** /**
* Deserialize a field from the buffer * Deserialize a field from the buffer
* @return The new offset (first) and bitFieldOffset (second) * @return The new offset (first) and bitFieldOffset (second)
*/ */
abstract fun deserialize( fun deserialize(
field: Field, field: Field,
flags: Array<out SyncFlag>, flags: Array<out SyncFlag>,
owner: Any?, owner: Any?,
byteBuffer: ByteBuffer, byteBuffer: ByteBuffer,
offset: Int, offset: Int,
bitFieldOffset: Int bitFieldOffset: Int
): Pair<Int, Int> = deserializeExplicit(field, flags, owner, byteBuffer, offset, bitFieldOffset, field.type)
abstract fun deserializeExplicit(
field: Field,
flags: Array<out SyncFlag>,
owner: Any?,
byteBuffer: ByteBuffer,
offset: Int,
bitFieldOffset: Int,
fieldType: Class<*>
): Pair<Int, Int> ): Pair<Int, Int>
fun getRegisteredTypes(): Array<Class<*>> = Arrays.copyOf(registeredTypes, registeredTypes.size) fun getRegisteredTypes(): Array<Class<*>> = Arrays.copyOf(registeredTypes, registeredTypes.size)

View File

@ -1,23 +1,40 @@
package net.tofvesson.networking package net.tofvesson.networking
import java.lang.reflect.Field
import java.nio.ByteBuffer import java.nio.ByteBuffer
import java.security.NoSuchAlgorithmException import java.security.NoSuchAlgorithmException
import java.security.MessageDigest import java.security.MessageDigest
/** /**
* @param permissiveMismatchCheck This should essentially never be set to true aside from some *very* odd edge cases * @param permissiveMismatchCheck This should essentially never be set to true aside from some *very* odd edge cases
*/ */
class SyncHandler(defaultSerializers: Boolean = true, private val permissiveMismatchCheck: Boolean = false) { class SyncHandler(private val permissiveMismatchCheck: Boolean = false) {
private val toSync: ArrayList<Any> = ArrayList() private val toSync: ArrayList<Any> = ArrayList()
private val serializers: ArrayList<Serializer> = ArrayList() companion object {
private val serializers: ArrayList<Serializer> = ArrayList()
init { init {
if(defaultSerializers) { // Standard serializers
serializers.add(PrimitiveSerializer.singleton) serializers.add(PrimitiveSerializer.singleton)
serializers.add(PrimitiveArraySerializer.singleton) serializers.add(PrimitiveArraySerializer.singleton)
} }
fun registerSerializer(serializer: Serializer) {
if(!serializers.contains(serializer))
serializers.add(serializer)
}
fun unregisterSerializer(serializer: Serializer) {
serializers.remove(serializer)
}
fun clearSerializers() = serializers.clear()
fun getRegisteredSerializers() = serializers.toArray()
fun getCompatibleSerializer(type: Class<*>): Serializer {
for(serializer in serializers)
if(serializer.canSerialize(type))
return serializer
throw UnsupportedTypeException("Cannot find a compatible serializer for $type")
}
} }
fun registerSyncObject(value: Any){ fun registerSyncObject(value: Any){
@ -28,11 +45,6 @@ class SyncHandler(defaultSerializers: Boolean = true, private val permissiveMism
toSync.remove(value) toSync.remove(value)
} }
fun withSerializer(serializer: Serializer): SyncHandler {
if(!serializers.contains(serializer)) serializers.add(serializer)
return this
}
fun serialize(): ByteArray{ fun serialize(): ByteArray{
var headerSize = 0 var headerSize = 0
var totalSize = 0 var totalSize = 0
@ -120,12 +132,6 @@ class SyncHandler(defaultSerializers: Boolean = true, private val permissiveMism
} }
return Pair(byteSize, bitSize) return Pair(byteSize, bitSize)
} }
private fun getCompatibleSerializer(type: Class<*>): Serializer {
for(serializer in serializers)
if(serializer.canSerialize(type))
return serializer
throw UnsupportedTypeException("Cannot find a compatible serializer for $type")
}
private fun readObject(value: Any, buffer: ByteArray, offset: Int, bitOffset: Int) = readType(value.javaClass, value, buffer, offset, bitOffset) private fun readObject(value: Any, buffer: ByteArray, offset: Int, bitOffset: Int) = readType(value.javaClass, value, buffer, offset, bitOffset)
private fun readClass(value: Class<*>, buffer: ByteArray, offset: Int, bitOffset: Int) = readType(value, null, buffer, offset, bitOffset) private fun readClass(value: Class<*>, buffer: ByteArray, offset: Int, bitOffset: Int) = readType(value, null, buffer, offset, bitOffset)

View File

@ -2,7 +2,6 @@ package net.tofvesson.reflect
import sun.misc.Unsafe import sun.misc.Unsafe
import java.lang.reflect.AccessibleObject import java.lang.reflect.AccessibleObject
import java.lang.reflect.Field
var AccessibleObject.forceAccessible: Boolean var AccessibleObject.forceAccessible: Boolean
get() = this.isAccessible get() = this.isAccessible
@ -13,22 +12,13 @@ var AccessibleObject.forceAccessible: Boolean
unsafe.getAndSetObject(this, overrideOffset, value) unsafe.getAndSetObject(this, overrideOffset, value)
} }
fun <T> T.access(): T where T: AccessibleObject {
this.forceAccessible = true
return this
}
fun getUnsafe(): Unsafe{ fun getUnsafe(): Unsafe{
val theUnsafe = Unsafe::class.java.getDeclaredField("theUnsafe") val theUnsafe = Unsafe::class.java.getDeclaredField("theUnsafe")
theUnsafe.trySetAccessible() theUnsafe.trySetAccessible()
return theUnsafe.get(null) as Unsafe return theUnsafe.get(null) as Unsafe
}
fun Field.setStaticFinalValue(value: Any?){
val factory = Class.forName("jdk.internal.reflect.UnsafeFieldAccessorFactory").getDeclaredMethod("newFieldAccessor", Field::class.java, Boolean::class.java)
val isReadonly = Class.forName("jdk.internal.reflect.UnsafeQualifiedStaticFieldAccessorImpl").getDeclaredField("isReadOnly")
isReadonly.forceAccessible = true
factory.forceAccessible = true
val overrideAccessor = Field::class.java.getDeclaredField("overrideFieldAccessor")
overrideAccessor.forceAccessible = true
val accessor = factory.invoke(null, this, true)
isReadonly.setBoolean(accessor, false)
overrideAccessor.set(this, accessor)
this.forceAccessible = true
this.set(null, value)
} }

View File

@ -0,0 +1,33 @@
package net.tofvesson.reflect
import java.lang.reflect.Field
fun Field.setStaticFinalValue(value: Any?){
val factory = Class.forName("jdk.internal.reflect.UnsafeFieldAccessorFactory").getDeclaredMethod("newFieldAccessor", Field::class.java, Boolean::class.java)
val isReadonly = Class.forName("jdk.internal.reflect.UnsafeQualifiedStaticFieldAccessorImpl").getDeclaredField("isReadOnly")
isReadonly.forceAccessible = true
factory.forceAccessible = true
val overrideAccessor = Field::class.java.getDeclaredField("overrideFieldAccessor")
overrideAccessor.forceAccessible = true
val accessor = factory.invoke(null, this, true)
isReadonly.setBoolean(accessor, false)
overrideAccessor.set(this, accessor)
this.forceAccessible = true
this.set(null, value)
}
fun Field.getBooleanAdaptive(obj: Any?) = if(this.type.isPrimitive) access().getBoolean(obj) else access().get(obj) as Boolean
fun Field.getByteAdaptive(obj: Any?) = if(this.type.isPrimitive) access().getByte(obj) else access().get(obj) as Byte
fun Field.getShortAdaptive(obj: Any?) = if(this.type.isPrimitive) access().getShort(obj) else access().get(obj) as Short
fun Field.getIntAdaptive(obj: Any?) = if(this.type.isPrimitive) access().getInt(obj) else access().get(obj) as Int
fun Field.getLongAdaptive(obj: Any?) = if(this.type.isPrimitive) access().getLong(obj) else access().get(obj) as Long
fun Field.getFloatAdaptive(obj: Any?) = if(this.type.isPrimitive) access().getFloat(obj) else access().get(obj) as Float
fun Field.getDoubleAdaptive(obj: Any?) = if(this.type.isPrimitive) access().getDouble(obj) else access().get(obj) as Double
fun Field.setBooleanAdaptive(obj: Any?, value: Boolean) = if(this.type.isPrimitive) access().setBoolean(obj, value) else access().set(obj, value)
fun Field.setByteAdaptive(obj: Any?, value: Byte) = if(this.type.isPrimitive) access().setByte(obj, value) else access().set(obj, value)
fun Field.setShortAdaptive(obj: Any?, value: Short) = if(this.type.isPrimitive) access().setShort(obj, value) else access().set(obj, value)
fun Field.setIntAdaptive(obj: Any?, value: Int) = if(this.type.isPrimitive) access().setInt(obj, value) else access().set(obj, value)
fun Field.setLongAdaptive(obj: Any?, value: Long) = if(this.type.isPrimitive) access().setLong(obj, value) else access().set(obj, value)
fun Field.setFloatAdaptive(obj: Any?, value: Float) = if(this.type.isPrimitive) access().setFloat(obj, value) else access().set(obj, value)
fun Field.setDoubleAdaptive(obj: Any?, value: Double) = if(this.type.isPrimitive) access().setDouble(obj, value) else access().set(obj, value)