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