Redesigned serialization
- Created a dynamic serialization system with add-in support - Created a custom flag system (may need rework) Moved primitive serialization system to its own Serializer module Added primitive array serialization module Added some fun reflection toys :)
This commit is contained in:
parent
2e32d9d984
commit
04dd29ad56
@ -2,7 +2,7 @@ import net.tofvesson.networking.SyncHandler;
|
|||||||
import net.tofvesson.networking.SyncedVar;
|
import net.tofvesson.networking.SyncedVar;
|
||||||
|
|
||||||
public class Main {
|
public class Main {
|
||||||
@SyncedVar(nonNegative = true)
|
@SyncedVar("NonNegative")
|
||||||
public int syncTest = 5;
|
public int syncTest = 5;
|
||||||
|
|
||||||
@SyncedVar
|
@SyncedVar
|
||||||
@ -17,35 +17,68 @@ public class Main {
|
|||||||
@SyncedVar
|
@SyncedVar
|
||||||
public static boolean testbool1 = true;
|
public static boolean testbool1 = true;
|
||||||
|
|
||||||
|
@SyncedVar
|
||||||
|
public static boolean[] test = {true, false};
|
||||||
|
|
||||||
public static void main(String[] args){
|
public static void main(String[] args){
|
||||||
Main testObject = new Main();
|
Main testObject = new Main();
|
||||||
SyncHandler sync = new SyncHandler();
|
SyncHandler sync = new SyncHandler();
|
||||||
sync.registerSyncObject(testObject);
|
sync.registerSyncObject(testObject);
|
||||||
sync.registerSyncObject(Main.class);
|
sync.registerSyncObject(Main.class);
|
||||||
|
|
||||||
|
// Generate mismatch check
|
||||||
byte[] mismatchCheck = sync.generateMismatchCheck();
|
byte[] mismatchCheck = sync.generateMismatchCheck();
|
||||||
|
|
||||||
|
// Generate snapshot of values to serialize
|
||||||
byte[] ser = sync.serialize();
|
byte[] ser = sync.serialize();
|
||||||
|
|
||||||
System.out.println("Created and serialized snapshot of field values:\n"+testObject.syncTest+"\n"+staticTest+"\n"+value+"\n"+testObject.testbool+"\n"+testbool1+"\n");
|
System.out.println("Created and serialized snapshot of field values:\n\t"+
|
||||||
|
testObject.syncTest+"\n\t"+
|
||||||
|
staticTest+"\n\t"+
|
||||||
|
value+"\n\t"+
|
||||||
|
testObject.testbool+"\n\t"+
|
||||||
|
testbool1+"\n\t"+
|
||||||
|
test[0]+"\n\t"+
|
||||||
|
test[1]+"\n"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Modify all the values
|
||||||
testObject.syncTest = 20;
|
testObject.syncTest = 20;
|
||||||
staticTest = 32;
|
staticTest = 32;
|
||||||
value = 9.0f;
|
value = 9.0f;
|
||||||
testObject.testbool = true;
|
testObject.testbool = true;
|
||||||
testbool1 = false;
|
testbool1 = false;
|
||||||
|
test = new boolean[3];
|
||||||
|
test[0] = false;
|
||||||
|
test[1] = true;
|
||||||
|
test[2] = true;
|
||||||
|
|
||||||
System.out.println("Set a new state of test values:\n"+testObject.syncTest+"\n"+staticTest+"\n"+value+"\n"+testObject.testbool+"\n"+testbool1+"\n");
|
System.out.println("Set a new state of test values:\n\t"+
|
||||||
|
testObject.syncTest+"\n\t"+
|
||||||
|
staticTest+"\n\t"+
|
||||||
|
value+"\n\t"+
|
||||||
|
testObject.testbool+"\n\t"+
|
||||||
|
testbool1+"\n\t"+
|
||||||
|
test[0]+"\n\t"+
|
||||||
|
test[1]+"\n\t"+
|
||||||
|
test[2]+"\n"
|
||||||
|
);
|
||||||
|
|
||||||
/* Swap the registry order
|
// Do mismatch check
|
||||||
sync.unregisterSyncObject(testObject);
|
|
||||||
sync.unregisterSyncObject(Main.class);
|
|
||||||
sync.registerSyncObject(Main.class);
|
|
||||||
sync.registerSyncObject(testObject);
|
|
||||||
*/
|
|
||||||
if(!sync.doMismatchCheck(mismatchCheck)) throw new RuntimeException("Target sync mismatch");
|
if(!sync.doMismatchCheck(mismatchCheck)) throw new RuntimeException("Target sync mismatch");
|
||||||
|
|
||||||
|
// Load snapshot values back
|
||||||
sync.deserialize(ser);
|
sync.deserialize(ser);
|
||||||
|
|
||||||
System.out.println("Deserialized snapshot values:\n"+testObject.syncTest+"\n"+staticTest+"\n"+value+"\n"+testObject.testbool+"\n"+testbool1+"\n");
|
System.out.println("Deserialized snapshot values:\n\t"+
|
||||||
System.out.println("Snapshot size: "+ser.length+" bytes");
|
testObject.syncTest+"\n\t"+
|
||||||
|
staticTest+"\n\t"+
|
||||||
|
value+"\n\t"+
|
||||||
|
testObject.testbool+"\n\t"+
|
||||||
|
testbool1+"\n\t"+
|
||||||
|
test[0]+"\n\t"+
|
||||||
|
test[1]+"\n\n" +
|
||||||
|
"Snapshot size: \"+ser.length+\" bytes"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package net.tofvesson.networking
|
package net.tofvesson.networking
|
||||||
|
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
|
import kotlin.experimental.or
|
||||||
|
|
||||||
fun varIntSize(value: Long): Int =
|
fun varIntSize(value: Long): Int =
|
||||||
when {
|
when {
|
||||||
@ -103,6 +104,15 @@ fun readVarInt(buffer: ByteBuffer, offset: Int): Long {
|
|||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun writeBit(bit: Boolean, buffer: ByteArray, index: Int){
|
||||||
|
buffer[index shr 3] = buffer[index shr 3].or(((if(bit) 1 else 0) shl (index and 7)).toByte())
|
||||||
|
}
|
||||||
|
fun readBit(buffer: ByteArray, index: Int): Boolean = buffer[index shr 3].toInt() and (1 shl (index and 7)) != 0
|
||||||
|
fun writeBit(bit: Boolean, buffer: ByteBuffer, index: Int){
|
||||||
|
buffer.put(index shr 3, buffer[index shr 3] or (((if(bit) 1 else 0) shl (index and 7)).toByte()))
|
||||||
|
}
|
||||||
|
fun readBit(buffer: ByteBuffer, index: Int): Boolean = buffer[index shr 3].toInt() and (1 shl (index and 7)) != 0
|
||||||
|
|
||||||
private val converter = ByteBuffer.allocateDirect(8)
|
private val converter = ByteBuffer.allocateDirect(8)
|
||||||
|
|
||||||
fun floatToInt(value: Float): Int =
|
fun floatToInt(value: Float): Int =
|
||||||
|
283
src/net/tofvesson/networking/PrimitiveArraySerializer.kt
Normal file
283
src/net/tofvesson/networking/PrimitiveArraySerializer.kt
Normal file
@ -0,0 +1,283 @@
|
|||||||
|
package net.tofvesson.networking
|
||||||
|
|
||||||
|
import java.lang.reflect.Field
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
|
||||||
|
class PrimitiveArraySerializer private constructor(): Serializer(arrayOf(
|
||||||
|
BooleanArray::class.java,
|
||||||
|
ByteArray::class.java,
|
||||||
|
ShortArray::class.java,
|
||||||
|
IntArray::class.java,
|
||||||
|
LongArray::class.java,
|
||||||
|
FloatArray::class.java,
|
||||||
|
DoubleArray::class.java
|
||||||
|
)) {
|
||||||
|
companion object {
|
||||||
|
const val flagKnownSize = "knownSize"
|
||||||
|
private val knownSize = SyncFlag.createFlag(flagKnownSize)
|
||||||
|
val singleton = PrimitiveArraySerializer()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun computeSize(field: Field, flags: Array<out SyncFlag>, owner: Any?): Pair<Int, Int> {
|
||||||
|
val arrayLength = java.lang.reflect.Array.getLength(field.get(owner))
|
||||||
|
var byteSize = if(flags.contains(knownSize)) 0 else varIntSize(arrayLength.toLong())
|
||||||
|
var bitSize = 0
|
||||||
|
when (field.type) {
|
||||||
|
BooleanArray::class.java -> bitSize = arrayLength
|
||||||
|
ByteArray::class.java -> byteSize += 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())
|
||||||
|
)
|
||||||
|
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())
|
||||||
|
)
|
||||||
|
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)
|
||||||
|
)
|
||||||
|
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)))
|
||||||
|
)
|
||||||
|
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))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return Pair(byteSize, bitSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun serialize(field: Field, flags: Array<out SyncFlag>, owner: Any?, byteBuffer: ByteBuffer, offset: Int, bitFieldOffset: Int): Pair<Int, Int> {
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
when (field.type) {
|
||||||
|
BooleanArray::class.java ->
|
||||||
|
for(value in field.get(owner) as BooleanArray)
|
||||||
|
writeBit(value, byteBuffer, localBitOffset++)
|
||||||
|
ByteArray::class.java ->
|
||||||
|
for(value in field.get(owner) as ByteArray)
|
||||||
|
byteBuffer.put(localByteOffset++, 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)
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
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) {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Pair(localByteOffset, localBitOffset)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun deserialize(field: Field, flags: Array<out SyncFlag>, owner: Any?, byteBuffer: ByteBuffer, offset: Int, bitFieldOffset: Int): Pair<Int, Int> {
|
||||||
|
var localByteOffset = offset
|
||||||
|
var localBitOffset = bitFieldOffset
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
|
||||||
|
val target =
|
||||||
|
if(arrayLength!=localLength) java.lang.reflect.Array.newInstance(field.type.componentType, arrayLength)
|
||||||
|
else field.get(owner)
|
||||||
|
|
||||||
|
when (field.type) {
|
||||||
|
BooleanArray::class.java -> {
|
||||||
|
val booleanTarget = target as BooleanArray
|
||||||
|
for (index in 0 until arrayLength)
|
||||||
|
booleanTarget[index] = readBit(byteBuffer, localBitOffset++)
|
||||||
|
}
|
||||||
|
ByteArray::class.java -> {
|
||||||
|
val byteTarget = target as ByteArray
|
||||||
|
for (index in 0 until arrayLength)
|
||||||
|
byteTarget[index] = byteBuffer[localByteOffset++]
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(arrayLength!=localLength) field.set(owner, target)
|
||||||
|
return Pair(localByteOffset, localBitOffset)
|
||||||
|
}
|
||||||
|
}
|
219
src/net/tofvesson/networking/PrimitiveSerializers.kt
Normal file
219
src/net/tofvesson/networking/PrimitiveSerializers.kt
Normal file
@ -0,0 +1,219 @@
|
|||||||
|
package net.tofvesson.networking
|
||||||
|
|
||||||
|
import java.lang.reflect.Field
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
|
||||||
|
class PrimitiveSerializer private constructor() : Serializer(arrayOf(
|
||||||
|
Boolean::class.java,
|
||||||
|
Byte::class.java,
|
||||||
|
Short::class.java,
|
||||||
|
Int::class.java,
|
||||||
|
Long::class.java,
|
||||||
|
Float::class.java,
|
||||||
|
Double::class.java
|
||||||
|
)) {
|
||||||
|
companion object { val singleton = PrimitiveSerializer() }
|
||||||
|
|
||||||
|
override fun computeSize(
|
||||||
|
field: Field,
|
||||||
|
flags: Array<out SyncFlag>,
|
||||||
|
owner: Any?
|
||||||
|
): Pair<Int, Int> =
|
||||||
|
when(field.type){
|
||||||
|
Boolean::class.java -> Pair(0, 1)
|
||||||
|
Byte::class.java -> Pair(1, 0)
|
||||||
|
Short::class.java ->
|
||||||
|
if(flags.contains(SyncFlag.NoCompress)) Pair(2, 0)
|
||||||
|
else Pair(varIntSize(
|
||||||
|
if(flags.contains(SyncFlag.NonNegative)) field.getShort(owner).toLong()
|
||||||
|
else zigZagEncode(field.getShort(owner).toLong())
|
||||||
|
), 0)
|
||||||
|
Int::class.java ->
|
||||||
|
if(flags.contains(SyncFlag.NoCompress)) Pair(4, 0)
|
||||||
|
else Pair(varIntSize(
|
||||||
|
if(flags.contains(SyncFlag.NonNegative)) field.getInt(owner).toLong()
|
||||||
|
else zigZagEncode(field.getInt(owner).toLong())
|
||||||
|
), 0)
|
||||||
|
Long::class.java ->
|
||||||
|
if(flags.contains(SyncFlag.NoCompress)) Pair(8, 0)
|
||||||
|
else Pair(varIntSize(
|
||||||
|
if(flags.contains(SyncFlag.NonNegative)) field.getLong(owner)
|
||||||
|
else zigZagEncode(field.getLong(owner))
|
||||||
|
), 0)
|
||||||
|
Float::class.java ->
|
||||||
|
if(flags.contains(SyncFlag.NoCompress)) Pair(4, 0)
|
||||||
|
else Pair(varIntSize(
|
||||||
|
if(flags.contains(SyncFlag.FloatEndianSwap)) bitConvert(swapEndian(floatToInt(field.getFloat(owner))))
|
||||||
|
else bitConvert(floatToInt(field.getFloat(owner)))
|
||||||
|
), 0)
|
||||||
|
Double::class.java ->
|
||||||
|
if(flags.contains(SyncFlag.NoCompress)) Pair(8, 0)
|
||||||
|
else Pair(varIntSize(
|
||||||
|
if(flags.contains(SyncFlag.FloatEndianSwap)) swapEndian(doubleToLong(field.getDouble(owner)))
|
||||||
|
else doubleToLong(field.getDouble(owner))
|
||||||
|
), 0)
|
||||||
|
else -> Pair(0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun serialize(
|
||||||
|
field: Field,
|
||||||
|
flags: Array<out SyncFlag>,
|
||||||
|
owner: Any?,
|
||||||
|
byteBuffer: ByteBuffer,
|
||||||
|
offset: Int,
|
||||||
|
bitFieldOffset: Int
|
||||||
|
): Pair<Int, Int> =
|
||||||
|
when(field.type){
|
||||||
|
Boolean::class.java -> {
|
||||||
|
writeBit(field.getBoolean(owner), byteBuffer, bitFieldOffset)
|
||||||
|
Pair(offset, bitFieldOffset+1)
|
||||||
|
}
|
||||||
|
Byte::class.java -> {
|
||||||
|
byteBuffer.put(offset, field.getByte(owner))
|
||||||
|
Pair(offset+1, bitFieldOffset)
|
||||||
|
}
|
||||||
|
Short::class.java ->
|
||||||
|
if(flags.contains(SyncFlag.NoCompress)){
|
||||||
|
byteBuffer.putShort(offset, field.getShort(owner))
|
||||||
|
Pair(offset+2, bitFieldOffset)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
val rawValue =
|
||||||
|
if(flags.contains(SyncFlag.NonNegative)) field.getShort(owner).toLong()
|
||||||
|
else zigZagEncode(field.getShort(owner).toLong())
|
||||||
|
writeVarInt(byteBuffer, offset, rawValue)
|
||||||
|
Pair(offset+varIntSize(rawValue), bitFieldOffset)
|
||||||
|
}
|
||||||
|
Int::class.java ->
|
||||||
|
if(flags.contains(SyncFlag.NoCompress)){
|
||||||
|
byteBuffer.putInt(offset, field.getInt(owner))
|
||||||
|
Pair(offset+4, bitFieldOffset)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
val rawValue =
|
||||||
|
if(flags.contains(SyncFlag.NonNegative)) field.getInt(owner).toLong()
|
||||||
|
else zigZagEncode(field.getInt(owner).toLong())
|
||||||
|
writeVarInt(byteBuffer, offset, rawValue)
|
||||||
|
Pair(offset+varIntSize(rawValue), bitFieldOffset)
|
||||||
|
}
|
||||||
|
Long::class.java ->
|
||||||
|
if(flags.contains(SyncFlag.NoCompress)){
|
||||||
|
byteBuffer.putLong(offset, field.getLong(owner))
|
||||||
|
Pair(offset+8, bitFieldOffset)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
val rawValue =
|
||||||
|
if(flags.contains(SyncFlag.NonNegative)) field.getLong(owner)
|
||||||
|
else zigZagEncode(field.getLong(owner))
|
||||||
|
writeVarInt(byteBuffer, offset, rawValue)
|
||||||
|
Pair(offset+varIntSize(rawValue), bitFieldOffset)
|
||||||
|
}
|
||||||
|
Float::class.java ->
|
||||||
|
if(flags.contains(SyncFlag.NoCompress)){
|
||||||
|
byteBuffer.putFloat(offset, field.getFloat(owner))
|
||||||
|
Pair(offset+4, bitFieldOffset)
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
val rawValue =
|
||||||
|
if(flags.contains(SyncFlag.FloatEndianSwap)) bitConvert(swapEndian(floatToInt(field.getFloat(owner))))
|
||||||
|
else bitConvert(floatToInt(field.getFloat(owner)))
|
||||||
|
writeVarInt(byteBuffer, offset, rawValue)
|
||||||
|
Pair(offset+varIntSize(rawValue), bitFieldOffset)
|
||||||
|
}
|
||||||
|
Double::class.java ->
|
||||||
|
if(flags.contains(SyncFlag.NoCompress)){
|
||||||
|
byteBuffer.putDouble(offset, field.getDouble(owner))
|
||||||
|
Pair(offset+8, bitFieldOffset)
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
val rawValue =
|
||||||
|
if(flags.contains(SyncFlag.FloatEndianSwap)) swapEndian(doubleToLong(field.getDouble(owner)))
|
||||||
|
else doubleToLong(field.getDouble(owner))
|
||||||
|
writeVarInt(byteBuffer, offset, rawValue)
|
||||||
|
Pair(offset+varIntSize(rawValue), bitFieldOffset)
|
||||||
|
}
|
||||||
|
else -> Pair(offset, bitFieldOffset)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun deserialize(
|
||||||
|
field: Field,
|
||||||
|
flags: Array<out SyncFlag>,
|
||||||
|
owner: Any?,
|
||||||
|
byteBuffer: ByteBuffer,
|
||||||
|
offset: Int,
|
||||||
|
bitFieldOffset: Int
|
||||||
|
): Pair<Int, Int> =
|
||||||
|
when(field.type){
|
||||||
|
Boolean::class.java -> {
|
||||||
|
field.setBoolean(owner, readBit(byteBuffer, bitFieldOffset))
|
||||||
|
Pair(offset, bitFieldOffset+1)
|
||||||
|
}
|
||||||
|
Byte::class.java -> {
|
||||||
|
field.setByte(owner, byteBuffer.get(offset))
|
||||||
|
Pair(offset+1, bitFieldOffset)
|
||||||
|
}
|
||||||
|
Short::class.java ->
|
||||||
|
if(flags.contains(SyncFlag.NoCompress)){
|
||||||
|
field.setShort(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.setShort(owner, rawValue.toShort())
|
||||||
|
Pair(offset+varIntSize(rawValue), bitFieldOffset)
|
||||||
|
}
|
||||||
|
Int::class.java ->
|
||||||
|
if(flags.contains(SyncFlag.NoCompress)){
|
||||||
|
field.setInt(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.setInt(owner, rawValue.toInt())
|
||||||
|
Pair(offset+varIntSize(rawValue), bitFieldOffset)
|
||||||
|
}
|
||||||
|
Long::class.java ->
|
||||||
|
if(flags.contains(SyncFlag.NoCompress)){
|
||||||
|
field.setLong(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.setLong(owner, rawValue)
|
||||||
|
Pair(offset+varIntSize(rawValue), bitFieldOffset)
|
||||||
|
}
|
||||||
|
Float::class.java ->
|
||||||
|
if(flags.contains(SyncFlag.NoCompress)){
|
||||||
|
field.setFloat(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.setFloat(owner, rawValue)
|
||||||
|
Pair(offset+varIntSize(readVal), bitFieldOffset)
|
||||||
|
}
|
||||||
|
Double::class.java ->
|
||||||
|
if(flags.contains(SyncFlag.NoCompress)){
|
||||||
|
field.setDouble(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.setDouble(owner, rawValue)
|
||||||
|
Pair(offset+varIntSize(readVal), bitFieldOffset)
|
||||||
|
}
|
||||||
|
else -> Pair(offset, bitFieldOffset)
|
||||||
|
}
|
||||||
|
}
|
49
src/net/tofvesson/networking/Serializer.kt
Normal file
49
src/net/tofvesson/networking/Serializer.kt
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
package net.tofvesson.networking
|
||||||
|
|
||||||
|
import java.lang.reflect.Field
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
abstract class Serializer(registeredTypes: Array<Class<*>>) {
|
||||||
|
private val registeredTypes: Array<Class<*>> = Arrays.copyOf(registeredTypes, registeredTypes.size)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute the size in bits that a field will occupy
|
||||||
|
* @return Size in the byteField (first) and bitField (second) that need to be allocated
|
||||||
|
*/
|
||||||
|
abstract fun computeSize(
|
||||||
|
field: Field,
|
||||||
|
flags: Array<out SyncFlag>,
|
||||||
|
owner: Any?
|
||||||
|
): Pair<Int, Int>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serialize a field to the buffer
|
||||||
|
* @return The new offset (first) and bitFieldOffset (second)
|
||||||
|
*/
|
||||||
|
abstract fun serialize(
|
||||||
|
field: Field,
|
||||||
|
flags: Array<out SyncFlag>,
|
||||||
|
owner: Any?,
|
||||||
|
byteBuffer: ByteBuffer,
|
||||||
|
offset: Int,
|
||||||
|
bitFieldOffset: Int
|
||||||
|
): Pair<Int, Int>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deserialize a field from the buffer
|
||||||
|
* @return The new offset (first) and bitFieldOffset (second)
|
||||||
|
*/
|
||||||
|
abstract fun deserialize(
|
||||||
|
field: Field,
|
||||||
|
flags: Array<out SyncFlag>,
|
||||||
|
owner: Any?,
|
||||||
|
byteBuffer: ByteBuffer,
|
||||||
|
offset: Int,
|
||||||
|
bitFieldOffset: Int
|
||||||
|
): 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)
|
||||||
|
}
|
@ -2,7 +2,6 @@ package net.tofvesson.networking
|
|||||||
|
|
||||||
import java.lang.reflect.Field
|
import java.lang.reflect.Field
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
import kotlin.experimental.or
|
|
||||||
import java.security.NoSuchAlgorithmException
|
import java.security.NoSuchAlgorithmException
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
|
|
||||||
@ -10,30 +9,46 @@ import java.security.MessageDigest
|
|||||||
/**
|
/**
|
||||||
* @param permissiveMismatchCheck This should essentially never be set to true aside from some *very* odd edge cases
|
* @param permissiveMismatchCheck This should essentially never be set to true aside from some *very* odd edge cases
|
||||||
*/
|
*/
|
||||||
class SyncHandler(private val permissiveMismatchCheck: Boolean = false) {
|
class SyncHandler(defaultSerializers: Boolean = true, private val permissiveMismatchCheck: Boolean = false) {
|
||||||
private val toSync: ArrayList<Pair<Any, Int>> = ArrayList()
|
private val toSync: ArrayList<Any> = ArrayList()
|
||||||
|
private val serializers: ArrayList<Serializer> = ArrayList()
|
||||||
|
|
||||||
|
init {
|
||||||
|
if(defaultSerializers) {
|
||||||
|
serializers.add(PrimitiveSerializer.singleton)
|
||||||
|
serializers.add(PrimitiveArraySerializer.singleton)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun registerSyncObject(value: Any){
|
fun registerSyncObject(value: Any){
|
||||||
if(!toSync.contains(value)) toSync.add(Pair(value, getBooleanFieldCount(value::class.java)))
|
if(!toSync.contains(value)) toSync.add(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun unregisterSyncObject(value: Any){
|
fun unregisterSyncObject(value: Any){
|
||||||
for(i in toSync.indices.reversed())
|
toSync.remove(value)
|
||||||
if(toSync[i].first == value)
|
}
|
||||||
toSync.removeAt(i)
|
|
||||||
|
fun withSerializer(serializer: Serializer): SyncHandler {
|
||||||
|
if(!serializers.contains(serializer)) serializers.add(serializer)
|
||||||
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
fun serialize(): ByteArray{
|
fun serialize(): ByteArray{
|
||||||
val headerBits = computeBitHeaderCount()
|
var headerSize = 0
|
||||||
val headerSize = (headerBits shr 3) + (if((headerBits and 7) != 0) 1 else 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 headerIndex = 0
|
||||||
var dataIndex = headerSize
|
var dataIndex = (headerSize shr 3) + (if(headerSize and 7 != 0) 1 else 0)
|
||||||
var totalSize = headerSize
|
|
||||||
for((entry, _) in toSync)
|
totalSize += dataIndex
|
||||||
totalSize += if(entry is Class<*>) computeClassSize(entry) else computeObjectSize(entry)
|
|
||||||
|
|
||||||
val buffer = ByteArray(totalSize)
|
val buffer = ByteArray(totalSize)
|
||||||
for((entry, _) in toSync){
|
for(entry in toSync){
|
||||||
val result =
|
val result =
|
||||||
if(entry is Class<*>) readClass(entry, buffer, dataIndex, headerIndex)
|
if(entry is Class<*>) readClass(entry, buffer, dataIndex, headerIndex)
|
||||||
else readObject(entry, buffer, dataIndex, headerIndex)
|
else readObject(entry, buffer, dataIndex, headerIndex)
|
||||||
@ -44,11 +59,12 @@ class SyncHandler(private val permissiveMismatchCheck: Boolean = false) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun deserialize(syncData: ByteArray){
|
fun deserialize(syncData: ByteArray){
|
||||||
val headerBits = computeBitHeaderCount()
|
var headerSize = 0
|
||||||
val headerSize = (headerBits shr 3) + (if((headerBits and 7) != 0) 1 else 0)
|
for(entry in toSync)
|
||||||
|
headerSize += (if(entry is Class<*>) computeClassSize(entry) else computeObjectSize(entry)).second
|
||||||
var headerIndex = 0
|
var headerIndex = 0
|
||||||
var dataIndex = headerSize
|
var dataIndex = (headerSize shr 3) + (if(headerSize and 7 != 0) 1 else 0)
|
||||||
for((entry, _) in toSync){
|
for(entry in toSync){
|
||||||
val result =
|
val result =
|
||||||
if(entry is Class<*>) writeClass(entry, syncData, dataIndex, headerIndex)
|
if(entry is Class<*>) writeClass(entry, syncData, dataIndex, headerIndex)
|
||||||
else writeObject(entry, syncData, dataIndex, headerIndex)
|
else writeObject(entry, syncData, dataIndex, headerIndex)
|
||||||
@ -59,16 +75,15 @@ class SyncHandler(private val permissiveMismatchCheck: Boolean = false) {
|
|||||||
|
|
||||||
fun generateMismatchCheck(): ByteArray {
|
fun generateMismatchCheck(): ByteArray {
|
||||||
val builder = StringBuilder()
|
val builder = StringBuilder()
|
||||||
for((entry, _) in toSync)
|
for(entry in toSync)
|
||||||
for(field in (entry as? Class<*> ?: entry::class.java).declaredFields){
|
for(field in (entry as? Class<*> ?: entry::class.java).declaredFields){
|
||||||
if((entry is Class<*> && field.modifiers and 8 == 0) || (entry !is Class<*> && field.modifiers and 8 != 0)) continue
|
if((entry is Class<*> && field.modifiers and 8 == 0) || (entry !is Class<*> && field.modifiers and 8 != 0)) continue
|
||||||
val annotation = field.getAnnotation(SyncedVar::class.java)
|
val annotation = field.getAnnotation(SyncedVar::class.java)
|
||||||
if(annotation!=null)
|
if(annotation!=null) {
|
||||||
builder
|
builder.append('{').append(if (permissiveMismatchCheck) field.type.name else field.toGenericString())
|
||||||
.append(if(permissiveMismatchCheck) field.type.name else field.toGenericString())
|
for(flag in annotation.value) builder.append(flag)
|
||||||
.append(if(annotation.floatEndianSwap) 1 else 0)
|
builder.append('}')
|
||||||
.append(if(annotation.noCompress) 1 else 0)
|
}
|
||||||
.append(if(annotation.nonNegative) 1 else 0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return try {
|
return try {
|
||||||
@ -87,11 +102,29 @@ class SyncHandler(private val permissiveMismatchCheck: Boolean = false) {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun computeBitHeaderCount(): Int {
|
|
||||||
var bitCount = 0
|
private fun computeObjectSize(value: Any) = computeTypeSize(value.javaClass, value)
|
||||||
for((_, bits) in toSync)
|
private fun computeClassSize(value: Class<*>) = computeTypeSize(value, null)
|
||||||
bitCount += bits
|
private fun computeTypeSize(type: Class<*>, value: Any?): Pair<Int, Int> {
|
||||||
return bitCount
|
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 getCompatibleSerializer(type: Class<*>): Serializer {
|
||||||
|
for(serializer in serializers)
|
||||||
|
if(serializer.canSerialize(type))
|
||||||
|
return serializer
|
||||||
|
throw UnsupportedTypeException("Cannot find a compatible serializer for $type")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun readObject(value: Any, buffer: ByteArray, offset: Int, bitOffset: Int) = readType(value.javaClass, value, buffer, offset, bitOffset)
|
private fun readObject(value: Any, buffer: ByteArray, offset: Int, bitOffset: Int) = readType(value.javaClass, value, buffer, offset, bitOffset)
|
||||||
@ -104,8 +137,9 @@ class SyncHandler(private val permissiveMismatchCheck: Boolean = false) {
|
|||||||
if((value == null && field.modifiers and 8 == 0) || (value != null && field.modifiers and 8 != 0)) continue
|
if((value == null && field.modifiers and 8 == 0) || (value != null && field.modifiers and 8 != 0)) continue
|
||||||
val annotation = field.getAnnotation(SyncedVar::class.java)
|
val annotation = field.getAnnotation(SyncedVar::class.java)
|
||||||
if(annotation != null){
|
if(annotation != null){
|
||||||
if(field.type!=Boolean::class.java) localOffset += readValue(field, annotation, value, byteBuffer, localOffset)
|
val result = getCompatibleSerializer(field.type).serialize(field, SyncFlag.parse(annotation.value), value, byteBuffer, localOffset, localBitOffset)
|
||||||
else writeBit(field.getBoolean(value), buffer, localBitOffset++)
|
localOffset = result.first
|
||||||
|
localBitOffset = result.second
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Pair(localOffset, localBitOffset)
|
return Pair(localOffset, localBitOffset)
|
||||||
@ -121,212 +155,11 @@ class SyncHandler(private val permissiveMismatchCheck: Boolean = false) {
|
|||||||
if((value == null && field.modifiers and 8 == 0) || (value != null && field.modifiers and 8 != 0)) continue
|
if((value == null && field.modifiers and 8 == 0) || (value != null && field.modifiers and 8 != 0)) continue
|
||||||
val annotation = field.getAnnotation(SyncedVar::class.java)
|
val annotation = field.getAnnotation(SyncedVar::class.java)
|
||||||
if(annotation != null){
|
if(annotation != null){
|
||||||
if(field.type!=Boolean::class.java) localOffset += writeValue(field, annotation, value, byteBuffer, localOffset)
|
val result = getCompatibleSerializer(field.type).deserialize(field, SyncFlag.parse(annotation.value), value, byteBuffer, localOffset, localBitOffset)
|
||||||
else field.setBoolean(value, readBit(buffer, localBitOffset++))
|
localOffset = result.first
|
||||||
|
localBitOffset = result.second
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Pair(localOffset, localBitOffset)
|
return Pair(localOffset, localBitOffset)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
|
||||||
private fun getBooleanFieldCount(type: Class<*>): Int {
|
|
||||||
var count = 0
|
|
||||||
for(field in type.declaredFields)
|
|
||||||
if(field.getAnnotation(SyncedVar::class.java)!=null && field.type==Boolean::class.java)
|
|
||||||
++count
|
|
||||||
return count
|
|
||||||
}
|
|
||||||
private fun writeBit(bit: Boolean, buffer: ByteArray, index: Int){
|
|
||||||
buffer[index shr 3] = buffer[index shr 3].or(((if(bit) 1 else 0) shl (index and 7)).toByte())
|
|
||||||
}
|
|
||||||
private fun readBit(buffer: ByteArray, index: Int): Boolean = buffer[index shr 3].toInt() and (1 shl (index and 7)) != 0
|
|
||||||
|
|
||||||
private fun computeObjectSize(value: Any) = computeTypeSize(value.javaClass, value)
|
|
||||||
private fun computeClassSize(value: Class<*>) = computeTypeSize(value, null)
|
|
||||||
private fun computeTypeSize(type: Class<*>, value: Any?): Int{
|
|
||||||
var byteSize = 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) byteSize += computeDataSize(field, annotation, value)
|
|
||||||
}
|
|
||||||
return byteSize
|
|
||||||
}
|
|
||||||
private fun computeDataSize(field: Field, annotation: SyncedVar, value: Any?): Int =
|
|
||||||
when(field.type){
|
|
||||||
Byte::class.java -> 1
|
|
||||||
Short::class.java ->
|
|
||||||
if(annotation.noCompress) 2
|
|
||||||
else varIntSize(
|
|
||||||
if(annotation.nonNegative) field.getShort(value).toLong()
|
|
||||||
else zigZagEncode(field.getShort(value).toLong())
|
|
||||||
)
|
|
||||||
Int::class.java ->
|
|
||||||
if(annotation.noCompress) 4
|
|
||||||
else varIntSize(
|
|
||||||
if(annotation.nonNegative) field.getInt(value).toLong()
|
|
||||||
else zigZagEncode(field.getInt(value).toLong())
|
|
||||||
)
|
|
||||||
Long::class.java ->
|
|
||||||
if(annotation.noCompress) 8
|
|
||||||
else varIntSize(
|
|
||||||
if(annotation.nonNegative) field.getLong(value)
|
|
||||||
else zigZagEncode(field.getLong(value))
|
|
||||||
)
|
|
||||||
Float::class.java ->
|
|
||||||
if(annotation.noCompress) 4
|
|
||||||
else varIntSize(
|
|
||||||
if(annotation.floatEndianSwap) bitConvert(swapEndian(floatToInt(field.getFloat(value))))
|
|
||||||
else bitConvert(floatToInt(field.getFloat(value)))
|
|
||||||
)
|
|
||||||
Double::class.java ->
|
|
||||||
if(annotation.noCompress) 8
|
|
||||||
else varIntSize(
|
|
||||||
if(annotation.floatEndianSwap) swapEndian(doubleToLong(field.getDouble(value)))
|
|
||||||
else doubleToLong(field.getDouble(value))
|
|
||||||
)
|
|
||||||
else -> 0
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun readValue(field: Field, annotation: SyncedVar, value: Any?, buffer: ByteBuffer, offset: Int): Int =
|
|
||||||
when(field.type){
|
|
||||||
Byte::class.java ->{
|
|
||||||
buffer.put(offset, field.getByte(value))
|
|
||||||
1
|
|
||||||
}
|
|
||||||
Short::class.java ->
|
|
||||||
if(annotation.noCompress){
|
|
||||||
buffer.putShort(offset, field.getShort(value))
|
|
||||||
2
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
val rawValue =
|
|
||||||
if(annotation.nonNegative) field.getShort(value).toLong()
|
|
||||||
else zigZagEncode(field.getShort(value).toLong())
|
|
||||||
writeVarInt(buffer, offset, rawValue)
|
|
||||||
varIntSize(rawValue)
|
|
||||||
}
|
|
||||||
Int::class.java ->
|
|
||||||
if(annotation.noCompress){
|
|
||||||
buffer.putInt(offset, field.getInt(value))
|
|
||||||
4
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
val rawValue =
|
|
||||||
if(annotation.nonNegative) field.getInt(value).toLong()
|
|
||||||
else zigZagEncode(field.getInt(value).toLong())
|
|
||||||
writeVarInt(buffer, offset, rawValue)
|
|
||||||
varIntSize(rawValue)
|
|
||||||
}
|
|
||||||
Long::class.java ->
|
|
||||||
if(annotation.noCompress){
|
|
||||||
buffer.putLong(offset, field.getLong(value))
|
|
||||||
8
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
val rawValue =
|
|
||||||
if(annotation.nonNegative) field.getLong(value)
|
|
||||||
else zigZagEncode(field.getLong(value))
|
|
||||||
writeVarInt(buffer, offset, rawValue)
|
|
||||||
varIntSize(rawValue)
|
|
||||||
}
|
|
||||||
Float::class.java ->
|
|
||||||
if(annotation.noCompress){
|
|
||||||
buffer.putFloat(offset, field.getFloat(value))
|
|
||||||
4
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
val rawValue =
|
|
||||||
if(annotation.floatEndianSwap) bitConvert(swapEndian(floatToInt(field.getFloat(value))))
|
|
||||||
else bitConvert(floatToInt(field.getFloat(value)))
|
|
||||||
writeVarInt(buffer, offset, rawValue)
|
|
||||||
varIntSize(rawValue)
|
|
||||||
}
|
|
||||||
Double::class.java ->
|
|
||||||
if(annotation.noCompress){
|
|
||||||
buffer.putDouble(offset, field.getDouble(value))
|
|
||||||
8
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
val rawValue =
|
|
||||||
if(annotation.floatEndianSwap) swapEndian(doubleToLong(field.getDouble(value)))
|
|
||||||
else doubleToLong(field.getDouble(value))
|
|
||||||
writeVarInt(buffer, offset, rawValue)
|
|
||||||
varIntSize(rawValue)
|
|
||||||
}
|
|
||||||
else -> 0
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun writeValue(field: Field, annotation: SyncedVar, value: Any?, buffer: ByteBuffer, offset: Int): Int =
|
|
||||||
when(field.type){
|
|
||||||
Byte::class.java ->{
|
|
||||||
field.setByte(value, buffer.get(offset))
|
|
||||||
1
|
|
||||||
}
|
|
||||||
Short::class.java ->
|
|
||||||
if(annotation.noCompress){
|
|
||||||
field.setShort(value, buffer.getShort(offset))
|
|
||||||
2
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
val rawValue =
|
|
||||||
if(annotation.nonNegative) readVarInt(buffer, offset)
|
|
||||||
else zigZagDecode(readVarInt(buffer, offset))
|
|
||||||
field.setShort(value, rawValue.toShort())
|
|
||||||
varIntSize(rawValue)
|
|
||||||
}
|
|
||||||
Int::class.java ->
|
|
||||||
if(annotation.noCompress){
|
|
||||||
field.setInt(value, buffer.getInt(offset))
|
|
||||||
4
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
val rawValue =
|
|
||||||
if(annotation.nonNegative) readVarInt(buffer, offset)
|
|
||||||
else zigZagDecode(readVarInt(buffer, offset))
|
|
||||||
field.setInt(value, rawValue.toInt())
|
|
||||||
varIntSize(rawValue)
|
|
||||||
}
|
|
||||||
Long::class.java ->
|
|
||||||
if(annotation.noCompress){
|
|
||||||
field.setLong(value, buffer.getLong(offset))
|
|
||||||
8
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
val rawValue =
|
|
||||||
if(annotation.nonNegative) readVarInt(buffer, offset)
|
|
||||||
else zigZagDecode(readVarInt(buffer, offset))
|
|
||||||
field.setLong(value, rawValue)
|
|
||||||
varIntSize(rawValue)
|
|
||||||
}
|
|
||||||
Float::class.java ->
|
|
||||||
if(annotation.noCompress){
|
|
||||||
field.setFloat(value, buffer.getFloat(offset))
|
|
||||||
4
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
val readVal = readVarInt(buffer, offset)
|
|
||||||
val rawValue =
|
|
||||||
if(annotation.floatEndianSwap) intToFloat(swapEndian(readVal.toInt()))
|
|
||||||
else intToFloat(readVal.toInt())
|
|
||||||
field.setFloat(value, rawValue)
|
|
||||||
varIntSize(readVal)
|
|
||||||
}
|
|
||||||
Double::class.java ->
|
|
||||||
if(annotation.noCompress){
|
|
||||||
field.setDouble(value, buffer.getDouble(offset))
|
|
||||||
8
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
val readVal = readVarInt(buffer, offset)
|
|
||||||
val rawValue =
|
|
||||||
if(annotation.floatEndianSwap) longToDouble(swapEndian(readVal))
|
|
||||||
else longToDouble(readVal)
|
|
||||||
field.setDouble(value, rawValue)
|
|
||||||
varIntSize(readVal)
|
|
||||||
}
|
|
||||||
else -> 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -1,5 +1,7 @@
|
|||||||
package net.tofvesson.networking
|
package net.tofvesson.networking
|
||||||
|
|
||||||
|
import net.tofvesson.reflect.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An annotation denoting that a field should be automatically serialized to bytes.
|
* An annotation denoting that a field should be automatically serialized to bytes.
|
||||||
* @param noCompress specifies whether or not the SyncedVar value should be losslessly compressed during serialization
|
* @param noCompress specifies whether or not the SyncedVar value should be losslessly compressed during serialization
|
||||||
@ -10,4 +12,70 @@ package net.tofvesson.networking
|
|||||||
* Swapping endianness may improve compression rates. This parameter is ignored if <b>noCompress</b> is set to <b>true</b>.
|
* Swapping endianness may improve compression rates. This parameter is ignored if <b>noCompress</b> is set to <b>true</b>.
|
||||||
*/
|
*/
|
||||||
@Target(allowedTargets = [(AnnotationTarget.FIELD)])
|
@Target(allowedTargets = [(AnnotationTarget.FIELD)])
|
||||||
annotation class SyncedVar(val noCompress: Boolean = false, val nonNegative: Boolean = false, val floatEndianSwap: Boolean = true)
|
annotation class SyncedVar(vararg val value: String = [])
|
||||||
|
|
||||||
|
enum class SyncFlag {
|
||||||
|
NoCompress,
|
||||||
|
NonNegative,
|
||||||
|
FloatEndianSwap;
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun getByName(name: String): SyncFlag? {
|
||||||
|
for(flag in SyncFlag.values())
|
||||||
|
if(flag.name == name)
|
||||||
|
return flag
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun parse(flagSet: Array<out String>): Array<SyncFlag> = Array(flagSet.size) { getByName(flagSet[it])!! }
|
||||||
|
|
||||||
|
fun createFlag(name: String): SyncFlag {
|
||||||
|
// Do duplication check
|
||||||
|
if(getByName(name)!=null) throw IllegalArgumentException("Flag \"$name\" is already registered!")
|
||||||
|
|
||||||
|
// Get unsafe
|
||||||
|
val unsafe = getUnsafe()
|
||||||
|
|
||||||
|
// Create new enum
|
||||||
|
val newFlag: SyncFlag = unsafe.allocateInstance(SyncFlag::class.java) as SyncFlag
|
||||||
|
|
||||||
|
// Set valid ordinal
|
||||||
|
val ordinalField = Enum::class.java.getDeclaredField("ordinal")
|
||||||
|
ordinalField.forceAccessible = true
|
||||||
|
ordinalField.setInt(newFlag, getUnallocatedOrdinal())
|
||||||
|
|
||||||
|
// Set valid name
|
||||||
|
val nameField = Enum::class.java.getDeclaredField("name")
|
||||||
|
nameField.forceAccessible = true
|
||||||
|
nameField.set(newFlag, name)
|
||||||
|
|
||||||
|
// Add the new flag to the array of values
|
||||||
|
val oldFlags = values()
|
||||||
|
val flagArray = Array(oldFlags.size+1){
|
||||||
|
if(it < oldFlags.size) oldFlags[it]
|
||||||
|
else newFlag
|
||||||
|
}
|
||||||
|
val flags = SyncFlag::class.java.getDeclaredField("\$VALUES")
|
||||||
|
flags.setStaticFinalValue(flagArray)
|
||||||
|
|
||||||
|
return newFlag
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getUnallocatedOrdinal(): Int {
|
||||||
|
var ordinal = 0
|
||||||
|
val flags = SyncFlag.values().toMutableList()
|
||||||
|
val remove = ArrayList<SyncFlag>()
|
||||||
|
while(flags.count()!=0) {
|
||||||
|
for (flag in flags)
|
||||||
|
if (flag.ordinal == ordinal) {
|
||||||
|
remove.add(flag)
|
||||||
|
++ordinal
|
||||||
|
if (ordinal == 0) throw IndexOutOfBoundsException("Full 32-bit range or enum ordinals occupied!")
|
||||||
|
}
|
||||||
|
flags.removeAll(remove)
|
||||||
|
remove.clear()
|
||||||
|
}
|
||||||
|
return ordinal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
9
src/net/tofvesson/networking/UnsupportedTypeException.kt
Normal file
9
src/net/tofvesson/networking/UnsupportedTypeException.kt
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
package net.tofvesson.networking
|
||||||
|
|
||||||
|
class UnsupportedTypeException: 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)
|
||||||
|
}
|
34
src/net/tofvesson/reflect/Accessible.kt
Normal file
34
src/net/tofvesson/reflect/Accessible.kt
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
package net.tofvesson.reflect
|
||||||
|
|
||||||
|
import sun.misc.Unsafe
|
||||||
|
import java.lang.reflect.AccessibleObject
|
||||||
|
import java.lang.reflect.Field
|
||||||
|
|
||||||
|
var AccessibleObject.forceAccessible: Boolean
|
||||||
|
get() = this.isAccessible
|
||||||
|
set(value) {
|
||||||
|
val fieldOverride = AccessibleObject::class.java.getDeclaredField("override")
|
||||||
|
val unsafe = getUnsafe()
|
||||||
|
val overrideOffset = unsafe.objectFieldOffset(fieldOverride)
|
||||||
|
unsafe.getAndSetObject(this, overrideOffset, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getUnsafe(): Unsafe{
|
||||||
|
val theUnsafe = Unsafe::class.java.getDeclaredField("theUnsafe")
|
||||||
|
theUnsafe.trySetAccessible()
|
||||||
|
return theUnsafe.get(null) as Unsafe
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Field.setStaticFinalValue(value: Any?){
|
||||||
|
val factory = Class.forName("jdk.internal.reflect.UnsafeFieldAccessorFactory").getDeclaredMethod("newFieldAccessor", Field::class.java, Boolean::class.java)
|
||||||
|
val isReadonly = Class.forName("jdk.internal.reflect.UnsafeQualifiedStaticFieldAccessorImpl").getDeclaredField("isReadOnly")
|
||||||
|
isReadonly.forceAccessible = true
|
||||||
|
factory.forceAccessible = true
|
||||||
|
val overrideAccessor = Field::class.java.getDeclaredField("overrideFieldAccessor")
|
||||||
|
overrideAccessor.forceAccessible = true
|
||||||
|
val accessor = factory.invoke(null, this, true)
|
||||||
|
isReadonly.setBoolean(accessor, false)
|
||||||
|
overrideAccessor.set(this, accessor)
|
||||||
|
this.forceAccessible = true
|
||||||
|
this.set(null, value)
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user