Skip to content

Commit 170b45b

Browse files
committed
Indexed fill & flush; Non-copy read/write API
* add indexes to Input.fill & Output.flush to avoid partial reads/writes. It unlocks the implementation of non-copy API for reading and writing, and it allows to read into a buffer with a small number of bytes at the start without additional flush. * add `readAvailableTo` and `writeBuffer` This API reads/writes bytes directly from source to provided buffer without creating an additional copy.
1 parent ecf282c commit 170b45b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+1261
-672
lines changed

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,8 @@ kotlinx-io-native/repo/
1515
!.idea/codeStyles/*
1616

1717
.gradletasknamecache
18+
.settings
19+
.classpath
20+
21+
logs
22+

benchmarks/commonMain/src/kotlinx/io/benchmarks/InputReadingBenchmark.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ class InputReadingBenchmark {
1212

1313
override fun closeSource() {}
1414

15-
override fun fill(buffer: Buffer): Int {
15+
override fun fill(buffer: Buffer, startIndex: Int, endIndex: Int): Int {
1616
val size = buffer.size
17-
for (index in 0 until size) {
17+
for (index in startIndex until endIndex) {
1818
buffer.storeByteAt(index, value++.toByte())
1919
}
2020
return size

benchmarks/commonMain/src/kotlinx/io/benchmarks/TextDecodingBenchmark.kt

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,19 @@ private val expected = "file content with unicode 🌀 : здороваться
88
private val length = expected.length
99

1010
// @formatter:off
11-
private val content = ubyteArrayOf(
12-
0x66u,0x69u,0x6cu,0x65u,0x20u,0x63u,0x6fu,0x6eu,0x74u,0x65u,0x6eu,0x74u,0x20u,
13-
0x77u,0x69u,0x74u,0x68u,0x20u,0x75u,0x6eu,0x69u,0x63u,0x6fu,0x64u,0x65u,0x20u,0xf0u,0x9fu,
14-
0x8cu,0x80u,0x20u,0x3au,0x20u,0xd0u,0xb7u,0xd0u,0xb4u,0xd0u,0xbeu,0xd1u,0x80u,0xd0u,0xbeu,
15-
0xd0u,0xb2u,0xd0u,0xb0u,0xd1u,0x82u,0xd1u,0x8cu,0xd1u,0x81u,0xd1u,0x8fu,0x20u,0x3au,0x20u,0xecu,
16-
0x97u,0xacu,0xebu,0xb3u,0xb4u,0xecu,0x84u,0xb8u,0xecu,0x9au,0x94u,0x20u,0x3au,0x20u,0xe4u,0xbdu,0xa0u,
17-
0xe5u,0xa5u,0xbdu,0x20u,0x3au,0x20u,0xc3u,0xb1u,0xc3u,0xa7u, 0x2eu)
18-
// @formatter:on
11+
private val content = ubyteArrayOf(
12+
0x66u, 0x69u, 0x6cu, 0x65u, 0x20u, 0x63u, 0x6fu, 0x6eu, 0x74u, 0x65u, 0x6eu, 0x74u, 0x20u,
13+
0x77u, 0x69u, 0x74u, 0x68u, 0x20u, 0x75u, 0x6eu, 0x69u, 0x63u, 0x6fu, 0x64u, 0x65u, 0x20u,
14+
0xf0u, 0x9fu, 0x8cu, 0x80u, 0x20u, 0x3au, 0x20u, 0xd0u, 0xb7u, 0xd0u, 0xb4u, 0xd0u, 0xbeu,
15+
0xd1u, 0x80u, 0xd0u, 0xbeu, 0xd0u, 0xb2u, 0xd0u, 0xb0u, 0xd1u, 0x82u, 0xd1u, 0x8cu, 0xd1u,
16+
0x81u, 0xd1u, 0x8fu, 0x20u, 0x3au, 0x20u, 0xecu, 0x97u, 0xacu, 0xebu, 0xb3u, 0xb4u, 0xecu,
17+
0x84u, 0xb8u, 0xecu, 0x9au, 0x94u, 0x20u, 0x3au, 0x20u, 0xe4u, 0xbdu, 0xa0u, 0xe5u, 0xa5u,
18+
0xbdu, 0x20u, 0x3au, 0x20u, 0xc3u, 0xb1u, 0xc3u, 0xa7u, 0x2eu
19+
)
20+
// @formatter:on
1921

2022
private val bytes = buildBytes {
21-
writeArray(content)
23+
writeByteArray(content)
2224
}
2325

2426
@State(Scope.Benchmark)
@@ -44,7 +46,7 @@ class TextDecodingBenchmark {
4446
*/
4547
return text
4648
}
47-
49+
4850
@Benchmark
4951
fun inputTextShort(): String {
5052
val input = bytes.input()

build.gradle

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ buildscript {
1010

1111
dependencies {
1212
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
13-
classpath "org.jetbrains.kotlinx:atomicfu-gradle-plugin:$atomic_fu_version"
1413
classpath "org.jetbrains.kotlin:kotlin-allopen:$kotlin_version"
1514
classpath "org.jetbrains.kotlinx:kotlinx.benchmark.gradle:$benchmarks_version"
1615
classpath "com.vanniktech:gradle-android-junit-jacoco-plugin:0.15.0"

core/build.gradle

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
plugins {
22
id "kotlin-multiplatform"
33
id "maven-publish"
4-
id "kotlinx-atomicfu"
54
id "jacoco"
5+
id 'org.jetbrains.dokka' version '0.10.1'
66
}
77

88
def ideaActive = System.getProperty('idea.active') == 'true'
@@ -131,6 +131,11 @@ kotlin {
131131
}
132132
}
133133

134+
dokka {
135+
outputFormat = 'gfm'
136+
outputDirectory = "$buildDir/dokka"
137+
}
138+
134139
jacoco {
135140
toolVersion = "0.8.5"
136141
reportsDir = file("${buildDir}/jacoco-reports")

core/commonMain/src/kotlinx/io/ByteOrder.common.kt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,25 @@
11
package kotlinx.io
22

33
/**
4-
* Enumeration that represent an endianness of the arbitrary binary data.
5-
* Endianness refers to the order of bytes within a binary representation of a binary data.
4+
* ByteOrder is the enumeration that represents an endianness of the arbitrary binary data.
5+
* Endianness refers to the order of bytes within a binary representation.
66
*/
77
public expect enum class ByteOrder {
88
/**
9-
* Big endian, the most significant byte is at the lowest address.
9+
* Big-endian: the most significant byte is at the lowest address.
1010
*/
1111
BIG_ENDIAN,
1212

1313
/**
14-
* Little endian, the least significant byte is at the lowest address.
14+
* Little-endian: the least significant byte is at the lowest address.
1515
*/
1616
LITTLE_ENDIAN;
1717

1818
public companion object {
1919
/**
20-
* The native byte order of the underlying platform.
20+
* The byte order of the underlying platform.
2121
*/
22-
public val native : ByteOrder
22+
public val native: ByteOrder
2323
}
2424
}
2525

core/commonMain/src/kotlinx/io/Bytes.kt

Lines changed: 27 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@ package kotlinx.io
22

33
import kotlinx.io.buffer.*
44
import kotlinx.io.pool.*
5+
import kotlin.contracts.*
56

67
internal typealias BytesPointer = Int
78

89
/**
910
* Read-only bytes container.
1011
*
1112
* Use [input] to create readable [Input].
12-
* All inputs from a [Bytes] instance are share the same buffers.
13+
* All inputs from a [Bytes] instance share the same buffers.
1314
*
1415
* ```
1516
* buildBytes {
@@ -20,28 +21,28 @@ internal typealias BytesPointer = Int
2021
* 3. example
2122
*/
2223
class Bytes internal constructor(internal val bufferPool: ObjectPool<Buffer>) : Closeable {
23-
private var buffers: Array<Buffer?> = arrayOfNulls(initialPreviewSize)
24-
private var limits: IntArray = IntArray(initialPreviewSize)
25-
private var head: Int = 0
26-
private var tail: Int = 0
24+
internal var buffers: Array<Buffer?> = arrayOfNulls(initialPreviewSize)
25+
internal var limits: IntArray = IntArray(initialPreviewSize)
26+
internal var head: Int = 0
27+
internal var tail: Int = 0
2728

2829
/**
29-
* Calculate size of [Bytes].
30+
* Calculate the size of [Bytes].
3031
*/
3132
fun size(): Int = size(StartPointer)
3233

3334
/**
3435
* Create [Input] view on content.
3536
*/
36-
fun input(): Input = object : Input(this@Bytes) {
37+
fun input(): Input = object : Input(this@Bytes, bufferPool) {
38+
override fun fill(buffer: Buffer, startIndex: Int, endIndex: Int): Int = 0
3739
override fun closeSource() {}
38-
override fun fill(buffer: Buffer): Int = 0
3940
}
4041

4142
override fun toString() = "Bytes($head..$tail)"
4243

4344
/**
44-
* Release all data. Every produced input is broken.
45+
* Release all data, brokes all input produced by [input()].
4546
*/
4647
override fun close(): Unit {
4748
(head until tail).forEach {
@@ -54,7 +55,7 @@ class Bytes internal constructor(internal val bufferPool: ObjectPool<Buffer>) :
5455

5556
internal fun append(buffer: Buffer, limit: Int): Unit {
5657
if (head > 0) {
57-
// if we are appending buffers after some were discarded,
58+
// if we are appending buffers after [discardFirst()],
5859
// compact arrays so we can store more without allocations
5960
buffers.copyInto(buffers, 0, head, tail)
6061
limits.copyInto(limits, 0, head, tail)
@@ -80,28 +81,17 @@ class Bytes internal constructor(internal val bufferPool: ObjectPool<Buffer>) :
8081
head++
8182
}
8283

83-
internal inline fun pointed(pointer: BytesPointer, consumer: (Int) -> Unit): Buffer {
84-
// Buffer is returned and not sent to `consumer` because we need to initialize field in Input's constructor
85-
val index = pointer + head
86-
val buffer = buffers[index] ?: throw NoSuchElementException("There is no buffer at pointer $pointer")
87-
val limit = limits[index]
88-
consumer(limit)
89-
return buffer
90-
}
91-
9284
internal fun limit(pointer: BytesPointer): Int = limits[pointer + head]
9385

94-
internal fun advancePointer(pointer: BytesPointer): BytesPointer = pointer + 1
95-
9686
internal fun isEmpty() = tail == head
9787

9888
internal fun isAfterLast(index: BytesPointer) = head + index >= tail
9989

10090
internal fun size(pointer: BytesPointer): Int {
101-
// ???: if Input.ensure operations are frequent enough, consider storing running size in yet another int array
10291
var sum = 0
103-
for (index in (pointer + head) until tail)
92+
for (index in (pointer + head) until tail) {
10493
sum += limits[index]
94+
}
10595
return sum
10696
}
10797

@@ -112,3 +102,17 @@ class Bytes internal constructor(internal val bufferPool: ObjectPool<Buffer>) :
112102
private const val initialPreviewSize = 1
113103
}
114104
}
105+
106+
/**
107+
* Get [Buffer] and limit according to [pointer] offset in [Bytes.buffers].
108+
*/
109+
internal inline fun Bytes.pointed(pointer: Int, consumer: (buffer: Buffer, limit: Int) -> Unit) {
110+
contract {
111+
callsInPlace(consumer, kind = InvocationKind.EXACTLY_ONCE)
112+
}
113+
114+
val index = pointer + head
115+
val buffer = buffers[index] ?: throw NoSuchElementException("There is no buffer at pointer $pointer.")
116+
val limit = limits[index]
117+
consumer(buffer, limit)
118+
}

core/commonMain/src/kotlinx/io/BytesBuilders.kt

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package kotlinx.io
22

33
import kotlinx.io.buffer.*
4-
import kotlinx.io.buffer.DEFAULT_BUFFER_SIZE
54
import kotlin.contracts.*
65

76
/**
@@ -15,15 +14,18 @@ fun buildBytes(bufferSize: Int = DEFAULT_BUFFER_SIZE, builder: Output.() -> Unit
1514
return BytesOutput(bufferSize).apply(builder).bytes()
1615
}
1716

18-
private class BytesOutput(bufferSize: Int = DEFAULT_BUFFER_SIZE) : Output(bufferSize) {
19-
private val bytes = Bytes(bufferPool)
17+
private class BytesOutput(bufferSize: Int = DEFAULT_BUFFER_SIZE) : Output(
18+
if (bufferSize == DEFAULT_BUFFER_SIZE) DefaultBufferPool.Instance else DefaultBufferPool(bufferSize)
19+
) {
20+
private val bytes = Bytes(pool)
2021

2122
fun bytes(): Bytes {
2223
close()
2324
return bytes
2425
}
2526

26-
override fun flush(source: Buffer, length: Int) {
27+
override fun flush(source: Buffer, startIndex: Int, endIndex: Int) {
28+
val length = source.compact(startIndex, endIndex)
2729
bytes.append(source, length)
2830
}
2931

core/commonMain/src/kotlinx/io/Console.common.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
package kotlinx.io
22

33
/**
4-
* Console incorporates all system inputs and outputs in a multiplatform manner.
5-
* All sources are open by default, ready to read from/write to and cannot be closed.
4+
* [Console] incorporates all system inputs and outputs in a multiplatform manner.
5+
* All sources are open by default, ready to read from/write to, and cannot be closed.
66
*/
77
public expect object Console {
88
/**
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,36 @@
11
package kotlinx.io
22

3+
import kotlinx.io.buffer.*
4+
35
expect open class IOException(message: String, cause: Throwable?) : Exception {
46
constructor(message: String)
57
}
68

79
expect open class EOFException(message: String) : IOException
10+
11+
internal fun checkBufferAndIndexes(buffer: Buffer, startIndex: Int, endIndex: Int) {
12+
require(startIndex >= 0) { "Start index ($startIndex) should be positive." }
13+
require(endIndex >= startIndex) { "End index ($endIndex) should be greater than start index ($startIndex)." }
14+
require(endIndex <= buffer.size) {
15+
"End index ($endIndex) cannot be greater than buffer size (${buffer.size})."
16+
}
17+
}
18+
19+
/**
20+
21+
*/
22+
internal fun checkArrayStartAndLength(array: ByteArray, startIndex: Int, length: Int) {
23+
require(startIndex >= 0) { "Start index ($startIndex) should be positive." }
24+
require(length >= 0) { "Length ($length) should be positive." }
25+
require(startIndex + length <= array.size) {
26+
"(start index + length) (${startIndex} + ${length}) cannot be greater than array size (${array.size})."
27+
}
28+
}
29+
30+
internal fun checkSize(size: Int) {
31+
require(size >= 0) { "Size ($size) cannot be negative." }
32+
}
33+
34+
internal fun checkCount(count: Int) {
35+
require(count >= 0) { "Count ($count) should be positive." }
36+
}

0 commit comments

Comments
 (0)