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:
parent
60cebfb966
commit
2af938169c
@ -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");
|
||||
|
13
src/net/tofvesson/math/Extensions.kt
Normal file
13
src/net/tofvesson/math/Extensions.kt
Normal 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
|
193
src/net/tofvesson/math/MathSerializer.kt
Normal file
193
src/net/tofvesson/math/MathSerializer.kt
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
29
src/net/tofvesson/math/Vector3.kt
Normal file
29
src/net/tofvesson/math/Vector3.kt
Normal 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)"
|
||||
}
|
@ -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)
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
7
src/net/tofvesson/networking/Holder.kt
Normal file
7
src/net/tofvesson/networking/Holder.kt
Normal file
@ -0,0 +1,7 @@
|
||||
package net.tofvesson.networking
|
||||
|
||||
data class Holder(var value: Any?){
|
||||
companion object {
|
||||
val valueField = Holder::class.java.getDeclaredField("value")!!
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
9
src/net/tofvesson/networking/MismatchedFlagException.kt
Normal file
9
src/net/tofvesson/networking/MismatchedFlagException.kt
Normal 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)
|
||||
}
|
6
src/net/tofvesson/networking/NoUpwardCascade.kt
Normal file
6
src/net/tofvesson/networking/NoUpwardCascade.kt
Normal file
@ -0,0 +1,6 @@
|
||||
package net.tofvesson.networking
|
||||
|
||||
/**
|
||||
* Prevents SyncedVar system from syncing superclasses
|
||||
*/
|
||||
annotation class NoUpwardCascade
|
@ -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)
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
129
src/net/tofvesson/networking/ReadBuffer.kt
Normal file
129
src/net/tofvesson/networking/ReadBuffer.kt
Normal 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!")
|
||||
}
|
||||
}
|
@ -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")
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
154
src/net/tofvesson/networking/WriteBuffer.kt
Normal file
154
src/net/tofvesson/networking/WriteBuffer.kt
Normal 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!")
|
||||
}
|
||||
}
|
22
src/net/tofvesson/networking/WriteState.kt
Normal file
22
src/net/tofvesson/networking/WriteState.kt
Normal 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()
|
||||
}
|
3
src/net/tofvesson/support/Pair.kt
Normal file
3
src/net/tofvesson/support/Pair.kt
Normal 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)
|
Loading…
x
Reference in New Issue
Block a user