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;
|
||||
|
||||
public class Main {
|
||||
@SyncedVar(nonNegative = true)
|
||||
@SyncedVar("NonNegative")
|
||||
public int syncTest = 5;
|
||||
|
||||
@SyncedVar
|
||||
@ -17,35 +17,68 @@ public class Main {
|
||||
@SyncedVar
|
||||
public static boolean testbool1 = true;
|
||||
|
||||
@SyncedVar
|
||||
public static boolean[] test = {true, false};
|
||||
|
||||
public static void main(String[] args){
|
||||
Main testObject = new Main();
|
||||
SyncHandler sync = new SyncHandler();
|
||||
sync.registerSyncObject(testObject);
|
||||
sync.registerSyncObject(Main.class);
|
||||
|
||||
// Generate mismatch check
|
||||
byte[] mismatchCheck = sync.generateMismatchCheck();
|
||||
|
||||
// Generate snapshot of values to 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;
|
||||
staticTest = 32;
|
||||
value = 9.0f;
|
||||
testObject.testbool = true;
|
||||
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
|
||||
sync.unregisterSyncObject(testObject);
|
||||
sync.unregisterSyncObject(Main.class);
|
||||
sync.registerSyncObject(Main.class);
|
||||
sync.registerSyncObject(testObject);
|
||||
*/
|
||||
// Do mismatch check
|
||||
if(!sync.doMismatchCheck(mismatchCheck)) throw new RuntimeException("Target sync mismatch");
|
||||
|
||||
// Load snapshot values back
|
||||
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("Snapshot size: "+ser.length+" bytes");
|
||||
System.out.println("Deserialized snapshot 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\n" +
|
||||
"Snapshot size: \"+ser.length+\" bytes"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package net.tofvesson.networking
|
||||
|
||||
import java.nio.ByteBuffer
|
||||
import kotlin.experimental.or
|
||||
|
||||
fun varIntSize(value: Long): Int =
|
||||
when {
|
||||
@ -103,6 +104,15 @@ fun readVarInt(buffer: ByteBuffer, offset: Int): Long {
|
||||
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)
|
||||
|
||||
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.nio.ByteBuffer
|
||||
import kotlin.experimental.or
|
||||
import java.security.NoSuchAlgorithmException
|
||||
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
|
||||
*/
|
||||
class SyncHandler(private val permissiveMismatchCheck: Boolean = false) {
|
||||
private val toSync: ArrayList<Pair<Any, Int>> = ArrayList()
|
||||
class SyncHandler(defaultSerializers: Boolean = true, private val permissiveMismatchCheck: Boolean = false) {
|
||||
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){
|
||||
if(!toSync.contains(value)) toSync.add(Pair(value, getBooleanFieldCount(value::class.java)))
|
||||
if(!toSync.contains(value)) toSync.add(value)
|
||||
}
|
||||
|
||||
fun unregisterSyncObject(value: Any){
|
||||
for(i in toSync.indices.reversed())
|
||||
if(toSync[i].first == value)
|
||||
toSync.removeAt(i)
|
||||
toSync.remove(value)
|
||||
}
|
||||
|
||||
fun withSerializer(serializer: Serializer): SyncHandler {
|
||||
if(!serializers.contains(serializer)) serializers.add(serializer)
|
||||
return this
|
||||
}
|
||||
|
||||
fun serialize(): ByteArray{
|
||||
val headerBits = computeBitHeaderCount()
|
||||
val headerSize = (headerBits shr 3) + (if((headerBits and 7) != 0) 1 else 0)
|
||||
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
|
||||
var totalSize = headerSize
|
||||
for((entry, _) in toSync)
|
||||
totalSize += if(entry is Class<*>) computeClassSize(entry) else computeObjectSize(entry)
|
||||
var dataIndex = (headerSize shr 3) + (if(headerSize and 7 != 0) 1 else 0)
|
||||
|
||||
totalSize += dataIndex
|
||||
|
||||
val buffer = ByteArray(totalSize)
|
||||
for((entry, _) in toSync){
|
||||
for(entry in toSync){
|
||||
val result =
|
||||
if(entry is Class<*>) readClass(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){
|
||||
val headerBits = computeBitHeaderCount()
|
||||
val headerSize = (headerBits shr 3) + (if((headerBits and 7) != 0) 1 else 0)
|
||||
var headerSize = 0
|
||||
for(entry in toSync)
|
||||
headerSize += (if(entry is Class<*>) computeClassSize(entry) else computeObjectSize(entry)).second
|
||||
var headerIndex = 0
|
||||
var dataIndex = headerSize
|
||||
for((entry, _) in toSync){
|
||||
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)
|
||||
@ -59,16 +75,15 @@ class SyncHandler(private val permissiveMismatchCheck: Boolean = false) {
|
||||
|
||||
fun generateMismatchCheck(): ByteArray {
|
||||
val builder = StringBuilder()
|
||||
for((entry, _) in toSync)
|
||||
for(entry in toSync)
|
||||
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
|
||||
val annotation = field.getAnnotation(SyncedVar::class.java)
|
||||
if(annotation!=null)
|
||||
builder
|
||||
.append(if(permissiveMismatchCheck) field.type.name else field.toGenericString())
|
||||
.append(if(annotation.floatEndianSwap) 1 else 0)
|
||||
.append(if(annotation.noCompress) 1 else 0)
|
||||
.append(if(annotation.nonNegative) 1 else 0)
|
||||
if(annotation!=null) {
|
||||
builder.append('{').append(if (permissiveMismatchCheck) field.type.name else field.toGenericString())
|
||||
for(flag in annotation.value) builder.append(flag)
|
||||
builder.append('}')
|
||||
}
|
||||
}
|
||||
|
||||
return try {
|
||||
@ -87,11 +102,29 @@ class SyncHandler(private val permissiveMismatchCheck: Boolean = false) {
|
||||
return true
|
||||
}
|
||||
|
||||
private fun computeBitHeaderCount(): Int {
|
||||
var bitCount = 0
|
||||
for((_, bits) in toSync)
|
||||
bitCount += bits
|
||||
return bitCount
|
||||
|
||||
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 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)
|
||||
@ -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
|
||||
val annotation = field.getAnnotation(SyncedVar::class.java)
|
||||
if(annotation != null){
|
||||
if(field.type!=Boolean::class.java) localOffset += readValue(field, annotation, value, byteBuffer, localOffset)
|
||||
else writeBit(field.getBoolean(value), buffer, localBitOffset++)
|
||||
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)
|
||||
@ -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
|
||||
val annotation = field.getAnnotation(SyncedVar::class.java)
|
||||
if(annotation != null){
|
||||
if(field.type!=Boolean::class.java) localOffset += writeValue(field, annotation, value, byteBuffer, localOffset)
|
||||
else field.setBoolean(value, readBit(buffer, localBitOffset++))
|
||||
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)
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
import net.tofvesson.reflect.*
|
||||
|
||||
/**
|
||||
* 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
|
||||
@ -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>.
|
||||
*/
|
||||
@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