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