From 85c3dd82927e3317d6df9ae9158eb5c66ee91d11 Mon Sep 17 00:00:00 2001 From: rnentjes Date: Fri, 6 Sep 2024 10:25:15 +0200 Subject: [PATCH] Slices and string cache --- build.gradle.kts | 30 +++- .../nl/astraeus/tba/ByteArrayDefinition.kt | 3 +- .../nl/astraeus/tba/ByteArrayHandler.kt | 92 ++----------- .../nl/astraeus/tba/ByteArrayProperties.kt | 28 +++- .../kotlin/nl/astraeus/tba/SlicedByteArray.kt | 130 +++++++++++++++++- .../tba/TypedByteArrayWithByteArrayTest.kt | 40 +++++- .../nl/astraeus/tba/TestCachedString.kt | 52 +++++++ 7 files changed, 285 insertions(+), 90 deletions(-) create mode 100644 src/jvmTest/kotlin/nl/astraeus/tba/TestCachedString.kt diff --git a/build.gradle.kts b/build.gradle.kts index f5213c0..1ca7cb5 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,7 +6,7 @@ plugins { } group = "nl.astraeus" -version = "0.1.0-SNAPSHOT" +version = "0.1.1-SNAPSHOT" repositories { mavenCentral() @@ -137,6 +137,32 @@ tasks.named("publishKotlinMultiplatformPublicationToMavenLocal") { dependsOn(tasks.named("signJsPublication")) } + +tasks.named("publishJsPublicationToMavenLocalRepository") { + dependsOn(tasks.named("signJvmPublication")) +} + +tasks.named("publishJvmPublicationToMavenLocalRepository") { + dependsOn(tasks.named("signJsPublication")) +} + +tasks.named("publishJvmPublicationToMavenLocalRepository") { + dependsOn(tasks.named("signKotlinMultiplatformPublication")) +} + +tasks.named("publishJsPublicationToMavenLocalRepository") { + dependsOn(tasks.named("signKotlinMultiplatformPublication")) +} + +tasks.named("publishKotlinMultiplatformPublicationToMavenLocalRepository") { + dependsOn(tasks.named("signJvmPublication")) +} + +tasks.named("publishKotlinMultiplatformPublicationToMavenLocalRepository") { + dependsOn(tasks.named("signJsPublication")) +} + + tasks.named("publishJsPublicationToGiteaRepository") { dependsOn(tasks.named("signJvmPublication")) } @@ -159,4 +185,4 @@ tasks.named("publishKotlinMultiplatformPublicationToGiteaRepository") { tasks.named("publishKotlinMultiplatformPublicationToGiteaRepository") { dependsOn(tasks.named("signJsPublication")) -} +} \ No newline at end of file diff --git a/src/commonMain/kotlin/nl/astraeus/tba/ByteArrayDefinition.kt b/src/commonMain/kotlin/nl/astraeus/tba/ByteArrayDefinition.kt index 9db6dbb..49fe027 100644 --- a/src/commonMain/kotlin/nl/astraeus/tba/ByteArrayDefinition.kt +++ b/src/commonMain/kotlin/nl/astraeus/tba/ByteArrayDefinition.kt @@ -12,7 +12,8 @@ enum class DataType( DOUBLE(8), STRING(-1, 2), // max length 65535 CLOB(-1, 4), // max length 2^32-1 - BLOB(-1, 4), // max length 2^32-1 + BLOB(-1, 4), + //SLICE(-1, 4) // max length 2^32-1 ; } diff --git a/src/commonMain/kotlin/nl/astraeus/tba/ByteArrayHandler.kt b/src/commonMain/kotlin/nl/astraeus/tba/ByteArrayHandler.kt index a9f047d..23d1707 100644 --- a/src/commonMain/kotlin/nl/astraeus/tba/ByteArrayHandler.kt +++ b/src/commonMain/kotlin/nl/astraeus/tba/ByteArrayHandler.kt @@ -13,61 +13,20 @@ class MutableByteArrayHandler( buffer[index] = value } - fun setShort(index: Int, value: Short) { - buffer[index] = (value.toInt() shr 8).toByte() - buffer[index + 1] = value.toByte() - } + fun setShort(index: Int, value: Short) = buffer.setShort(index, value) - fun setInt(index: Int, value: Int) { - buffer[index] = ((value shr 24) and 0xff).toByte() - buffer[index + 1] = ((value shr 16) and 0xff).toByte() - buffer[index + 2] = ((value shr 8) and 0xff).toByte() - buffer[index + 3] = (value and 0xff).toByte() - } + fun setInt(index: Int, value: Int) = buffer.setInt(index, value) - fun setLong(index: Int, value: Long) { - buffer[index] = ((value shr 56) and 0xff).toByte() - buffer[index + 1] = ((value shr 48) and 0xff).toByte() - buffer[index + 2] = ((value shr 40) and 0xff).toByte() - buffer[index + 3] = ((value shr 32) and 0xff).toByte() - buffer[index + 4] = ((value shr 24) and 0xff).toByte() - buffer[index + 5] = ((value shr 16) and 0xff).toByte() - buffer[index + 6] = ((value shr 8) and 0xff).toByte() - buffer[index + 7] = (value and 0xff).toByte() - } + fun setLong(index: Int, value: Long) = buffer.setLong(index, value) - fun setFloat(index: Int, value: Float) = setInt(index, value.toRawBits()) - fun setDouble(index: Int, value: Double) = setLong(index, value.toRawBits()) + fun setFloat(index: Int, value: Float) = buffer.setFloat(index, value) + fun setDouble(index: Int, value: Double) = buffer.setDouble(index, value) - fun setString(index: Int, value: String, maxLength: Int): Int { - val bytes = value.encodeToByteArray() - if (bytes.size > maxLength) { - throw IllegalArgumentException("String is too long") - } - this.setShort(index, bytes.size.toShort()) - bytes.copyInto(buffer.data, index + 2) - return index + bytes.size + 2 - } + fun setString(index: Int, value: String, maxLength: Int) = buffer.setString(index, value, maxLength) - fun setClob(index: Int, value: String, maxLength: Int): Int { - val bytes = value.encodeToByteArray() - if (bytes.size > maxLength) { - throw IllegalArgumentException("String is too long") - } - this.setInt(index, bytes.size) - bytes.copyInto(buffer.data, index + 4) - return index + bytes.size + 4 - } - - fun setBlob(index: Int, bytes: ByteArray, maxLength: Int): Int { - if (bytes.size > maxLength) { - throw IllegalArgumentException("String is too long") - } - this.setInt(index, bytes.size) - bytes.copyInto(buffer.data, index + 4) - return index + bytes.size + 4 - } + fun setClob(index: Int, value: String, maxLength: Int) = buffer.setClob(index, value, maxLength) + fun setBlob(index: Int, bytes: SlicedByteArray, maxLength: Int) = buffer.setBlob(index, bytes, maxLength) } open class ByteArrayHandler( @@ -83,18 +42,9 @@ open class ByteArrayHandler( return buffer[index] } - fun getShort(index: Int): Short { - return ((buffer[index].toInt() shl 8) or (buffer[index + 1].toInt() and 0xff)).toShort() - } + fun getShort(index: Int) = buffer.getShort(index) - fun getInt(index: Int): Int { - return ( - (buffer[index].toInt() shl 24) or - ((buffer[index+1].toInt() shl 16) and 0xff0000) or - ((buffer[index+2].toInt() shl 8) and 0xff00) or - (buffer[index+3].toInt() and 0xff) - ) - } + fun getInt(index: Int) = buffer.getInt(index) fun getLong(index: Int): Long { return ( @@ -109,26 +59,14 @@ open class ByteArrayHandler( ) } - fun getFloat(index: Int): Float = Float.fromBits(getInt(index)) - fun getDouble(index: Int): Double = Double.fromBits(getLong(index)) + fun getFloat(index: Int): Float = buffer.getFloat(index) + fun getDouble(index: Int): Double = buffer.getDouble(index) - fun getString(index: Int): String { - val length = getShort(index) - val str = buffer.data.copyOfRange(index + 2, index + 2 + length).decodeToString() - return str - } + fun getString(index: Int) = buffer.getString(index) - fun getClob(index: Int): String { - val length = getInt(index) - val str = buffer.data.copyOfRange(index + 4, index + 4 + length).decodeToString() - return str - } + fun getClob(index: Int): String = buffer.getClob(index) - fun getBlob(index: Int): ByteArray { - val length = getInt(index) - val str = buffer.data.copyOfRange(index + 4, index + 4 + length) - return str - } + fun getBlob(index: Int): SlicedByteArray = buffer.getBlob(index) fun slice(range: IntRange): ByteArrayHandler { return ByteArrayHandler( diff --git a/src/commonMain/kotlin/nl/astraeus/tba/ByteArrayProperties.kt b/src/commonMain/kotlin/nl/astraeus/tba/ByteArrayProperties.kt index 0f26c76..c645b50 100644 --- a/src/commonMain/kotlin/nl/astraeus/tba/ByteArrayProperties.kt +++ b/src/commonMain/kotlin/nl/astraeus/tba/ByteArrayProperties.kt @@ -117,8 +117,9 @@ class DoubleProperty( } class StringProperty( - name: String + name: String, ) : ByteArrayPropertyWithLength(name) { + override fun getValue(thisRef: TypedByteArray, property: KProperty<*>): String { return thisRef.data.getString(getIndex(thisRef)) } @@ -128,6 +129,24 @@ class StringProperty( } } +class CachedStringProperty( + name: String, +) : ByteArrayPropertyWithLength(name) { + var cachedValue: String? = null + + override fun getValue(thisRef: TypedByteArray, property: KProperty<*>): String { + if (cachedValue == null) { + cachedValue = thisRef.data.getString(getIndex(thisRef)) + } + return cachedValue!! + } + + override fun setValue(thisRef: TypedByteArray, property: KProperty<*>, value: String) { + thisRef.data.setString(getIndex(thisRef), value, getMaxLength(thisRef)) + cachedValue = value + } +} + class ClobProperty( name: String ) : ByteArrayPropertyWithLength(name) { @@ -142,12 +161,12 @@ class ClobProperty( class BlobProperty( name: String -) : ByteArrayPropertyWithLength(name) { - override fun getValue(thisRef: TypedByteArray, property: KProperty<*>): ByteArray { +) : ByteArrayPropertyWithLength(name) { + override fun getValue(thisRef: TypedByteArray, property: KProperty<*>): SlicedByteArray { return thisRef.data.getBlob(getIndex(thisRef)) } - override fun setValue(thisRef: TypedByteArray, property: KProperty<*>, value: ByteArray) { + override fun setValue(thisRef: TypedByteArray, property: KProperty<*>, value: SlicedByteArray) { thisRef.data.setBlob(getIndex(thisRef), value, getMaxLength(thisRef)) } } @@ -160,5 +179,6 @@ fun long(propertyName: String) = LongProperty(propertyName) fun float(propertyName: String) = FloatProperty(propertyName) fun double(propertyName: String) = DoubleProperty(propertyName) fun string(propertyName: String) = StringProperty(propertyName) +fun cachedString(propertyName: String) = CachedStringProperty(propertyName) fun clob(propertyName: String) = ClobProperty(propertyName) fun blob(propertyName: String) = BlobProperty(propertyName) diff --git a/src/commonMain/kotlin/nl/astraeus/tba/SlicedByteArray.kt b/src/commonMain/kotlin/nl/astraeus/tba/SlicedByteArray.kt index 8896962..41f7f1e 100644 --- a/src/commonMain/kotlin/nl/astraeus/tba/SlicedByteArray.kt +++ b/src/commonMain/kotlin/nl/astraeus/tba/SlicedByteArray.kt @@ -16,15 +16,141 @@ class SlicedByteArray( data[offset + index] = value } + fun setShort(index: Int, value: Short) { + check(offset + index + 1 < offset + length) { "Index out of bounds" } + + data[offset + index] = (value.toInt() shr 8).toByte() + data[offset + index + 1] = value.toByte() + } + + fun getShort(index: Int): Short { + check(offset + index + 1 < offset + length) { "Index out of bounds" } + + return ((data[offset + index].toInt() shl 8) or (data[offset + index + 1].toInt() and 0xff)).toShort() + } + + fun setInt(index: Int, value: Int) { + check(offset + index + 3 < offset + length) { "Index out of bounds" } + + data[offset + index] = ((value shr 24) and 0xff).toByte() + data[offset + index + 1] = ((value shr 16) and 0xff).toByte() + data[offset + index + 2] = ((value shr 8) and 0xff).toByte() + data[offset + index + 3] = (value and 0xff).toByte() + } + + fun getInt(index: Int): Int { + check(offset + index + 3 < offset + length) { "Index out of bounds" } + return ( + (data[offset + index].toInt() shl 24) or + ((data[offset + index+1].toInt() shl 16) and 0xff0000) or + ((data[offset + index+2].toInt() shl 8) and 0xff00) or + (data[offset + index+3].toInt() and 0xff) + ) + } + + fun setLong(index: Int, value: Long) { + check(offset + index + 7 < offset + length) { "Index out of bounds" } + data[offset + index] = ((value shr 56) and 0xff).toByte() + data[offset + index + 1] = ((value shr 48) and 0xff).toByte() + data[offset + index + 2] = ((value shr 40) and 0xff).toByte() + data[offset + index + 3] = ((value shr 32) and 0xff).toByte() + data[offset + index + 4] = ((value shr 24) and 0xff).toByte() + data[offset + index + 5] = ((value shr 16) and 0xff).toByte() + data[offset + index + 6] = ((value shr 8) and 0xff).toByte() + data[offset + index + 7] = (value and 0xff).toByte() + } + + fun getLong(index: Int): Long { + check(offset + index + 7 < offset + length) { "Index out of bounds" } + return ( + (data[offset + index].toLong() shl 56) or + ((data[offset + index+1].toLong() and 0xff) shl 48) or + ((data[offset + index+2].toLong() and 0xff) shl 40) or + ((data[offset + index+3].toLong() and 0xff) shl 32) or + ((data[offset + index+4].toLong() and 0xff) shl 24) or + ((data[offset + index+5].toLong() and 0xff) shl 16) or + ((data[offset + index+6].toLong() and 0xff) shl 8) or + (data[offset + index+7].toLong() and 0xff) + ) + } + + fun setFloat(index: Int, value: Float) = setInt(index, value.toRawBits()) + fun getFloat(index: Int): Float = Float.fromBits(getInt(index)) + + fun setDouble(index: Int, value: Double) = setLong(index, value.toRawBits()) + fun getDouble(index: Int): Double = Double.fromBits(getLong(index)) + + fun getClob(index: Int): String { + val length = getInt(index) + check(offset + index + length < offset + this.length) { "Index out of bounds" } + val str = data.copyOfRange(offset + index + 4, offset + index + 4 + length).decodeToString() + return str + } + + fun getBlob(index: Int): SlicedByteArray { + val length = getInt(index) + check(offset + index + length < offset + this.length) { "Index out of bounds" } + val str = SlicedByteArray(data, offset + index + 4, length) + return str + } + fun copyInto(dest: ByteArray, destOffset: Int, length: Int) { data.copyInto(dest, destOffset, offset, offset + length) } + fun iterator(): Iterator = object : Iterator { + var index = offset + + override fun hasNext(): Boolean { + return index < offset + length + } + + override fun next(): Byte { + return data[offset + index++] + } + } + + fun toByteArray(): ByteArray = data.copyOfRange(offset, offset + length) + + fun getString(index: Int): String { + val length = getShort(offset + index) + val str = data.decodeToString(offset + index + 2, offset + index + 2 + length.toInt()) + return str + } + + fun getString(start: Int, length: Int): String = data.decodeToString(offset + start, offset + start + length) + + fun setString(index: Int, value: String, maxLength: Int) { + val bytes = value.encodeToByteArray() + if (bytes.size > maxLength) { + throw IllegalArgumentException("String is too long") + } + this.setShort(offset + index, bytes.size.toShort()) + bytes.copyInto(data, offset + index + 2) + } + + fun setClob(index: Int, value: String, maxLength: Int) { + val bytes = value.encodeToByteArray() + if (offset + index + bytes.size + 4 > maxLength) { + throw IllegalArgumentException("String is too long") + } + this.setInt(offset + index, bytes.size) + bytes.copyInto(data, offset + index + 4) + } + + fun setBlob(index: Int, bytes: SlicedByteArray, maxLength: Int) { + if (bytes.size > maxLength) { + throw IllegalArgumentException("Blob is too long") + } + this.setInt(offset + index, bytes.size) + bytes.copyInto(data, offset + index + 4, maxLength - 4) + } + companion object { fun wrap( data: ByteArray, - offset: Int, - length: Int + offset: Int = 0, + length: Int = data.size ): SlicedByteArray { return SlicedByteArray(data, offset,length) } diff --git a/src/commonTest/kotlin/nl/astraeus/tba/TypedByteArrayWithByteArrayTest.kt b/src/commonTest/kotlin/nl/astraeus/tba/TypedByteArrayWithByteArrayTest.kt index 429f0aa..6be8abe 100644 --- a/src/commonTest/kotlin/nl/astraeus/tba/TypedByteArrayWithByteArrayTest.kt +++ b/src/commonTest/kotlin/nl/astraeus/tba/TypedByteArrayWithByteArrayTest.kt @@ -14,7 +14,7 @@ class TypedByteArrayWithByteArrayTest { this.name = name } - constructor(data: ByteArray): this() { + constructor(data: SlicedByteArray): this() { this.data = MutableByteArrayHandler(buffer = data) } } @@ -29,7 +29,7 @@ class TypedByteArrayWithByteArrayTest { constructor(name: String, person: Person1): this() { this.name = name - this.personData = person.data.buffer.data + this.personData = person.data.buffer } constructor(data: ByteArray): this() { @@ -47,8 +47,40 @@ class TypedByteArrayWithByteArrayTest { assertEquals("A Name", company.person.name) - company.person.name = "2nd Name" + //company.person.name = "2nd Name" - assertEquals("2nd Name", company.person.name) + //assertEquals("2nd Name", company.person.name) + } + + class Data() : TypedByteArray( + Type("name", DataType.STRING, 10), + Type("binaryData", DataType.BLOB, 10) + ) { + var name by string("name") + var binaryData by blob("binaryData") + + constructor(name: String, data: SlicedByteArray): this() { + this.name = name + this.binaryData = data + } + + constructor(data: ByteArray): this() { + this.data = MutableByteArrayHandler(buffer = data) + } + } + + @Test + fun testBlob() { + val binaryData = ByteArray(10) + for(index in 0..<10) { + binaryData[index] = index.toByte() + } + val data = Data("A Name", SlicedByteArray.wrap(binaryData)) + + assertEquals(1, data.data[17]) + + data.binaryData[0] = 100 + + assertEquals(100, data.data[16]) } } diff --git a/src/jvmTest/kotlin/nl/astraeus/tba/TestCachedString.kt b/src/jvmTest/kotlin/nl/astraeus/tba/TestCachedString.kt new file mode 100644 index 0000000..50d125e --- /dev/null +++ b/src/jvmTest/kotlin/nl/astraeus/tba/TestCachedString.kt @@ -0,0 +1,52 @@ +package nl.astraeus.tba + +import kotlin.test.Test + +class TestCachedString { + + class PersonCached : TypedByteArray( + Type("name", DataType.STRING, 100), + ) { + var name by cachedString("name") + } + + class PersonNotCached : TypedByteArray( + Type("name", DataType.STRING, 100), + ) { + var name by string("name") + } + + @Test + fun testCachedString() { + val cachedPerson = PersonCached() + val notCachedPerson = PersonNotCached() + + cachedPerson.name = "A Longer Longer Longer Longer Longer Test" + notCachedPerson.name = "A Longer Longer Longer Longer Longer Test" + + var total = 0 + repeat(1000000) { + total += cachedPerson.name.hashCode() + total += notCachedPerson.name.hashCode() + } + + println("Total warmup: $total") + + var start1 = System.nanoTime() + var total1 = 0 + repeat(1000000) { + total1 += cachedPerson.name.hashCode() + } + val stop1 = System.nanoTime() + + var start2 = System.nanoTime() + var total2 = 0 + repeat(1000000) { + total2 += notCachedPerson.name.hashCode() + } + val stop2 = System.nanoTime() + + println("Total1 ($total1) time: ${(stop1 - start1) / 1000000f}ms") + println("Total2 ($total2) time: ${(stop2 - start2) / 1000000f}ms") + } +}