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.*;
public class Main {
@ -25,10 +27,13 @@ public class Main {
@SyncedVar
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){
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");

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 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)
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
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<out SyncFlag>, owner: Any?, fieldType: Class<*>): Pair<Int, Int> =
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<out SyncFlag>, 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<out SyncFlag>, owner: Any?, byteBuffer: ByteBuffer, offset: Int, bitFieldOffset: Int, fieldType: Class<*>): Pair<Int, Int> =
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<out SyncFlag>, owner: Any?, byteBuffer: ByteBuffer, offset: Int, bitFieldOffset: Int, fieldType: Class<*>): Pair<Int, Int> =
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<out SyncFlag>, 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<out SyncFlag>, 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<Any?>
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)
}
}
}

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
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<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))
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<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))
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<out SyncFlag>, owner: Any?, byteBuffer: ByteBuffer, offset: Int, bitFieldOffset: Int, fieldType: Class<*>): Pair<Int, Int> {
var localByteOffset = offset
var localBitOffset = bitFieldOffset
override fun deserializeExplicit(field: Field, flags: Array<out SyncFlag>, 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)
}
}

View File

@ -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<out SyncFlag>,
owner: Any?,
state: WriteState,
fieldType: Class<*>
): Pair<Int, Int> =
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<out SyncFlag>,
owner: Any?,
byteBuffer: ByteBuffer,
offset: Int,
bitFieldOffset: Int,
writeBuffer: WriteBuffer,
fieldType: Class<*>
): Pair<Int, Int> =
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<out SyncFlag>,
owner: Any?,
byteBuffer: ByteBuffer,
offset: Int,
bitFieldOffset: Int,
readBuffer: ReadBuffer,
fieldType: Class<*>
): Pair<Int, Int> =
) =
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)
}
}

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
import java.lang.reflect.Field
import java.nio.ByteBuffer
import java.util.*
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
* @return Size in the byteField (first) and bitField (second) that need to be allocated
*/
fun computeSize(
field: Field,
flags: Array<out SyncFlag>,
owner: Any?
): Pair<Int, Int> = computeSizeExplicit(field, flags, owner, field.type)
owner: Any?,
state: WriteState
) = computeSizeExplicit(field, flags, owner, state, field.type)
abstract fun computeSizeExplicit(
field: Field,
flags: Array<out SyncFlag>,
owner: Any?,
state: WriteState,
fieldType: Class<*>
): Pair<Int, Int>
)
/**
* Serialize a field to the buffer
@ -32,20 +32,16 @@ abstract class Serializer(registeredTypes: Array<Class<*>>) {
field: Field,
flags: Array<out SyncFlag>,
owner: Any?,
byteBuffer: ByteBuffer,
offset: Int,
bitFieldOffset: Int
): Pair<Int, Int> = 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<out SyncFlag>,
owner: Any?,
byteBuffer: ByteBuffer,
offset: Int,
bitFieldOffset: Int,
writeBuffer: WriteBuffer,
fieldType: Class<*>
): Pair<Int, Int>
)
/**
* Deserialize a field from the buffer
@ -55,22 +51,20 @@ abstract class Serializer(registeredTypes: Array<Class<*>>) {
field: Field,
flags: Array<out SyncFlag>,
owner: Any?,
byteBuffer: ByteBuffer,
offset: Int,
bitFieldOffset: Int
): Pair<Int, Int> = 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<out SyncFlag>,
owner: Any?,
byteBuffer: ByteBuffer,
offset: Int,
bitFieldOffset: Int,
readBuffer: ReadBuffer,
fieldType: Class<*>
): Pair<Int, Int>
)
fun getRegisteredTypes(): Array<Class<*>> = 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")
}

View File

@ -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<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){
@ -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<Int, Int> {
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<Int, Int> {
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<Int, Int> {
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)
}
}

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)