From 60cebfb966b116ddcbef0752e5597bc40cbcaf73 Mon Sep 17 00:00:00 2001 From: Gabriel Tofvesson Date: Wed, 25 Jul 2018 08:39:07 +0200 Subject: [PATCH] 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 --- src/Main.java | 46 ++++-- src/net/tofvesson/networking/DiffTracked.kt | 17 ++ .../tofvesson/networking/DiffTrackedArray.kt | 31 ++++ .../networking/DiffTrackedSerializer.kt | 129 +++++++++++++++ .../networking/PrimitiveArraySerializer.kt | 12 +- .../networking/PrimitiveSerializers.kt | 151 ++++++++++-------- src/net/tofvesson/networking/Serializer.kt | 33 +++- src/net/tofvesson/networking/SyncHandler.kt | 40 +++-- src/net/tofvesson/reflect/Accessible.kt | 20 +-- src/net/tofvesson/reflect/Field.kt | 33 ++++ 10 files changed, 391 insertions(+), 121 deletions(-) create mode 100644 src/net/tofvesson/networking/DiffTracked.kt create mode 100644 src/net/tofvesson/networking/DiffTrackedArray.kt create mode 100644 src/net/tofvesson/networking/DiffTrackedSerializer.kt create mode 100644 src/net/tofvesson/reflect/Field.kt diff --git a/src/Main.java b/src/Main.java index 0e93ec1..4d83fa6 100644 --- a/src/Main.java +++ b/src/Main.java @@ -1,5 +1,4 @@ -import net.tofvesson.networking.SyncHandler; -import net.tofvesson.networking.SyncedVar; +import net.tofvesson.networking.*; public class Main { @SyncedVar("NonNegative") @@ -20,8 +19,16 @@ public class Main { @SyncedVar public static boolean[] test = {true, false}; + @SyncedVar + public static DiffTracked tracker = new DiffTracked<>(5, Integer.class); + + @SyncedVar + public static DiffTrackedArray tracker2 = new DiffTrackedArray<>(Long.class, 8, i -> (long)i); + public static void main(String[] args){ Main testObject = new Main(); + SyncHandler.Companion.registerSerializer(DiffTrackedSerializer.Companion.getSingleton()); + SyncHandler sync = new SyncHandler(); sync.registerSyncObject(testObject); sync.registerSyncObject(Main.class); @@ -29,18 +36,27 @@ public class Main { // Generate mismatch check byte[] mismatchCheck = sync.generateMismatchCheck(); + // Trigger change flags + tracker.setValue(9); + tracker2.set(3L, 2); + tracker2.set(5L, 0); + // Generate snapshot of values to 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"+ staticTest+"\n\t"+ value+"\n\t"+ testObject.testbool+"\n\t"+ testbool1+"\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 testObject.syncTest = 20; @@ -52,8 +68,11 @@ public class Main { test[0] = false; test[1] = 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"+ staticTest+"\n\t"+ value+"\n\t"+ @@ -61,8 +80,12 @@ public class Main { testbool1+"\n\t"+ test[0]+"\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 if(!sync.doMismatchCheck(mismatchCheck)) throw new RuntimeException("Target sync mismatch"); @@ -70,15 +93,18 @@ public class Main { // Load snapshot values back sync.deserialize(ser); - System.out.println("Deserialized snapshot values:\n\t"+ + System.out.print("Deserialized snapshot values:\n\t"+ testObject.syncTest+"\n\t"+ staticTest+"\n\t"+ value+"\n\t"+ testObject.testbool+"\n\t"+ testbool1+"\n\t"+ test[0]+"\n\t"+ - test[1]+"\n\n" + - "Snapshot size: \"+ser.length+\" bytes" - ); + test[1]+"\n\t"+ + tracker); + for(Long value : tracker2.getValues()) + System.out.print("\n\t"+value); + System.out.println("\n\nSnapshot size: "+ser.length+" bytes"); + } } diff --git a/src/net/tofvesson/networking/DiffTracked.kt b/src/net/tofvesson/networking/DiffTracked.kt new file mode 100644 index 0000000..7c9be7e --- /dev/null +++ b/src/net/tofvesson/networking/DiffTracked.kt @@ -0,0 +1,17 @@ +package net.tofvesson.networking + +import java.util.* + +class DiffTracked(initial: T, val valueType: Class) { + 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)!! +} \ No newline at end of file diff --git a/src/net/tofvesson/networking/DiffTrackedArray.kt b/src/net/tofvesson/networking/DiffTrackedArray.kt new file mode 100644 index 0000000..6287a36 --- /dev/null +++ b/src/net/tofvesson/networking/DiffTrackedArray.kt @@ -0,0 +1,31 @@ +package net.tofvesson.networking + +class DiffTrackedArray(val elementType: Class, val size: Int, gen: (Int) -> T) { + + val values: Array = java.lang.reflect.Array.newInstance(elementType, size) as Array + 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() +} \ No newline at end of file diff --git a/src/net/tofvesson/networking/DiffTrackedSerializer.kt b/src/net/tofvesson/networking/DiffTrackedSerializer.kt new file mode 100644 index 0000000..5945908 --- /dev/null +++ b/src/net/tofvesson/networking/DiffTrackedSerializer.kt @@ -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, owner: Any?, fieldType: Class<*>): Pair = + 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, owner: Any?, byteBuffer: ByteBuffer, offset: Int, bitFieldOffset: Int, fieldType: Class<*>): Pair = + 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, owner: Any?, byteBuffer: ByteBuffer, offset: Int, bitFieldOffset: Int, fieldType: Class<*>): Pair = + 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 + + 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?) +} \ No newline at end of file diff --git a/src/net/tofvesson/networking/PrimitiveArraySerializer.kt b/src/net/tofvesson/networking/PrimitiveArraySerializer.kt index 98276d2..2a4141c 100644 --- a/src/net/tofvesson/networking/PrimitiveArraySerializer.kt +++ b/src/net/tofvesson/networking/PrimitiveArraySerializer.kt @@ -18,11 +18,11 @@ class PrimitiveArraySerializer private constructor(): Serializer(arrayOf( val singleton = PrimitiveArraySerializer() } - override fun computeSize(field: Field, flags: Array, owner: Any?): Pair { + override fun computeSizeExplicit(field: Field, flags: Array, owner: Any?, fieldType: Class<*>): Pair { val arrayLength = java.lang.reflect.Array.getLength(field.get(owner)) var byteSize = if(flags.contains(knownSize)) 0 else varIntSize(arrayLength.toLong()) var bitSize = 0 - when (field.type) { + when (fieldType) { BooleanArray::class.java -> bitSize = arrayLength ByteArray::class.java -> byteSize += arrayLength ShortArray::class.java -> @@ -74,7 +74,7 @@ class PrimitiveArraySerializer private constructor(): Serializer(arrayOf( return Pair(byteSize, bitSize) } - override fun serialize(field: Field, flags: Array, owner: Any?, byteBuffer: ByteBuffer, offset: Int, bitFieldOffset: Int): Pair { + override fun serializeExplicit(field: Field, flags: Array, owner: Any?, byteBuffer: ByteBuffer, offset: Int, bitFieldOffset: Int, fieldType: Class<*>): Pair { val arrayLength = java.lang.reflect.Array.getLength(field.get(owner)) var localByteOffset = offset var localBitOffset = bitFieldOffset @@ -82,7 +82,7 @@ class PrimitiveArraySerializer private constructor(): Serializer(arrayOf( writeVarInt(byteBuffer, offset, arrayLength.toLong()) localByteOffset += varIntSize(arrayLength.toLong()) } - when (field.type) { + when (fieldType) { BooleanArray::class.java -> for(value in field.get(owner) as BooleanArray) writeBit(value, byteBuffer, localBitOffset++) @@ -167,7 +167,7 @@ class PrimitiveArraySerializer private constructor(): Serializer(arrayOf( return Pair(localByteOffset, localBitOffset) } - override fun deserialize(field: Field, flags: Array, owner: Any?, byteBuffer: ByteBuffer, offset: Int, bitFieldOffset: Int): Pair { + override fun deserializeExplicit(field: Field, flags: Array, owner: Any?, byteBuffer: ByteBuffer, offset: Int, bitFieldOffset: Int, fieldType: Class<*>): Pair { var localByteOffset = offset var localBitOffset = bitFieldOffset 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) else field.get(owner) - when (field.type) { + when (fieldType) { BooleanArray::class.java -> { val booleanTarget = target as BooleanArray for (index in 0 until arrayLength) diff --git a/src/net/tofvesson/networking/PrimitiveSerializers.kt b/src/net/tofvesson/networking/PrimitiveSerializers.kt index 76eabdd..66a89b9 100644 --- a/src/net/tofvesson/networking/PrimitiveSerializers.kt +++ b/src/net/tofvesson/networking/PrimitiveSerializers.kt @@ -1,196 +1,207 @@ package net.tofvesson.networking +import net.tofvesson.reflect.* import java.lang.reflect.Field import java.nio.ByteBuffer class PrimitiveSerializer private constructor() : Serializer(arrayOf( Boolean::class.java, + java.lang.Boolean::class.java, Byte::class.java, + java.lang.Byte::class.java, Short::class.java, + java.lang.Short::class.java, Int::class.java, + java.lang.Integer::class.java, Long::class.java, + java.lang.Long::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() } - override fun computeSize( + override fun computeSizeExplicit( field: Field, flags: Array, - owner: Any? + owner: Any?, + fieldType: Class<*> ): Pair = - when(field.type){ - Boolean::class.java -> Pair(0, 1) - Byte::class.java -> Pair(1, 0) - Short::class.java -> + when(fieldType){ + java.lang.Boolean::class.java, Boolean::class.java -> Pair(0, 1) + java.lang.Byte::class.java, Byte::class.java -> Pair(1, 0) + java.lang.Short::class.java, Short::class.java -> if(flags.contains(SyncFlag.NoCompress)) Pair(2, 0) else Pair(varIntSize( - if(flags.contains(SyncFlag.NonNegative)) field.getShort(owner).toLong() - else zigZagEncode(field.getShort(owner).toLong()) + if(flags.contains(SyncFlag.NonNegative)) field.getShortAdaptive(owner).toLong() + else zigZagEncode(field.getShortAdaptive(owner).toLong()) ), 0) - Int::class.java -> + java.lang.Integer::class.java, Int::class.java -> if(flags.contains(SyncFlag.NoCompress)) Pair(4, 0) else Pair(varIntSize( - if(flags.contains(SyncFlag.NonNegative)) field.getInt(owner).toLong() - else zigZagEncode(field.getInt(owner).toLong()) + if(flags.contains(SyncFlag.NonNegative)) field.getIntAdaptive(owner).toLong() + else zigZagEncode(field.getIntAdaptive(owner).toLong()) ), 0) - Long::class.java -> + java.lang.Long::class.java, Long::class.java -> if(flags.contains(SyncFlag.NoCompress)) Pair(8, 0) else Pair(varIntSize( - if(flags.contains(SyncFlag.NonNegative)) field.getLong(owner) - else zigZagEncode(field.getLong(owner)) + if(flags.contains(SyncFlag.NonNegative)) field.getLongAdaptive(owner) + else zigZagEncode(field.getLongAdaptive(owner)) ), 0) - Float::class.java -> + java.lang.Float::class.java, Float::class.java -> if(flags.contains(SyncFlag.NoCompress)) Pair(4, 0) else Pair(varIntSize( - if(flags.contains(SyncFlag.FloatEndianSwap)) bitConvert(swapEndian(floatToInt(field.getFloat(owner)))) - else bitConvert(floatToInt(field.getFloat(owner))) + if(flags.contains(SyncFlag.FloatEndianSwap)) bitConvert(swapEndian(floatToInt(field.getFloatAdaptive(owner)))) + else bitConvert(floatToInt(field.getFloatAdaptive(owner))) ), 0) - Double::class.java -> + java.lang.Double::class.java, Double::class.java -> if(flags.contains(SyncFlag.NoCompress)) Pair(8, 0) else Pair(varIntSize( - if(flags.contains(SyncFlag.FloatEndianSwap)) swapEndian(doubleToLong(field.getDouble(owner))) - else doubleToLong(field.getDouble(owner)) + if(flags.contains(SyncFlag.FloatEndianSwap)) swapEndian(doubleToLong(field.getDoubleAdaptive(owner))) + else doubleToLong(field.getDoubleAdaptive(owner)) ), 0) else -> Pair(0, 0) } - override fun serialize( + override fun serializeExplicit( field: Field, flags: Array, owner: Any?, byteBuffer: ByteBuffer, offset: Int, - bitFieldOffset: Int + bitFieldOffset: Int, + fieldType: Class<*> ): Pair = - when(field.type){ - Boolean::class.java -> { - writeBit(field.getBoolean(owner), byteBuffer, bitFieldOffset) + when(fieldType){ + java.lang.Boolean::class.java, Boolean::class.java -> { + writeBit(field.getBooleanAdaptive(owner), byteBuffer, bitFieldOffset) Pair(offset, bitFieldOffset+1) } - Byte::class.java -> { - byteBuffer.put(offset, field.getByte(owner)) + java.lang.Byte::class.java, Byte::class.java -> { + byteBuffer.put(offset, field.getByteAdaptive(owner)) Pair(offset+1, bitFieldOffset) } - Short::class.java -> + java.lang.Short::class.java, Short::class.java -> if(flags.contains(SyncFlag.NoCompress)){ - byteBuffer.putShort(offset, field.getShort(owner)) + byteBuffer.putShort(offset, field.getShortAdaptive(owner)) Pair(offset+2, bitFieldOffset) } else { val rawValue = - if(flags.contains(SyncFlag.NonNegative)) field.getShort(owner).toLong() - else zigZagEncode(field.getShort(owner).toLong()) + if(flags.contains(SyncFlag.NonNegative)) field.getShortAdaptive(owner).toLong() + else zigZagEncode(field.getShortAdaptive(owner).toLong()) writeVarInt(byteBuffer, offset, rawValue) Pair(offset+varIntSize(rawValue), bitFieldOffset) } - Int::class.java -> + java.lang.Integer::class.java, Int::class.java -> if(flags.contains(SyncFlag.NoCompress)){ - byteBuffer.putInt(offset, field.getInt(owner)) + byteBuffer.putInt(offset, field.getIntAdaptive(owner)) Pair(offset+4, bitFieldOffset) } else { val rawValue = - if(flags.contains(SyncFlag.NonNegative)) field.getInt(owner).toLong() - else zigZagEncode(field.getInt(owner).toLong()) + if(flags.contains(SyncFlag.NonNegative)) field.getIntAdaptive(owner).toLong() + else zigZagEncode(field.getIntAdaptive(owner).toLong()) writeVarInt(byteBuffer, offset, rawValue) Pair(offset+varIntSize(rawValue), bitFieldOffset) } - Long::class.java -> + java.lang.Long::class.java, Long::class.java -> if(flags.contains(SyncFlag.NoCompress)){ - byteBuffer.putLong(offset, field.getLong(owner)) + byteBuffer.putLong(offset, field.getLongAdaptive(owner)) Pair(offset+8, bitFieldOffset) } else { val rawValue = - if(flags.contains(SyncFlag.NonNegative)) field.getLong(owner) - else zigZagEncode(field.getLong(owner)) + if(flags.contains(SyncFlag.NonNegative)) field.getLongAdaptive(owner) + else zigZagEncode(field.getLongAdaptive(owner)) writeVarInt(byteBuffer, offset, rawValue) Pair(offset+varIntSize(rawValue), bitFieldOffset) } - Float::class.java -> + java.lang.Float::class.java, Float::class.java -> if(flags.contains(SyncFlag.NoCompress)){ - byteBuffer.putFloat(offset, field.getFloat(owner)) + byteBuffer.putFloat(offset, field.getFloatAdaptive(owner)) Pair(offset+4, bitFieldOffset) } else{ val rawValue = - if(flags.contains(SyncFlag.FloatEndianSwap)) bitConvert(swapEndian(floatToInt(field.getFloat(owner)))) - else bitConvert(floatToInt(field.getFloat(owner))) + if(flags.contains(SyncFlag.FloatEndianSwap)) bitConvert(swapEndian(floatToInt(field.getFloatAdaptive(owner)))) + else bitConvert(floatToInt(field.getFloatAdaptive(owner))) writeVarInt(byteBuffer, offset, rawValue) Pair(offset+varIntSize(rawValue), bitFieldOffset) } - Double::class.java -> + java.lang.Double::class.java, Double::class.java -> if(flags.contains(SyncFlag.NoCompress)){ - byteBuffer.putDouble(offset, field.getDouble(owner)) + byteBuffer.putDouble(offset, field.getDoubleAdaptive(owner)) Pair(offset+8, bitFieldOffset) } else{ val rawValue = - if(flags.contains(SyncFlag.FloatEndianSwap)) swapEndian(doubleToLong(field.getDouble(owner))) - else doubleToLong(field.getDouble(owner)) + if(flags.contains(SyncFlag.FloatEndianSwap)) swapEndian(doubleToLong(field.getDoubleAdaptive(owner))) + else doubleToLong(field.getDoubleAdaptive(owner)) writeVarInt(byteBuffer, offset, rawValue) Pair(offset+varIntSize(rawValue), bitFieldOffset) } else -> Pair(offset, bitFieldOffset) } - override fun deserialize( + override fun deserializeExplicit( field: Field, flags: Array, owner: Any?, byteBuffer: ByteBuffer, offset: Int, - bitFieldOffset: Int + bitFieldOffset: Int, + fieldType: Class<*> ): Pair = - when(field.type){ - Boolean::class.java -> { - field.setBoolean(owner, readBit(byteBuffer, bitFieldOffset)) + when(fieldType){ + java.lang.Boolean::class.java, Boolean::class.java -> { + field.setBooleanAdaptive(owner, readBit(byteBuffer, bitFieldOffset)) Pair(offset, bitFieldOffset+1) } - Byte::class.java -> { - field.setByte(owner, byteBuffer.get(offset)) + java.lang.Byte::class.java, Byte::class.java -> { + field.setByteAdaptive(owner, byteBuffer.get(offset)) Pair(offset+1, bitFieldOffset) } - Short::class.java -> + java.lang.Short::class.java, Short::class.java -> if(flags.contains(SyncFlag.NoCompress)){ - field.setShort(owner, byteBuffer.getShort(offset)) + field.setShortAdaptive(owner, byteBuffer.getShort(offset)) Pair(offset+2, bitFieldOffset) } else { val rawValue = if(flags.contains(SyncFlag.NonNegative)) readVarInt(byteBuffer, offset) else zigZagDecode(readVarInt(byteBuffer, offset)) - field.setShort(owner, rawValue.toShort()) + field.setShortAdaptive(owner, rawValue.toShort()) Pair(offset+varIntSize(rawValue), bitFieldOffset) } - Int::class.java -> + java.lang.Integer::class.java, Int::class.java -> if(flags.contains(SyncFlag.NoCompress)){ - field.setInt(owner, byteBuffer.getInt(offset)) + field.setIntAdaptive(owner, byteBuffer.getInt(offset)) Pair(offset+4, bitFieldOffset) } else { val rawValue = if(flags.contains(SyncFlag.NonNegative)) readVarInt(byteBuffer, offset) else zigZagDecode(readVarInt(byteBuffer, offset)) - field.setInt(owner, rawValue.toInt()) + field.setIntAdaptive(owner, rawValue.toInt()) Pair(offset+varIntSize(rawValue), bitFieldOffset) } - Long::class.java -> + java.lang.Long::class.java, Long::class.java -> if(flags.contains(SyncFlag.NoCompress)){ - field.setLong(owner, byteBuffer.getLong(offset)) + field.setLongAdaptive(owner, byteBuffer.getLong(offset)) Pair(offset+8, bitFieldOffset) } else { val rawValue = if(flags.contains(SyncFlag.NonNegative)) readVarInt(byteBuffer, offset) else zigZagDecode(readVarInt(byteBuffer, offset)) - field.setLong(owner, rawValue) + field.setLongAdaptive(owner, rawValue) Pair(offset+varIntSize(rawValue), bitFieldOffset) } - Float::class.java -> + java.lang.Float::class.java, Float::class.java -> if(flags.contains(SyncFlag.NoCompress)){ - field.setFloat(owner, byteBuffer.getFloat(offset)) + field.setFloatAdaptive(owner, byteBuffer.getFloat(offset)) Pair(offset+4, bitFieldOffset) } else{ @@ -198,12 +209,12 @@ class PrimitiveSerializer private constructor() : Serializer(arrayOf( val rawValue = if(flags.contains(SyncFlag.FloatEndianSwap)) intToFloat(swapEndian(readVal.toInt())) else intToFloat(readVal.toInt()) - field.setFloat(owner, rawValue) + field.setFloatAdaptive(owner, rawValue) Pair(offset+varIntSize(readVal), bitFieldOffset) } - Double::class.java -> + java.lang.Double::class.java, Double::class.java -> if(flags.contains(SyncFlag.NoCompress)){ - field.setDouble(owner, byteBuffer.getDouble(offset)) + field.setDoubleAdaptive(owner, byteBuffer.getDouble(offset)) Pair(offset+8, bitFieldOffset) } else{ @@ -211,7 +222,7 @@ class PrimitiveSerializer private constructor() : Serializer(arrayOf( val rawValue = if(flags.contains(SyncFlag.FloatEndianSwap)) longToDouble(swapEndian(readVal)) else longToDouble(readVal) - field.setDouble(owner, rawValue) + field.setDoubleAdaptive(owner, rawValue) Pair(offset+varIntSize(readVal), bitFieldOffset) } else -> Pair(offset, bitFieldOffset) diff --git a/src/net/tofvesson/networking/Serializer.kt b/src/net/tofvesson/networking/Serializer.kt index 9b1acd7..c459e0c 100644 --- a/src/net/tofvesson/networking/Serializer.kt +++ b/src/net/tofvesson/networking/Serializer.kt @@ -11,36 +11,63 @@ abstract class Serializer(registeredTypes: Array>) { * Compute the size in bits that a field will occupy * @return Size in the byteField (first) and bitField (second) that need to be allocated */ - abstract fun computeSize( + fun computeSize( field: Field, flags: Array, owner: Any? + ): Pair = computeSizeExplicit(field, flags, owner, field.type) + + abstract fun computeSizeExplicit( + field: Field, + flags: Array, + owner: Any?, + fieldType: Class<*> ): Pair /** * Serialize a field to the buffer * @return The new offset (first) and bitFieldOffset (second) */ - abstract fun serialize( + fun serialize( field: Field, flags: Array, owner: Any?, byteBuffer: ByteBuffer, offset: Int, bitFieldOffset: Int + ): Pair = serializeExplicit(field, flags, owner, byteBuffer, offset, bitFieldOffset, field.type) + + abstract fun serializeExplicit( + field: Field, + flags: Array, + owner: Any?, + byteBuffer: ByteBuffer, + offset: Int, + bitFieldOffset: Int, + fieldType: Class<*> ): Pair /** * Deserialize a field from the buffer * @return The new offset (first) and bitFieldOffset (second) */ - abstract fun deserialize( + fun deserialize( field: Field, flags: Array, owner: Any?, byteBuffer: ByteBuffer, offset: Int, bitFieldOffset: Int + ): Pair = deserializeExplicit(field, flags, owner, byteBuffer, offset, bitFieldOffset, field.type) + + abstract fun deserializeExplicit( + field: Field, + flags: Array, + owner: Any?, + byteBuffer: ByteBuffer, + offset: Int, + bitFieldOffset: Int, + fieldType: Class<*> ): Pair fun getRegisteredTypes(): Array> = Arrays.copyOf(registeredTypes, registeredTypes.size) diff --git a/src/net/tofvesson/networking/SyncHandler.kt b/src/net/tofvesson/networking/SyncHandler.kt index c96677e..faae0ad 100644 --- a/src/net/tofvesson/networking/SyncHandler.kt +++ b/src/net/tofvesson/networking/SyncHandler.kt @@ -1,23 +1,40 @@ package net.tofvesson.networking -import java.lang.reflect.Field import java.nio.ByteBuffer import java.security.NoSuchAlgorithmException import java.security.MessageDigest - /** * @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 = ArrayList() - private val serializers: ArrayList = ArrayList() + companion object { + private val serializers: ArrayList = ArrayList() - init { - if(defaultSerializers) { + init { + // Standard serializers serializers.add(PrimitiveSerializer.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){ @@ -28,11 +45,6 @@ class SyncHandler(defaultSerializers: Boolean = true, private val permissiveMism toSync.remove(value) } - fun withSerializer(serializer: Serializer): SyncHandler { - if(!serializers.contains(serializer)) serializers.add(serializer) - return this - } - fun serialize(): ByteArray{ var headerSize = 0 var totalSize = 0 @@ -120,12 +132,6 @@ class SyncHandler(defaultSerializers: Boolean = true, private val permissiveMism } 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 readClass(value: Class<*>, buffer: ByteArray, offset: Int, bitOffset: Int) = readType(value, null, buffer, offset, bitOffset) diff --git a/src/net/tofvesson/reflect/Accessible.kt b/src/net/tofvesson/reflect/Accessible.kt index 0756f87..7a5dde6 100644 --- a/src/net/tofvesson/reflect/Accessible.kt +++ b/src/net/tofvesson/reflect/Accessible.kt @@ -2,7 +2,6 @@ package net.tofvesson.reflect import sun.misc.Unsafe import java.lang.reflect.AccessibleObject -import java.lang.reflect.Field var AccessibleObject.forceAccessible: Boolean get() = this.isAccessible @@ -13,22 +12,13 @@ var AccessibleObject.forceAccessible: Boolean unsafe.getAndSetObject(this, overrideOffset, value) } +fun T.access(): T where T: AccessibleObject { + this.forceAccessible = true + return this +} + fun getUnsafe(): Unsafe{ val theUnsafe = Unsafe::class.java.getDeclaredField("theUnsafe") theUnsafe.trySetAccessible() 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) } \ No newline at end of file diff --git a/src/net/tofvesson/reflect/Field.kt b/src/net/tofvesson/reflect/Field.kt new file mode 100644 index 0000000..2e54b42 --- /dev/null +++ b/src/net/tofvesson/reflect/Field.kt @@ -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) \ No newline at end of file