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
This commit is contained in:
Gabriel Tofvesson 2018-07-27 19:44:52 +02:00
parent 60cebfb966
commit 2af938169c
18 changed files with 953 additions and 577 deletions

View File

@ -1,3 +1,5 @@
import net.tofvesson.math.MathSerializer;
import net.tofvesson.math.Vector3;
import net.tofvesson.networking.*; import net.tofvesson.networking.*;
public class Main { public class Main {
@ -25,10 +27,13 @@ public class Main {
@SyncedVar @SyncedVar
public static DiffTrackedArray<Long> tracker2 = new DiffTrackedArray<>(Long.class, 8, i -> (long)i); public static DiffTrackedArray<Long> 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){ public static void main(String[] args){
Main testObject = new Main(); Main testObject = new Main();
SyncHandler.Companion.registerSerializer(DiffTrackedSerializer.Companion.getSingleton());
SyncHandler.Companion.registerSerializer(MathSerializer.Companion.getSingleton());
SyncHandler sync = new SyncHandler(); SyncHandler sync = new SyncHandler();
sync.registerSyncObject(testObject); sync.registerSyncObject(testObject);
sync.registerSyncObject(Main.class); sync.registerSyncObject(Main.class);
@ -40,9 +45,11 @@ public class Main {
tracker.setValue(9); tracker.setValue(9);
tracker2.set(3L, 2); tracker2.set(3L, 2);
tracker2.set(5L, 0); tracker2.set(5L, 0);
lookDirection.setX(355);
lookDirection.setZ(0);
// Generate snapshot of values to serialize // 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"+ System.out.print("Created and serialized snapshot of field values:\n\t"+
testObject.syncTest+"\n\t"+ testObject.syncTest+"\n\t"+
@ -52,7 +59,8 @@ public class Main {
testbool1+"\n\t"+ testbool1+"\n\t"+
test[0]+"\n\t"+ test[0]+"\n\t"+
test[1]+"\n\t"+ test[1]+"\n\t"+
tracker tracker+"\n\t"+
lookDirection
); );
for(Long value : tracker2.getValues()) for(Long value : tracker2.getValues())
System.out.print("\n\t"+value); System.out.print("\n\t"+value);
@ -64,13 +72,13 @@ public class Main {
value = 9.0f; value = 9.0f;
testObject.testbool = true; testObject.testbool = true;
testbool1 = false; testbool1 = false;
test = new boolean[3];
test[0] = false; test[0] = false;
test[1] = true; test[1] = true;
test[2] = true;
tracker.setValue(400); tracker.setValue(400);
tracker2.set(8L, 2);
tracker2.set(100L, 0); 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"+ System.out.print("Set a new state of test values:\n\t"+
testObject.syncTest+"\n\t"+ testObject.syncTest+"\n\t"+
@ -80,8 +88,8 @@ public class Main {
testbool1+"\n\t"+ testbool1+"\n\t"+
test[0]+"\n\t"+ test[0]+"\n\t"+
test[1]+"\n\t"+ test[1]+"\n\t"+
test[2]+"\n\t"+ tracker+"\n\t"+
tracker lookDirection
); );
for(Long value : tracker2.getValues()) for(Long value : tracker2.getValues())
System.out.print("\n\t"+value); System.out.print("\n\t"+value);
@ -101,7 +109,8 @@ public class Main {
testbool1+"\n\t"+ testbool1+"\n\t"+
test[0]+"\n\t"+ test[0]+"\n\t"+
test[1]+"\n\t"+ test[1]+"\n\t"+
tracker); tracker+"\n\t"+
lookDirection);
for(Long value : tracker2.getValues()) for(Long value : tracker2.getValues())
System.out.print("\n\t"+value); System.out.print("\n\t"+value);
System.out.println("\n\nSnapshot size: "+ser.length+" bytes"); System.out.println("\n\nSnapshot size: "+ser.length+" bytes");

View File

@ -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

View File

@ -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<out SyncFlag>, 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<Float>
val yDiff = yField.get(vector) as DiffTracked<Float>
val zDiff = zField.get(vector) as DiffTracked<Float>
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<out SyncFlag>, 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<Float>
val yDiff = yField.get(vector) as DiffTracked<Float>
val zDiff = zField.get(vector) as DiffTracked<Float>
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<out SyncFlag>, 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<Float>
val yDiff = yField.get(vector) as DiffTracked<Float>
val zDiff = zField.get(vector) as DiffTracked<Float>
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)
}
}
}

View File

@ -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)"
}

View File

@ -2,6 +2,7 @@ package net.tofvesson.networking
import java.nio.ByteBuffer import java.nio.ByteBuffer
import kotlin.experimental.or import kotlin.experimental.or
import kotlin.math.roundToInt
fun varIntSize(value: Long): Int = fun varIntSize(value: Long): Int =
when { when {
@ -158,4 +159,29 @@ fun swapEndian(value: Long) =
fun bitConvert(value: Int): Long = value.toLong() and 0xFFFFFFFFL fun bitConvert(value: Int): Long = value.toLong() and 0xFFFFFFFFL
fun zigZagEncode(value: Long): Long = (value shl 1) xor (value shr 63) 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) 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)
}

View File

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

View File

