From 2af938169cba902f34e1ba099df992ed9e392cd9 Mon Sep 17 00:00:00 2001 From: Gabriel Tofvesson Date: Fri, 27 Jul 2018 19:44:52 +0200 Subject: [PATCH] Added specialized Vector3 as an example Added a specialized Vector3 serializer Fixed issues relating to the differences between header bits and data bits Fixed issues with deserializing data with mismatched sizes compared to the surrogate data Added WriteBuffer and ReadBuffer for simpler serialization and deserialization Added annotation to prevent superclass serialization --- src/Main.java | 27 +- src/net/tofvesson/math/Extensions.kt | 13 + src/net/tofvesson/math/MathSerializer.kt | 193 +++++++++++ src/net/tofvesson/math/Vector3.kt | 29 ++ src/net/tofvesson/networking/Arithmetic.kt | 28 +- .../networking/DiffTrackedSerializer.kt | 169 +++++----- src/net/tofvesson/networking/Holder.kt | 7 + .../InsufficientCapacityException.kt | 9 + .../networking/MismatchedFlagException.kt | 9 + .../tofvesson/networking/NoUpwardCascade.kt | 6 + .../networking/PrimitiveArraySerializer.kt | 299 +++++++----------- .../networking/PrimitiveSerializers.kt | 258 +++++---------- src/net/tofvesson/networking/ReadBuffer.kt | 129 ++++++++ src/net/tofvesson/networking/Serializer.kt | 36 +-- src/net/tofvesson/networking/SyncHandler.kt | 139 ++++---- src/net/tofvesson/networking/WriteBuffer.kt | 154 +++++++++ src/net/tofvesson/networking/WriteState.kt | 22 ++ src/net/tofvesson/support/Pair.kt | 3 + 18 files changed, 953 insertions(+), 577 deletions(-) create mode 100644 src/net/tofvesson/math/Extensions.kt create mode 100644 src/net/tofvesson/math/MathSerializer.kt create mode 100644 src/net/tofvesson/math/Vector3.kt create mode 100644 src/net/tofvesson/networking/Holder.kt create mode 100644 src/net/tofvesson/networking/InsufficientCapacityException.kt create mode 100644 src/net/tofvesson/networking/MismatchedFlagException.kt create mode 100644 src/net/tofvesson/networking/NoUpwardCascade.kt create mode 100644 src/net/tofvesson/networking/ReadBuffer.kt create mode 100644 src/net/tofvesson/networking/WriteBuffer.kt create mode 100644 src/net/tofvesson/networking/WriteState.kt create mode 100644 src/net/tofvesson/support/Pair.kt diff --git a/src/Main.java b/src/Main.java index 4d83fa6..dfec8d3 100644 --- a/src/Main.java +++ b/src/Main.java @@ -1,3 +1,5 @@ +import net.tofvesson.math.MathSerializer; +import net.tofvesson.math.Vector3; import net.tofvesson.networking.*; public class Main { @@ -25,10 +27,13 @@ public class Main { @SyncedVar public static DiffTrackedArray tracker2 = new DiffTrackedArray<>(Long.class, 8, i -> (long)i); + @SyncedVar({MathSerializer.flagLowCompressionRotation, MathSerializer.flagPassiveCompress}) + public static Vector3 lookDirection = new Vector3(60, 90, 80); + public static void main(String[] args){ Main testObject = new Main(); - SyncHandler.Companion.registerSerializer(DiffTrackedSerializer.Companion.getSingleton()); + SyncHandler.Companion.registerSerializer(MathSerializer.Companion.getSingleton()); SyncHandler sync = new SyncHandler(); sync.registerSyncObject(testObject); sync.registerSyncObject(Main.class); @@ -40,9 +45,11 @@ public class Main { tracker.setValue(9); tracker2.set(3L, 2); tracker2.set(5L, 0); + lookDirection.setX(355); + lookDirection.setZ(0); // Generate snapshot of values to serialize - byte[] ser = sync.serialize(); + byte[] ser = sync.serialize().array(); System.out.print("Created and serialized snapshot of field values:\n\t"+ testObject.syncTest+"\n\t"+ @@ -52,7 +59,8 @@ public class Main { testbool1+"\n\t"+ test[0]+"\n\t"+ test[1]+"\n\t"+ - tracker + tracker+"\n\t"+ + lookDirection ); for(Long value : tracker2.getValues()) System.out.print("\n\t"+value); @@ -64,13 +72,13 @@ public class Main { value = 9.0f; testObject.testbool = true; testbool1 = false; - test = new boolean[3]; test[0] = false; test[1] = true; - test[2] = true; tracker.setValue(400); - tracker2.set(8L, 2); tracker2.set(100L, 0); + tracker2.set(8L, 2); + lookDirection.setX(200); + lookDirection.setZ(360); System.out.print("Set a new state of test values:\n\t"+ testObject.syncTest+"\n\t"+ @@ -80,8 +88,8 @@ public class Main { testbool1+"\n\t"+ test[0]+"\n\t"+ test[1]+"\n\t"+ - test[2]+"\n\t"+ - tracker + tracker+"\n\t"+ + lookDirection ); for(Long value : tracker2.getValues()) System.out.print("\n\t"+value); @@ -101,7 +109,8 @@ public class Main { testbool1+"\n\t"+ test[0]+"\n\t"+ test[1]+"\n\t"+ - tracker); + tracker+"\n\t"+ + lookDirection); 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/math/Extensions.kt b/src/net/tofvesson/math/Extensions.kt new file mode 100644 index 0000000..ebe1bf3 --- /dev/null +++ b/src/net/tofvesson/math/Extensions.kt @@ -0,0 +1,13 @@ +package net.tofvesson.math + +fun Boolean.toNumber() = if(this) 1 else 0 +fun Number.toBoolean() = this!=0 + +fun Int.collapseLowerByte(): Int = + ((this ushr 7) or + (this ushr 6) or + (this ushr 5) or + (this ushr 4) or + (this ushr 3) or + (this ushr 2) or + (this ushr 1)) and 1 \ No newline at end of file diff --git a/src/net/tofvesson/math/MathSerializer.kt b/src/net/tofvesson/math/MathSerializer.kt new file mode 100644 index 0000000..faff4be --- /dev/null +++ b/src/net/tofvesson/math/MathSerializer.kt @@ -0,0 +1,193 @@ +package net.tofvesson.math + +import net.tofvesson.networking.* +import net.tofvesson.reflect.access +import java.lang.reflect.Field + +@Suppress("UNCHECKED_CAST", "MemberVisibilityCanBePrivate") +class MathSerializer: Serializer(arrayOf( + Vector3::class.java +)){ + companion object { + val singleton = MathSerializer() + const val flagHighCompressionRotation = "rotCompress1" + const val flagMidCompressionRotation = "rotCompress2" + const val flagLowCompressionRotation = "rotCompress3" + const val flagPassiveCompress = "passiveCompress" + private val compressedRotationVector1 = SyncFlag.createFlag(flagHighCompressionRotation) + private val compressedRotationVector2 = SyncFlag.createFlag(flagMidCompressionRotation) + private val compressedRotationVector3 = SyncFlag.createFlag(flagLowCompressionRotation) + private val passiveCompress = SyncFlag.createFlag(flagPassiveCompress) + + private val xField = Vector3::class.java.getDeclaredField("_x").access() + private val yField = Vector3::class.java.getDeclaredField("_y").access() + private val zField = Vector3::class.java.getDeclaredField("_z").access() + + private val diffValue = DiffTracked::class.java.getDeclaredField("_value") + } + + override fun computeSizeExplicit(field: Field, flags: Array, owner: Any?, state: WriteState, fieldType: Class<*>) { + when (fieldType) { + Vector3::class.java -> { + val vector = field.access().get(owner) as Vector3 + val xDiff = xField.get(vector) as DiffTracked + val yDiff = yField.get(vector) as DiffTracked + val zDiff = zField.get(vector) as DiffTracked + val c1 = flags.contains(compressedRotationVector1) + val c2 = flags.contains(compressedRotationVector2) + val c3 = flags.contains(compressedRotationVector3) + if ((c1 and c2) or (c2 and c3) or (c1 and c3)) throw MismatchedFlagException("Cannot have more than one rotation compression flag!") + + state.registerHeader(3) + when { + c1 || c2 || c3 -> { + val bytes = + when { + c1 -> 1 + c2 -> 2 + else -> 3 + } + state + .registerBytes(if (xDiff.hasChanged()) varIntSize(xDiff.value.encodeRotation(bytes).toLong()) else 0) + .registerBytes(if (yDiff.hasChanged()) varIntSize(yDiff.value.encodeRotation(bytes).toLong()) else 0) + .registerBytes(if (zDiff.hasChanged()) varIntSize(zDiff.value.encodeRotation(bytes).toLong()) else 0) + } + else -> { + val floatSerializer = SyncHandler.getCompatibleSerializer(Float::class.java) + if (xDiff.hasChanged()) floatSerializer.computeSizeExplicit(diffValue, flags, xDiff, state, Float::class.java) + if (yDiff.hasChanged()) floatSerializer.computeSizeExplicit(diffValue, flags, yDiff, state, Float::class.java) + if (zDiff.hasChanged()) floatSerializer.computeSizeExplicit(diffValue, flags, zDiff, state, Float::class.java) + } + } + } + else -> throwInvalidType(fieldType) + } + } + + override fun serializeExplicit(field: Field, _flags: Array, owner: Any?, writeBuffer: WriteBuffer, fieldType: Class<*>) { + when (fieldType) { + Vector3::class.java -> { + var flags = _flags + val vector = field.access().get(owner) as Vector3 + val c1 = flags.contains(compressedRotationVector1) + val c2 = flags.contains(compressedRotationVector2) + val c3 = flags.contains(compressedRotationVector3) + val xDiff = xField.get(vector) as DiffTracked + val yDiff = yField.get(vector) as DiffTracked + val zDiff = zField.get(vector) as DiffTracked + + if ((c1 and c2) or (c2 and c3) or (c1 and c3)) throw MismatchedFlagException("Cannot have more than one rotation compression flag!") + + writeBuffer.writeHeader(xDiff.hasChanged()) + writeBuffer.writeHeader(yDiff.hasChanged()) + writeBuffer.writeHeader(zDiff.hasChanged()) + + when { + c1 || c2 || c3 -> { + val intSerializer = SyncHandler.getCompatibleSerializer(Int::class.java) + val bytes = + when { + c1 -> 1 + c2 -> 2 + else -> 3 + } + + val xHolder = Holder(vector.x.encodeRotation(bytes)) + val yHolder = Holder(vector.y.encodeRotation(bytes)) + val zHolder = Holder(vector.z.encodeRotation(bytes)) + + val nn = flags.contains(SyncFlag.NonNegative) + val pc = flags.contains(passiveCompress) + val resize = nn.toNumber() + pc.toNumber() + if(nn || pc){ + var track = 0 + flags = Array(flags.size - resize){ + if(flags[track]==SyncFlag.NonNegative && nn){ + if(flags[++track]==SyncFlag.NoCompress && pc) + flags[++track] + else flags[track] + } + else if(flags[track]==SyncFlag.NoCompress && nn){ + if(flags[++track]==SyncFlag.NonNegative && pc) + flags[++track] + else flags[track] + }else flags[track] + } + } + + if (xDiff.hasChanged()) intSerializer.serializeExplicit(Holder.valueField, flags, xHolder, writeBuffer, Int::class.java) + if (yDiff.hasChanged()) intSerializer.serializeExplicit(Holder.valueField, flags, yHolder, writeBuffer, Int::class.java) + if (zDiff.hasChanged()) intSerializer.serializeExplicit(Holder.valueField, flags, zHolder, writeBuffer, Int::class.java) + } + else -> { + val floatSerializer = SyncHandler.getCompatibleSerializer(Float::class.java) + + if (xDiff.hasChanged()) floatSerializer.serializeExplicit(diffValue, _flags, xDiff, writeBuffer, Float::class.java) + if (yDiff.hasChanged()) floatSerializer.serializeExplicit(diffValue, _flags, yDiff, writeBuffer, Float::class.java) + if (zDiff.hasChanged()) floatSerializer.serializeExplicit(diffValue, _flags, zDiff, writeBuffer, Float::class.java) + } + } + } + else -> throwInvalidType(fieldType) + } + } + + override fun deserializeExplicit(field: Field, flags: Array, owner: Any?, readBuffer: ReadBuffer, fieldType: Class<*>) { + when (fieldType) { + Vector3::class.java -> { + val vector = field.access().get(owner) as Vector3 + val c1 = flags.contains(compressedRotationVector1) + val c2 = flags.contains(compressedRotationVector2) + val c3 = flags.contains(compressedRotationVector3) + val xHolder = Holder(null) + val yHolder = Holder(null) + val zHolder = Holder(null) + val xDiff = xField.get(vector) as DiffTracked + val yDiff = yField.get(vector) as DiffTracked + val zDiff = zField.get(vector) as DiffTracked + + if ((c1 and c2) or (c2 and c3) or (c1 and c3)) throw MismatchedFlagException("Cannot have more than one rotation compression flag!") + + when { + c1 || c2 || c3 -> { + val intSerializer = SyncHandler.getCompatibleSerializer(Int::class.java) + val bytes = if (c1) 1 else if (c2) 2 else 3 + + if (readBuffer.readHeader()) { + intSerializer.deserializeExplicit(Holder.valueField, flags, xHolder, readBuffer, Int::class.java) + xDiff.value = (xHolder.value as Int).decodeRotation(bytes) + xDiff.clearChangeState() + } + if (readBuffer.readHeader()) { + intSerializer.deserializeExplicit(Holder.valueField, flags, yHolder, readBuffer, Int::class.java) + yDiff.value = (yHolder.value as Int).decodeRotation(bytes) + yDiff.clearChangeState() + } + if (readBuffer.readHeader()) { + intSerializer.deserializeExplicit(Holder.valueField, flags, zHolder, readBuffer, Int::class.java) + zDiff.value = (zHolder.value as Int).decodeRotation(bytes) + zDiff.clearChangeState() + } + } + else -> { + val floatSerializer = SyncHandler.getCompatibleSerializer(Float::class.java) + + if (readBuffer.readHeader()){ + floatSerializer.deserializeExplicit(diffValue, flags, xField.get(vector), readBuffer, Float::class.java) + xDiff.clearChangeState() + } + if (readBuffer.readHeader()){ + floatSerializer.deserializeExplicit(diffValue, flags, yField.get(vector), readBuffer, Float::class.java) + yDiff.clearChangeState() + } + if (readBuffer.readHeader()){ + floatSerializer.deserializeExplicit(diffValue, flags, zField.get(vector), readBuffer, Float::class.java) + zDiff.clearChangeState() + } + } + } + } + else -> throwInvalidType(fieldType) + } + } +} \ No newline at end of file diff --git a/src/net/tofvesson/math/Vector3.kt b/src/net/tofvesson/math/Vector3.kt new file mode 100644 index 0000000..382bc9c --- /dev/null +++ b/src/net/tofvesson/math/Vector3.kt @@ -0,0 +1,29 @@ +package net.tofvesson.math + +import net.tofvesson.networking.DiffTracked + +class Vector3(xPos: Float, yPos: Float, zPos: Float) { + + private val _x = DiffTracked(xPos, Float::class.java) + private val _y = DiffTracked(yPos, Float::class.java) + private val _z = DiffTracked(zPos, Float::class.java) + + + var x: Float + get() = _x.value + set(value) { + _x.value = value + } + var y: Float + get() = _y.value + set(value) { + _y.value = value + } + var z: Float + get() = _z.value + set(value) { + _z.value = value + } + + override fun toString() = "($x; $y; $z)" +} \ No newline at end of file diff --git a/src/net/tofvesson/networking/Arithmetic.kt b/src/net/tofvesson/networking/Arithmetic.kt index 5196465..c115bb6 100644 --- a/src/net/tofvesson/networking/Arithmetic.kt +++ b/src/net/tofvesson/networking/Arithmetic.kt @@ -2,6 +2,7 @@ package net.tofvesson.networking import java.nio.ByteBuffer import kotlin.experimental.or +import kotlin.math.roundToInt fun varIntSize(value: Long): Int = when { @@ -158,4 +159,29 @@ fun swapEndian(value: Long) = fun bitConvert(value: Int): Long = value.toLong() and 0xFFFFFFFFL fun zigZagEncode(value: Long): Long = (value shl 1) xor (value shr 63) -fun zigZagDecode(value: Long): Long = (value shr 1) xor ((value shl 63) shr 63) \ No newline at end of file +fun zigZagDecode(value: Long): Long = (value shr 1) xor ((value shl 63) shr 63) + +fun Float.encodeRotation(byteCount: Int): Int { + if(this < 0 || this > 360) throw RotationOutOfBoundsException() + if(byteCount<0) throw IllegalArgumentException("Cannot encode rotation with a negative amount of bytes") + if(byteCount>4) throw IllegalArgumentException("Cannot encode rotation with more bytes than in the original value") + if(byteCount==4) return this.roundToInt() + return ((this/360f)*(-1 ushr (8*(4-byteCount)))).roundToInt() +} + +fun Int.decodeRotation(byteCount: Int): Float { + if(byteCount<0) throw IllegalArgumentException("Cannot decode rotation with a negative amount of bytes") + if(byteCount>4) throw IllegalArgumentException("Cannot decode rotation with more bytes than in the original value") + if(byteCount==4) return this.toFloat() + val mask = (-1 ushr (8*(4-byteCount))) + if(this < 0 || this > mask) throw RotationOutOfBoundsException() + return (this/mask.toFloat())*360f +} + +class RotationOutOfBoundsException: RuntimeException { + constructor() : super() + constructor(message: String?) : super(message) + constructor(message: String?, cause: Throwable?) : super(message, cause) + constructor(cause: Throwable?) : super(cause) + constructor(message: String?, cause: Throwable?, enableSuppression: Boolean, writableStackTrace: Boolean) : super(message, cause, enableSuppression, writableStackTrace) +} \ No newline at end of file diff --git a/src/net/tofvesson/networking/DiffTrackedSerializer.kt b/src/net/tofvesson/networking/DiffTrackedSerializer.kt index 5945908..bfef8e4 100644 --- a/src/net/tofvesson/networking/DiffTrackedSerializer.kt +++ b/src/net/tofvesson/networking/DiffTrackedSerializer.kt @@ -1,7 +1,6 @@ package net.tofvesson.networking import java.lang.reflect.Field -import java.nio.ByteBuffer class DiffTrackedSerializer private constructor(): Serializer(arrayOf( DiffTracked::class.java, @@ -9,121 +8,95 @@ class DiffTrackedSerializer private constructor(): Serializer(arrayOf( )) { 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 computeSizeExplicit(field: Field, flags: Array, owner: Any?, state: WriteState, fieldType: Class<*>) { + when (fieldType) { + DiffTracked::class.java -> { + val tracker = field.get(owner) as DiffTracked<*> + val serializer = SyncHandler.getCompatibleSerializer(tracker.valueType) + state.registerHeader(1) + if (tracker.hasChanged()) + serializer.computeSizeExplicit(trackedField, flags, tracker, state, tracker.valueType) } - - 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 - } + DiffTrackedArray::class.java -> { + val tracker = field.get(owner) as DiffTrackedArray<*> + val serializer = SyncHandler.getCompatibleSerializer(tracker.elementType) + state.registerHeader(1) + if (tracker.hasChanged()) { + val holder = Holder(null) + state.registerHeader(tracker.size) + for (index in tracker.changeMap.indices) + if (tracker.changeMap[index]) { + holder.value = tracker[index] + serializer.computeSizeExplicit(Holder.valueField, flags, holder, state, tracker.elementType) } - tracker.clearChangeState() - Pair(bytes, bits) - }else{ - tracker.clearChangeState() - Pair(offset, bitFieldOffset+tracker.size) - } } - else -> Pair(0, 0) } + else -> throwInvalidType(fieldType) + } + } - 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 + override fun serializeExplicit(field: Field, flags: Array, owner: Any?, writeBuffer: WriteBuffer, fieldType: Class<*>) { + when (fieldType) { + DiffTracked::class.java -> { + val tracker = field.get(owner) as DiffTracked<*> + val serializer = SyncHandler.getCompatibleSerializer(tracker.valueType) + writeBuffer.writeHeader(tracker.hasChanged()) + if (tracker.hasChanged()) { + serializer.serializeExplicit(trackedField, flags, tracker, writeBuffer, tracker.valueType) + tracker.clearChangeState() + } + } + DiffTrackedArray::class.java -> { + val tracker = field.get(owner) as DiffTrackedArray<*> + val serializer = SyncHandler.getCompatibleSerializer(tracker.elementType) + writeBuffer.writeHeader(tracker.hasChanged()) + if (tracker.hasChanged()) { + val holder = Holder(null) + + for (index in tracker.changeMap.indices) { + writeBuffer.writeHeader(tracker.changeMap[index]) + if (tracker.changeMap[index]) { + holder.value = tracker[index] + serializer.serializeExplicit(Holder.valueField, flags, holder, writeBuffer, tracker.elementType) + } } tracker.clearChangeState() - Pair(bytes, bits) } - DiffTrackedArray::class.java -> { - val tracker = field.get(owner) as DiffTrackedArray<*> - val serializer = SyncHandler.getCompatibleSerializer(tracker.elementType) + } + else -> throwInvalidType(fieldType) + } + } - var bits = bitFieldOffset - var bytes = offset + override fun deserializeExplicit(field: Field, flags: Array, owner: Any?, readBuffer: ReadBuffer, fieldType: Class<*>) { + when (fieldType) { + DiffTracked::class.java -> { + val tracker = field.get(owner) as DiffTracked<*> + val serializer = SyncHandler.getCompatibleSerializer(tracker.valueType) + if (readBuffer.readHeader()) + serializer.deserializeExplicit(trackedField, flags, tracker, readBuffer, tracker.valueType) + tracker.clearChangeState() + } + DiffTrackedArray::class.java -> { + val tracker = field.get(owner) as DiffTrackedArray<*> + val serializer = SyncHandler.getCompatibleSerializer(tracker.elementType) + + if(readBuffer.readHeader()) { 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 + for (index in tracker.changeMap.indices) { + if (readBuffer.readHeader()) { + serializer.deserializeExplicit(Holder.valueField, flags, holder, readBuffer, tracker.elementType) array[index] = holder.value } } - tracker.clearChangeState() - Pair(bytes, bits) } - else -> Pair(0, 0) + tracker.clearChangeState() } - - private data class Holder(var value: Any?) + else -> throwInvalidType(fieldType) + } + } } \ No newline at end of file diff --git a/src/net/tofvesson/networking/Holder.kt b/src/net/tofvesson/networking/Holder.kt new file mode 100644 index 0000000..4964ead --- /dev/null +++ b/src/net/tofvesson/networking/Holder.kt @@ -0,0 +1,7 @@ +package net.tofvesson.networking + +data class Holder(var value: Any?){ + companion object { + val valueField = Holder::class.java.getDeclaredField("value")!! + } +} \ No newline at end of file diff --git a/src/net/tofvesson/networking/InsufficientCapacityException.kt b/src/net/tofvesson/networking/InsufficientCapacityException.kt new file mode 100644 index 0000000..de26cd2 --- /dev/null +++ b/src/net/tofvesson/networking/InsufficientCapacityException.kt @@ -0,0 +1,9 @@ +package net.tofvesson.networking + +class InsufficientCapacityException: RuntimeException { + constructor() : super() + constructor(message: String?) : super(message) + constructor(message: String?, cause: Throwable?) : super(message, cause) + constructor(cause: Throwable?) : super(cause) + constructor(message: String?, cause: Throwable?, enableSuppression: Boolean, writableStackTrace: Boolean) : super(message, cause, enableSuppression, writableStackTrace) +} \ No newline at end of file diff --git a/src/net/tofvesson/networking/MismatchedFlagException.kt b/src/net/tofvesson/networking/MismatchedFlagException.kt new file mode 100644 index 0000000..f8514b4 --- /dev/null +++ b/src/net/tofvesson/networking/MismatchedFlagException.kt @@ -0,0 +1,9 @@ +package net.tofvesson.networking + +class MismatchedFlagException: RuntimeException { + constructor() : super() + constructor(message: String?) : super(message) + constructor(message: String?, cause: Throwable?) : super(message, cause) + constructor(cause: Throwable?) : super(cause) + constructor(message: String?, cause: Throwable?, enableSuppression: Boolean, writableStackTrace: Boolean) : super(message, cause, enableSuppression, writableStackTrace) +} \ No newline at end of file diff --git a/src/net/tofvesson/networking/NoUpwardCascade.kt b/src/net/tofvesson/networking/NoUpwardCascade.kt new file mode 100644 index 0000000..70d4d5c --- /dev/null +++ b/src/net/tofvesson/networking/NoUpwardCascade.kt @@ -0,0 +1,6 @@ +package net.tofvesson.networking + +/** + * Prevents SyncedVar system from syncing superclasses + */ +annotation class NoUpwardCascade \ No newline at end of file diff --git a/src/net/tofvesson/networking/PrimitiveArraySerializer.kt b/src/net/tofvesson/networking/PrimitiveArraySerializer.kt index 2a4141c..ae140f9 100644 --- a/src/net/tofvesson/networking/PrimitiveArraySerializer.kt +++ b/src/net/tofvesson/networking/PrimitiveArraySerializer.kt @@ -1,8 +1,8 @@ package net.tofvesson.networking import java.lang.reflect.Field -import java.nio.ByteBuffer +@Suppress("MemberVisibilityCanBePrivate") class PrimitiveArraySerializer private constructor(): Serializer(arrayOf( BooleanArray::class.java, ByteArray::class.java, @@ -18,166 +18,136 @@ class PrimitiveArraySerializer private constructor(): Serializer(arrayOf( val singleton = PrimitiveArraySerializer() } - override fun computeSizeExplicit(field: Field, flags: Array, owner: Any?, fieldType: Class<*>): Pair { + override fun computeSizeExplicit(field: Field, flags: Array, owner: Any?, state: WriteState, fieldType: Class<*>) { val arrayLength = java.lang.reflect.Array.getLength(field.get(owner)) - var byteSize = if(flags.contains(knownSize)) 0 else varIntSize(arrayLength.toLong()) - var bitSize = 0 + val holder = Holder(null) when (fieldType) { - BooleanArray::class.java -> bitSize = arrayLength - ByteArray::class.java -> byteSize += arrayLength + BooleanArray::class.java -> state.registerBits(arrayLength) + ByteArray::class.java -> state.registerBytes(arrayLength) ShortArray::class.java -> - if(flags.contains(SyncFlag.NoCompress)) byteSize += arrayLength * 2 - else - for(value in field.get(owner) as ShortArray) - byteSize += - varIntSize( - if(flags.contains(SyncFlag.NonNegative)) value.toLong() - else zigZagEncode(value.toLong()) - ) + if(flags.contains(SyncFlag.NoCompress)) state.registerBytes(arrayLength * 2) + else { + val shortSerializer = SyncHandler.getCompatibleSerializer(Short::class.java) + for (value in field.get(owner) as ShortArray) { + holder.value = value + shortSerializer.computeSizeExplicit(Holder.valueField, flags, holder, state, Short::class.java) + } + } IntArray::class.java -> - if(flags.contains(SyncFlag.NoCompress)) byteSize += arrayLength * 4 - else - for(value in field.get(owner) as IntArray) - byteSize += - varIntSize( - if(flags.contains(SyncFlag.NonNegative)) value.toLong() - else zigZagEncode(value.toLong()) - ) + if(flags.contains(SyncFlag.NoCompress)) state.registerBytes(arrayLength * 4) + else { + val intSerializer = SyncHandler.getCompatibleSerializer(Int::class.java) + for (value in field.get(owner) as IntArray) { + holder.value = value + intSerializer.computeSizeExplicit(Holder.valueField, flags, holder, state, Int::class.java) + } + } LongArray::class.java -> - if(flags.contains(SyncFlag.NoCompress)) byteSize += arrayLength * 8 - else - for(value in field.get(owner) as LongArray) - byteSize += - varIntSize( - if(flags.contains(SyncFlag.NonNegative)) value - else zigZagEncode(value) - ) + if(flags.contains(SyncFlag.NoCompress)) state.registerBytes(arrayLength * 8) + else { + val longSerializer = SyncHandler.getCompatibleSerializer(Long::class.java) + for (value in field.get(owner) as LongArray) { + holder.value = value + longSerializer.computeSizeExplicit(Holder.valueField, flags, holder, state, Long::class.java) + } + } FloatArray::class.java -> - if(flags.contains(SyncFlag.NoCompress)) byteSize += arrayLength * 4 - else - for (value in field.get(owner) as LongArray) - byteSize += - varIntSize( - if(flags.contains(SyncFlag.FloatEndianSwap)) bitConvert(swapEndian(floatToInt(field.getFloat(owner)))) - else bitConvert(floatToInt(field.getFloat(owner))) - ) + if(flags.contains(SyncFlag.NoCompress)) state.registerBytes(arrayLength * 4) + else { + val floatSerializer = SyncHandler.getCompatibleSerializer(Float::class.java) + for (value in field.get(owner) as FloatArray) { + holder.value = value + floatSerializer.computeSizeExplicit(Holder.valueField, flags, holder, state, Float::class.java) + } + } DoubleArray::class.java -> - if(flags.contains(SyncFlag.NoCompress)) byteSize += arrayLength * 8 - else - for (value in field.get(owner) as LongArray) - byteSize += - varIntSize( - if(flags.contains(SyncFlag.FloatEndianSwap)) swapEndian(doubleToLong(field.getDouble(owner))) - else doubleToLong(field.getDouble(owner)) - ) + if(flags.contains(SyncFlag.NoCompress)) state.registerBytes(arrayLength * 8) + else { + val doubleSerializer = SyncHandler.getCompatibleSerializer(Double::class.java) + for (value in field.get(owner) as DoubleArray) { + holder.value = value + doubleSerializer.computeSizeExplicit(Holder.valueField, flags, holder, state, Double::class.java) + } + } + else -> throwInvalidType(fieldType) } - return Pair(byteSize, bitSize) } - override fun serializeExplicit(field: Field, flags: Array, owner: Any?, byteBuffer: ByteBuffer, offset: Int, bitFieldOffset: Int, fieldType: Class<*>): Pair { + override fun serializeExplicit(field: Field, flags: Array, owner: Any?, writeBuffer: WriteBuffer, fieldType: Class<*>) { val arrayLength = java.lang.reflect.Array.getLength(field.get(owner)) - var localByteOffset = offset - var localBitOffset = bitFieldOffset - if(!flags.contains(knownSize)){ - writeVarInt(byteBuffer, offset, arrayLength.toLong()) - localByteOffset += varIntSize(arrayLength.toLong()) - } + val holder = Holder(null) + if(!flags.contains(knownSize)) writeBuffer.writePackedInt(arrayLength, true) when (fieldType) { BooleanArray::class.java -> for(value in field.get(owner) as BooleanArray) - writeBit(value, byteBuffer, localBitOffset++) + writeBuffer.writeBit(value) ByteArray::class.java -> for(value in field.get(owner) as ByteArray) - byteBuffer.put(localByteOffset++, value) + writeBuffer.writeByte(value) ShortArray::class.java -> if(flags.contains(SyncFlag.NoCompress)) - for(value in field.get(owner) as ShortArray){ - byteBuffer.putShort(localByteOffset, value) - localByteOffset += 2 - } - else - for(value in field.get(owner) as ShortArray) { - val rawVal = - if(flags.contains(SyncFlag.NonNegative)) value.toLong() - else zigZagEncode(value.toLong()) - writeVarInt(byteBuffer, localByteOffset, rawVal) - localByteOffset += varIntSize(rawVal) + for(value in field.get(owner) as ShortArray) + writeBuffer.writeShort(value) + else { + val shortSerializer = SyncHandler.getCompatibleSerializer(Short::class.java) + for (value in field.get(owner) as ShortArray) { + holder.value = value + shortSerializer.serializeExplicit(Holder.valueField, flags, holder, writeBuffer, fieldType) } + } IntArray::class.java -> if(flags.contains(SyncFlag.NoCompress)) - for(value in field.get(owner) as IntArray){ - byteBuffer.putInt(localByteOffset, value) - localByteOffset += 4 - } - else - for(value in field.get(owner) as IntArray) { - val rawVal = - if(flags.contains(SyncFlag.NonNegative)) value.toLong() - else zigZagEncode(value.toLong()) - writeVarInt(byteBuffer, localByteOffset, rawVal) - localByteOffset += varIntSize(rawVal) + for(value in field.get(owner) as IntArray) + writeBuffer.writeInt(value) + else { + val intSerializer = SyncHandler.getCompatibleSerializer(Int::class.java) + for (value in field.get(owner) as IntArray) { + holder.value = value + intSerializer.serializeExplicit(Holder.valueField, flags, holder, writeBuffer, fieldType) } + } LongArray::class.java -> if(flags.contains(SyncFlag.NoCompress)) - for(value in field.get(owner) as LongArray){ - byteBuffer.putLong(localByteOffset, value) - localByteOffset += 8 - } - else - for(value in field.get(owner) as LongArray) { - val rawVal = - if(flags.contains(SyncFlag.NonNegative)) value - else zigZagEncode(value) - writeVarInt( - byteBuffer, - localByteOffset, - rawVal - ) - localByteOffset += varIntSize(rawVal) + for(value in field.get(owner) as LongArray) + writeBuffer.writeLong(value) + else { + val longSerializer = SyncHandler.getCompatibleSerializer(Long::class.java) + for (value in field.get(owner) as LongArray) { + holder.value = value + longSerializer.serializeExplicit(Holder.valueField, flags, holder, writeBuffer, fieldType) } + } FloatArray::class.java -> if(flags.contains(SyncFlag.NoCompress)) - for(value in field.get(owner) as FloatArray){ - byteBuffer.putFloat(localByteOffset, value) - localByteOffset += 4 - } - else + for(value in field.get(owner) as FloatArray) + writeBuffer.writeFloat(value) + else { + val floatSerializer = SyncHandler.getCompatibleSerializer(Float::class.java) for (value in field.get(owner) as FloatArray) { - val rawVal = - if(flags.contains(SyncFlag.FloatEndianSwap)) bitConvert(swapEndian(floatToInt(field.getFloat(owner)))) - else bitConvert(floatToInt(field.getFloat(owner))) - writeVarInt(byteBuffer, localByteOffset, rawVal) - localByteOffset += varIntSize(rawVal) + holder.value = value + floatSerializer.serializeExplicit(Holder.valueField, flags, holder, writeBuffer, fieldType) } + } DoubleArray::class.java -> if(flags.contains(SyncFlag.NoCompress)) - for(value in field.get(owner) as DoubleArray){ - byteBuffer.putDouble(localByteOffset, value) - localByteOffset += 8 - } - else - for (value in field.get(owner) as DoubleArray){ - val rawVal = - if(flags.contains(SyncFlag.FloatEndianSwap)) swapEndian(doubleToLong(field.getDouble(owner))) - else doubleToLong(field.getDouble(owner)) - writeVarInt(byteBuffer, localByteOffset, rawVal) - localByteOffset += varIntSize(rawVal) + for(value in field.get(owner) as DoubleArray) + writeBuffer.writeDouble(value) + else { + val doubleSerializer = SyncHandler.getCompatibleSerializer(Double::class.java) + for (value in field.get(owner) as DoubleArray) { + holder.value = value + doubleSerializer.serializeExplicit(Holder.valueField, flags, holder, writeBuffer, fieldType) } + } + else -> throwInvalidType(fieldType) } - return Pair(localByteOffset, localBitOffset) } - override fun deserializeExplicit(field: Field, flags: Array, owner: Any?, byteBuffer: ByteBuffer, offset: Int, bitFieldOffset: Int, fieldType: Class<*>): Pair { - var localByteOffset = offset - var localBitOffset = bitFieldOffset + override fun deserializeExplicit(field: Field, flags: Array, owner: Any?, readBuffer: ReadBuffer, fieldType: Class<*>) { val localLength = java.lang.reflect.Array.getLength(field.get(owner)) val arrayLength = if(flags.contains(knownSize)) localLength - else{ - val v = readVarInt(byteBuffer, offset) - localByteOffset += varIntSize(v) - v.toInt() - } + else readBuffer.readPackedInt(true) val target = if(arrayLength!=localLength) java.lang.reflect.Array.newInstance(field.type.componentType, arrayLength) @@ -187,97 +157,60 @@ class PrimitiveArraySerializer private constructor(): Serializer(arrayOf( BooleanArray::class.java -> { val booleanTarget = target as BooleanArray for (index in 0 until arrayLength) - booleanTarget[index] = readBit(byteBuffer, localBitOffset++) + booleanTarget[index] = readBuffer.readBit() } ByteArray::class.java -> { val byteTarget = target as ByteArray for (index in 0 until arrayLength) - byteTarget[index] = byteBuffer[localByteOffset++] + byteTarget[index] = readBuffer.readByte() } ShortArray::class.java -> { val shortTarget = target as ShortArray if (flags.contains(SyncFlag.NoCompress)) - for (index in 0 until arrayLength) { - shortTarget[index] = byteBuffer.getShort(localByteOffset) - localByteOffset += 2 - } + for (index in 0 until arrayLength) + shortTarget[index] = readBuffer.readShort() else - for (index in 0 until arrayLength) { - val rawValue = - if(flags.contains(SyncFlag.NonNegative)) readVarInt(byteBuffer, offset) - else zigZagDecode(readVarInt(byteBuffer, offset)) - shortTarget[index] = rawValue.toShort() - localByteOffset += varIntSize(rawValue) - } + for (index in 0 until arrayLength) + shortTarget[index] = readBuffer.readPackedShort(flags.contains(SyncFlag.NonNegative)) } IntArray::class.java -> { val intTarget = target as IntArray if (flags.contains(SyncFlag.NoCompress)) - for (index in 0 until arrayLength) { - intTarget[index] = byteBuffer.getInt(localByteOffset) - localByteOffset += 4 - } + for (index in 0 until arrayLength) + intTarget[index] = readBuffer.readInt() else - for (index in 0 until arrayLength) { - val rawValue = - if(flags.contains(SyncFlag.NonNegative)) readVarInt(byteBuffer, offset) - else zigZagDecode(readVarInt(byteBuffer, offset)) - intTarget[index] = rawValue.toInt() - localByteOffset += varIntSize(rawValue) - } + for (index in 0 until arrayLength) + intTarget[index] = readBuffer.readPackedInt(flags.contains(SyncFlag.NonNegative)) } LongArray::class.java -> { val longTarget = target as LongArray if (flags.contains(SyncFlag.NoCompress)) - for (index in 0 until arrayLength) { - longTarget[index] = byteBuffer.getLong(localByteOffset) - localByteOffset += 8 - } + for (index in 0 until arrayLength) + longTarget[index] = readBuffer.readLong() else - for (index in 0 until arrayLength) { - val rawValue = - if(flags.contains(SyncFlag.NonNegative)) readVarInt(byteBuffer, offset) - else zigZagDecode(readVarInt(byteBuffer, offset)) - longTarget[index] = rawValue - localByteOffset += varIntSize(rawValue) - } + for (index in 0 until arrayLength) + longTarget[index] = readBuffer.readPackedLong(flags.contains(SyncFlag.NonNegative)) } FloatArray::class.java -> { val floatTarget = target as FloatArray if (flags.contains(SyncFlag.NoCompress)) - for (index in 0 until arrayLength) { - floatTarget[index] = byteBuffer.getFloat(localByteOffset) - localByteOffset += 4 - } + for (index in 0 until arrayLength) + floatTarget[index] = readBuffer.readFloat() else - for (index in 0 until arrayLength) { - val readVal = readVarInt(byteBuffer, offset) - val rawValue = - if(flags.contains(SyncFlag.FloatEndianSwap)) intToFloat(swapEndian(readVal.toInt())) - else intToFloat(readVal.toInt()) - floatTarget[index] = rawValue - localBitOffset += varIntSize(readVal) - } + for (index in 0 until arrayLength) + floatTarget[index] = readBuffer.readPackedFloat(flags.contains(SyncFlag.NonNegative)) } DoubleArray::class.java -> { val doubleTarget = target as DoubleArray if (flags.contains(SyncFlag.NoCompress)) - for (index in 0 until arrayLength) { - doubleTarget[index] = byteBuffer.getDouble(localByteOffset) - localByteOffset += 8 - } + for (index in 0 until arrayLength) + doubleTarget[index] = readBuffer.readDouble() else - for (index in 0 until arrayLength) { - val readVal = readVarInt(byteBuffer, offset) - val rawValue = - if(flags.contains(SyncFlag.FloatEndianSwap)) longToDouble(swapEndian(readVal)) - else longToDouble(readVal) - doubleTarget[index] = rawValue - localBitOffset += varIntSize(readVal) - } + for (index in 0 until arrayLength) + doubleTarget[index] = readBuffer.readPackedDouble(flags.contains(SyncFlag.NonNegative)) } + else -> throwInvalidType(fieldType) } if(arrayLength!=localLength) field.set(owner, target) - return Pair(localByteOffset, localBitOffset) } } \ No newline at end of file diff --git a/src/net/tofvesson/networking/PrimitiveSerializers.kt b/src/net/tofvesson/networking/PrimitiveSerializers.kt index 66a89b9..f686725 100644 --- a/src/net/tofvesson/networking/PrimitiveSerializers.kt +++ b/src/net/tofvesson/networking/PrimitiveSerializers.kt @@ -2,7 +2,6 @@ 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, @@ -26,205 +25,100 @@ class PrimitiveSerializer private constructor() : Serializer(arrayOf( field: Field, flags: Array, owner: Any?, + state: WriteState, fieldType: Class<*> - ): Pair = - 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.getShortAdaptive(owner).toLong() - else zigZagEncode(field.getShortAdaptive(owner).toLong()) - ), 0) - 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.getIntAdaptive(owner).toLong() - else zigZagEncode(field.getIntAdaptive(owner).toLong()) - ), 0) - 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.getLongAdaptive(owner) - else zigZagEncode(field.getLongAdaptive(owner)) - ), 0) - 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.getFloatAdaptive(owner)))) - else bitConvert(floatToInt(field.getFloatAdaptive(owner))) - ), 0) - 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.getDoubleAdaptive(owner))) - else doubleToLong(field.getDoubleAdaptive(owner)) - ), 0) - else -> Pair(0, 0) - } + ) { + when (fieldType) { + java.lang.Boolean::class.java, Boolean::class.java -> state.registerBits(1) + java.lang.Byte::class.java, Byte::class.java -> state.registerBytes(1) + java.lang.Short::class.java, Short::class.java -> + if (flags.contains(SyncFlag.NoCompress)) state.registerBytes(2) + else state.registerBytes(varIntSize( + if (flags.contains(SyncFlag.NonNegative)) field.getShortAdaptive(owner).toLong() + else zigZagEncode(field.getShortAdaptive(owner).toLong()) + )) + java.lang.Integer::class.java, Int::class.java -> + if (flags.contains(SyncFlag.NoCompress)) state.registerBytes(4) + else state.registerBytes(varIntSize( + if (flags.contains(SyncFlag.NonNegative)) field.getIntAdaptive(owner).toLong() + else zigZagEncode(field.getIntAdaptive(owner).toLong()) + )) + java.lang.Long::class.java, Long::class.java -> + if (flags.contains(SyncFlag.NoCompress)) state.registerBytes(8) + else state.registerBytes(varIntSize( + if (flags.contains(SyncFlag.NonNegative)) field.getLongAdaptive(owner) + else zigZagEncode(field.getLongAdaptive(owner)) + )) + java.lang.Float::class.java, Float::class.java -> + if (flags.contains(SyncFlag.NoCompress)) state.registerBytes(4) + else state.registerBytes(varIntSize( + if (flags.contains(SyncFlag.FloatEndianSwap)) bitConvert(swapEndian(floatToInt(field.getFloatAdaptive(owner)))) + else bitConvert(floatToInt(field.getFloatAdaptive(owner))) + )) + java.lang.Double::class.java, Double::class.java -> + if (flags.contains(SyncFlag.NoCompress)) state.registerBytes(8) + else state.registerBytes(varIntSize( + if (flags.contains(SyncFlag.FloatEndianSwap)) swapEndian(doubleToLong(field.getDoubleAdaptive(owner))) + else doubleToLong(field.getDoubleAdaptive(owner)) + )) + else -> throwInvalidType(fieldType) + } + } override fun serializeExplicit( field: Field, flags: Array, owner: Any?, - byteBuffer: ByteBuffer, - offset: Int, - bitFieldOffset: Int, + writeBuffer: WriteBuffer, fieldType: Class<*> - ): Pair = - when(fieldType){ - java.lang.Boolean::class.java, Boolean::class.java -> { - writeBit(field.getBooleanAdaptive(owner), byteBuffer, bitFieldOffset) - Pair(offset, bitFieldOffset+1) - } - java.lang.Byte::class.java, Byte::class.java -> { - byteBuffer.put(offset, field.getByteAdaptive(owner)) - Pair(offset+1, bitFieldOffset) - } - java.lang.Short::class.java, Short::class.java -> - if(flags.contains(SyncFlag.NoCompress)){ - byteBuffer.putShort(offset, field.getShortAdaptive(owner)) - Pair(offset+2, bitFieldOffset) - } - else { - val rawValue = - if(flags.contains(SyncFlag.NonNegative)) field.getShortAdaptive(owner).toLong() - else zigZagEncode(field.getShortAdaptive(owner).toLong()) - writeVarInt(byteBuffer, offset, rawValue) - Pair(offset+varIntSize(rawValue), bitFieldOffset) - } - java.lang.Integer::class.java, Int::class.java -> - if(flags.contains(SyncFlag.NoCompress)){ - byteBuffer.putInt(offset, field.getIntAdaptive(owner)) - Pair(offset+4, bitFieldOffset) - } - else { - val rawValue = - if(flags.contains(SyncFlag.NonNegative)) field.getIntAdaptive(owner).toLong() - else zigZagEncode(field.getIntAdaptive(owner).toLong()) - writeVarInt(byteBuffer, offset, rawValue) - Pair(offset+varIntSize(rawValue), bitFieldOffset) - } - java.lang.Long::class.java, Long::class.java -> - if(flags.contains(SyncFlag.NoCompress)){ - byteBuffer.putLong(offset, field.getLongAdaptive(owner)) - Pair(offset+8, bitFieldOffset) - } - else { - val rawValue = - if(flags.contains(SyncFlag.NonNegative)) field.getLongAdaptive(owner) - else zigZagEncode(field.getLongAdaptive(owner)) - writeVarInt(byteBuffer, offset, rawValue) - Pair(offset+varIntSize(rawValue), bitFieldOffset) - } - java.lang.Float::class.java, Float::class.java -> - if(flags.contains(SyncFlag.NoCompress)){ - byteBuffer.putFloat(offset, field.getFloatAdaptive(owner)) - Pair(offset+4, bitFieldOffset) - } - else{ - val rawValue = - 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) - } - java.lang.Double::class.java, Double::class.java -> - if(flags.contains(SyncFlag.NoCompress)){ - byteBuffer.putDouble(offset, field.getDoubleAdaptive(owner)) - Pair(offset+8, bitFieldOffset) - } - else{ - val rawValue = - 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) - } + ){ + when (fieldType) { + java.lang.Boolean::class.java, Boolean::class.java -> writeBuffer.writeBit(field.getBooleanAdaptive(owner)) + java.lang.Byte::class.java, Byte::class.java -> writeBuffer.writeByte(field.getByteAdaptive(owner)) + java.lang.Short::class.java, Short::class.java -> + if (flags.contains(SyncFlag.NoCompress)) writeBuffer.writeShort(field.getShortAdaptive(owner)) + else writeBuffer.writePackedShort(field.getShortAdaptive(owner), flags.contains(SyncFlag.NonNegative)) + java.lang.Integer::class.java, Int::class.java -> + if (flags.contains(SyncFlag.NoCompress)) writeBuffer.writeInt(field.getIntAdaptive(owner)) + else writeBuffer.writePackedInt(field.getIntAdaptive(owner), flags.contains(SyncFlag.NonNegative)) + java.lang.Long::class.java, Long::class.java -> + if (flags.contains(SyncFlag.NoCompress)) writeBuffer.writeLong(field.getLongAdaptive(owner)) + else writeBuffer.writePackedLong(field.getLongAdaptive(owner), flags.contains(SyncFlag.NonNegative)) + java.lang.Float::class.java, Float::class.java -> + if (flags.contains(SyncFlag.NoCompress)) writeBuffer.writeFloat(field.getFloatAdaptive(owner)) + else writeBuffer.writePackedFloat(field.getFloatAdaptive(owner), flags.contains(SyncFlag.FloatEndianSwap)) + java.lang.Double::class.java, Double::class.java -> + if (flags.contains(SyncFlag.NoCompress)) writeBuffer.writeDouble(field.getDoubleAdaptive(owner)) + else writeBuffer.writePackedDouble(field.getDoubleAdaptive(owner), flags.contains(SyncFlag.FloatEndianSwap)) + else -> throwInvalidType(fieldType) + } + } override fun deserializeExplicit( field: Field, flags: Array, owner: Any?, - byteBuffer: ByteBuffer, - offset: Int, - bitFieldOffset: Int, + readBuffer: ReadBuffer, fieldType: Class<*> - ): Pair = + ) = when(fieldType){ - java.lang.Boolean::class.java, Boolean::class.java -> { - field.setBooleanAdaptive(owner, readBit(byteBuffer, bitFieldOffset)) - Pair(offset, bitFieldOffset+1) - } - java.lang.Byte::class.java, Byte::class.java -> { - field.setByteAdaptive(owner, byteBuffer.get(offset)) - Pair(offset+1, bitFieldOffset) - } + java.lang.Boolean::class.java, Boolean::class.java -> field.setBooleanAdaptive(owner, readBuffer.readBit()) + java.lang.Byte::class.java, Byte::class.java -> field.setByteAdaptive(owner, readBuffer.readByte()) java.lang.Short::class.java, Short::class.java -> - if(flags.contains(SyncFlag.NoCompress)){ - 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.setShortAdaptive(owner, rawValue.toShort()) - Pair(offset+varIntSize(rawValue), bitFieldOffset) - } + if(flags.contains(SyncFlag.NoCompress)) field.setShortAdaptive(owner, readBuffer.readShort()) + else field.setShortAdaptive(owner, readBuffer.readPackedShort(flags.contains(SyncFlag.NonNegative))) java.lang.Integer::class.java, Int::class.java -> - if(flags.contains(SyncFlag.NoCompress)){ - 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.setIntAdaptive(owner, rawValue.toInt()) - Pair(offset+varIntSize(rawValue), bitFieldOffset) - } + if(flags.contains(SyncFlag.NoCompress)) field.setIntAdaptive(owner, readBuffer.readInt()) + else field.setIntAdaptive(owner, readBuffer.readPackedInt(flags.contains(SyncFlag.NonNegative))) java.lang.Long::class.java, Long::class.java -> - if(flags.contains(SyncFlag.NoCompress)){ - 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.setLongAdaptive(owner, rawValue) - Pair(offset+varIntSize(rawValue), bitFieldOffset) - } + if(flags.contains(SyncFlag.NoCompress)) field.setLongAdaptive(owner, readBuffer.readLong()) + else field.setLongAdaptive(owner, readBuffer.readPackedLong(flags.contains(SyncFlag.NonNegative))) java.lang.Float::class.java, Float::class.java -> - if(flags.contains(SyncFlag.NoCompress)){ - field.setFloatAdaptive(owner, byteBuffer.getFloat(offset)) - Pair(offset+4, bitFieldOffset) - } - else{ - val readVal = readVarInt(byteBuffer, offset) - val rawValue = - if(flags.contains(SyncFlag.FloatEndianSwap)) intToFloat(swapEndian(readVal.toInt())) - else intToFloat(readVal.toInt()) - field.setFloatAdaptive(owner, rawValue) - Pair(offset+varIntSize(readVal), bitFieldOffset) - } + if(flags.contains(SyncFlag.NoCompress)) field.setFloatAdaptive(owner, readBuffer.readFloat()) + else field.setFloatAdaptive(owner, readBuffer.readPackedFloat(flags.contains(SyncFlag.FloatEndianSwap))) java.lang.Double::class.java, Double::class.java -> - if(flags.contains(SyncFlag.NoCompress)){ - field.setDoubleAdaptive(owner, byteBuffer.getDouble(offset)) - Pair(offset+8, bitFieldOffset) - } - else{ - val readVal = readVarInt(byteBuffer, offset) - val rawValue = - if(flags.contains(SyncFlag.FloatEndianSwap)) longToDouble(swapEndian(readVal)) - else longToDouble(readVal) - field.setDoubleAdaptive(owner, rawValue) - Pair(offset+varIntSize(readVal), bitFieldOffset) - } - else -> Pair(offset, bitFieldOffset) + if(flags.contains(SyncFlag.NoCompress)) field.setDoubleAdaptive(owner, readBuffer.readDouble()) + else field.setDoubleAdaptive(owner, readBuffer.readPackedDouble(flags.contains(SyncFlag.FloatEndianSwap))) + else -> throwInvalidType(fieldType) } } \ No newline at end of file diff --git a/src/net/tofvesson/networking/ReadBuffer.kt b/src/net/tofvesson/networking/ReadBuffer.kt new file mode 100644 index 0000000..3dc9c62 --- /dev/null +++ b/src/net/tofvesson/networking/ReadBuffer.kt @@ -0,0 +1,129 @@ +package net.tofvesson.networking + +import net.tofvesson.math.collapseLowerByte +import java.nio.ByteBuffer + +@Suppress("MemberVisibilityCanBePrivate", "unused") +class ReadBuffer(state: WriteState, private val _buffer: ByteBuffer, bufferBitOffset: Int = 0) { + + /* + Buffer layout: + {BufferBitOffset}[Header][Bits]{BitOffset}[Bytes] + BufferBitOffset is optional and usually set to 0 + BitOffset is between 0 and 7 bits long (to realign data to the next free byte) + */ + private var headerIndex = bufferBitOffset + private var bitOffset = bufferBitOffset + state.header + private var byteIndex = state.computeBitFieldOffset(bufferBitOffset) + + // Maximum size for the above values + private val maxHeaderSize = bufferBitOffset + state.header + private val maxBitOffset: Int + private val maxByteOffset = _buffer.capacity() + + val buffer: ByteBuffer + get() = this._buffer + + init { + if(_buffer.capacity() - (bufferBitOffset ushr 3) - bufferBitOffset.collapseLowerByte() < state.computeBitFieldOffset()) + throw InsufficientCapacityException() + byteIndex = readPackedInt(true) + maxBitOffset = bufferBitOffset + byteIndex*8 + } + + + private fun readBit(head: Boolean): Boolean { + if(head && headerIndex >= maxHeaderSize) + throw IndexOutOfBoundsException("Attempt to read more headers than available space permits!") + + val index = (if(head) headerIndex else bitOffset) ushr 3 + val shift = (if(head) headerIndex else bitOffset) and 7 + if(head) ++headerIndex + else ++bitOffset + return (_buffer[index].toInt() and (1 shl shift)) != 0 + } + + fun readHeader() = readBit(true) + fun readBit() = readBit(false) + + fun readByte(): Byte { + //doBoundaryCheck(1) + return _buffer.get(byteIndex++) + } + + fun readShort(): Short { + //doBoundaryCheck(2) + val res = _buffer.getShort(byteIndex) + byteIndex += 2 + return res + } + + fun readInt(): Int { + //doBoundaryCheck(4) + val res = _buffer.getInt(byteIndex) + byteIndex += 4 + return res + } + + fun readLong(): Long { + //doBoundaryCheck(8) + val res = _buffer.getLong(byteIndex) + byteIndex += 8 + return res + } + + fun readFloat(): Float { + //doBoundaryCheck(4) + val res = _buffer.getFloat(byteIndex) + byteIndex += 4 + return res + } + + fun readDouble(): Double { + //doBoundaryCheck(8) + val res = _buffer.getDouble(byteIndex) + byteIndex += 8 + return res + } + + fun readPackedShort(noZigZag: Boolean = false) = readPackedLong(noZigZag).toShort() + fun readPackedInt(noZigZag: Boolean = false) = readPackedLong(noZigZag).toInt() + fun readPackedLong(noZigZag: Boolean = false): Long { + //doBoundaryCheck(1) + val header: Long = buffer[byteIndex++].toLong() and 0xFF + if (header <= 240L) return if(noZigZag) header else zigZagDecode(header) + if (header <= 248L){ + //doBoundaryCheck(2) + val res = 240L + ((header - 241L).shl(8)) + (buffer[byteIndex++].toLong() and 0xFF) + return if(noZigZag) res else zigZagDecode(res) + } + if (header == 249L){ + //doBoundaryCheck(3) + val res = 2288 + ((buffer[byteIndex++].toLong() and 0xFF).shl(8)) + (buffer[byteIndex++].toLong() and 0xFF) + return if(noZigZag) res else zigZagDecode(res) + } + val hdr = header - 247 + //doBoundaryCheck(hdr.toInt()) + var res = (buffer[byteIndex++].toLong() and 0xFF).or(((buffer[byteIndex++].toLong() and 0xFF).shl(8)).or((buffer[byteIndex++].toLong() and 0xFF).shl(16))) + var cmp = 2 + while (hdr > ++cmp) + res = res.or((buffer[byteIndex++].toLong() and 0xFF).shl(cmp.shl(3))) + + return if(noZigZag) res else zigZagDecode(res) + } + + fun readPackedFloat(noSwapEndian: Boolean = false): Float { + val readVal = readPackedInt(true) + return if(noSwapEndian) intToFloat(readVal) else intToFloat(swapEndian(readVal)) + } + + fun readPackedDouble(noSwapEndian: Boolean = false): Double { + val readVal = readPackedLong(true) + return if(noSwapEndian) longToDouble(readVal) else longToDouble(swapEndian(readVal)) + } + + private fun doBoundaryCheck(byteCount: Int) { + if(byteIndex + byteCount > maxByteOffset) + throw IndexOutOfBoundsException("Attempt to read value past maximum range!") + } +} \ No newline at end of file diff --git a/src/net/tofvesson/networking/Serializer.kt b/src/net/tofvesson/networking/Serializer.kt index c459e0c..de5e040 100644 --- a/src/net/tofvesson/networking/Serializer.kt +++ b/src/net/tofvesson/networking/Serializer.kt @@ -1,7 +1,6 @@ package net.tofvesson.networking import java.lang.reflect.Field -import java.nio.ByteBuffer import java.util.* abstract class Serializer(registeredTypes: Array>) { @@ -9,20 +8,21 @@ 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 */ fun computeSize( field: Field, flags: Array, - owner: Any? - ): Pair = computeSizeExplicit(field, flags, owner, field.type) + owner: Any?, + state: WriteState + ) = computeSizeExplicit(field, flags, owner, state, field.type) abstract fun computeSizeExplicit( field: Field, flags: Array, owner: Any?, + state: WriteState, fieldType: Class<*> - ): Pair + ) /** * Serialize a field to the buffer @@ -32,20 +32,16 @@ abstract class Serializer(registeredTypes: Array>) { field: Field, flags: Array, owner: Any?, - byteBuffer: ByteBuffer, - offset: Int, - bitFieldOffset: Int - ): Pair = serializeExplicit(field, flags, owner, byteBuffer, offset, bitFieldOffset, field.type) + writeBuffer: WriteBuffer + ) = serializeExplicit(field, flags, owner, writeBuffer, field.type) abstract fun serializeExplicit( field: Field, flags: Array, owner: Any?, - byteBuffer: ByteBuffer, - offset: Int, - bitFieldOffset: Int, + writeBuffer: WriteBuffer, fieldType: Class<*> - ): Pair + ) /** * Deserialize a field from the buffer @@ -55,22 +51,20 @@ abstract class Serializer(registeredTypes: Array>) { field: Field, flags: Array, owner: Any?, - byteBuffer: ByteBuffer, - offset: Int, - bitFieldOffset: Int - ): Pair = deserializeExplicit(field, flags, owner, byteBuffer, offset, bitFieldOffset, field.type) + readBuffer: ReadBuffer + ) = deserializeExplicit(field, flags, owner, readBuffer, field.type) abstract fun deserializeExplicit( field: Field, flags: Array, owner: Any?, - byteBuffer: ByteBuffer, - offset: Int, - bitFieldOffset: Int, + readBuffer: ReadBuffer, fieldType: Class<*> - ): Pair + ) fun getRegisteredTypes(): Array> = Arrays.copyOf(registeredTypes, registeredTypes.size) fun canSerialize(field: Field): Boolean = registeredTypes.contains(field.type) fun canSerialize(type: Class<*>): Boolean = registeredTypes.contains(type) + + protected fun throwInvalidType(type: Class<*>): Nothing = throw UnsupportedTypeException("Type ${this.javaClass} cannot serialize $type") } \ No newline at end of file diff --git a/src/net/tofvesson/networking/SyncHandler.kt b/src/net/tofvesson/networking/SyncHandler.kt index faae0ad..bdfa3b3 100644 --- a/src/net/tofvesson/networking/SyncHandler.kt +++ b/src/net/tofvesson/networking/SyncHandler.kt @@ -1,9 +1,12 @@ package net.tofvesson.networking +import net.tofvesson.reflect.access +import java.lang.reflect.Field import java.nio.ByteBuffer import java.security.NoSuchAlgorithmException import java.security.MessageDigest +@Suppress("MemberVisibilityCanBePrivate", "unused") /** * @param permissiveMismatchCheck This should essentially never be set to true aside from some *very* odd edge cases */ @@ -16,6 +19,7 @@ class SyncHandler(private val permissiveMismatchCheck: Boolean = false) { // Standard serializers serializers.add(PrimitiveSerializer.singleton) serializers.add(PrimitiveArraySerializer.singleton) + serializers.add(DiffTrackedSerializer.singleton) } fun registerSerializer(serializer: Serializer) { @@ -35,6 +39,21 @@ class SyncHandler(private val permissiveMismatchCheck: Boolean = false) { return serializer throw UnsupportedTypeException("Cannot find a compatible serializer for $type") } + + + private fun collectSyncable(fieldType: Class<*>, collectStatic: Boolean): List{ + var type = fieldType + val collect = ArrayList() + + while(type!=Object::class.java && !type.isPrimitive && type.getAnnotation(NoUpwardCascade::class.java)==null){ + for(field in type.declaredFields) + if(field.getAnnotation(SyncedVar::class.java)!=null && ((collectStatic && field.modifiers and 8 != 0) || (!collectStatic && field.modifiers and 8 == 0))) + collect.add(field) + type = type.superclass + } + + return collect + } } fun registerSyncObject(value: Any){ @@ -45,44 +64,28 @@ class SyncHandler(private val permissiveMismatchCheck: Boolean = false) { toSync.remove(value) } - fun serialize(): ByteArray{ - var headerSize = 0 - var totalSize = 0 - for(entry in toSync){ - val result = if(entry is Class<*>) computeClassSize(entry) else computeObjectSize(entry) - totalSize += result.first - headerSize += result.second - } - - var headerIndex = 0 - var dataIndex = (headerSize shr 3) + (if(headerSize and 7 != 0) 1 else 0) - - totalSize += dataIndex - - val buffer = ByteArray(totalSize) - for(entry in toSync){ - val result = - if(entry is Class<*>) readClass(entry, buffer, dataIndex, headerIndex) - else readObject(entry, buffer, dataIndex, headerIndex) - dataIndex = result.first - headerIndex = result.second - } - return buffer + fun serialize(): ByteBuffer { + val writeState = WriteState(0, 0, 0) + for(entry in toSync) + if(entry is Class<*>) computeClassSize(entry, writeState) + else computeObjectSize(entry, writeState) + val writeBuffer = WriteBuffer(writeState) + for(entry in toSync) + if(entry is Class<*>) readClass(entry, writeBuffer) + else readObject(entry, writeBuffer) + return writeBuffer.buffer } - fun deserialize(syncData: ByteArray){ - var headerSize = 0 + fun deserialize(syncData: ByteArray) = deserialize(syncData, 0) + fun deserialize(syncData: ByteArray, bitOffset: Int){ + val writeState = WriteState(0, 0, 0) for(entry in toSync) - headerSize += (if(entry is Class<*>) computeClassSize(entry) else computeObjectSize(entry)).second - var headerIndex = 0 - var dataIndex = (headerSize shr 3) + (if(headerSize and 7 != 0) 1 else 0) - for(entry in toSync){ - val result = - if(entry is Class<*>) writeClass(entry, syncData, dataIndex, headerIndex) - else writeObject(entry, syncData, dataIndex, headerIndex) - dataIndex = result.first - headerIndex = result.second - } + if(entry is Class<*>) computeClassSize(entry, writeState) + else computeObjectSize(entry, writeState) + val readBuffer = ReadBuffer(writeState, ByteBuffer.wrap(syncData), bitOffset) + for(entry in toSync) + if(entry is Class<*>) writeClass(entry, readBuffer) + else writeObject(entry, readBuffer) } fun generateMismatchCheck(): ByteArray { @@ -115,57 +118,27 @@ class SyncHandler(private val permissiveMismatchCheck: Boolean = false) { } - private fun computeObjectSize(value: Any) = computeTypeSize(value.javaClass, value) - private fun computeClassSize(value: Class<*>) = computeTypeSize(value, null) - private fun computeTypeSize(type: Class<*>, value: Any?): Pair { - var byteSize = 0 - var bitSize = 0 - for(field in type.declaredFields){ - if((value == null && field.modifiers and 8 == 0) || (value != null && field.modifiers and 8 != 0)) continue - val annotation = field.getAnnotation(SyncedVar::class.java) - field.trySetAccessible() - if(annotation!=null){ - val result = getCompatibleSerializer(field.type).computeSize(field, SyncFlag.parse(annotation.value), value) - byteSize += result.first - bitSize += result.second - } - } - return Pair(byteSize, bitSize) + private fun computeObjectSize(value: Any, writeState: WriteState) = computeTypeSize(value.javaClass, value, writeState) + private fun computeClassSize(value: Class<*>, writeState: WriteState) = computeTypeSize(value, null, writeState) + private fun computeTypeSize(type: Class<*>, value: Any?, writeState: WriteState) { + for(field in collectSyncable(type, value==null)) + getCompatibleSerializer(field.access().type) + .computeSize(field, SyncFlag.parse(field.getAnnotation(SyncedVar::class.java).value), value, writeState) } - 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 readType(type: Class<*>, value: Any?, buffer: ByteArray, offset: Int, bitOffset: Int): Pair { - val byteBuffer = ByteBuffer.wrap(buffer) - var localOffset = offset - var localBitOffset = bitOffset - for(field in type.declaredFields){ - if((value == null && field.modifiers and 8 == 0) || (value != null && field.modifiers and 8 != 0)) continue - val annotation = field.getAnnotation(SyncedVar::class.java) - if(annotation != null){ - val result = getCompatibleSerializer(field.type).serialize(field, SyncFlag.parse(annotation.value), value, byteBuffer, localOffset, localBitOffset) - localOffset = result.first - localBitOffset = result.second - } - } - return Pair(localOffset, localBitOffset) + private fun readObject(value: Any, writeBuffer: WriteBuffer) = readType(value.javaClass, value, writeBuffer) + private fun readClass(value: Class<*>, writeBuffer: WriteBuffer) = readType(value, null, writeBuffer) + private fun readType(type: Class<*>, value: Any?, writeBuffer: WriteBuffer) { + for(field in collectSyncable(type, value==null)) + getCompatibleSerializer(field.type) + .serialize(field, SyncFlag.parse(field.getAnnotation(SyncedVar::class.java).value), value, writeBuffer) } - private fun writeObject(value: Any, buffer: ByteArray, offset: Int, bitOffset: Int) = writeType(value.javaClass, value, buffer, offset, bitOffset) - private fun writeClass(value: Class<*>, buffer: ByteArray, offset: Int, bitOffset: Int) = writeType(value, null, buffer, offset, bitOffset) - private fun writeType(type: Class<*>, value: Any?, buffer: ByteArray, offset: Int, bitOffset: Int): Pair { - val byteBuffer = ByteBuffer.wrap(buffer) - var localOffset = offset - var localBitOffset = bitOffset - for(field in type.declaredFields){ - if((value == null && field.modifiers and 8 == 0) || (value != null && field.modifiers and 8 != 0)) continue - val annotation = field.getAnnotation(SyncedVar::class.java) - if(annotation != null){ - val result = getCompatibleSerializer(field.type).deserialize(field, SyncFlag.parse(annotation.value), value, byteBuffer, localOffset, localBitOffset) - localOffset = result.first - localBitOffset = result.second - } - } - return Pair(localOffset, localBitOffset) + private fun writeObject(value: Any, readBuffer: ReadBuffer) = writeType(value.javaClass, value, readBuffer) + private fun writeClass(value: Class<*>, readBuffer: ReadBuffer) = writeType(value, null, readBuffer) + private fun writeType(type: Class<*>, value: Any?, readBuffer: ReadBuffer) { + for(field in collectSyncable(type, value==null)) + getCompatibleSerializer(field.type) + .deserialize(field, SyncFlag.parse(field.getAnnotation(SyncedVar::class.java).value), value, readBuffer) } } \ No newline at end of file diff --git a/src/net/tofvesson/networking/WriteBuffer.kt b/src/net/tofvesson/networking/WriteBuffer.kt new file mode 100644 index 0000000..b69360b --- /dev/null +++ b/src/net/tofvesson/networking/WriteBuffer.kt @@ -0,0 +1,154 @@ +package net.tofvesson.networking + +import net.tofvesson.math.collapseLowerByte +import net.tofvesson.math.toNumber +import java.nio.ByteBuffer +import kotlin.experimental.and +import kotlin.experimental.or + +@Suppress("MemberVisibilityCanBePrivate", "unused") +class WriteBuffer(state: WriteState, _buffer: ByteBuffer? = null, bufferBitOffset: Int = 0) { + /* + Buffer layout: + {BufferBitOffset}[Header][Bits]{BitOffset}[Bytes] + BufferBitOffset is optional and usually set to 0 + BitOffset is between 0 and 7 bits long (to realign data to the next free byte) + */ + private var headerIndex = bufferBitOffset + private var bitOffset = bufferBitOffset + state.header + private var byteIndex = state.computeBitFieldOffset(bufferBitOffset) + + // Maximum size for the above values + private val maxHeaderSize = bufferBitOffset + state.header + private val maxBitOffset = bufferBitOffset + state.header + state.bits + private val maxByteOffset = state.computeRequiredBytes(0, bufferBitOffset) + + val buffer: ByteBuffer + + init{ + buffer = _buffer ?: ByteBuffer.allocate(state.computeRequiredBytes() + varIntSize(byteIndex.toLong())) + if(buffer.capacity() + (bufferBitOffset ushr 3) + bufferBitOffset.collapseLowerByte() + varIntSize(byteIndex.toLong()) < state.computeRequiredBytes()) + throw InsufficientCapacityException() + writePackedInt(byteIndex + varIntSize(byteIndex.toLong()), true) + } + + private fun writeBit(bit: Boolean, head: Boolean){ + if((head && headerIndex >= maxHeaderSize) || (!head && bitOffset >= maxBitOffset)) + throw IndexOutOfBoundsException("Attempt to write more ${if(head)"headers" else "bits"} than available space permits!") + + val index = (if(head) headerIndex else bitOffset) ushr 3 + val shift = (if(head) headerIndex else bitOffset) and 7 + buffer.put(index, (buffer[index] and (1 shl shift).inv().toByte()) or (bit.toNumber() shl shift).toByte()) + if(head) ++headerIndex + else ++bitOffset + } + + fun writeHeader(bit: Boolean): WriteBuffer { + writeBit(bit, true) + return this + } + + fun writeBit(bit: Boolean): WriteBuffer { + writeBit(bit, false) + return this + } + + fun writeByte(value: Byte): WriteBuffer { + doBoundaryCheck(1) + buffer.put(byteIndex, value) + ++byteIndex + return this + } + + fun writeShort(value: Short): WriteBuffer { + doBoundaryCheck(2) + buffer.putShort(byteIndex, value) + byteIndex += 2 + return this + } + + fun writeInt(value: Int): WriteBuffer { + doBoundaryCheck(4) + buffer.putInt(byteIndex, value) + byteIndex += 4 + return this + } + + fun writeLong(value: Long): WriteBuffer { + doBoundaryCheck(8) + buffer.putLong(byteIndex, value) + byteIndex += 8 + return this + } + + fun writeFloat(value: Float): WriteBuffer { + doBoundaryCheck(4) + buffer.putFloat(byteIndex, value) + byteIndex += 4 + return this + } + + fun writeDouble(value: Double): WriteBuffer { + doBoundaryCheck(8) + buffer.putDouble(byteIndex, value) + byteIndex += 8 + return this + } + + fun writePackedShort(value: Short, noZigZag: Boolean = false) = writePackedLong(value.toLong(), noZigZag) + fun writePackedInt(value: Int, noZigZag: Boolean = false) = writePackedLong(value.toLong(), noZigZag) + fun writePackedLong(value: Long, noZigZag: Boolean = false): WriteBuffer { + val toWrite = if(noZigZag) value else zigZagEncode(value) + val size = varIntSize(toWrite) + + doBoundaryCheck(size) + + when(toWrite) { + in 0..240 -> buffer.put(byteIndex, toWrite.toByte()) + in 241..2287 -> { + buffer.put(byteIndex, (((toWrite - 240) shr 8) + 241).toByte()) + buffer.put(byteIndex + 1, (toWrite - 240).toByte()) + } + in 2288..67823 -> { + buffer.put(byteIndex, 249.toByte()) + buffer.put(byteIndex + 1, ((toWrite - 2288) shr 8).toByte()) + buffer.put(byteIndex + 2, (toWrite - 2288).toByte()) + } + else -> { + var header = 255 + var match = 0x00FF_FFFF_FFFF_FFFFL + while (toWrite in 67824..match) { + --header + match = match shr 8 + } + buffer.put(byteIndex, header.toByte()) + val max = header - 247 + for (i in 0 until max) + buffer.put(byteIndex + 1 + i, (toWrite shr (i shl 3)).toByte()) + } + } + + byteIndex += size + + return this + } + + fun writePackedFloat(value: Float, noSwapEndian: Boolean = false): WriteBuffer { + val write = + if(noSwapEndian) bitConvert(floatToInt(value)) + else bitConvert(swapEndian(floatToInt(value))) + return writePackedLong(write, true) + } + + fun writePackedDouble(value: Double, noSwapEndian: Boolean = false): WriteBuffer { + val write = + if(noSwapEndian) doubleToLong(value) + else swapEndian(doubleToLong(value)) + return writePackedLong(write, true) + } + + private fun doBoundaryCheck(byteCount: Int) { + if(byteIndex + byteCount > maxByteOffset) + throw IndexOutOfBoundsException("Attempt to write value past maximum range!") + } +} \ No newline at end of file diff --git a/src/net/tofvesson/networking/WriteState.kt b/src/net/tofvesson/networking/WriteState.kt new file mode 100644 index 0000000..8c22f49 --- /dev/null +++ b/src/net/tofvesson/networking/WriteState.kt @@ -0,0 +1,22 @@ +package net.tofvesson.networking + +import net.tofvesson.math.collapseLowerByte + +data class WriteState(private var _bytes: Int, private var _bits: Int, private var _header: Int){ + var bytes: Int = _bytes + private set + var bits: Int = _bits + private set + var header: Int = _header + private set + + fun registerBytes(bytes: Int): WriteState { this.bytes += bytes; return this } + fun registerBits(bits: Int) : WriteState { this.bits += bits; return this } + fun registerHeader(header: Int): WriteState{ this.header += header; return this } + + fun computeRequiredBytes(additionalBytes: Int = 0, additionalBits: Int = 0) = + bytes + additionalBytes + computeBitFieldOffset(additionalBits) + fun computeBitFieldOffset(additionalBits: Int = 0) = roundUpBitsToBytes(bits + header + additionalBits) + + private fun roundUpBitsToBytes(bits: Int) = (bits ushr 3) + bits.collapseLowerByte() +} \ No newline at end of file diff --git a/src/net/tofvesson/support/Pair.kt b/src/net/tofvesson/support/Pair.kt new file mode 100644 index 0000000..52061be --- /dev/null +++ b/src/net/tofvesson/support/Pair.kt @@ -0,0 +1,3 @@ +package net.tofvesson.support + +operator fun Pair.plus(other: Pair) = Pair(first + other.first, second + other.second) \ No newline at end of file