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:
Gabriel Tofvesson 2018-07-25 01:07:35 +02:00
parent 2e32d9d984
commit 04dd29ad56
9 changed files with 786 additions and 248 deletions

View File

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

View File

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

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

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

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

View File

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

View File

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

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

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