@ -0,0 +1,7 @@
package net.tofvesson.networking
data class Holder(var value: Any?){
companion object {
val valueField = Holder::class.java.getDeclaredField("value")!!
}
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -0,0 +1,6 @@
package net.tofvesson.networking
/**
* Prevents SyncedVar system from syncing superclasses
*/
annotation class NoUpwardCascade

View File

@ -1,8 +1,8 @@
package net.tofvesson.networking package net.tofvesson.networking
import java.lang.reflect.Field import java.lang.reflect.Field
import java.nio.ByteBuffer
@Suppress("MemberVisibilityCanBePrivate")
class PrimitiveArraySerializer private constructor(): Serializer(arrayOf( class PrimitiveArraySerializer private constructor(): Serializer(arrayOf(
BooleanArray::class.java, BooleanArray::class.java,
ByteArray::class.java, ByteArray::class.java,
@ -18,166 +18,136 @@ class PrimitiveArraySerializer private constructor(): Serializer(arrayOf(
val singleton = PrimitiveArraySerializer() val singleton = PrimitiveArraySerializer()
} }
override fun computeSizeExplicit(field: Field, flags: Array<out SyncFlag>, owner: Any?, fieldType: Class<*>): Pair<Int, Int> { override fun computeSizeExplicit(field: Field, flags: Array<out SyncFlag>, owner: Any?, state: WriteState, fieldType: Class<*>) {
val arrayLength = java.lang.reflect.Array.getLength(field.get(owner)) val arrayLength = java.lang.reflect.Array.getLength(field.get(owner))
var byteSize = if(flags.contains(knownSize)) 0 else varIntSize(arrayLength.toLong()) val holder = Holder(null)
var bitSize = 0
when (fieldType) { when (fieldType) {
BooleanArray::class.java -> bitSize = arrayLength BooleanArray::class.java -> state.registerBits(arrayLength)
ByteArray::class.java -> byteSize += arrayLength ByteArray::class.java -> state.registerBytes(arrayLength)
ShortArray::class.java -> ShortArray::class.java ->
if(flags.contains(SyncFlag.NoCompress)) byteSize += arrayLength * 2 if(flags.contains(SyncFlag.NoCompress)) state.registerBytes(arrayLength * 2)
else else {
for(value in field.get(owner) as ShortArray) val shortSerializer = SyncHandler.getCompatibleSerializer(Short::class.java)
byteSize += for (value in field.get(owner) as ShortArray) {
varIntSize( holder.value = value
if(flags.contains(SyncFlag.NonNegative)) value.toLong() shortSerializer.computeSizeExplicit(Holder.valueField, flags, holder, state, Short::class.java)
else zigZagEncode(value.toLong()) }
) }
IntArray::class.java -> IntArray::class.java ->
if(flags.contains(SyncFlag.NoCompress)) byteSize += arrayLength * 4 if(flags.contains(SyncFlag.NoCompress)) state.registerBytes(arrayLength * 4)
else else {
for(value in field.get(owner) as IntArray) val intSerializer = SyncHandler.getCompatibleSerializer(Int::class.java)
byteSize += for (value in field.get(owner) as IntArray) {
varIntSize( holder.value = value
if(flags.contains(SyncFlag.NonNegative)) value.toLong() intSerializer.computeSizeExplicit(Holder.valueField, flags, holder, state, Int::class.java)
else zigZagEncode(value.toLong()) }
) }
LongArray::class.java -> LongArray::class.java ->
if(flags.contains(SyncFlag.NoCompress)) byteSize += arrayLength * 8 if(flags.contains(SyncFlag.NoCompress)) state.registerBytes(arrayLength * 8)
else else {
for(value in field.get(owner) as LongArray) val longSerializer = SyncHandler.getCompatibleSerializer(Long::class.java)
byteSize += for (value in field.get(owner) as LongArray) {
varIntSize( holder.value = value
if(flags.contains(SyncFlag.NonNegative)) value longSerializer.computeSizeExplicit(Holder.valueField, flags, holder, state, Long::class.java)
else zigZagEncode(value) }
) }
FloatArray::class.java -> FloatArray::class.java ->
if(flags.contains(SyncFlag.NoCompress)) byteSize += arrayLength * 4 if(flags.contains(SyncFlag.NoCompress)) state.registerBytes(arrayLength * 4)
else else {
for (value in field.get(owner) as LongArray) val floatSerializer = SyncHandler.getCompatibleSerializer(Float::class.java)
byteSize += for (value in field.get(owner) as FloatArray) {
varIntSize( holder.value = value
if(flags.contains(SyncFlag.FloatEndianSwap)) bitConvert(swapEndian(floatToInt(field.getFloat(owner)))) floatSerializer.computeSizeExplicit(Holder.valueField, flags, holder, state, Float::class.java)
else bitConvert(floatToInt(field.getFloat(owner))) }
) }
DoubleArray::class.java -> DoubleArray::class.java ->
if(flags.contains(SyncFlag.NoCompress)) byteSize += arrayLength * 8 if(flags.contains(SyncFlag.NoCompress)) state.registerBytes(arrayLength * 8)
else else {
for (value in field.get(owner) as LongArray) val doubleSerializer = SyncHandler.getCompatibleSerializer(Double::class.java)
byteSize += for (value in field.get(owner) as DoubleArray) {
varIntSize( holder.value = value
if(flags.contains(SyncFlag.FloatEndianSwap)) swapEndian(doubleToLong(field.getDouble(owner))) doubleSerializer.computeSizeExplicit(Holder.valueField, flags, holder, state, Double::class.java)
else doubleToLong(field.getDouble(owner)) }
) }
else -> throwInvalidType(fieldType)
} }
return Pair(byteSize, bitSize)
} }
override fun serializeExplicit(field: Field, flags: Array<out SyncFlag>, owner: Any?, byteBuffer: ByteBuffer, offset: Int, bitFieldOffset: Int, fieldType: Class<*>): Pair<Int, Int> { override fun serializeExplicit(field: Field, flags: Array<out SyncFlag>, owner: Any?, writeBuffer: WriteBuffer, fieldType: Class<*>) {
val arrayLength = java.lang.reflect.Array.getLength(field.get(owner)) val arrayLength = java.lang.reflect.Array.getLength(field.get(owner))
var localByteOffset = offset val holder = Holder(null)
var localBitOffset = bitFieldOffset if(!flags.contains(knownSize)) writeBuffer.writePackedInt(arrayLength, true)
if(!flags.contains(knownSize)){
writeVarInt(byteBuffer, offset, arrayLength.toLong())
localByteOffset += varIntSize(arrayLength.toLong())
}
when (fieldType) { when (fieldType) {
BooleanArray::class.java -> BooleanArray::class.java ->
for(value in field.get(owner) as BooleanArray) for(value in field.get(owner) as BooleanArray)
writeBit(value, byteBuffer, localBitOffset++) writeBuffer.writeBit(value)
ByteArray::class.java -> ByteArray::class.java ->
for(value in field.get(owner) as ByteArray) for(value in field.get(owner) as ByteArray)
byteBuffer.put(localByteOffset++, value) writeBuffer.writeByte(value)
ShortArray::class.java -> ShortArray::class.java ->
if(flags.contains(SyncFlag.NoCompress)) if(flags.contains(SyncFlag.NoCompress))
for(value in field.get(owner) as ShortArray){ for(value in field.get(owner) as ShortArray)
byteBuffer.putShort(localByteOffset, value) writeBuffer.writeShort(value)
localByteOffset += 2 else {
} val shortSerializer = SyncHandler.getCompatibleSerializer(Short::class.java)
else for (value in field.get(owner) as ShortArray) {
for(value in field.get(owner) as ShortArray) { holder.value = value
val rawVal = shortSerializer.serializeExplicit(Holder.valueField, flags, holder, writeBuffer, fieldType)
if(flags.contains(SyncFlag.NonNegative)) value.toLong()
else zigZagEncode(value.toLong())
writeVarInt(byteBuffer, localByteOffset, rawVal)
localByteOffset += varIntSize(rawVal)
} }
}
IntArray::class.java -> IntArray::class.java ->
if(flags.contains(SyncFlag.NoCompress)) if(flags.contains(SyncFlag.NoCompress))
for(value in field.get(owner) as IntArray){ for(value in field.get(owner) as IntArray)
byteBuffer.putInt(localByteOffset, value) writeBuffer.writeInt(value)
localByteOffset += 4 else {
} val intSerializer = SyncHandler.getCompatibleSerializer(Int::class.java)
else for (value in field.get(owner) as IntArray) {
for(value in field.get(owner) as IntArray) { holder.value = value
val rawVal = intSerializer.serializeExplicit(Holder.valueField, flags, holder, writeBuffer, fieldType)
if(flags.contains(SyncFlag.NonNegative)) value.toLong()
else zigZagEncode(value.toLong())
writeVarInt(byteBuffer, localByteOffset, rawVal)
localByteOffset += varIntSize(rawVal)
} }
}
LongArray::class.java -> LongArray::class.java ->
if(flags.contains(SyncFlag.NoCompress)) if(flags.contains(SyncFlag.NoCompress))
for(value in field.get(owner) as LongArray){ for(value in field.get(owner) as LongArray)
byteBuffer.putLong(localByteOffset, value) writeBuffer.writeLong(value)
localByteOffset += 8 else {
} val longSerializer = SyncHandler.getCompatibleSerializer(Long::class.java)
else for (value in field.get(owner) as LongArray) {
for(value in field.get(owner) as LongArray) { holder.value = value
val rawVal = longSerializer.serializeExplicit(Holder.valueField, flags, holder, writeBuffer, fieldType)
if(flags.contains(SyncFlag.NonNegative)) value
else zigZagEncode(value)
writeVarInt(
byteBuffer,
localByteOffset,
rawVal
)
localByteOffset += varIntSize(rawVal)
} }
}
FloatArray::class.java -> FloatArray::class.java ->
if(flags.contains(SyncFlag.NoCompress)) if(flags.contains(SyncFlag.NoCompress))
for(value in field.get(owner) as FloatArray){ for(value in field.get(owner) as FloatArray)
byteBuffer.putFloat(localByteOffset, value) writeBuffer.writeFloat(value)
localByteOffset += 4 else {
} val floatSerializer = SyncHandler.getCompatibleSerializer(Float::class.java)
else
for (value in field.get(owner) as FloatArray) { for (value in field.get(owner) as FloatArray) {
val rawVal = holder.value = value
if(flags.contains(SyncFlag.FloatEndianSwap)) bitConvert(swapEndian(floatToInt(field.getFloat(owner)))) floatSerializer.serializeExplicit(Holder.valueField, flags, holder, writeBuffer, fieldType)
else bitConvert(floatToInt(field.getFloat(owner)))
writeVarInt(byteBuffer, localByteOffset, rawVal)
localByteOffset += varIntSize(rawVal)
} }
}
DoubleArray::class.java -> DoubleArray::class.java ->
if(flags.contains(SyncFlag.NoCompress)) if(flags.contains(SyncFlag.NoCompress))
for(value in field.get(owner) as DoubleArray){ for(value in field.get(owner) as DoubleArray)
byteBuffer.putDouble(localByteOffset, value) writeBuffer.writeDouble(value)
localByteOffset += 8 else {
} val doubleSerializer = SyncHandler.getCompatibleSerializer(Double::class.java)
else for (value in field.get(owner) as DoubleArray) {
for (value in field.get(owner) as DoubleArray){ holder.value = value
val rawVal = doubleSerializer.serializeExplicit(Holder.valueField, flags, holder, writeBuffer, fieldType)
if(flags.contains(SyncFlag.FloatEndianSwap)) swapEndian(doubleToLong(field.getDouble(owner)))
else doubleToLong(field.getDouble(owner))
writeVarInt(byteBuffer, localByteOffset, rawVal)
localByteOffset += varIntSize(rawVal)
} }
}
else -> throwInvalidType(fieldType)
} }
return Pair(localByteOffset, localBitOffset)
} }
override fun deserializeExplicit(field: Field, flags: Array<out SyncFlag>, owner: Any?, byteBuffer: ByteBuffer, offset: Int, bitFieldOffset: Int, fieldType: Class<*>): Pair<Int, Int> { override fun deserializeExplicit(field: Field, flags: Array<out SyncFlag>, owner: Any?, readBuffer: ReadBuffer, fieldType: Class<*>) {
var localByteOffset = offset
var localBitOffset = bitFieldOffset
val localLength = java.lang.reflect.Array.getLength(field.get(owner)) val localLength = java.lang.reflect.Array.getLength(field.get(owner))
val arrayLength = val arrayLength =
if(flags.contains(knownSize)) localLength if(flags.contains(knownSize)) localLength
else{ else readBuffer.readPackedInt(true)
val v = readVarInt(byteBuffer, offset)
localByteOffset += varIntSize(v)
v.toInt()
}
val target = val target =
if(arrayLength!=localLength) java.lang.reflect.Array.newInstance(field.type.componentType, arrayLength) 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 -> { BooleanArray::class.java -> {
val booleanTarget = target as BooleanArray val booleanTarget = target as BooleanArray
for (index in 0 until arrayLength) for (index in 0 until arrayLength)
booleanTarget[index] = readBit(byteBuffer, localBitOffset++) booleanTarget[index] = readBuffer.readBit()
} }
ByteArray::class.java -> { ByteArray::class.java -> {
val byteTarget = target as ByteArray val byteTarget = target as ByteArray
for (index in 0 until arrayLength) for (index in 0 until arrayLength)
byteTarget[index] = byteBuffer[localByteOffset++] byteTarget[index] = readBuffer.readByte()
} }
ShortArray::class.java -> { ShortArray::class.java -> {
val shortTarget = target as ShortArray val shortTarget = target as ShortArray
if (flags.contains(SyncFlag.NoCompress)) if (flags.contains(SyncFlag.NoCompress))
for (index in 0 until arrayLength) { for (index in 0 until arrayLength)
shortTarget[index] = byteBuffer.getShort(localByteOffset) shortTarget[index] = readBuffer.readShort()
localByteOffset += 2
}
else else
for (index in 0 until arrayLength) { for (index in 0 until arrayLength)
val rawValue = shortTarget[index] = readBuffer.readPackedShort(flags.contains(SyncFlag.NonNegative))
if(flags.contains(SyncFlag.NonNegative)) readVarInt(byteBuffer, offset)
else zigZagDecode(readVarInt(byteBuffer, offset))
shortTarget[index] = rawValue.toShort()
localByteOffset += varIntSize(rawValue)
}
} }
IntArray::class.java -> { IntArray::class.java -> {
val intTarget = target as IntArray val intTarget = target as IntArray
if (flags.contains(SyncFlag.NoCompress)) if (flags.contains(SyncFlag.NoCompress))
for (index in 0 until arrayLength) { for (index in 0 until arrayLength)
intTarget[index] = byteBuffer.getInt(localByteOffset) intTarget[index] = readBuffer.readInt()
localByteOffset += 4
}
else else
for (index in 0 until arrayLength) { for (index in 0 until arrayLength)
val rawValue = intTarget[index] = readBuffer.readPackedInt(flags.contains(SyncFlag.NonNegative))
if(flags.contains(SyncFlag.NonNegative)) readVarInt(byteBuffer, offset)
else zigZagDecode(readVarInt(byteBuffer, offset))
intTarget[index] = rawValue.toInt()
localByteOffset += varIntSize(rawValue)
}
} }
LongArray::class.java -> { LongArray::class.java -> {
val longTarget = target as LongArray val longTarget = target as LongArray
if (flags.contains(SyncFlag.NoCompress)) if (flags.contains(SyncFlag.NoCompress))
for (index in 0 until arrayLength) { for (index in 0 until arrayLength)
longTarget[index] = byteBuffer.getLong(localByteOffset) longTarget[index] = readBuffer.readLong()
localByteOffset += 8
}
else else
for (index in 0 until arrayLength) { for (index in 0 until arrayLength)
val rawValue = longTarget[index] = readBuffer.readPackedLong(flags.contains(SyncFlag.NonNegative))
if(flags.contains(SyncFlag.NonNegative)) readVarInt(byteBuffer, offset)
else zigZagDecode(readVarInt(byteBuffer, offset))
longTarget[index] = rawValue
localByteOffset += varIntSize(rawValue)
}
} }
FloatArray::class.java -> { FloatArray::class.java -> {
val floatTarget = target as FloatArray val floatTarget = target as FloatArray
if (flags.contains(SyncFlag.NoCompress)) if (flags.contains(SyncFlag.NoCompress))
for (index in 0 until arrayLength) { for (index in 0 until arrayLength)
floatTarget[index] = byteBuffer.getFloat(localByteOffset) floatTarget[index] = readBuffer.readFloat()
localByteOffset += 4
}
else else
for (index in 0 until arrayLength) { for (index in 0 until arrayLength)
val readVal = readVarInt(byteBuffer, offset) floatTarget[index] = readBuffer.readPackedFloat(flags.contains(SyncFlag.NonNegative))
val rawValue =
if(flags.contains(SyncFlag.FloatEndianSwap)) intToFloat(swapEndian(readVal.toInt()))
else intToFloat(readVal.toInt())
floatTarget[index] = rawValue
localBitOffset += varIntSize(readVal)
}
} }
DoubleArray::class.java -> { DoubleArray::class.java -> {
val doubleTarget = target as DoubleArray val doubleTarget = target as DoubleArray
if (flags.contains(SyncFlag.NoCompress)) if (flags.contains(SyncFlag.NoCompress))
for (index in 0 until arrayLength) { for (index in 0 until arrayLength)
doubleTarget[index] = byteBuffer.getDouble(localByteOffset) doubleTarget[index] = readBuffer.readDouble()
localByteOffset += 8
}
else else
for (index in 0 until arrayLength) { for (index in 0 until arrayLength)
val readVal = readVarInt(byteBuffer, offset) doubleTarget[index] = readBuffer.readPackedDouble(flags.contains(SyncFlag.NonNegative))
val rawValue =
if(flags.contains(SyncFlag.FloatEndianSwap)) longToDouble(swapEndian(readVal))
else longToDouble(readVal)
doubleTarget[index] = rawValue
localBitOffset += varIntSize(readVal)
}
} }
else -> throwInvalidType(fieldType)
} }
if(arrayLength!=localLength) field.set(owner, target) if(arrayLength!=localLength) field.set(owner, target)
return Pair(localByteOffset, localBitOffset)
} }
} }

View File

@ -2,7 +2,6 @@ package net.tofvesson.networking
import net.tofvesson.reflect.* import net.tofvesson.reflect.*
import java.lang.reflect.Field import java.lang.reflect.Field
import java.nio.ByteBuffer
class PrimitiveSerializer private constructor() : Serializer(arrayOf( class PrimitiveSerializer private constructor() : Serializer(arrayOf(
Boolean::class.java, Boolean::class.java,
@ -26,205 +25,100 @@ class PrimitiveSerializer private constructor() : Serializer(arrayOf(
field: Field, field: Field,
flags: Array<out SyncFlag>, flags: Array<out SyncFlag>,
owner: Any?, owner: Any?,
state: WriteState,
fieldType: Class<*> fieldType: Class<*>
): Pair<Int, Int> = ) {
when(fieldType){ when (fieldType) {
java.lang.Boolean::class.java, Boolean::class.java -> Pair(0, 1) java.lang.Boolean::class.java, Boolean::class.java -> state.registerBits(1)
java.lang.Byte::class.java, Byte::class.java -> Pair(1, 0) java.lang.Byte::class.java, Byte::class.java -> state.registerBytes(1)
java.lang.Short::class.java, Short::class.java -> java.lang.Short::class.java, Short::class.java ->
if(flags.contains(SyncFlag.NoCompress)) Pair(2, 0) if (flags.contains(SyncFlag.NoCompress)) state.registerBytes(2)
else Pair(varIntSize( else state.registerBytes(varIntSize(
if(flags.contains(SyncFlag.NonNegative)) field.getShortAdaptive(owner).toLong() if (flags.contains(SyncFlag.NonNegative)) field.getShortAdaptive(owner).toLong()
else zigZagEncode(field.getShortAdaptive(owner).toLong()) else zigZagEncode(field.getShortAdaptive(owner).toLong())
), 0) ))
java.lang.Integer::class.java, Int::class.java -> java.lang.Integer::class.java, Int::class.java ->
if(flags.contains(SyncFlag.NoCompress)) Pair(4, 0) if (flags.contains(SyncFlag.NoCompress)) state.registerBytes(4)
else Pair(varIntSize( else state.registerBytes(varIntSize(
if(flags.contains(SyncFlag.NonNegative)) field.getIntAdaptive(owner).toLong() if (flags.contains(SyncFlag.NonNegative)) field.getIntAdaptive(owner).toLong()
else zigZagEncode(field.getIntAdaptive(owner).toLong()) else zigZagEncode(field.getIntAdaptive(owner).toLong())
), 0) ))
java.lang.Long::class.java, Long::class.java -> java.lang.Long::class.java, Long::class.java ->
if(flags.contains(SyncFlag.NoCompress)) Pair(8, 0) if (flags.contains(SyncFlag.NoCompress)) state.registerBytes(8)
else Pair(varIntSize( else state.registerBytes(varIntSize(
if(flags.contains(SyncFlag.NonNegative)) field.getLongAdaptive(owner) if (flags.contains(SyncFlag.NonNegative)) field.getLongAdaptive(owner)
else zigZagEncode(field.getLongAdaptive(owner)) else zigZagEncode(field.getLongAdaptive(owner))
), 0) ))
java.lang.Float::class.java, Float::class.java -> java.lang.Float::class.java, Float::class.java ->
if(flags.contains(SyncFlag.NoCompress)) Pair(4, 0) if (flags.contains(SyncFlag.NoCompress)) state.registerBytes(4)
else Pair(varIntSize( else state.registerBytes(varIntSize(
if(flags.contains(SyncFlag.FloatEndianSwap)) bitConvert(swapEndian(floatToInt(field.getFloatAdaptive(owner)))) if (flags.contains(SyncFlag.FloatEndianSwap)) bitConvert(swapEndian(floatToInt(field.getFloatAdaptive(owner))))
else bitConvert(floatToInt(field.getFloatAdaptive(owner))) else bitConvert(floatToInt(field.getFloatAdaptive(owner)))
), 0) ))
java.lang.Double::class.java, Double::class.java -> java.lang.Double::class.java, Double::class.java ->
if(flags.contains(SyncFlag.NoCompress)) Pair(8, 0) if (flags.contains(SyncFlag.NoCompress)) state.registerBytes(8)
else Pair(varIntSize( else state.registerBytes(varIntSize(
if(flags.contains(SyncFlag.FloatEndianSwap)) swapEndian(doubleToLong(field.getDoubleAdaptive(owner))) if (flags.contains(SyncFlag.FloatEndianSwap)) swapEndian(doubleToLong(field.getDoubleAdaptive(owner)))
else doubleToLong(field.getDoubleAdaptive(owner)) else doubleToLong(field.getDoubleAdaptive(owner))
), 0) ))
else -> Pair(0, 0) else -> throwInvalidType(fieldType)
} }
}
override fun serializeExplicit( override fun serializeExplicit(
field: Field, field: Field,
flags: Array<out SyncFlag>, flags: Array<out SyncFlag>,
owner: Any?, owner: Any?,
byteBuffer: ByteBuffer, writeBuffer: WriteBuffer,
offset: Int,
bitFieldOffset: Int,
fieldType: Class<*> fieldType: Class<*>
): Pair<Int, Int> = ){
when(fieldType){ when (fieldType) {
java.lang.Boolean::class.java, Boolean::class.java -> { java.lang.Boolean::class.java, Boolean::class.java -> writeBuffer.writeBit(field.getBooleanAdaptive(owner))
writeBit(field.getBooleanAdaptive(owner), byteBuffer, bitFieldOffset) java.lang.Byte::class.java, Byte::class.java -> writeBuffer.writeByte(field.getByteAdaptive(owner))
Pair(offset, bitFieldOffset+1) java.lang.Short::class.java, Short::class.java ->
} if (flags.contains(SyncFlag.NoCompress)) writeBuffer.writeShort(field.getShortAdaptive(owner))
java.lang.Byte::class.java, Byte::class.java -> { else writeBuffer.writePackedShort(field.getShortAdaptive(owner), flags.contains(SyncFlag.NonNegative))
byteBuffer.put(offset, field.getByteAdaptive(owner)) java.lang.Integer::class.java, Int::class.java ->
Pair(offset+1, bitFieldOffset) if (flags.contains(SyncFlag.NoCompress)) writeBuffer.writeInt(field.getIntAdaptive(owner))
} else writeBuffer.writePackedInt(field.getIntAdaptive(owner), flags.contains(SyncFlag.NonNegative))
java.lang.Short::class.java, Short::class.java -> java.lang.Long::class.java, Long::class.java ->
if(flags.contains(SyncFlag.NoCompress)){ if (flags.contains(SyncFlag.NoCompress)) writeBuffer.writeLong(field.getLongAdaptive(owner))
byteBuffer.putShort(offset, field.getShortAdaptive(owner)) else writeBuffer.writePackedLong(field.getLongAdaptive(owner), flags.contains(SyncFlag.NonNegative))
Pair(offset+2, bitFieldOffset) java.lang.Float::class.java, Float::class.java ->
} if (flags.contains(SyncFlag.NoCompress)) writeBuffer.writeFloat(field.getFloatAdaptive(owner))
else { else writeBuffer.writePackedFloat(field.getFloatAdaptive(owner), flags.contains(SyncFlag.FloatEndianSwap))
val rawValue = java.lang.Double::class.java, Double::class.java ->
if(flags.contains(SyncFlag.NonNegative)) field.getShortAdaptive(owner).toLong() if (flags.contains(SyncFlag.NoCompress)) writeBuffer.writeDouble(field.getDoubleAdaptive(owner))
else zigZagEncode(field.getShortAdaptive(owner).toLong()) else writeBuffer.writePackedDouble(field.getDoubleAdaptive(owner), flags.contains(SyncFlag.FloatEndianSwap))
writeVarInt(byteBuffer, offset, rawValue) else -> throwInvalidType(fieldType)
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)
}
override fun deserializeExplicit( override fun deserializeExplicit(
field: Field, field: Field,
flags: Array<out SyncFlag>, flags: Array<out SyncFlag>,
owner: Any?, owner: Any?,
byteBuffer: ByteBuffer, readBuffer: ReadBuffer,
offset: Int,
bitFieldOffset: Int,
fieldType: Class<*> fieldType: Class<*>
): Pair<Int, Int> = ) =
when(fieldType){ when(fieldType){
java.lang.Boolean::class.java, Boolean::class.java -> { java.lang.Boolean::class.java, Boolean::class.java -> field.setBooleanAdaptive(owner, readBuffer.readBit())
field.setBooleanAdaptive(owner, readBit(byteBuffer, bitFieldOffset)) java.lang.Byte::class.java, Byte::class.java -> field.setByteAdaptive(owner, readBuffer.readByte())
Pair(offset, bitFieldOffset+1)
}
java.lang.Byte::class.java, Byte::class.java -> {
field.setByteAdaptive(owner, byteBuffer.get(offset))
Pair(offset+1, bitFieldOffset)
}
java.lang.Short::class.java, Short::class.java -> java.lang.Short::class.java, Short::class.java ->
if(flags.contains(SyncFlag.NoCompress)){ if(flags.contains(SyncFlag.NoCompress)) field.setShortAdaptive(owner, readBuffer.readShort())
field.setShortAdaptive(owner, byteBuffer.getShort(offset)) else field.setShortAdaptive(owner, readBuffer.readPackedShort(flags.contains(SyncFlag.NonNegative)))
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)
}
java.lang.Integer::class.java, Int::class.java -> java.lang.Integer::class.java, Int::class.java ->
if(flags.contains(SyncFlag.NoCompress)){ if(flags.contains(SyncFlag.NoCompress)) field.setIntAdaptive(owner, readBuffer.readInt())
field.setIntAdaptive(owner, byteBuffer.getInt(offset)) else field.setIntAdaptive(owner, readBuffer.readPackedInt(flags.contains(SyncFlag.NonNegative)))
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)
}
java.lang.Long::class.java, Long::class.java -> java.lang.Long::class.java, Long::class.java ->
if(flags.contains(SyncFlag.NoCompress)){ if(flags.contains(SyncFlag.NoCompress)) field.setLongAdaptive(owner, readBuffer.readLong())
field.setLongAdaptive(owner, byteBuffer.getLong(offset)) else field.setLongAdaptive(owner, readBuffer.readPackedLong(flags.contains(SyncFlag.NonNegative)))
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)
}
java.lang.Float::class.java, Float::class.java -> java.lang.Float::class.java, Float::class.java ->
if(flags.contains(SyncFlag.NoCompress)){ if(flags.contains(SyncFlag.NoCompress)) field.setFloatAdaptive(owner, readBuffer.readFloat())
field.setFloatAdaptive(owner, byteBuffer.getFloat(offset)) else field.setFloatAdaptive(owner, readBuffer.readPackedFloat(flags.contains(SyncFlag.FloatEndianSwap)))
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)
}
java.lang.Double::class.java, Double::class.java -> java.lang.Double::class.java, Double::class.java ->
if(flags.contains(SyncFlag.NoCompress)){ if(flags.contains(SyncFlag.NoCompress)) field.setDoubleAdaptive(owner, readBuffer.readDouble())
field.setDoubleAdaptive(owner, byteBuffer.getDouble(offset)) else field.setDoubleAdaptive(owner, readBuffer.readPackedDouble(flags.contains(SyncFlag.FloatEndianSwap)))
Pair(offset+8, bitFieldOffset) else -> throwInvalidType(fieldType)
}
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)
} }
} }

