Added support for value delta tracking

Added support for delta serialization
Tweaked Serializer class to be more robust when handling edge cases regarding typing issues
Added support for boxed field types to PrimitiveSerializers
This commit is contained in:
Gabriel Tofvesson 2018-07-25 08:39:07 +02:00
parent 04dd29ad56
commit 60cebfb966
10 changed files with 391 additions and 121 deletions

View File

@ -1,5 +1,4 @@
import net.tofvesson.networking.SyncHandler;
import net.tofvesson.networking.SyncedVar;
import net.tofvesson.networking.*;
public class Main {
@SyncedVar("NonNegative")
@ -20,8 +19,16 @@ public class Main {
@SyncedVar
public static boolean[] test = {true, false};
@SyncedVar
public static DiffTracked<Integer> tracker = new DiffTracked<>(5, Integer.class);
@SyncedVar
public static DiffTrackedArray<Long> tracker2 = new DiffTrackedArray<>(Long.class, 8, i -> (long)i);
public static void main(String[] args){
Main testObject = new Main();
SyncHandler.Companion.registerSerializer(DiffTrackedSerializer.Companion.getSingleton());
SyncHandler sync = new SyncHandler();
sync.registerSyncObject(testObject);
sync.registerSyncObject(Main.class);
@ -29,18 +36,27 @@ public class Main {
// Generate mismatch check
byte[] mismatchCheck = sync.generateMismatchCheck();
// Trigger change flags
tracker.setValue(9);
tracker2.set(3L, 2);
tracker2.set(5L, 0);
// Generate snapshot of values to serialize
byte[] ser = sync.serialize();
System.out.println("Created and serialized snapshot of field values:\n\t"+
System.out.print("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"
test[1]+"\n\t"+
tracker
);
for(Long value : tracker2.getValues())
System.out.print("\n\t"+value);
System.out.println('\n');
// Modify all the values
testObject.syncTest = 20;
@ -52,8 +68,11 @@ public class Main {
test[0] = false;
test[1] = true;
test[2] = true;
tracker.setValue(400);
tracker2.set(8L, 2);
tracker2.set(100L, 0);
System.out.println("Set a new state of test values:\n\t"+
System.out.print("Set a new state of test values:\n\t"+
testObject.syncTest+"\n\t"+
staticTest+"\n\t"+
value+"\n\t"+
@ -61,8 +80,12 @@ public class Main {
testbool1+"\n\t"+
test[0]+"\n\t"+
test[1]+"\n\t"+
test[2]+"\n"
test[2]+"\n\t"+
tracker
);
for(Long value : tracker2.getValues())
System.out.print("\n\t"+value);
System.out.println('\n');
// Do mismatch check
if(!sync.doMismatchCheck(mismatchCheck)) throw new RuntimeException("Target sync mismatch");
@ -70,15 +93,18 @@ public class Main {
// Load snapshot values back
sync.deserialize(ser);
System.out.println("Deserialized snapshot values:\n\t"+
System.out.print("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"
);
test[1]+"\n\t"+
tracker);
for(Long value : tracker2.getValues())
System.out.print("\n\t"+value);
System.out.println("\n\nSnapshot size: "+ser.length+" bytes");
}
}

View File

@ -0,0 +1,17 @@
package net.tofvesson.networking
import java.util.*
class DiffTracked<T>(initial: T, val valueType: Class<T>) {
private var changed = false
private var _value = initial
var value: T
get() = _value
set(value) {
changed = changed or (value != this._value)
_value = value
}
fun hasChanged() = changed
fun clearChangeState() { changed = false }
override fun toString() = Objects.toString(_value)!!
}

View File

@ -0,0 +1,31 @@
package net.tofvesson.networking
class DiffTrackedArray<T>(val elementType: Class<T>, val size: Int, gen: (Int) -> T) {
val values: Array<T> = java.lang.reflect.Array.newInstance(elementType, size) as Array<T>
val changeMap = Array(size) {false}
init{
for(index in 0 until size)
values[index] = gen(index)
}
operator fun get(index: Int) = values[index]
operator fun set(value: T, index: Int) {
changeMap[index] = changeMap[index] or (values[index] != value)
values[index] = value
}
fun clearChangeState(){
for (index in changeMap.indices)
changeMap[index] = false
}
fun hasChanged(): Boolean {
for(value in changeMap)
if(value)
return true
return false
}
override fun toString() = values.toString()
}

View File

@ -0,0 +1,129 @@
package net.tofvesson.networking
import java.lang.reflect.Field
import java.nio.ByteBuffer
class DiffTrackedSerializer private constructor(): Serializer(arrayOf(
DiffTracked::class.java,
DiffTrackedArray::class.java
)) {
companion object {
private val trackedField = DiffTracked::class.java.getDeclaredField("_value")
private val holderValue = Holder::class.java.getDeclaredField("value")
val singleton = DiffTrackedSerializer()
}
override fun computeSizeExplicit(field: Field, flags: Array<out SyncFlag>, owner: Any?, fieldType: Class<*>): Pair<Int, Int> =
when(fieldType) {
DiffTracked::class.java -> {
val tracker = field.get(owner) as DiffTracked<*>
val serializer = SyncHandler.getCompatibleSerializer(tracker.valueType)
if(tracker.hasChanged()){
val result = serializer.computeSizeExplicit(trackedField, flags, tracker, tracker.valueType)
Pair(result.first, result.second+1)
}else Pair(0, 1)
}
DiffTrackedArray::class.java -> {
val tracker = field.get(owner) as DiffTrackedArray<*>
val serializer = SyncHandler.getCompatibleSerializer(tracker.elementType)
if(tracker.hasChanged()){
var bits = 0
var bytes = 0
val holder = Holder(null)
for(index in tracker.changeMap.indices)
if(tracker.changeMap[index]){
holder.value = tracker[index]
val result = serializer.computeSizeExplicit(holderValue, flags, holder, tracker.elementType)
bytes += result.first
bits += result.second
}
Pair(bytes, bits+tracker.size)
}else Pair(0, tracker.size)
}
else -> Pair(0, 0)
}
override fun serializeExplicit(field: Field, flags: Array<out SyncFlag>, owner: Any?, byteBuffer: ByteBuffer, offset: Int, bitFieldOffset: Int, fieldType: Class<*>): Pair<Int, Int> =
when(fieldType) {
DiffTracked::class.java -> {
val tracker = field.get(owner) as DiffTracked<*>
val serializer = SyncHandler.getCompatibleSerializer(tracker.valueType)
writeBit(tracker.hasChanged(), byteBuffer, bitFieldOffset)
if(tracker.hasChanged()){
val result = serializer.serializeExplicit(trackedField, flags, tracker, byteBuffer, offset, bitFieldOffset+1, tracker.valueType)
tracker.clearChangeState()
Pair(result.first, result.second)
}else{
tracker.clearChangeState()
Pair(offset, bitFieldOffset+1)
}
}
DiffTrackedArray::class.java -> {
val tracker = field.get(owner) as DiffTrackedArray<*>
val serializer = SyncHandler.getCompatibleSerializer(tracker.elementType)
if(tracker.hasChanged()){
var bits = bitFieldOffset
var bytes = offset
val holder = Holder(null)
for(index in tracker.changeMap.indices) {
writeBit(tracker.changeMap[index], byteBuffer, bits++)
if (tracker.changeMap[index]) {
holder.value = tracker[index]
val result = serializer.serializeExplicit(holderValue, flags, holder, byteBuffer, bytes, bits, tracker.elementType)
bytes = result.first
bits = result.second
}
}
tracker.clearChangeState()
Pair(bytes, bits)
}else{
tracker.clearChangeState()
Pair(offset, bitFieldOffset+tracker.size)
}
}
else -> Pair(0, 0)
}
override fun deserializeExplicit(field: Field, flags: Array<out SyncFlag>, owner: Any?, byteBuffer: ByteBuffer, offset: Int, bitFieldOffset: Int, fieldType: Class<*>): Pair<Int, Int> =
when(fieldType) {
DiffTracked::class.java -> {
val tracker = field.get(owner) as DiffTracked<*>
val serializer = SyncHandler.getCompatibleSerializer(tracker.valueType)
var bytes = offset
var bits = bitFieldOffset
if(readBit(byteBuffer, bits++)){
val result = serializer.deserializeExplicit(trackedField, flags, tracker, byteBuffer, bytes, bits, tracker.valueType)
bytes = result.first
bits = result.second
}
tracker.clearChangeState()
Pair(bytes, bits)
}
DiffTrackedArray::class.java -> {
val tracker = field.get(owner) as DiffTrackedArray<*>
val serializer = SyncHandler.getCompatibleSerializer(tracker.elementType)
var bits = bitFieldOffset
var bytes = offset
val holder = Holder(null)
val array = tracker.values as Array<Any?>
for(index in tracker.changeMap.indices){
if(readBit(byteBuffer, bits++)){
holder.value = tracker[index]
val result = serializer.deserializeExplicit(holderValue, flags, holder, byteBuffer, bytes, bits, tracker.elementType)
bytes = result.first
bits = result.second
array[index] = holder.value
}
}
tracker.clearChangeState()
Pair(bytes, bits)
}
else -> Pair(0, 0)
}
private data class Holder(var value: Any?)
}

View File

@ -18,11 +18,11 @@ class PrimitiveArraySerializer private constructor(): Serializer(arrayOf(
val singleton = PrimitiveArraySerializer()
}
override fun computeSize(field: Field, flags: Array<out SyncFlag>, owner: Any?): Pair<Int, Int> {
override fun computeSizeExplicit(field: Field, flags: Array<out SyncFlag>, owner: Any?, fieldType: Class<*>): 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) {
when (fieldType) {
BooleanArray::class.java -> bitSize = arrayLength
ByteArray::class.java -> byteSize += arrayLength
ShortArray::class.java ->
@ -74,7 +74,7 @@ class PrimitiveArraySerializer private constructor(): Serializer(arrayOf(
return Pair(byteSize, bitSize)
}
override fun serialize(field: Field, flags: Array<out SyncFlag>, owner: Any?, byteBuffer: ByteBuffer, offset: Int, bitFieldOffset: Int): Pair<Int, Int> {
override fun serializeExplicit(field: Field, flags: Array<out SyncFlag>, owner: Any?, byteBuffer: ByteBuffer, offset: Int, bitFieldOffset: Int, fieldType: Class<*>): Pair<Int, Int> {
val arrayLength = java.lang.reflect.Array.getLength(field.get(owner))
var localByteOffset = offset
var localBitOffset = bitFieldOffset
@ -82,7 +82,7 @@ class PrimitiveArraySerializer private constructor(): Serializer(arrayOf(
writeVarInt(byteBuffer, offset, arrayLength.toLong())
localByteOffset += varIntSize(arrayLength.toLong())
}
when (field.type) {
when (fieldType) {
BooleanArray::class.java ->
for(value in field.get(owner) as BooleanArray)
writeBit(value, byteBuffer, localBitOffset++)
@ -167,7 +167,7 @@ class PrimitiveArraySerializer private constructor(): Serializer(arrayOf(
return Pair(localByteOffset, localBitOffset)
}
override fun deserialize(field: Field, flags: Array<out SyncFlag>, owner: Any?, byteBuffer: ByteBuffer, offset: Int, bitFieldOffset: Int): Pair<Int, Int> {
override fun deserializeExplicit(field: Field, flags: Array<out SyncFlag>, owner: Any?, byteBuffer: ByteBuffer, offset: Int, bitFieldOffset: Int, fieldType: Class<*>): Pair<Int, Int> {
var localByteOffset = offset
var localBitOffset = bitFieldOffset
val localLength = java.lang.reflect.Array.getLength(field.get(owner))
@ -183,7 +183,7 @@ class PrimitiveArraySerializer private constructor(): Serializer(arrayOf(
if(arrayLength!=localLength) java.lang.reflect.Array.newInstance(field.type.componentType, arrayLength)
else field.get(owner)
when (field.type) {
when (fieldType) {
BooleanArray::class.java -> {
val booleanTarget = target as BooleanArray
for (index in 0 until arrayLength)

View File

@ -1,196 +1,207 @@
package net.tofvesson.networking
import net.tofvesson.reflect.*
import java.lang.reflect.Field
import java.nio.ByteBuffer
class PrimitiveSerializer private constructor() : Serializer(arrayOf(
Boolean::class.java,
java.lang.Boolean::class.java,
Byte::class.java,
java.lang.Byte::class.java,
Short::class.java,
java.lang.Short::class.java,
Int::class.java,
java.lang.Integer::class.java,
Long::class.java,
java.lang.Long::class.java,
Float::class.java,
Double::class.java
java.lang.Float::class.java,
Double::class.java,
java.lang.Double::class.java
)) {
companion object { val singleton = PrimitiveSerializer() }
override fun computeSize(
override fun computeSizeExplicit(
field: Field,
flags: Array<out SyncFlag>,
owner: Any?
owner: Any?,
fieldType: Class<*>
): Pair<Int, Int> =
when(field.type){
Boolean::class.java -> Pair(0, 1)
Byte::class.java -> Pair(1, 0)
Short::class.java ->
when(fieldType){
java.lang.Boolean::class.java, Boolean::class.java -> Pair(0, 1)
java.lang.Byte::class.java, Byte::class.java -> Pair(1, 0)
java.lang.Short::class.java, Short::class.java ->
if(flags.contains(SyncFlag.NoCompress)) Pair(2, 0)
else Pair(varIntSize(
if(flags.contains(SyncFlag.NonNegative)) field.getShort(owner).toLong()
else zigZagEncode(field.getShort(owner).toLong())
if(flags.contains(SyncFlag.NonNegative)) field.getShortAdaptive(owner).toLong()
else zigZagEncode(field.getShortAdaptive(owner).toLong())
), 0)
Int::class.java ->
java.lang.Integer::class.java, Int::class.java ->
if(flags.contains(SyncFlag.NoCompress)) Pair(4, 0)
else Pair(varIntSize(
if(flags.contains(SyncFlag.NonNegative)) field.getInt(owner).toLong()
else zigZagEncode(field.getInt(owner).toLong())
if(flags.contains(SyncFlag.NonNegative)) field.getIntAdaptive(owner).toLong()
else zigZagEncode(field.getIntAdaptive(owner).toLong())
), 0)
Long::class.java ->
java.lang.Long::class.java, Long::class.java ->
if(flags.contains(SyncFlag.NoCompress)) Pair(8, 0)
else Pair(varIntSize(
if(flags.contains(SyncFlag.NonNegative)) field.getLong(owner)
else zigZagEncode(field.getLong(owner))
if(flags.contains(SyncFlag.NonNegative)) field.getLongAdaptive(owner)
else zigZagEncode(field.getLongAdaptive(owner))
), 0)
Float::class.java ->
java.lang.Float::class.java, Float::class.java ->
if(flags.contains(SyncFlag.NoCompress)) Pair(4, 0)
else Pair(varIntSize(
if(flags.contains(SyncFlag.FloatEndianSwap)) bitConvert(swapEndian(floatToInt(field.getFloat(owner))))
else bitConvert(floatToInt(field.getFloat(owner)))
if(flags.contains(SyncFlag.FloatEndianSwap)) bitConvert(swapEndian(floatToInt(field.getFloatAdaptive(owner))))
else bitConvert(floatToInt(field.getFloatAdaptive(owner)))
), 0)
Double::class.java ->
java.lang.Double::class.java, Double::class.java ->
if(flags.contains(SyncFlag.NoCompress)) Pair(8, 0)
else Pair(varIntSize(
if(flags.contains(SyncFlag.FloatEndianSwap)) swapEndian(doubleToLong(field.getDouble(owner)))
else doubleToLong(field.getDouble(owner))
if(flags.contains(SyncFlag.FloatEndianSwap)) swapEndian(doubleToLong(field.getDoubleAdaptive(owner)))
else doubleToLong(field.getDoubleAdaptive(owner))
), 0)
else -> Pair(0, 0)
}
override fun serialize(
override fun serializeExplicit(
field: Field,
flags: Array<out SyncFlag>,
owner: Any?,
byteBuffer: ByteBuffer,
offset: Int,
bitFieldOffset: Int
bitFieldOffset: Int,
fieldType: Class<*>
): Pair<Int, Int> =
when(field.type){
Boolean::class.java -> {
writeBit(field.getBoolean(owner), byteBuffer, bitFieldOffset)
when(fieldType){
java.lang.Boolean::class.java, Boolean::class.java -> {
writeBit(field.getBooleanAdaptive(owner), byteBuffer, bitFieldOffset)
Pair(offset, bitFieldOffset+1)
}
Byte::class.java -> {
byteBuffer.put(offset, field.getByte(owner))
java.lang.Byte::class.java, Byte::class.java -> {
byteBuffer.put(offset, field.getByteAdaptive(owner))
Pair(offset+1, bitFieldOffset)
}
Short::class.java ->
java.lang.Short::class.java, Short::class.java ->
if(flags.contains(SyncFlag.NoCompress)){
byteBuffer.putShort(offset, field.getShort(owner))
byteBuffer.putShort(offset, field.getShortAdaptive(owner))
Pair(offset+2, bitFieldOffset)
}
else {
val rawValue =
if(flags.contains(SyncFlag.NonNegative)) field.getShort(owner).toLong()
else zigZagEncode(field.getShort(owner).toLong())
if(flags.contains(SyncFlag.NonNegative)) field.getShortAdaptive(owner).toLong()
else zigZagEncode(field.getShortAdaptive(owner).toLong())
writeVarInt(byteBuffer, offset, rawValue)
Pair(offset+varIntSize(rawValue), bitFieldOffset)
}
Int::class.java ->
java.lang.Integer::class.java, Int::class.java ->
if(flags.contains(SyncFlag.NoCompress)){
byteBuffer.putInt(offset, field.getInt(owner))
byteBuffer.putInt(offset, field.getIntAdaptive(owner))
Pair(offset+4, bitFieldOffset)
}
else {
val rawValue =
if(flags.contains(SyncFlag.NonNegative)) field.getInt(owner).toLong()
else zigZagEncode(field.getInt(owner).toLong())
if(flags.contains(SyncFlag.NonNegative)) field.getIntAdaptive(owner).toLong()
else zigZagEncode(field.getIntAdaptive(owner).toLong())
writeVarInt(byteBuffer, offset, rawValue)
Pair(offset+varIntSize(rawValue), bitFieldOffset)
}
Long::class.java ->
java.lang.Long::class.java, Long::class.java ->
if(flags.contains(SyncFlag.NoCompress)){
byteBuffer.putLong(offset, field.getLong(owner))
byteBuffer.putLong(offset, field.getLongAdaptive(owner))
Pair(offset+8, bitFieldOffset)
}
else {
val rawValue =
if(flags.contains(SyncFlag.NonNegative)) field.getLong(owner)
else zigZagEncode(field.getLong(owner))
if(flags.contains(SyncFlag.NonNegative)) field.getLongAdaptive(owner)
else zigZagEncode(field.getLongAdaptive(owner))
writeVarInt(byteBuffer, offset, rawValue)
Pair(offset+varIntSize(rawValue), bitFieldOffset)
}
Float::class.java ->
java.lang.Float::class.java, Float::class.java ->
if(flags.contains(SyncFlag.NoCompress)){
byteBuffer.putFloat(offset, field.getFloat(owner))
byteBuffer.putFloat(offset, field.getFloatAdaptive(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)))
if(flags.contains(SyncFlag.FloatEndianSwap)) bitConvert(swapEndian(floatToInt(field.getFloatAdaptive(owner))))
else bitConvert(floatToInt(field.getFloatAdaptive(owner)))
writeVarInt(byteBuffer, offset, rawValue)
Pair(offset+varIntSize(rawValue), bitFieldOffset)
}
Double::class.java ->
java.lang.Double::class.java, Double::class.java ->
if(flags.contains(SyncFlag.NoCompress)){
byteBuffer.putDouble(offset, field.getDouble(owner))
byteBuffer.putDouble(offset, field.getDoubleAdaptive(owner))
Pair(offset+8, bitFieldOffset)
}
else{
val rawValue =
if(flags.contains(SyncFlag.FloatEndianSwap)) swapEndian(doubleToLong(field.getDouble(owner)))
else doubleToLong(field.getDouble(owner))
if(flags.contains(SyncFlag.FloatEndianSwap)) swapEndian(doubleToLong(field.getDoubleAdaptive(owner)))
else doubleToLong(field.getDoubleAdaptive(owner))
writeVarInt(byteBuffer, offset, rawValue)
Pair(offset+varIntSize(rawValue), bitFieldOffset)
}
else -> Pair(offset, bitFieldOffset)
}
override fun deserialize(
override fun deserializeExplicit(
field: Field,
flags: Array<out SyncFlag>,
owner: Any?,
byteBuffer: ByteBuffer,
offset: Int,
bitFieldOffset: Int
bitFieldOffset: Int,
fieldType: Class<*>
): Pair<Int, Int> =
when(field.type){
Boolean::class.java -> {
field.setBoolean(owner, readBit(byteBuffer, bitFieldOffset))
when(fieldType){
java.lang.Boolean::class.java, Boolean::class.java -> {
field.setBooleanAdaptive(owner, readBit(byteBuffer, bitFieldOffset))
Pair(offset, bitFieldOffset+1)
}
Byte::class.java -> {
field.setByte(owner, byteBuffer.get(offset))
java.lang.Byte::class.java, Byte::class.java -> {
field.setByteAdaptive(owner, byteBuffer.get(offset))
Pair(offset+1, bitFieldOffset)
}
Short::class.java ->
java.lang.Short::class.java, Short::class.java ->
if(flags.contains(SyncFlag.NoCompress)){
field.setShort(owner, byteBuffer.getShort(offset))
field.setShortAdaptive(owner, byteBuffer.getShort(offset))
Pair(offset+2, bitFieldOffset)
}
else {
val rawValue =
if(flags.contains(SyncFlag.NonNegative)) readVarInt(byteBuffer, offset)
else zigZagDecode(readVarInt(byteBuffer, offset))
field.setShort(owner, rawValue.toShort())
field.setShortAdaptive(owner, rawValue.toShort())
Pair(offset+varIntSize(rawValue), bitFieldOffset)
}
Int::class.java ->
java.lang.Integer::class.java, Int::class.java ->
if(flags.contains(SyncFlag.NoCompress)){
field.setInt(owner, byteBuffer.getInt(offset))
field.setIntAdaptive(owner, byteBuffer.getInt(offset))
Pair(offset+4, bitFieldOffset)
}
else {
val rawValue =
if(flags.contains(SyncFlag.NonNegative)) readVarInt(byteBuffer, offset)
else zigZagDecode(readVarInt(byteBuffer, offset))
field.setInt(owner, rawValue.toInt())
field.setIntAdaptive(owner, rawValue.toInt())
Pair(offset+varIntSize(rawValue), bitFieldOffset)
}
Long::class.java ->
java.lang.Long::class.java, Long::class.java ->
if(flags.contains(SyncFlag.NoCompress)){
field.setLong(owner, byteBuffer.getLong(offset))
field.setLongAdaptive(owner, byteBuffer.getLong(offset))
Pair(offset+8, bitFieldOffset)
}
else {
val rawValue =
if(flags.contains(SyncFlag.NonNegative)) readVarInt(byteBuffer, offset)
else zigZagDecode(readVarInt(byteBuffer, offset))
field.setLong(owner, rawValue)
field.setLongAdaptive(owner, rawValue)
Pair(offset+varIntSize(rawValue), bitFieldOffset)
}
Float::class.java ->
java.lang.Float::class.java, Float::class.java ->
if(flags.contains(SyncFlag.NoCompress)){
field.setFloat(owner, byteBuffer.getFloat(offset))
field.setFloatAdaptive(owner, byteBuffer.getFloat(offset))
Pair(offset+4, bitFieldOffset)
}
else{
@ -198,12 +209,12 @@ class PrimitiveSerializer private constructor() : Serializer(arrayOf(
val rawValue =
if(flags.contains(SyncFlag.FloatEndianSwap)) intToFloat(swapEndian(readVal.toInt()))
else intToFloat(readVal.toInt())
field.setFloat(owner, rawValue)
field.setFloatAdaptive(owner, rawValue)
Pair(offset+varIntSize(readVal), bitFieldOffset)
}
Double::class.java ->
java.lang.Double::class.java, Double::class.java ->
if(flags.contains(SyncFlag.NoCompress)){
field.setDouble(owner, byteBuffer.getDouble(offset))
field.setDoubleAdaptive(owner, byteBuffer.getDouble(offset))
Pair(offset+8, bitFieldOffset)
}
else{
@ -211,7 +222,7 @@ class PrimitiveSerializer private constructor() : Serializer(arrayOf(
val rawValue =
if(flags.contains(SyncFlag.FloatEndianSwap)) longToDouble(swapEndian(readVal))
else longToDouble(readVal)
field.setDouble(owner, rawValue)
field.setDoubleAdaptive(owner, rawValue)
Pair(offset+varIntSize(readVal), bitFieldOffset)
}
else -> Pair(offset, bitFieldOffset)

View File

@ -11,36 +11,63 @@ abstract class Serializer(registeredTypes: Array<Class<*>>) {
* Compute the size in bits that a field will occupy
* @return Size in the byteField (first) and bitField (second) that need to be allocated
*/
abstract fun computeSize(
fun computeSize(
field: Field,
flags: Array<out SyncFlag>,
owner: Any?
): Pair<Int, Int> = computeSizeExplicit(field, flags, owner, field.type)
abstract fun computeSizeExplicit(
field: Field,
flags: Array<out SyncFlag>,
owner: Any?,
fieldType: Class<*>
): Pair<Int, Int>
/**
* Serialize a field to the buffer
* @return The new offset (first) and bitFieldOffset (second)
*/
abstract fun serialize(
fun serialize(
field: Field,
flags: Array<out SyncFlag>,
owner: Any?,
byteBuffer: ByteBuffer,
offset: Int,
bitFieldOffset: Int
): Pair<Int, Int> = serializeExplicit(field, flags, owner, byteBuffer, offset, bitFieldOffset, field.type)
abstract fun serializeExplicit(
field: Field,
flags: Array<out SyncFlag>,
owner: Any?,
byteBuffer: ByteBuffer,
offset: Int,
bitFieldOffset: Int,
fieldType: Class<*>
): Pair<Int, Int>
/**
* Deserialize a field from the buffer
* @return The new offset (first) and bitFieldOffset (second)
*/
abstract fun deserialize(
fun deserialize(
field: Field,
flags: Array<out SyncFlag>,
owner: Any?,
byteBuffer: ByteBuffer,
offset: Int,
bitFieldOffset: Int
): Pair<Int, Int> = deserializeExplicit(field, flags, owner, byteBuffer, offset, bitFieldOffset, field.type)
abstract fun deserializeExplicit(
field: Field,
flags: Array<out SyncFlag>,
owner: Any?,
byteBuffer: ByteBuffer,
offset: Int,
bitFieldOffset: Int,
fieldType: Class<*>
): Pair<Int, Int>
fun getRegisteredTypes(): Array<Class<*>> = Arrays.copyOf(registeredTypes, registeredTypes.size)

View File

@ -1,23 +1,40 @@
package net.tofvesson.networking
import java.lang.reflect.Field
import java.nio.ByteBuffer
import java.security.NoSuchAlgorithmException
import java.security.MessageDigest
/**
* @param permissiveMismatchCheck This should essentially never be set to true aside from some *very* odd edge cases
*/
class SyncHandler(defaultSerializers: Boolean = true, private val permissiveMismatchCheck: Boolean = false) {
class SyncHandler(private val permissiveMismatchCheck: Boolean = false) {
private val toSync: ArrayList<Any> = ArrayList()
private val serializers: ArrayList<Serializer> = ArrayList()
companion object {
private val serializers: ArrayList<Serializer> = ArrayList()
init {
if(defaultSerializers) {
init {
// Standard serializers
serializers.add(PrimitiveSerializer.singleton)
serializers.add(PrimitiveArraySerializer.singleton)
}
fun registerSerializer(serializer: Serializer) {
if(!serializers.contains(serializer))
serializers.add(serializer)
}
fun unregisterSerializer(serializer: Serializer) {
serializers.remove(serializer)
}
fun clearSerializers() = serializers.clear()
fun getRegisteredSerializers() = serializers.toArray()
fun getCompatibleSerializer(type: Class<*>): Serializer {
for(serializer in serializers)
if(serializer.canSerialize(type))
return serializer
throw UnsupportedTypeException("Cannot find a compatible serializer for $type")
}
}
fun registerSyncObject(value: Any){
@ -28,11 +45,6 @@ class SyncHandler(defaultSerializers: Boolean = true, private val permissiveMism
toSync.remove(value)
}
fun withSerializer(serializer: Serializer): SyncHandler {
if(!serializers.contains(serializer)) serializers.add(serializer)
return this
}
fun serialize(): ByteArray{
var headerSize = 0
var totalSize = 0
@ -120,12 +132,6 @@ class SyncHandler(defaultSerializers: Boolean = true, private val permissiveMism
}
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 readClass(value: Class<*>, buffer: ByteArray, offset: Int, bitOffset: Int) = readType(value, null, buffer, offset, bitOffset)

View File

@ -2,7 +2,6 @@ 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
@ -13,22 +12,13 @@ var AccessibleObject.forceAccessible: Boolean
unsafe.getAndSetObject(this, overrideOffset, value)
}
fun <T> T.access(): T where T: AccessibleObject {
this.forceAccessible = true
return this
}
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)
}

View File

@ -0,0 +1,33 @@
package net.tofvesson.reflect
import java.lang.reflect.Field
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)
}
fun Field.getBooleanAdaptive(obj: Any?) = if(this.type.isPrimitive) access().getBoolean(obj) else access().get(obj) as Boolean
fun Field.getByteAdaptive(obj: Any?) = if(this.type.isPrimitive) access().getByte(obj) else access().get(obj) as Byte
fun Field.getShortAdaptive(obj: Any?) = if(this.type.isPrimitive) access().getShort(obj) else access().get(obj) as Short
fun Field.getIntAdaptive(obj: Any?) = if(this.type.isPrimitive) access().getInt(obj) else access().get(obj) as Int
fun Field.getLongAdaptive(obj: Any?) = if(this.type.isPrimitive) access().getLong(obj) else access().get(obj) as Long
fun Field.getFloatAdaptive(obj: Any?) = if(this.type.isPrimitive) access().getFloat(obj) else access().get(obj) as Float
fun Field.getDoubleAdaptive(obj: Any?) = if(this.type.isPrimitive) access().getDouble(obj) else access().get(obj) as Double
fun Field.setBooleanAdaptive(obj: Any?, value: Boolean) = if(this.type.isPrimitive) access().setBoolean(obj, value) else access().set(obj, value)
fun Field.setByteAdaptive(obj: Any?, value: Byte) = if(this.type.isPrimitive) access().setByte(obj, value) else access().set(obj, value)
fun Field.setShortAdaptive(obj: Any?, value: Short) = if(this.type.isPrimitive) access().setShort(obj, value) else access().set(obj, value)
fun Field.setIntAdaptive(obj: Any?, value: Int) = if(this.type.isPrimitive) access().setInt(obj, value) else access().set(obj, value)
fun Field.setLongAdaptive(obj: Any?, value: Long) = if(this.type.isPrimitive) access().setLong(obj, value) else access().set(obj, value)
fun Field.setFloatAdaptive(obj: Any?, value: Float) = if(this.type.isPrimitive) access().setFloat(obj, value) else access().set(obj, value)
fun Field.setDoubleAdaptive(obj: Any?, value: Double) = if(this.type.isPrimitive) access().setDouble(obj, value) else access().set(obj, value)