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:
parent
04dd29ad56
commit
60cebfb966
@ -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");
|
||||
|
||||
}
|
||||
}
|
||||
|
17
src/net/tofvesson/networking/DiffTracked.kt
Normal file
17
src/net/tofvesson/networking/DiffTracked.kt
Normal 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)!!
|
||||
}
|
31
src/net/tofvesson/networking/DiffTrackedArray.kt
Normal file
31
src/net/tofvesson/networking/DiffTrackedArray.kt
Normal 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()
|
||||
}
|
129
src/net/tofvesson/networking/DiffTrackedSerializer.kt
Normal file
129
src/net/tofvesson/networking/DiffTrackedSerializer.kt
Normal 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?)
|
||||
}
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
33
src/net/tofvesson/reflect/Field.kt
Normal file
33
src/net/tofvesson/reflect/Field.kt
Normal 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)
|
Loading…
x
Reference in New Issue
Block a user