View File

@ -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!")
}
}

View File

@ -1,7 +1,6 @@
package net.tofvesson.networking package net.tofvesson.networking
import java.lang.reflect.Field import java.lang.reflect.Field
import java.nio.ByteBuffer
import java.util.* import java.util.*
abstract class Serializer(registeredTypes: Array<Class<*>>) { abstract class Serializer(registeredTypes: Array<Class<*>>) {
@ -9,20 +8,21 @@ abstract class Serializer(registeredTypes: Array<Class<*>>) {
/** /**
* Compute the size in bits that a field will occupy * Compute the size in bits that a field will occupy
* @return Size in the byteField (first) and bitField (second) that need to be allocated
*/ */
fun computeSize( fun computeSize(
field: Field, field: Field,
flags: Array<out SyncFlag>, flags: Array<out SyncFlag>,
owner: Any? owner: Any?,
): Pair<Int, Int> = computeSizeExplicit(field, flags, owner, field.type) state: WriteState
) = computeSizeExplicit(field, flags, owner, state, field.type)
abstract fun computeSizeExplicit( abstract fun computeSizeExplicit(
field: Field, field: Field,
flags: Array<out SyncFlag>, flags: Array<out SyncFlag>,
owner: Any?, owner: Any?,
state: WriteState,
fieldType: Class<*> fieldType: Class<*>
): Pair<Int, Int> )
/** /**
* Serialize a field to the buffer * Serialize a field to the buffer
@ -32,20 +32,16 @@ abstract class Serializer(registeredTypes: Array<Class<*>>) {
field: Field, field: Field,
flags: Array<out SyncFlag>, flags: Array<out SyncFlag>,
owner: Any?, owner: Any?,
byteBuffer: ByteBuffer, writeBuffer: WriteBuffer
offset: Int, ) = serializeExplicit(field, flags, owner, writeBuffer, field.type)
bitFieldOffset: Int
): Pair<Int, Int> = serializeExplicit(field, flags, owner, byteBuffer, offset, bitFieldOffset, field.type)
abstract fun serializeExplicit( abstract fun serializeExplicit(
field: Field, field: Field,
flags: Array<out SyncFlag>, flags: Array<out SyncFlag>,
owner: Any?, owner: Any?,
byteBuffer: ByteBuffer, writeBuffer: WriteBuffer,
offset: Int,
bitFieldOffset: Int,
fieldType: Class<*> fieldType: Class<*>
): Pair<Int, Int> )
/** /**
* Deserialize a field from the buffer * Deserialize a field from the buffer
@ -55,22 +51,20 @@ abstract class Serializer(registeredTypes: Array<Class<*>>) {
field: Field, field: Field,
flags: Array<out SyncFlag>, flags: Array<out SyncFlag>,
owner: Any?, owner: Any?,
byteBuffer: ByteBuffer, readBuffer: ReadBuffer
offset: Int, ) = deserializeExplicit(field, flags, owner, readBuffer, field.type)
bitFieldOffset: Int
): Pair<Int, Int> = deserializeExplicit(field, flags, owner, byteBuffer, offset, bitFieldOffset, field.type)
abstract fun deserializeExplicit( abstract fun deserializeExplicit(
field: Field, field: Field,
flags: Array<out SyncFlag>, flags: Array<out SyncFlag>,
owner: Any?, owner: Any?,
byteBuffer: ByteBuffer, readBuffer: ReadBuffer,
offset: Int,
bitFieldOffset: Int,
fieldType: Class<*> fieldType: Class<*>
): Pair<Int, Int> )
fun getRegisteredTypes(): Array<Class<*>> = Arrays.copyOf(registeredTypes, registeredTypes.size) fun getRegisteredTypes(): Array<Class<*>> = Arrays.copyOf(registeredTypes, registeredTypes.size)
fun canSerialize(field: Field): Boolean = registeredTypes.contains(field.type) fun canSerialize(field: Field): Boolean = registeredTypes.contains(field.type)
fun canSerialize(type: Class<*>): Boolean = registeredTypes.contains(type) fun canSerialize(type: Class<*>): Boolean = registeredTypes.contains(type)
protected fun throwInvalidType(type: Class<*>): Nothing = throw UnsupportedTypeException("Type ${this.javaClass} cannot serialize $type")
} }

View File

@ -1,9 +1,12 @@
package net.tofvesson.networking package net.tofvesson.networking
import net.tofvesson.reflect.access
import java.lang.reflect.Field
import java.nio.ByteBuffer import java.nio.ByteBuffer
import java.security.NoSuchAlgorithmException import java.security.NoSuchAlgorithmException
import java.security.MessageDigest import java.security.MessageDigest
@Suppress("MemberVisibilityCanBePrivate", "unused")
/** /**
* @param permissiveMismatchCheck This should essentially never be set to true aside from some *very* odd edge cases * @param permissiveMismatchCheck This should essentially never be set to true aside from some *very* odd edge cases
*/ */
@ -16,6 +19,7 @@ class SyncHandler(private val permissiveMismatchCheck: Boolean = false) {
// Standard serializers // Standard serializers
serializers.add(PrimitiveSerializer.singleton) serializers.add(PrimitiveSerializer.singleton)
serializers.add(PrimitiveArraySerializer.singleton) serializers.add(PrimitiveArraySerializer.singleton)
serializers.add(DiffTrackedSerializer.singleton)
} }
fun registerSerializer(serializer: Serializer) { fun registerSerializer(serializer: Serializer) {
@ -35,6 +39,21 @@ class SyncHandler(private val permissiveMismatchCheck: Boolean = false) {
return serializer return serializer
throw UnsupportedTypeException("Cannot find a compatible serializer for $type") throw UnsupportedTypeException("Cannot find a compatible serializer for $type")
} }
private fun collectSyncable(fieldType: Class<*>, collectStatic: Boolean): List<Field>{
var type = fieldType
val collect = ArrayList<Field>()
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){ fun registerSyncObject(value: Any){
@ -45,44 +64,28 @@ class SyncHandler(private val permissiveMismatchCheck: Boolean = false) {
toSync.remove(value) toSync.remove(value)
} }
fun serialize(): ByteArray{ fun serialize(): ByteBuffer {
var headerSize = 0 val writeState = WriteState(0, 0, 0)
var totalSize = 0 for(entry in toSync)
for(entry in toSync){ if(entry is Class<*>) computeClassSize(entry, writeState)
val result = if(entry is Class<*>) computeClassSize(entry) else computeObjectSize(entry) else computeObjectSize(entry, writeState)
totalSize += result.first val writeBuffer = WriteBuffer(writeState)
headerSize += result.second for(entry in toSync)
} if(entry is Class<*>) readClass(entry, writeBuffer)
else readObject(entry, writeBuffer)
var headerIndex = 0 return writeBuffer.buffer
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 deserialize(syncData: ByteArray){ fun deserialize(syncData: ByteArray) = deserialize(syncData, 0)
var headerSize = 0 fun deserialize(syncData: ByteArray, bitOffset: Int){
val writeState = WriteState(0, 0, 0)
for(entry in toSync) for(entry in toSync)
headerSize += (if(entry is Class<*>) computeClassSize(entry) else computeObjectSize(entry)).second if(entry is Class<*>) computeClassSize(entry, writeState)
var headerIndex = 0 else computeObjectSize(entry, writeState)
var dataIndex = (headerSize shr 3) + (if(headerSize and 7 != 0) 1 else 0) val readBuffer = ReadBuffer(writeState, ByteBuffer.wrap(syncData), bitOffset)
for(entry in toSync){ for(entry in toSync)
val result = if(entry is Class<*>) writeClass(entry, readBuffer)
if(entry is Class<*>) writeClass(entry, syncData, dataIndex, headerIndex) else writeObject(entry, readBuffer)
else writeObject(entry, syncData, dataIndex, headerIndex)
dataIndex = result.first
headerIndex = result.second
}
} }
fun generateMismatchCheck(): ByteArray { 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 computeObjectSize(value: Any, writeState: WriteState) = computeTypeSize(value.javaClass, value, writeState)
private fun computeClassSize(value: Class<*>) = computeTypeSize(value, null) private fun computeClassSize(value: Class<*>, writeState: WriteState) = computeTypeSize(value, null, writeState)
private fun computeTypeSize(type: Class<*>, value: Any?): Pair<Int, Int> { private fun computeTypeSize(type: Class<*>, value: Any?, writeState: WriteState) {
var byteSize = 0 for(field in collectSyncable(type, value==null))
var bitSize = 0 getCompatibleSerializer(field.access().type)
for(field in type.declaredFields){ .computeSize(field, SyncFlag.parse(field.getAnnotation(SyncedVar::class.java).value), value, writeState)
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 readObject(value: Any, buffer: ByteArray, offset: Int, bitOffset: Int) = readType(value.javaClass, value, buffer, offset, bitOffset) private fun readObject(value: Any, writeBuffer: WriteBuffer) = readType(value.javaClass, value, writeBuffer)
private fun readClass(value: Class<*>, buffer: ByteArray, offset: Int, bitOffset: Int) = readType(value, null, buffer, offset, bitOffset) private fun readClass(value: Class<*>, writeBuffer: WriteBuffer) = readType(value, null, writeBuffer)
private fun readType(type: Class<*>, value: Any?, buffer: ByteArray, offset: Int, bitOffset: Int): Pair<Int, Int> { private fun readType(type: Class<*>, value: Any?, writeBuffer: WriteBuffer) {
val byteBuffer = ByteBuffer.wrap(buffer) for(field in collectSyncable(type, value==null))
var localOffset = offset getCompatibleSerializer(field.type)
var localBitOffset = bitOffset .serialize(field, SyncFlag.parse(field.getAnnotation(SyncedVar::class.java).value), value, writeBuffer)
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 writeObject(value: Any, buffer: ByteArray, offset: Int, bitOffset: Int) = writeType(value.javaClass, value, buffer, offset, bitOffset) private fun writeObject(value: Any, readBuffer: ReadBuffer) = writeType(value.javaClass, value, readBuffer)
private fun writeClass(value: Class<*>, buffer: ByteArray, offset: Int, bitOffset: Int) = writeType(value, null, buffer, offset, bitOffset) private fun writeClass(value: Class<*>, readBuffer: ReadBuffer) = writeType(value, null, readBuffer)
private fun writeType(type: Class<*>, value: Any?, buffer: ByteArray, offset: Int, bitOffset: Int): Pair<Int, Int> { private fun writeType(type: Class<*>, value: Any?, readBuffer: ReadBuffer) {
val byteBuffer = ByteBuffer.wrap(buffer) for(field in collectSyncable(type, value==null))
var localOffset = offset getCompatibleSerializer(field.type)
var localBitOffset = bitOffset .deserialize(field, SyncFlag.parse(field.getAnnotation(SyncedVar::class.java).value), value, readBuffer)
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)
} }
} }

View File

@ -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!")
}
}

View File

@ -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()
}

View File

@ -0,0 +1,3 @@
package net.tofvesson.support
operator fun Pair<Int, Int>.plus(other: Pair<Int, Int>) = Pair(first + other.first, second + other.second)