Added JVM support for core and lib.

This commit is contained in:
Daan Vanden Bosch 2020-10-27 22:55:05 +01:00
parent 0d05714730
commit bab01061f0
40 changed files with 639 additions and 392 deletions

View File

@ -2,7 +2,6 @@ plugins {
kotlin("multiplatform") kotlin("multiplatform")
} }
val coroutinesVersion: String by project.ext
val kotlinLoggingVersion: String by project.extra val kotlinLoggingVersion: String by project.extra
kotlin { kotlin {
@ -10,10 +9,11 @@ kotlin {
browser {} browser {}
} }
jvm()
sourceSets { sourceSets {
commonMain { commonMain {
dependencies { dependencies {
api("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion")
api("io.github.microutils:kotlin-logging:$kotlinLoggingVersion") api("io.github.microutils:kotlin-logging:$kotlinLoggingVersion")
} }
} }
@ -25,14 +25,16 @@ kotlin {
} }
} }
val jsTest by getting { getByName("jsTest") {
dependencies { dependencies {
implementation(kotlin("test-js")) implementation(kotlin("test-js"))
} }
} }
getByName("jvmTest") {
dependencies {
implementation(kotlin("test-junit"))
}
}
} }
} }
tasks.register("test") {
dependsOn("allTests")
}

View File

@ -1,3 +0,0 @@
package world.phantasmal.core
expect fun <T> Any?.fastCast(): T

View File

@ -0,0 +1,3 @@
package world.phantasmal.core
expect fun <T> T?.unsafeToNonNull(): T

View File

@ -1,6 +1,5 @@
package world.phantasmal.core.disposable package world.phantasmal.core.disposable
import kotlinx.coroutines.Job
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertFalse import kotlin.test.assertFalse
@ -11,7 +10,7 @@ class TrackedDisposableTests {
fun count_should_go_up_when_created_and_down_when_disposed() { fun count_should_go_up_when_created_and_down_when_disposed() {
val initialCount = TrackedDisposable.disposableCount val initialCount = TrackedDisposable.disposableCount
val disposable = object : TrackedDisposable(DummyScope()) { val disposable = object : TrackedDisposable() {
override fun internalDispose() {} override fun internalDispose() {}
} }
@ -26,7 +25,7 @@ class TrackedDisposableTests {
fun double_dispose_should_not_increase_count() { fun double_dispose_should_not_increase_count() {
val initialCount = TrackedDisposable.disposableCount val initialCount = TrackedDisposable.disposableCount
val disposable = object : TrackedDisposable(DummyScope()) { val disposable = object : TrackedDisposable() {
override fun internalDispose() {} override fun internalDispose() {}
} }
@ -39,7 +38,7 @@ class TrackedDisposableTests {
@Test @Test
fun disposed_property_should_be_set_correctly() { fun disposed_property_should_be_set_correctly() {
val disposable = object : TrackedDisposable(DummyScope()) { val disposable = object : TrackedDisposable() {
override fun internalDispose() {} override fun internalDispose() {}
} }
@ -49,14 +48,4 @@ class TrackedDisposableTests {
assertTrue(disposable.disposed) assertTrue(disposable.disposed)
} }
private class DummyScope : Scope {
override val coroutineContext = Job()
override fun add(disposable: Disposable) {
// Do nothing.
}
override fun scope(): Scope = throw NotImplementedError()
}
} }

View File

@ -1,3 +0,0 @@
package world.phantasmal.core
actual fun <T> Any?.fastCast(): T = unsafeCast<T>()

View File

@ -0,0 +1,3 @@
package world.phantasmal.core
actual fun <T> T?.unsafeToNonNull(): T = unsafeCast<T>()

View File

@ -0,0 +1,4 @@
package world.phantasmal.core
@Suppress("UNCHECKED_CAST")
actual fun <T> T?.unsafeToNonNull(): T = this as T

View File

@ -13,6 +13,7 @@ buildscript {
} }
} }
val coroutinesVersion: String by project.extra
val kotlinLoggingVersion: String by project.extra val kotlinLoggingVersion: String by project.extra
kotlin { kotlin {
@ -26,6 +27,8 @@ kotlin {
} }
} }
jvm()
sourceSets { sourceSets {
all { all {
languageSettings.useExperimentalAnnotation("kotlin.ExperimentalUnsignedTypes") languageSettings.useExperimentalAnnotation("kotlin.ExperimentalUnsignedTypes")
@ -35,6 +38,7 @@ kotlin {
kotlin.setSrcDirs(kotlin.srcDirs + file("build/generated-src/commonMain/kotlin")) kotlin.setSrcDirs(kotlin.srcDirs + file("build/generated-src/commonMain/kotlin"))
dependencies { dependencies {
api(project(":core")) api(project(":core"))
api("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion")
api("io.github.microutils:kotlin-logging:$kotlinLoggingVersion") api("io.github.microutils:kotlin-logging:$kotlinLoggingVersion")
} }
} }
@ -46,16 +50,18 @@ kotlin {
} }
} }
val jsTest by getting { getByName("jsTest") {
dependencies { dependencies {
implementation(kotlin("test-js")) implementation(kotlin("test-js"))
} }
} }
}
}
tasks.register("test") { getByName("jvmTest") {
dependsOn("allTests") dependencies {
implementation(kotlin("test-junit"))
}
}
}
} }
val generateOpcodes = tasks.register("generateOpcodes") { val generateOpcodes = tasks.register("generateOpcodes") {

View File

@ -180,7 +180,7 @@ private class Assembler(private val assembly: List<String>, private val manualSt
seg.data.size += bytes.size seg.data.size += bytes.size
for (i in bytes.indices) { for (i in bytes.indices) {
seg.data.setI8(i + oldSize, bytes[i]) seg.data.setByte(i + oldSize, bytes[i])
} }
} }

View File

@ -18,37 +18,37 @@ expect class Buffer {
/** /**
* Reads an unsigned 8-bit integer at the given offset. * Reads an unsigned 8-bit integer at the given offset.
*/ */
fun getU8(offset: Int): UByte fun getUByte(offset: Int): UByte
/** /**
* Reads an unsigned 16-bit integer at the given offset. * Reads an unsigned 16-bit integer at the given offset.
*/ */
fun getU16(offset: Int): UShort fun getUShort(offset: Int): UShort
/** /**
* Reads an unsigned 32-bit integer at the given offset. * Reads an unsigned 32-bit integer at the given offset.
*/ */
fun getU32(offset: Int): UInt fun getUInt(offset: Int): UInt
/** /**
* Reads a signed 8-bit integer at the given offset. * Reads a signed 8-bit integer at the given offset.
*/ */
fun getI8(offset: Int): Byte fun getByte(offset: Int): Byte
/** /**
* Reads a signed 16-bit integer at the given offset. * Reads a signed 16-bit integer at the given offset.
*/ */
fun getI16(offset: Int): Short fun getShort(offset: Int): Short
/** /**
* Reads a signed 32-bit integer at the given offset. * Reads a signed 32-bit integer at the given offset.
*/ */
fun getI32(offset: Int): Int fun getInt(offset: Int): Int
/** /**
* Reads a 32-bit floating point number at the given offset. * Reads a 32-bit floating point number at the given offset.
*/ */
fun getF32(offset: Int): Float fun getFloat(offset: Int): Float
/** /**
* Reads a UTF-16-encoded string at the given offset. * Reads a UTF-16-encoded string at the given offset.
@ -63,37 +63,37 @@ expect class Buffer {
/** /**
* Writes an unsigned 8-bit integer at the given offset. * Writes an unsigned 8-bit integer at the given offset.
*/ */
fun setU8(offset: Int, value: UByte): Buffer fun setUByte(offset: Int, value: UByte): Buffer
/** /**
* Writes an unsigned 16-bit integer at the given offset. * Writes an unsigned 16-bit integer at the given offset.
*/ */
fun setU16(offset: Int, value: UShort): Buffer fun setUShort(offset: Int, value: UShort): Buffer
/** /**
* Writes an unsigned 32-bit integer at the given offset. * Writes an unsigned 32-bit integer at the given offset.
*/ */
fun setU32(offset: Int, value: UInt): Buffer fun setUInt(offset: Int, value: UInt): Buffer
/** /**
* Writes a signed 8-bit integer at the given offset. * Writes a signed 8-bit integer at the given offset.
*/ */
fun setI8(offset: Int, value: Byte): Buffer fun setByte(offset: Int, value: Byte): Buffer
/** /**
* Writes a signed 16-bit integer at the given offset. * Writes a signed 16-bit integer at the given offset.
*/ */
fun setI16(offset: Int, value: Short): Buffer fun setShort(offset: Int, value: Short): Buffer
/** /**
* Writes a signed 32-bit integer at the given offset. * Writes a signed 32-bit integer at the given offset.
*/ */
fun setI32(offset: Int, value: Int): Buffer fun setInt(offset: Int, value: Int): Buffer
/** /**
* Writes a 32-bit floating point number at the given offset. * Writes a 32-bit floating point number at the given offset.
*/ */
fun setF32(offset: Int, value: Float): Buffer fun setFloat(offset: Int, value: Float): Buffer
/** /**
* Writes 0 bytes to the entire buffer. * Writes 0 bytes to the entire buffer.
@ -103,7 +103,7 @@ expect class Buffer {
/** /**
* Writes [value] to every byte in the buffer. * Writes [value] to every byte in the buffer.
*/ */
fun fill(value: Byte): Buffer fun fillByte(value: Byte): Buffer
companion object { companion object {
/** /**

View File

@ -24,7 +24,10 @@ fun prsCompress(cursor: Cursor): Cursor {
comparisonCursor.seekStart(i) comparisonCursor.seekStart(i)
var size = 0 var size = 0
while (cursor.hasBytesLeft() && size <= 254 && cursor.u8() == comparisonCursor.u8()) { while (cursor.hasBytesLeft() &&
size <= 254 &&
cursor.uByte() == comparisonCursor.uByte()
) {
size++ size++
} }
@ -41,7 +44,7 @@ fun prsCompress(cursor: Cursor): Cursor {
} }
if (bestSize < 3) { if (bestSize < 3) {
compressor.addU8(cursor.u8()) compressor.addUByte(cursor.uByte())
} else { } else {
compressor.copy(bestOffset - cursor.position, bestSize) compressor.copy(bestOffset - cursor.position, bestSize)
cursor.seek(bestSize) cursor.seek(bestSize)
@ -57,9 +60,9 @@ private class PrsCompressor(capacity: Int, endianness: Endianness) {
private var flagBitsLeft = 0 private var flagBitsLeft = 0
private var flagOffset = 0 private var flagOffset = 0
fun addU8(value: UByte) { fun addUByte(value: UByte) {
writeControlBit(1) writeControlBit(1)
writeU8(value) writeUByte(value)
} }
fun copy(offset: Int, size: Int) { fun copy(offset: Int, size: Int) {
@ -76,10 +79,10 @@ private class PrsCompressor(capacity: Int, endianness: Endianness) {
flags = flags ushr flagBitsLeft flags = flags ushr flagBitsLeft
val pos = output.position val pos = output.position
output.seekStart(flagOffset).writeU8(flags.toUByte()).seekStart(pos) output.seekStart(flagOffset).writeUByte(flags.toUByte()).seekStart(pos)
writeU8(0u) writeUByte(0u)
writeU8(0u) writeUByte(0u)
return output.seekStart(0) return output.seekStart(0)
} }
@ -89,37 +92,37 @@ private class PrsCompressor(capacity: Int, endianness: Endianness) {
// position. // position.
val pos = output.position val pos = output.position
output.seekStart(flagOffset) output.seekStart(flagOffset)
output.writeU8(flags.toUByte()) output.writeUByte(flags.toUByte())
output.seekStart(pos) output.seekStart(pos)
output.writeU8(0u) // Placeholder for the next flags byte. output.writeUByte(0u) // Placeholder for the next flags byte.
flagOffset = pos flagOffset = pos
flagBitsLeft = 8 flagBitsLeft = 8
} }
flags = flags ushr 1 flags = flags ushr 1
if (bit!=0) { if (bit != 0) {
flags = flags or 0x80 flags = flags or 0x80
} }
flagBitsLeft-- flagBitsLeft--
} }
private fun writeU8(data: UByte) { private fun writeUByte(data: UByte) {
output.writeU8(data) output.writeUByte(data)
} }
private fun writeU8(data: Int) { private fun writeUByte(data: Int) {
output.writeU8(data.toUByte()) output.writeUByte(data.toUByte())
} }
private fun shortCopy(offset: Int, size: Int) { private fun shortCopy(offset: Int, size: Int) {
val s = size - 2 val s = size - 2
writeControlBit(0) writeControlBit(0)
writeControlBit(0) writeControlBit(0)
writeControlBit(((s ushr 1) and 1) ) writeControlBit(((s ushr 1) and 1))
writeControlBit((s and 1)) writeControlBit((s and 1))
writeU8(offset and 0xFF) writeUByte(offset and 0xFF)
} }
private fun longCopy(offset: Int, size: Int) { private fun longCopy(offset: Int, size: Int) {
@ -127,12 +130,12 @@ private class PrsCompressor(capacity: Int, endianness: Endianness) {
writeControlBit(1) writeControlBit(1)
if (size <= 9) { if (size <= 9) {
writeU8(((offset shl 3) and 0xF8) or ((size - 2) and 0x07)) writeUByte(((offset shl 3) and 0xF8) or ((size - 2) and 0x07))
writeU8((offset ushr 5) and 0xFF) writeUByte((offset ushr 5) and 0xFF)
} else { } else {
writeU8((offset shl 3) and 0xF8) writeUByte((offset shl 3) and 0xF8)
writeU8((offset ushr 5) and 0xFF) writeUByte((offset ushr 5) and 0xFF)
writeU8(size - 1) writeUByte(size - 1)
} }
} }
} }

View File

@ -92,12 +92,12 @@ private class PrsDecompressor(cursor: Cursor) {
} }
fun copyU8() { fun copyU8() {
dst.writeU8(readU8()) dst.writeUByte(readU8())
} }
fun readU8(): UByte = src.u8() fun readU8(): UByte = src.uByte()
fun readU16(): UShort = src.u16() fun readU16(): UShort = src.uShort()
fun offsetCopy(offset: Int, length: Int) { fun offsetCopy(offset: Int, length: Int) {
require(offset in -8192..0) { require(offset in -8192..0) {

View File

@ -42,7 +42,7 @@ protected constructor(protected val offset: Int) : WritableCursor {
): String = ): String =
buildString { buildString {
for (i in 0 until maxByteLength) { for (i in 0 until maxByteLength) {
val codePoint = u8() val codePoint = uByte()
if (nullTerminated && codePoint == ZERO_U8) { if (nullTerminated && codePoint == ZERO_U8) {
if (dropRemaining) { if (dropRemaining) {
@ -65,7 +65,7 @@ protected constructor(protected val offset: Int) : WritableCursor {
val len = maxByteLength / 2 val len = maxByteLength / 2
for (i in 0 until len) { for (i in 0 until len) {
val codePoint = u16() val codePoint = uShort()
if (nullTerminated && codePoint == ZERO_U16) { if (nullTerminated && codePoint == ZERO_U16) {
if (dropRemaining) { if (dropRemaining) {
@ -79,45 +79,45 @@ protected constructor(protected val offset: Int) : WritableCursor {
} }
} }
override fun writeU8Array(array: UByteArray): WritableCursor { override fun writeUByteArray(array: UByteArray): WritableCursor {
val len = array.size val len = array.size
requireSize(len) requireSize(len)
for (i in 0 until len) { for (i in 0 until len) {
writeU8(array[i]) writeUByte(array[i])
} }
return this return this
} }
override fun writeU16Array(array: UShortArray): WritableCursor { override fun writeUShortArray(array: UShortArray): WritableCursor {
val len = array.size val len = array.size
requireSize(2 * len) requireSize(2 * len)
for (i in 0 until len) { for (i in 0 until len) {
writeU16(array[i]) writeUShort(array[i])
} }
return this return this
} }
override fun writeU32Array(array: UIntArray): WritableCursor { override fun writeUIntArray(array: UIntArray): WritableCursor {
val len = array.size val len = array.size
requireSize(4 * len) requireSize(4 * len)
for (i in 0 until len) { for (i in 0 until len) {
writeU32(array[i]) writeUInt(array[i])
} }
return this return this
} }
override fun writeI32Array(array: IntArray): WritableCursor { override fun writeIntArray(array: IntArray): WritableCursor {
val len = array.size val len = array.size
requireSize(4 * len) requireSize(4 * len)
for (i in 0 until len) { for (i in 0 until len) {
writeI32(array[i]) writeInt(array[i])
} }
return this return this
@ -127,7 +127,7 @@ protected constructor(protected val offset: Int) : WritableCursor {
val size = other.bytesLeft val size = other.bytesLeft
requireSize(size) requireSize(size)
for (i in 0 until size) { for (i in 0 until size) {
writeI8(other.i8()) writeByte(other.byte())
} }
return this return this
@ -139,13 +139,13 @@ protected constructor(protected val offset: Int) : WritableCursor {
val len = min(byteLength, str.length) val len = min(byteLength, str.length)
for (i in 0 until len) { for (i in 0 until len) {
writeI8(str[i].toByte()) writeByte(str[i].toByte())
} }
val padLen = byteLength - len val padLen = byteLength - len
for (i in 0 until padLen) { for (i in 0 until padLen) {
writeI8(0) writeByte(0)
} }
return this return this
@ -158,13 +158,13 @@ protected constructor(protected val offset: Int) : WritableCursor {
val len = min(maxLen, str.length) val len = min(maxLen, str.length)
for (i in 0 until len) { for (i in 0 until len) {
writeI16(str[i].toShort()) writeShort(str[i].toShort())
} }
val padLen = maxLen - len val padLen = maxLen - len
for (i in 0 until padLen) { for (i in 0 until padLen) {
writeI16(0) writeShort(0)
} }
return this return this

View File

@ -44,94 +44,94 @@ class BufferCursor(
} }
} }
override fun u8(): UByte { override fun uByte(): UByte {
val r = buffer.getU8(absolutePosition) val r = buffer.getUByte(absolutePosition)
position++ position++
return r return r
} }
override fun u16(): UShort { override fun uShort(): UShort {
val r = buffer.getU16(absolutePosition) val r = buffer.getUShort(absolutePosition)
position += 2 position += 2
return r return r
} }
override fun u32(): UInt { override fun uInt(): UInt {
val r = buffer.getU32(absolutePosition) val r = buffer.getUInt(absolutePosition)
position += 4 position += 4
return r return r
} }
override fun i8(): Byte { override fun byte(): Byte {
val r = buffer.getI8(absolutePosition) val r = buffer.getByte(absolutePosition)
position++ position++
return r return r
} }
override fun i16(): Short { override fun short(): Short {
val r = buffer.getI16(absolutePosition) val r = buffer.getShort(absolutePosition)
position += 2 position += 2
return r return r
} }
override fun i32(): Int { override fun int(): Int {
val r = buffer.getI32(absolutePosition) val r = buffer.getInt(absolutePosition)
position += 4 position += 4
return r return r
} }
override fun f32(): Float { override fun float(): Float {
val r = buffer.getF32(absolutePosition) val r = buffer.getFloat(absolutePosition)
position += 4 position += 4
return r return r
} }
override fun u8Array(n: Int): UByteArray { override fun uByteArray(n: Int): UByteArray {
requireSize(n) requireSize(n)
val array = UByteArray(n) val array = UByteArray(n)
for (i in 0 until n) { for (i in 0 until n) {
array[i] = buffer.getU8(absolutePosition) array[i] = buffer.getUByte(absolutePosition)
position++ position++
} }
return array return array
} }
override fun u16Array(n: Int): UShortArray { override fun uShortArray(n: Int): UShortArray {
requireSize(2 * n) requireSize(2 * n)
val array = UShortArray(n) val array = UShortArray(n)
for (i in 0 until n) { for (i in 0 until n) {
array[i] = buffer.getU16(absolutePosition) array[i] = buffer.getUShort(absolutePosition)
position += 2 position += 2
} }
return array return array
} }
override fun u32Array(n: Int): UIntArray { override fun uIntArray(n: Int): UIntArray {
requireSize(4 * n) requireSize(4 * n)
val array = UIntArray(n) val array = UIntArray(n)
for (i in 0 until n) { for (i in 0 until n) {
array[i] = buffer.getU32(absolutePosition) array[i] = buffer.getUInt(absolutePosition)
position += 4 position += 4
} }
return array return array
} }
override fun i32Array(n: Int): IntArray { override fun intArray(n: Int): IntArray {
requireSize(4 * n) requireSize(4 * n)
val array = IntArray(n) val array = IntArray(n)
for (i in 0 until n) { for (i in 0 until n) {
array[i] = buffer.getI32(absolutePosition) array[i] = buffer.getInt(absolutePosition)
position += 4 position += 4
} }
@ -150,73 +150,73 @@ class BufferCursor(
return wrapper return wrapper
} }
override fun writeU8(value: UByte): WritableCursor { override fun writeUByte(value: UByte): WritableCursor {
ensureSpace(1) ensureSpace(1)
buffer.setU8(absolutePosition, value) buffer.setUByte(absolutePosition, value)
position++ position++
return this return this
} }
override fun writeU16(value: UShort): WritableCursor { override fun writeUShort(value: UShort): WritableCursor {
ensureSpace(2) ensureSpace(2)
buffer.setU16(absolutePosition, value) buffer.setUShort(absolutePosition, value)
position += 2 position += 2
return this return this
} }
override fun writeU32(value: UInt): WritableCursor { override fun writeUInt(value: UInt): WritableCursor {
ensureSpace(4) ensureSpace(4)
buffer.setU32(absolutePosition, value) buffer.setUInt(absolutePosition, value)
position += 4 position += 4
return this return this
} }
override fun writeI8(value: Byte): WritableCursor { override fun writeByte(value: Byte): WritableCursor {
ensureSpace(1) ensureSpace(1)
buffer.setI8(absolutePosition, value) buffer.setByte(absolutePosition, value)
position++ position++
return this return this
} }
override fun writeI16(value: Short): WritableCursor { override fun writeShort(value: Short): WritableCursor {
ensureSpace(2) ensureSpace(2)
buffer.setI16(absolutePosition, value) buffer.setShort(absolutePosition, value)
position += 2 position += 2
return this return this
} }
override fun writeI32(value: Int): WritableCursor { override fun writeInt(value: Int): WritableCursor {
ensureSpace(4) ensureSpace(4)
buffer.setI32(absolutePosition, value) buffer.setInt(absolutePosition, value)
position += 4 position += 4
return this return this
} }
override fun writeF32(value: Float): WritableCursor { override fun writeFloat(value: Float): WritableCursor {
ensureSpace(4) ensureSpace(4)
buffer.setF32(absolutePosition, value) buffer.setFloat(absolutePosition, value)
position += 4 position += 4
return this return this
} }
override fun writeU8Array(array: UByteArray): WritableCursor { override fun writeUByteArray(array: UByteArray): WritableCursor {
ensureSpace(array.size) ensureSpace(array.size)
return super.writeU8Array(array) return super.writeUByteArray(array)
} }
override fun writeU16Array(array: UShortArray): WritableCursor { override fun writeUShortArray(array: UShortArray): WritableCursor {
ensureSpace(2 * array.size) ensureSpace(2 * array.size)
return super.writeU16Array(array) return super.writeUShortArray(array)
} }
override fun writeU32Array(array: UIntArray): WritableCursor { override fun writeUIntArray(array: UIntArray): WritableCursor {
ensureSpace(4 * array.size) ensureSpace(4 * array.size)
return super.writeU32Array(array) return super.writeUIntArray(array)
} }
override fun writeI32Array(array: IntArray): WritableCursor { override fun writeIntArray(array: IntArray): WritableCursor {
ensureSpace(4 * array.size) ensureSpace(4 * array.size)
return super.writeI32Array(array) return super.writeIntArray(array)
} }
override fun writeCursor(other: Cursor): WritableCursor { override fun writeCursor(other: Cursor): WritableCursor {

View File

@ -48,57 +48,57 @@ interface Cursor {
/** /**
* Reads an unsigned 8-bit integer and increments position by 1. * Reads an unsigned 8-bit integer and increments position by 1.
*/ */
fun u8(): UByte fun uByte(): UByte
/** /**
* Reads an unsigned 16-bit integer and increments position by 2. * Reads an unsigned 16-bit integer and increments position by 2.
*/ */
fun u16(): UShort fun uShort(): UShort
/** /**
* Reads an unsigned 32-bit integer and increments position by 4. * Reads an unsigned 32-bit integer and increments position by 4.
*/ */
fun u32(): UInt fun uInt(): UInt
/** /**
* Reads an signed 8-bit integer and increments position by 1. * Reads an signed 8-bit integer and increments position by 1.
*/ */
fun i8(): Byte fun byte(): Byte
/** /**
* Reads a signed 16-bit integer and increments position by 2. * Reads a signed 16-bit integer and increments position by 2.
*/ */
fun i16(): Short fun short(): Short
/** /**
* Reads a signed 32-bit integer and increments position by 4. * Reads a signed 32-bit integer and increments position by 4.
*/ */
fun i32(): Int fun int(): Int
/** /**
* Reads a 32-bit floating point number and increments position by 4. * Reads a 32-bit floating point number and increments position by 4.
*/ */
fun f32(): Float fun float(): Float
/** /**
* Reads [n] unsigned 8-bit integers and increments position by [n]. * Reads [n] unsigned 8-bit integers and increments position by [n].
*/ */
fun u8Array(n: Int): UByteArray fun uByteArray(n: Int): UByteArray
/** /**
* Reads [n] unsigned 16-bit integers and increments position by 2[n]. * Reads [n] unsigned 16-bit integers and increments position by 2[n].
*/ */
fun u16Array(n: Int): UShortArray fun uShortArray(n: Int): UShortArray
/** /**
* Reads [n] unsigned 32-bit integers and increments position by 4[n]. * Reads [n] unsigned 32-bit integers and increments position by 4[n].
*/ */
fun u32Array(n: Int): UIntArray fun uIntArray(n: Int): UIntArray
/** /**
* Reads [n] signed 32-bit integers and increments position by 4[n]. * Reads [n] signed 32-bit integers and increments position by 4[n].
*/ */
fun i32Array(n: Int): IntArray fun intArray(n: Int): IntArray
/** /**
* Consumes a variable number of bytes. * Consumes a variable number of bytes.

View File

@ -15,60 +15,60 @@ interface WritableCursor : Cursor {
/** /**
* Writes an unsigned 8-bit integer and increments position by 1. * Writes an unsigned 8-bit integer and increments position by 1.
*/ */
fun writeU8(value: UByte): WritableCursor fun writeUByte(value: UByte): WritableCursor
/** /**
* Writes an unsigned 16-bit integer and increments position by 2. * Writes an unsigned 16-bit integer and increments position by 2.
*/ */
fun writeU16(value: UShort): WritableCursor fun writeUShort(value: UShort): WritableCursor
/** /**
* Writes an unsigned 32-bit integer and increments position by 4. * Writes an unsigned 32-bit integer and increments position by 4.
*/ */
fun writeU32(value: UInt): WritableCursor fun writeUInt(value: UInt): WritableCursor
/** /**
* Writes a signed 8-bit integer and increments position by 1. * Writes a signed 8-bit integer and increments position by 1.
*/ */
fun writeI8(value: Byte): WritableCursor fun writeByte(value: Byte): WritableCursor
/** /**
* Writes a signed 16-bit integer and increments position by 2. * Writes a signed 16-bit integer and increments position by 2.
*/ */
fun writeI16(value: Short): WritableCursor fun writeShort(value: Short): WritableCursor
/** /**
* Writes a signed 32-bit integer and increments position by 4. * Writes a signed 32-bit integer and increments position by 4.
*/ */
fun writeI32(value: Int): WritableCursor fun writeInt(value: Int): WritableCursor
/** /**
* Writes a 32-bit floating point number and increments position by 4. * Writes a 32-bit floating point number and increments position by 4.
*/ */
fun writeF32(value: Float): WritableCursor fun writeFloat(value: Float): WritableCursor
/** /**
* Writes an array of unsigned 8-bit integers and increments position by the array's length. * Writes an array of unsigned 8-bit integers and increments position by the array's length.
*/ */
fun writeU8Array(array: UByteArray): WritableCursor fun writeUByteArray(array: UByteArray): WritableCursor
/** /**
* Writes an array of unsigned 16-bit integers and increments position by twice the array's * Writes an array of unsigned 16-bit integers and increments position by twice the array's
* length. * length.
*/ */
fun writeU16Array(array: UShortArray): WritableCursor fun writeUShortArray(array: UShortArray): WritableCursor
/** /**
* Writes an array of unsigned 32-bit integers and increments position by four times the array's * Writes an array of unsigned 32-bit integers and increments position by four times the array's
* length. * length.
*/ */
fun writeU32Array(array: UIntArray): WritableCursor fun writeUIntArray(array: UIntArray): WritableCursor
/** /**
* Writes an array of signed 32-bit integers and increments position by four times the array's * Writes an array of signed 32-bit integers and increments position by four times the array's
* length. * length.
*/ */
fun writeI32Array(array: IntArray): WritableCursor fun writeIntArray(array: IntArray): WritableCursor
/** /**
* Writes the contents of the given cursor from its position to its end. Increments this * Writes the contents of the given cursor from its position to its end. Increments this

View File

@ -34,9 +34,9 @@ private fun <T> parse(
var corrupted = false var corrupted = false
while (cursor.bytesLeft >= 8) { while (cursor.bytesLeft >= 8) {
val type = cursor.i32() val type = cursor.int()
val sizePos = cursor.position val sizePos = cursor.position
val size = cursor.i32() val size = cursor.int()
if (size > cursor.bytesLeft) { if (size > cursor.bytesLeft) {
corrupted = true corrupted = true

View File

@ -6,4 +6,4 @@ class Vec2(val x: Float, val y: Float)
class Vec3(val x: Float, val y: Float, val z: Float) class Vec3(val x: Float, val y: Float, val z: Float)
fun Cursor.vec3F32(): Vec3 = Vec3(f32(), f32(), f32()) fun Cursor.vec3F32(): Vec3 = Vec3(float(), float(), float())

View File

@ -62,7 +62,7 @@ private fun <Model, Context> parseSiblingObjects(
parse_model: (cursor: Cursor, context: Context) -> Model, parse_model: (cursor: Cursor, context: Context) -> Model,
context: Context, context: Context,
): List<NjObject<Model>> { ): List<NjObject<Model>> {
val evalFlags = cursor.u32() val evalFlags = cursor.uInt()
val noTranslate = (evalFlags and 0b1u) != 0u val noTranslate = (evalFlags and 0b1u) != 0u
val noRotate = (evalFlags and 0b10u) != 0u val noRotate = (evalFlags and 0b10u) != 0u
val noScale = (evalFlags and 0b100u) != 0u val noScale = (evalFlags and 0b100u) != 0u
@ -72,16 +72,16 @@ private fun <Model, Context> parseSiblingObjects(
val skip = (evalFlags and 0b1000000u) != 0u val skip = (evalFlags and 0b1000000u) != 0u
val shapeSkip = (evalFlags and 0b10000000u) != 0u val shapeSkip = (evalFlags and 0b10000000u) != 0u
val modelOffset = cursor.i32() val modelOffset = cursor.int()
val pos = cursor.vec3F32() val pos = cursor.vec3F32()
val rotation = Vec3( val rotation = Vec3(
angleToRad(cursor.i32()), angleToRad(cursor.int()),
angleToRad(cursor.i32()), angleToRad(cursor.int()),
angleToRad(cursor.i32()), angleToRad(cursor.int()),
) )
val scale = cursor.vec3F32() val scale = cursor.vec3F32()
val childOffset = cursor.i32() val childOffset = cursor.int()
val siblingOffset = cursor.i32() val siblingOffset = cursor.int()
val model = if (modelOffset == 0) { val model = if (modelOffset == 0) {
null null

View File

@ -123,10 +123,10 @@ class NjcmErgb(
) )
fun parseNjcmModel(cursor: Cursor, cachedChunkOffsets: MutableMap<UByte, Int>): NjcmModel { fun parseNjcmModel(cursor: Cursor, cachedChunkOffsets: MutableMap<UByte, Int>): NjcmModel {
val vlistOffset = cursor.i32() // Vertex list val vlistOffset = cursor.int() // Vertex list
val plistOffset = cursor.i32() // Triangle strip index list val plistOffset = cursor.int() // Triangle strip index list
val boundingSphereCenter = cursor.vec3F32() val boundingSphereCenter = cursor.vec3F32()
val boundingSphereRadius = cursor.f32() val boundingSphereRadius = cursor.float()
val vertices: MutableList<NjcmVertex> = mutableListOf() val vertices: MutableList<NjcmVertex> = mutableListOf()
val meshes: MutableList<NjcmTriangleStrip> = mutableListOf() val meshes: MutableList<NjcmTriangleStrip> = mutableListOf()
@ -156,6 +156,7 @@ fun parseNjcmModel(cursor: Cursor, cachedChunkOffsets: MutableMap<UByte, Int>):
var dstAlpha: UByte? = null var dstAlpha: UByte? = null
for (chunk in parseChunks(cursor, cachedChunkOffsets, false)) { for (chunk in parseChunks(cursor, cachedChunkOffsets, false)) {
@Suppress("UNUSED_VALUE") // Ignore useless warning due to compiler bug.
when (chunk) { when (chunk) {
is NjcmChunk.Bits -> { is NjcmChunk.Bits -> {
srcAlpha = chunk.srcAlpha srcAlpha = chunk.srcAlpha
@ -210,8 +211,8 @@ private fun parseChunks(
var loop = true var loop = true
while (loop) { while (loop) {
val typeId = cursor.u8() val typeId = cursor.uByte()
val flags = cursor.u8() val flags = cursor.uByte()
val flagsUInt = flags.toUInt() val flagsUInt = flags.toUInt()
val chunkStartPosition = cursor.position val chunkStartPosition = cursor.position
var size = 0 var size = 0
@ -254,7 +255,7 @@ private fun parseChunks(
} }
in 8..9 -> { in 8..9 -> {
size = 2 size = 2
val textureBitsAndId = cursor.u16().toUInt() val textureBitsAndId = cursor.uShort().toUInt()
chunks.add(NjcmChunk.Tiny( chunks.add(NjcmChunk.Tiny(
typeId, typeId,
@ -269,7 +270,7 @@ private fun parseChunks(
)) ))
} }
in 17..31 -> { in 17..31 -> {
size = 2 + 2 * cursor.i16() size = 2 + 2 * cursor.short()
var diffuse: NjcmArgb? = null var diffuse: NjcmArgb? = null
var ambient: NjcmArgb? = null var ambient: NjcmArgb? = null
@ -277,28 +278,28 @@ private fun parseChunks(
if ((flagsUInt and 0b1u) != 0u) { if ((flagsUInt and 0b1u) != 0u) {
diffuse = NjcmArgb( diffuse = NjcmArgb(
b = cursor.u8().toFloat() / 255f, b = cursor.uByte().toFloat() / 255f,
g = cursor.u8().toFloat() / 255f, g = cursor.uByte().toFloat() / 255f,
r = cursor.u8().toFloat() / 255f, r = cursor.uByte().toFloat() / 255f,
a = cursor.u8().toFloat() / 255f, a = cursor.uByte().toFloat() / 255f,
) )
} }
if ((flagsUInt and 0b10u) != 0u) { if ((flagsUInt and 0b10u) != 0u) {
ambient = NjcmArgb( ambient = NjcmArgb(
b = cursor.u8().toFloat() / 255f, b = cursor.uByte().toFloat() / 255f,
g = cursor.u8().toFloat() / 255f, g = cursor.uByte().toFloat() / 255f,
r = cursor.u8().toFloat() / 255f, r = cursor.uByte().toFloat() / 255f,
a = cursor.u8().toFloat() / 255f, a = cursor.uByte().toFloat() / 255f,
) )
} }
if ((flagsUInt and 0b100u) != 0u) { if ((flagsUInt and 0b100u) != 0u) {
specular = NjcmErgb( specular = NjcmErgb(
b = cursor.u8(), b = cursor.uByte(),
g = cursor.u8(), g = cursor.uByte(),
r = cursor.u8(), r = cursor.uByte(),
e = cursor.u8(), e = cursor.uByte(),
) )
} }
@ -312,20 +313,20 @@ private fun parseChunks(
)) ))
} }
in 32..50 -> { in 32..50 -> {
size = 2 + 4 * cursor.i16() size = 2 + 4 * cursor.short()
chunks.add(NjcmChunk.Vertex( chunks.add(NjcmChunk.Vertex(
typeId, typeId,
vertices = parseVertexChunk(cursor, typeId, flags), vertices = parseVertexChunk(cursor, typeId, flags),
)) ))
} }
in 56..58 -> { in 56..58 -> {
size = 2 + 2 * cursor.i16() size = 2 + 2 * cursor.short()
chunks.add(NjcmChunk.Volume( chunks.add(NjcmChunk.Volume(
typeId, typeId,
)) ))
} }
in 64..75 -> { in 64..75 -> {
size = 2 + 2 * cursor.i16() size = 2 + 2 * cursor.short()
chunks.add(NjcmChunk.Strip( chunks.add(NjcmChunk.Strip(
typeId, typeId,
triangleStrips = parseTriangleStripChunk(cursor, typeId, flags), triangleStrips = parseTriangleStripChunk(cursor, typeId, flags),
@ -337,7 +338,7 @@ private fun parseChunks(
loop = false loop = false
} }
else -> { else -> {
size = 2 + 2 * cursor.i16() size = 2 + 2 * cursor.short()
chunks.add(NjcmChunk.Unknown( chunks.add(NjcmChunk.Unknown(
typeId, typeId,
)) ))
@ -359,8 +360,8 @@ private fun parseVertexChunk(
val boneWeightStatus = flags and 0b11u val boneWeightStatus = flags and 0b11u
val calcContinue = (flags and 0x80u) != ZERO_U8 val calcContinue = (flags and 0x80u) != ZERO_U8
val index = cursor.u16() val index = cursor.uShort()
val vertexCount = cursor.u16() val vertexCount = cursor.uShort()
val vertices: MutableList<NjcmChunkVertex> = mutableListOf() val vertices: MutableList<NjcmChunkVertex> = mutableListOf()
@ -385,8 +386,8 @@ private fun parseVertexChunk(
if (chunkTypeId == (37u).toUByte()) { if (chunkTypeId == (37u).toUByte()) {
// NJDCVNF // NJDCVNF
// NinjaFlags32 // NinjaFlags32
vertexIndex = index + cursor.u16() vertexIndex = index + cursor.uShort()
boneWeight = cursor.u16().toFloat() / 255f boneWeight = cursor.uShort().toFloat() / 255f
} else { } else {
// Skip user flags and material information. // Skip user flags and material information.
cursor.seek(4) cursor.seek(4)
@ -401,8 +402,8 @@ private fun parseVertexChunk(
if (chunkTypeId == (44u).toUByte()) { if (chunkTypeId == (44u).toUByte()) {
// NJDCVVNNF // NJDCVVNNF
// NinjaFlags32 // NinjaFlags32
vertexIndex = index + cursor.u16() vertexIndex = index + cursor.uShort()
boneWeight = cursor.u16().toFloat() / 255f boneWeight = cursor.uShort().toFloat() / 255f
} else { } else {
// Skip user flags and material information. // Skip user flags and material information.
cursor.seek(4) cursor.seek(4)
@ -410,7 +411,7 @@ private fun parseVertexChunk(
} }
in 48..50 -> { in 48..50 -> {
// 32-Bit vertex normal in format: reserved(2)|x(10)|y(10)|z(10) // 32-Bit vertex normal in format: reserved(2)|x(10)|y(10)|z(10)
val n = cursor.u32() val n = cursor.uInt()
normal = Vec3( normal = Vec3(
((n shr 20) and 0x3ffu).toFloat() / 0x3ff, ((n shr 20) and 0x3ffu).toFloat() / 0x3ff,
((n shr 10) and 0x3ffu).toFloat() / 0x3ff, ((n shr 10) and 0x3ffu).toFloat() / 0x3ff,
@ -450,7 +451,7 @@ private fun parseTriangleStripChunk(
val flatShading = (flags and 0b100000u) != ZERO_U8 val flatShading = (flags and 0b100000u) != ZERO_U8
val environmentMapping = (flags and 0b1000000u) != ZERO_U8 val environmentMapping = (flags and 0b1000000u) != ZERO_U8
val userOffsetAndStripCount = cursor.u16() val userOffsetAndStripCount = cursor.uShort()
val userFlagsSize = (userOffsetAndStripCount.toUInt() shr 14).toInt() val userFlagsSize = (userOffsetAndStripCount.toUInt() shr 14).toInt()
val stripCount = userOffsetAndStripCount and 0x3fffu val stripCount = userOffsetAndStripCount and 0x3fffu
@ -490,17 +491,17 @@ private fun parseTriangleStripChunk(
val strips: MutableList<NjcmTriangleStrip> = mutableListOf() val strips: MutableList<NjcmTriangleStrip> = mutableListOf()
repeat(stripCount.toInt()) { repeat(stripCount.toInt()) {
val windingFlagAndIndexCount = cursor.i16() val windingFlagAndIndexCount = cursor.short()
val clockwiseWinding = windingFlagAndIndexCount < 1 val clockwiseWinding = windingFlagAndIndexCount < 1
val indexCount = abs(windingFlagAndIndexCount.toInt()) val indexCount = abs(windingFlagAndIndexCount.toInt())
val vertices: MutableList<NjcmMeshVertex> = mutableListOf() val vertices: MutableList<NjcmMeshVertex> = mutableListOf()
for (j in 0..indexCount) { for (j in 0..indexCount) {
val index = cursor.u16() val index = cursor.uShort()
val texCoords = if (hasTexCoords) { val texCoords = if (hasTexCoords) {
Vec2(cursor.u16().toFloat() / 255f, cursor.u16().toFloat() / 255f) Vec2(cursor.uShort().toFloat() / 255f, cursor.uShort().toFloat() / 255f)
} else null } else null
// Ignore ARGB8888 color. // Ignore ARGB8888 color.
@ -510,9 +511,9 @@ private fun parseTriangleStripChunk(
val normal = if (hasNormal) { val normal = if (hasNormal) {
Vec3( Vec3(
cursor.u16().toFloat() / 255f, cursor.uShort().toFloat() / 255f,
cursor.u16().toFloat() / 255f, cursor.uShort().toFloat() / 255f,
cursor.u16().toFloat() / 255f, cursor.uShort().toFloat() / 255f,
) )
} else null } else null

View File

@ -40,9 +40,9 @@ enum class BinFormat {
} }
fun parseBin(cursor: Cursor): BinFile { fun parseBin(cursor: Cursor): BinFile {
val objectCodeOffset = cursor.i32() val objectCodeOffset = cursor.int()
val labelOffsetTableOffset = cursor.i32() // Relative offsets val labelOffsetTableOffset = cursor.int() // Relative offsets
val size = cursor.i32() val size = cursor.int()
cursor.seek(4) // Always seems to be 0xFFFFFFFF. cursor.seek(4) // Always seems to be 0xFFFFFFFF.
val format = when (objectCodeOffset) { val format = when (objectCodeOffset) {
@ -65,14 +65,14 @@ fun parseBin(cursor: Cursor): BinFile {
if (format == BinFormat.DC_GC) { if (format == BinFormat.DC_GC) {
cursor.seek(1) cursor.seek(1)
language = cursor.u8().toUInt() language = cursor.uByte().toUInt()
questId = cursor.u16().toUInt() questId = cursor.uShort().toUInt()
questName = cursor.stringAscii(32, nullTerminated = true, dropRemaining = true) questName = cursor.stringAscii(32, nullTerminated = true, dropRemaining = true)
shortDescription = cursor.stringAscii(128, nullTerminated = true, dropRemaining = true) shortDescription = cursor.stringAscii(128, nullTerminated = true, dropRemaining = true)
longDescription = cursor.stringAscii(288, nullTerminated = true, dropRemaining = true) longDescription = cursor.stringAscii(288, nullTerminated = true, dropRemaining = true)
} else { } else {
questId = cursor.u32() questId = cursor.uInt()
language = cursor.u32() language = cursor.uInt()
questName = cursor.stringUtf16(64, nullTerminated = true, dropRemaining = true) questName = cursor.stringUtf16(64, nullTerminated = true, dropRemaining = true)
shortDescription = cursor.stringUtf16(256, nullTerminated = true, dropRemaining = true) shortDescription = cursor.stringUtf16(256, nullTerminated = true, dropRemaining = true)
longDescription = cursor.stringUtf16(576, nullTerminated = true, dropRemaining = true) longDescription = cursor.stringUtf16(576, nullTerminated = true, dropRemaining = true)
@ -84,7 +84,7 @@ fun parseBin(cursor: Cursor): BinFile {
val shopItems = if (format == BinFormat.BB) { val shopItems = if (format == BinFormat.BB) {
cursor.seek(4) // Skip padding. cursor.seek(4) // Skip padding.
cursor.u32Array(932) cursor.uIntArray(932)
} else { } else {
UIntArray(0) UIntArray(0)
} }
@ -92,7 +92,7 @@ fun parseBin(cursor: Cursor): BinFile {
val labelOffsetCount = (cursor.size - labelOffsetTableOffset) / 4 val labelOffsetCount = (cursor.size - labelOffsetTableOffset) / 4
val labelOffsets = cursor val labelOffsets = cursor
.seekStart(labelOffsetTableOffset) .seekStart(labelOffsetTableOffset)
.i32Array(labelOffsetCount) .intArray(labelOffsetCount)
val objectCode = cursor val objectCode = cursor
.seekStart(objectCodeOffset) .seekStart(objectCodeOffset)

View File

@ -70,10 +70,10 @@ fun parseDat(cursor: Cursor): DatFile {
val unknowns = mutableListOf<DatUnknown>() val unknowns = mutableListOf<DatUnknown>()
while (cursor.hasBytesLeft()) { while (cursor.hasBytesLeft()) {
val entityType = cursor.i32() val entityType = cursor.int()
val totalSize = cursor.i32() val totalSize = cursor.int()
val areaId = cursor.i32() val areaId = cursor.int()
val entitiesSize = cursor.i32() val entitiesSize = cursor.int()
if (entityType == 0) { if (entityType == 0) {
break break
@ -95,7 +95,7 @@ fun parseDat(cursor: Cursor): DatFile {
totalSize, totalSize,
areaId, areaId,
entitiesSize, entitiesSize,
data = cursor.u8Array(entitiesSize), data = cursor.uByteArray(entitiesSize),
)) ))
} }
} }
@ -133,11 +133,11 @@ private fun parseEntities(
} }
private fun parseEvents(cursor: Cursor, areaId: Int, events: MutableList<DatEvent>) { private fun parseEvents(cursor: Cursor, areaId: Int, events: MutableList<DatEvent>) {
val actionsOffset = cursor.i32() val actionsOffset = cursor.int()
cursor.seek(4) // Always 0x10 cursor.seek(4) // Always 0x10
val eventCount = cursor.i32() val eventCount = cursor.int()
cursor.seek(3) // Always 0 cursor.seek(3) // Always 0
val eventType = cursor.u8() val eventType = cursor.uByte()
require(eventType != (0x32u).toUByte()) { require(eventType != (0x32u).toUByte()) {
"Can't parse challenge mode quests yet." "Can't parse challenge mode quests yet."
@ -148,13 +148,13 @@ private fun parseEvents(cursor: Cursor, areaId: Int, events: MutableList<DatEven
cursor.seekStart(16) cursor.seekStart(16)
repeat(eventCount) { repeat(eventCount) {
val id = cursor.u32() val id = cursor.uInt()
cursor.seek(4) // Always 0x100 cursor.seek(4) // Always 0x100
val sectionId = cursor.u16() val sectionId = cursor.uShort()
val wave = cursor.u16() val wave = cursor.uShort()
val delay = cursor.u16() val delay = cursor.uShort()
val unknown = cursor.u16() // "wavesetting"? val unknown = cursor.uShort() // "wavesetting"?
val eventActionsOffset = cursor.i32() val eventActionsOffset = cursor.int()
val actions: MutableList<DatEventAction> = val actions: MutableList<DatEventAction> =
if (eventActionsOffset < actionsCursor.size) { if (eventActionsOffset < actionsCursor.size) {
@ -185,7 +185,7 @@ private fun parseEvents(cursor: Cursor, areaId: Int, events: MutableList<DatEven
var lastU8: UByte = 0xffu var lastU8: UByte = 0xffu
while (actionsCursor.hasBytesLeft()) { while (actionsCursor.hasBytesLeft()) {
lastU8 = actionsCursor.u8() lastU8 = actionsCursor.uByte()
if (lastU8 != (0xffu).toUByte()) { if (lastU8 != (0xffu).toUByte()) {
break break
@ -204,28 +204,28 @@ private fun parseEventActions(cursor: Cursor): MutableList<DatEventAction> {
val actions = mutableListOf<DatEventAction>() val actions = mutableListOf<DatEventAction>()
outer@ while (cursor.hasBytesLeft()) { outer@ while (cursor.hasBytesLeft()) {
when (val type = cursor.u8().toInt()) { when (val type = cursor.uByte().toInt()) {
1 -> break@outer 1 -> break@outer
EVENT_ACTION_SPAWN_NPCS -> EVENT_ACTION_SPAWN_NPCS ->
actions.add(DatEventAction.SpawnNpcs( actions.add(DatEventAction.SpawnNpcs(
sectionId = cursor.u16(), sectionId = cursor.uShort(),
appearFlag = cursor.u16(), appearFlag = cursor.uShort(),
)) ))
EVENT_ACTION_UNLOCK -> EVENT_ACTION_UNLOCK ->
actions.add(DatEventAction.Unlock( actions.add(DatEventAction.Unlock(
doorId = cursor.u16(), doorId = cursor.uShort(),
)) ))
EVENT_ACTION_LOCK -> EVENT_ACTION_LOCK ->
actions.add(DatEventAction.Lock( actions.add(DatEventAction.Lock(
doorId = cursor.u16(), doorId = cursor.uShort(),
)) ))
EVENT_ACTION_TRIGGER_EVENT -> EVENT_ACTION_TRIGGER_EVENT ->
actions.add(DatEventAction.TriggerEvent( actions.add(DatEventAction.TriggerEvent(
eventId = cursor.u32(), eventId = cursor.uInt(),
)) ))
else -> { else -> {

View File

@ -387,10 +387,10 @@ private fun parseInstructionsSegment(
while (cursor.position < endOffset) { while (cursor.position < endOffset) {
// Parse the opcode. // Parse the opcode.
val mainOpcode = cursor.u8() val mainOpcode = cursor.uByte()
val fullOpcode = when (mainOpcode.toInt()) { val fullOpcode = when (mainOpcode.toInt()) {
0xF8, 0xF9 -> ((mainOpcode.toInt() shl 8) or cursor.u8().toInt()) 0xF8, 0xF9 -> ((mainOpcode.toInt() shl 8) or cursor.uByte().toInt())
else -> mainOpcode.toInt() else -> mainOpcode.toInt()
} }
@ -496,23 +496,23 @@ private fun parseInstructionArguments(
for (param in opcode.params) { for (param in opcode.params) {
when (param.type) { when (param.type) {
is ByteType -> is ByteType ->
args.add(Arg(cursor.u8().toInt())) args.add(Arg(cursor.uByte().toInt()))
is WordType -> is WordType ->
args.add(Arg(cursor.u16().toInt())) args.add(Arg(cursor.uShort().toInt()))
is DWordType -> is DWordType ->
args.add(Arg(cursor.i32())) args.add(Arg(cursor.int()))
is FloatType -> is FloatType ->
args.add(Arg(cursor.f32())) args.add(Arg(cursor.float()))
is LabelType, is LabelType,
is ILabelType, is ILabelType,
is DLabelType, is DLabelType,
is SLabelType, is SLabelType,
-> { -> {
args.add(Arg(cursor.u16().toInt())) args.add(Arg(cursor.uShort().toInt()))
} }
is StringType -> { is StringType -> {
@ -536,20 +536,20 @@ private fun parseInstructionArguments(
is ILabelVarType -> { is ILabelVarType -> {
varargCount++ varargCount++
val argSize = cursor.u8() val argSize = cursor.uByte()
args.addAll(cursor.u16Array(argSize.toInt()).map { Arg(it.toInt()) }) args.addAll(cursor.uShortArray(argSize.toInt()).map { Arg(it.toInt()) })
} }
is RegRefType, is RegRefType,
is RegTupRefType, is RegTupRefType,
-> { -> {
args.add(Arg(cursor.u8().toInt())) args.add(Arg(cursor.uByte().toInt()))
} }
is RegRefVarType -> { is RegRefVarType -> {
varargCount++ varargCount++
val argSize = cursor.u8() val argSize = cursor.uByte()
args.addAll(cursor.u8Array(argSize.toInt()).map { Arg(it.toInt()) }) args.addAll(cursor.uByteArray(argSize.toInt()).map { Arg(it.toInt()) })
} }
else -> error("Parameter type ${param.type} not implemented.") else -> error("Parameter type ${param.type} not implemented.")

View File

@ -8,15 +8,15 @@ class QuestNpc(var episode: Episode, var areaId: Int, val data: Buffer) {
* Only seems to be valid for non-enemies. * Only seems to be valid for non-enemies.
*/ */
var scriptLabel: Int var scriptLabel: Int
get() = data.getF32(60).roundToInt() get() = data.getFloat(60).roundToInt()
set(value) { set(value) {
data.setF32(60, value.toFloat()) data.setFloat(60, value.toFloat())
} }
var skin: Int var skin: Int
get() = data.getI32(64) get() = data.getInt(64)
set(value) { set(value) {
data.setI32(64, value) data.setInt(64, value)
} }
init { init {

View File

@ -38,25 +38,25 @@ class BufferTests {
assertEquals(101, buffer.size) assertEquals(101, buffer.size)
assertTrue(buffer.capacity >= 101) assertTrue(buffer.capacity >= 101)
buffer.setU8(100, (0xABu).toUByte()) buffer.setUByte(100, (0xABu).toUByte())
assertEquals(0xABu, buffer.getU8(100).toUInt()) assertEquals(0xABu, buffer.getUByte(100).toUInt())
} }
@Test @Test
fun fill_and_zero() { fun fill_and_zero() {
val buffer = Buffer.withSize(100) val buffer = Buffer.withSize(100)
buffer.fill(100) buffer.fillByte(100)
for (i in 0 until buffer.size) { for (i in 0 until buffer.size) {
assertEquals(100u, buffer.getU8(i)) assertEquals(100u, buffer.getUByte(i))
} }
buffer.zero() buffer.zero()
for (i in 0 until buffer.size) { for (i in 0 until buffer.size) {
assertEquals(0u, buffer.getU8(i)) assertEquals(0u, buffer.getUByte(i))
} }
} }
} }

View File

@ -17,7 +17,7 @@ class PrsCompressTests {
@Test @Test
fun edge_case_1_byte() { fun edge_case_1_byte() {
val compressed = prsCompress(Buffer.withSize(1).fill(111).cursor()) val compressed = prsCompress(Buffer.withSize(1).fillByte(111).cursor())
assertEquals(4, compressed.size) assertEquals(4, compressed.size)
} }
@ -38,7 +38,7 @@ class PrsCompressTests {
@Test @Test
fun best_case() { fun best_case() {
val compressed = prsCompress(Buffer.withSize(10_000).fill(127).cursor()) val compressed = prsCompress(Buffer.withSize(10_000).fillByte(127).cursor())
assertEquals(475, compressed.size) assertEquals(475, compressed.size)
} }
@ -49,7 +49,7 @@ class PrsCompressTests {
val buffer = Buffer.withSize(10_000) val buffer = Buffer.withSize(10_000)
for (i in 0 until buffer.size step 4) { for (i in 0 until buffer.size step 4) {
buffer.setU32(i, random.nextUInt()) buffer.setUInt(i, random.nextUInt())
} }
val compressed = prsCompress(buffer.cursor()) val compressed = prsCompress(buffer.cursor())
@ -64,7 +64,7 @@ class PrsCompressTests {
val buffer = Buffer.withSize(1000 * pattern.size) val buffer = Buffer.withSize(1000 * pattern.size)
for (i in 0 until buffer.size) { for (i in 0 until buffer.size) {
buffer.setI8(i, (pattern[i % pattern.size] + random.nextInt(10)).toByte()) buffer.setByte(i, (pattern[i % pattern.size] + random.nextInt(10)).toByte())
} }
val compressed = prsCompress(buffer.cursor()) val compressed = prsCompress(buffer.cursor())

View File

@ -2,8 +2,9 @@ package world.phantasmal.lib.compression.prs
import world.phantasmal.lib.buffer.Buffer import world.phantasmal.lib.buffer.Buffer
import world.phantasmal.lib.cursor.cursor import world.phantasmal.lib.cursor.cursor
import world.phantasmal.lib.test.asyncTest
import world.phantasmal.lib.test.readFile
import kotlin.random.Random import kotlin.random.Random
import kotlin.random.nextUInt
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
@ -15,7 +16,7 @@ class PrsDecompressTests {
@Test @Test
fun edge_case_1_byte() { fun edge_case_1_byte() {
testWithBuffer(Buffer.withSize(1).fill(111)) testWithBuffer(Buffer.withSize(1).fillByte(111))
} }
@Test @Test
@ -30,7 +31,7 @@ class PrsDecompressTests {
@Test @Test
fun best_case() { fun best_case() {
testWithBuffer(Buffer.withSize(10_000).fill(127)) testWithBuffer(Buffer.withSize(10_000).fillByte(127))
} }
@Test @Test
@ -39,7 +40,7 @@ class PrsDecompressTests {
val buffer = Buffer.withSize(10_000) val buffer = Buffer.withSize(10_000)
for (i in 0 until buffer.size step 4) { for (i in 0 until buffer.size step 4) {
buffer.setU32(i, random.nextUInt()) buffer.setInt(i, random.nextInt())
} }
testWithBuffer(buffer) testWithBuffer(buffer)
@ -52,7 +53,7 @@ class PrsDecompressTests {
val buffer = Buffer.withSize(1000 * pattern.size) val buffer = Buffer.withSize(1000 * pattern.size)
for (i in 0 until buffer.size) { for (i in 0 until buffer.size) {
buffer.setI8(i, (pattern[i % pattern.size] + random.nextInt(10)).toByte()) buffer.setByte(i, (pattern[i % pattern.size] + random.nextInt(10)).toByte())
} }
testWithBuffer(buffer) testWithBuffer(buffer)
@ -68,8 +69,8 @@ class PrsDecompressTests {
assertEquals(cursor.size, decompressedCursor.size) assertEquals(cursor.size, decompressedCursor.size)
while (cursor.hasBytesLeft()) { while (cursor.hasBytesLeft()) {
val expected = cursor.i8() val expected = cursor.byte()
val actual = decompressedCursor.i8() val actual = decompressedCursor.byte()
if (expected != actual) { if (expected != actual) {
// Assert after check for performance. // Assert after check for performance.
@ -81,4 +82,32 @@ class PrsDecompressTests {
} }
} }
} }
@Test
fun decompress_towards_the_future() = asyncTest {
prsDecompress(readFile("/quest118_e.bin")).unwrap()
}
@Test
fun compress_and_decompress_towards_the_future() = asyncTest {
val orig = readFile("/quest118_e_decompressed.bin")
val test = prsDecompress(prsCompress(orig)).unwrap()
orig.seekStart(0)
assertEquals(orig.size, test.size)
while (orig.hasBytesLeft()) {
val expected = orig.byte()
val actual = test.byte()
if (expected != actual) {
// Assert after check for performance.
assertEquals(
expected,
actual,
"Got $actual, expected $expected at ${orig.position - 1}."
)
}
}
}
} }

View File

@ -11,38 +11,38 @@ class BufferCursorTests : WritableCursorTests() {
@Test @Test
fun writeU8_increases_size_correctly() { fun writeU8_increases_size_correctly() {
testIntegerWriteSize(1, { writeU8(it.toUByte()) }, Endianness.Little) testIntegerWriteSize(1, { writeUByte(it.toUByte()) }, Endianness.Little)
testIntegerWriteSize(1, { writeU8(it.toUByte()) }, Endianness.Big) testIntegerWriteSize(1, { writeUByte(it.toUByte()) }, Endianness.Big)
} }
@Test @Test
fun writeU16_increases_size_correctly() { fun writeU16_increases_size_correctly() {
testIntegerWriteSize(2, { writeU16(it.toUShort()) }, Endianness.Little) testIntegerWriteSize(2, { writeUShort(it.toUShort()) }, Endianness.Little)
testIntegerWriteSize(2, { writeU16(it.toUShort()) }, Endianness.Big) testIntegerWriteSize(2, { writeUShort(it.toUShort()) }, Endianness.Big)
} }
@Test @Test
fun writeU32_increases_size_correctly() { fun writeU32_increases_size_correctly() {
testIntegerWriteSize(4, { writeU32(it.toUInt()) }, Endianness.Little) testIntegerWriteSize(4, { writeUInt(it.toUInt()) }, Endianness.Little)
testIntegerWriteSize(4, { writeU32(it.toUInt()) }, Endianness.Big) testIntegerWriteSize(4, { writeUInt(it.toUInt()) }, Endianness.Big)
} }
@Test @Test
fun writeI8_increases_size_correctly() { fun writeI8_increases_size_correctly() {
testIntegerWriteSize(1, { writeI8(it.toByte()) }, Endianness.Little) testIntegerWriteSize(1, { writeByte(it.toByte()) }, Endianness.Little)
testIntegerWriteSize(1, { writeI8(it.toByte()) }, Endianness.Big) testIntegerWriteSize(1, { writeByte(it.toByte()) }, Endianness.Big)
} }
@Test @Test
fun writeI16_increases_size_correctly() { fun writeI16_increases_size_correctly() {
testIntegerWriteSize(2, { writeI16(it.toShort()) }, Endianness.Little) testIntegerWriteSize(2, { writeShort(it.toShort()) }, Endianness.Little)
testIntegerWriteSize(2, { writeI16(it.toShort()) }, Endianness.Big) testIntegerWriteSize(2, { writeShort(it.toShort()) }, Endianness.Big)
} }
@Test @Test
fun writeI32_increases_size_correctly() { fun writeI32_increases_size_correctly() {
testIntegerWriteSize(4, { writeI32(it) }, Endianness.Little) testIntegerWriteSize(4, { writeInt(it) }, Endianness.Little)
testIntegerWriteSize(4, { writeI32(it) }, Endianness.Big) testIntegerWriteSize(4, { writeInt(it) }, Endianness.Big)
} }
private fun testIntegerWriteSize( private fun testIntegerWriteSize(

View File

@ -46,46 +46,46 @@ abstract class CursorTests {
val cursor = createCursor(byteArrayOf(1, 2, 3, 4), endianness) val cursor = createCursor(byteArrayOf(1, 2, 3, 4), endianness)
if (endianness == Endianness.Little) { if (endianness == Endianness.Little) {
assertEquals(0x04030201u, cursor.u32()) assertEquals(0x04030201u, cursor.uInt())
} else { } else {
assertEquals(0x01020304u, cursor.u32()) assertEquals(0x01020304u, cursor.uInt())
} }
} }
@Test @Test
fun u8() { fun u8() {
testIntegerRead(1, { u8().toInt() }, Endianness.Little) testIntegerRead(1, { uByte().toInt() }, Endianness.Little)
testIntegerRead(1, { u8().toInt() }, Endianness.Big) testIntegerRead(1, { uByte().toInt() }, Endianness.Big)
} }
@Test @Test
fun u16() { fun u16() {
testIntegerRead(2, { u16().toInt() }, Endianness.Little) testIntegerRead(2, { uShort().toInt() }, Endianness.Little)
testIntegerRead(2, { u16().toInt() }, Endianness.Big) testIntegerRead(2, { uShort().toInt() }, Endianness.Big)
} }
@Test @Test
fun u32() { fun u32() {
testIntegerRead(4, { u32().toInt() }, Endianness.Little) testIntegerRead(4, { uInt().toInt() }, Endianness.Little)
testIntegerRead(4, { u32().toInt() }, Endianness.Big) testIntegerRead(4, { uInt().toInt() }, Endianness.Big)
} }
@Test @Test
fun i8() { fun i8() {
testIntegerRead(1, { i8().toInt() }, Endianness.Little) testIntegerRead(1, { byte().toInt() }, Endianness.Little)
testIntegerRead(1, { i8().toInt() }, Endianness.Big) testIntegerRead(1, { byte().toInt() }, Endianness.Big)
} }
@Test @Test
fun i16() { fun i16() {
testIntegerRead(2, { i16().toInt() }, Endianness.Little) testIntegerRead(2, { short().toInt() }, Endianness.Little)
testIntegerRead(2, { i16().toInt() }, Endianness.Big) testIntegerRead(2, { short().toInt() }, Endianness.Big)
} }
@Test @Test
fun i32() { fun i32() {
testIntegerRead(4, { i32() }, Endianness.Little) testIntegerRead(4, { int() }, Endianness.Little)
testIntegerRead(4, { i32() }, Endianness.Big) testIntegerRead(4, { int() }, Endianness.Big)
} }
/** /**
@ -138,17 +138,17 @@ abstract class CursorTests {
val cursor = createCursor(bytes, endianness) val cursor = createCursor(bytes, endianness)
assertEquals(2.5f, cursor.f32()) assertEquals(2.5f, cursor.float())
assertEquals(4, cursor.position) assertEquals(4, cursor.position)
assertEquals(32.25f, cursor.f32()) assertEquals(32.25f, cursor.float())
assertEquals(8, cursor.position) assertEquals(8, cursor.position)
} }
@Test @Test
fun u8Array() { fun u8Array() {
val read: Cursor.(Int) -> IntArray = { n -> val read: Cursor.(Int) -> IntArray = { n ->
val arr = u8Array(n) val arr = uByteArray(n)
IntArray(n) { arr[it].toInt() } IntArray(n) { arr[it].toInt() }
} }
@ -159,7 +159,7 @@ abstract class CursorTests {
@Test @Test
fun u16Array() { fun u16Array() {
val read: Cursor.(Int) -> IntArray = { n -> val read: Cursor.(Int) -> IntArray = { n ->
val arr = u16Array(n) val arr = uShortArray(n)
IntArray(n) { arr[it].toInt() } IntArray(n) { arr[it].toInt() }
} }
@ -170,7 +170,7 @@ abstract class CursorTests {
@Test @Test
fun u32Array() { fun u32Array() {
val read: Cursor.(Int) -> IntArray = { n -> val read: Cursor.(Int) -> IntArray = { n ->
val arr = u32Array(n) val arr = uIntArray(n)
IntArray(n) { arr[it].toInt() } IntArray(n) { arr[it].toInt() }
} }
@ -181,7 +181,7 @@ abstract class CursorTests {
@Test @Test
fun i32Array() { fun i32Array() {
val read: Cursor.(Int) -> IntArray = { n -> val read: Cursor.(Int) -> IntArray = { n ->
val arr = i32Array(n) val arr = intArray(n)
IntArray(n) { arr[it] } IntArray(n) { arr[it] }
} }
@ -254,10 +254,10 @@ abstract class CursorTests {
assertEquals(6, cursor.position) assertEquals(6, cursor.position)
assertEquals(4, newCursor.size) assertEquals(4, newCursor.size)
assertEquals(3u, newCursor.u8()) assertEquals(3u, newCursor.uByte())
assertEquals(4u, newCursor.u8()) assertEquals(4u, newCursor.uByte())
assertEquals(5u, newCursor.u8()) assertEquals(5u, newCursor.uByte())
assertEquals(6u, newCursor.u8()) assertEquals(6u, newCursor.uByte())
} }
@Test @Test
@ -284,7 +284,7 @@ abstract class CursorTests {
val chars = byteArrayOf(7, 65, 66, 0, (255).toByte(), 13) val chars = byteArrayOf(7, 65, 66, 0, (255).toByte(), 13)
val bytes = ByteArray(chars.size * byteCount) val bytes = ByteArray(chars.size * byteCount)
for (i in 0..chars.size) { for (i in chars.indices) {
if (endianness == Endianness.Little) { if (endianness == Endianness.Little) {
bytes[byteCount * i] = chars[i] bytes[byteCount * i] = chars[i]
} else { } else {
@ -332,9 +332,9 @@ abstract class CursorTests {
assertEquals(6, cursor.position) assertEquals(6, cursor.position)
assertEquals(4, buf.size) assertEquals(4, buf.size)
assertEquals(3u, buf.getU8(0)) assertEquals(3u, buf.getUByte(0))
assertEquals(4u, buf.getU8(1)) assertEquals(4u, buf.getUByte(1))
assertEquals(5u, buf.getU8(2)) assertEquals(5u, buf.getUByte(2))
assertEquals(6u, buf.getU8(3)) assertEquals(6u, buf.getUByte(3))
} }
} }

View File

@ -21,7 +21,7 @@ abstract class WritableCursorTests : CursorTests() {
assertEquals(0, cursor.position) assertEquals(0, cursor.position)
cursor.writeU8(99u).writeU8(99u).writeU8(99u).writeU8(99u) cursor.writeUByte(99u).writeUByte(99u).writeUByte(99u).writeUByte(99u)
cursor.seek(-1) cursor.seek(-1)
assertEquals(cursor.position + cursor.bytesLeft, cursor.size) assertEquals(cursor.position + cursor.bytesLeft, cursor.size)
@ -33,38 +33,38 @@ abstract class WritableCursorTests : CursorTests() {
@Test @Test
fun writeU8() { fun writeU8() {
testIntegerWrite(1, { u8().toInt() }, { writeU8(it.toUByte()) }, Endianness.Little) testIntegerWrite(1, { uByte().toInt() }, { writeUByte(it.toUByte()) }, Endianness.Little)
testIntegerWrite(1, { u8().toInt() }, { writeU8(it.toUByte()) }, Endianness.Big) testIntegerWrite(1, { uByte().toInt() }, { writeUByte(it.toUByte()) }, Endianness.Big)
} }
@Test @Test
fun writeU16() { fun writeU16() {
testIntegerWrite(2, { u16().toInt() }, { writeU16(it.toUShort()) }, Endianness.Little) testIntegerWrite(2, { uShort().toInt() }, { writeUShort(it.toUShort()) }, Endianness.Little)
testIntegerWrite(2, { u16().toInt() }, { writeU16(it.toUShort()) }, Endianness.Big) testIntegerWrite(2, { uShort().toInt() }, { writeUShort(it.toUShort()) }, Endianness.Big)
} }
@Test @Test
fun writeU32() { fun writeU32() {
testIntegerWrite(4, { u32().toInt() }, { writeU32(it.toUInt()) }, Endianness.Little) testIntegerWrite(4, { uInt().toInt() }, { writeUInt(it.toUInt()) }, Endianness.Little)
testIntegerWrite(4, { u32().toInt() }, { writeU32(it.toUInt()) }, Endianness.Big) testIntegerWrite(4, { uInt().toInt() }, { writeUInt(it.toUInt()) }, Endianness.Big)
} }
@Test @Test
fun writeI8() { fun writeI8() {
testIntegerWrite(1, { i8().toInt() }, { writeI8(it.toByte()) }, Endianness.Little) testIntegerWrite(1, { byte().toInt() }, { writeByte(it.toByte()) }, Endianness.Little)
testIntegerWrite(1, { i8().toInt() }, { writeI8(it.toByte()) }, Endianness.Big) testIntegerWrite(1, { byte().toInt() }, { writeByte(it.toByte()) }, Endianness.Big)
} }
@Test @Test
fun writeI16() { fun writeI16() {
testIntegerWrite(2, { i16().toInt() }, { writeI16(it.toShort()) }, Endianness.Little) testIntegerWrite(2, { short().toInt() }, { writeShort(it.toShort()) }, Endianness.Little)
testIntegerWrite(2, { i16().toInt() }, { writeI16(it.toShort()) }, Endianness.Big) testIntegerWrite(2, { short().toInt() }, { writeShort(it.toShort()) }, Endianness.Big)
} }
@Test @Test
fun writeI32() { fun writeI32() {
testIntegerWrite(4, { i32() }, { writeI32(it) }, Endianness.Little) testIntegerWrite(4, { int() }, { writeInt(it) }, Endianness.Little)
testIntegerWrite(4, { i32() }, { writeI32(it) }, Endianness.Big) testIntegerWrite(4, { int() }, { writeInt(it) }, Endianness.Big)
} }
/** /**
@ -104,8 +104,8 @@ abstract class WritableCursorTests : CursorTests() {
private fun writeF32(endianness: Endianness) { private fun writeF32(endianness: Endianness) {
val cursor = createCursor(ByteArray(8), endianness) val cursor = createCursor(ByteArray(8), endianness)
cursor.writeF32(1337.9001f) cursor.writeFloat(1337.9001f)
cursor.writeF32(103.502f) cursor.writeFloat(103.502f)
assertEquals(8, cursor.position) assertEquals(8, cursor.position)
@ -113,8 +113,8 @@ abstract class WritableCursorTests : CursorTests() {
// The read floats won't be exactly the same as the written floats in Kotlin JS, because // The read floats won't be exactly the same as the written floats in Kotlin JS, because
// they're backed by numbers (64-bit floats). // they're backed by numbers (64-bit floats).
assertTrue(abs(1337.9001f - cursor.f32()) < 0.001) assertTrue(abs(1337.9001f - cursor.float()) < 0.001)
assertTrue(abs(103.502f - cursor.f32()) < 0.001) assertTrue(abs(103.502f - cursor.float()) < 0.001)
assertEquals(8, cursor.position) assertEquals(8, cursor.position)
} }
@ -122,11 +122,11 @@ abstract class WritableCursorTests : CursorTests() {
@Test @Test
fun writeU8Array() { fun writeU8Array() {
val read: Cursor.(Int) -> IntArray = { n -> val read: Cursor.(Int) -> IntArray = { n ->
val arr = u8Array(n) val arr = uByteArray(n)
IntArray(n) { arr[it].toInt() } IntArray(n) { arr[it].toInt() }
} }
val write: WritableCursor.(IntArray) -> Unit = { a -> val write: WritableCursor.(IntArray) -> Unit = { a ->
writeU8Array(UByteArray(a.size) { a[it].toUByte() }) writeUByteArray(UByteArray(a.size) { a[it].toUByte() })
} }
testIntegerArrayWrite(1, read, write, Endianness.Little) testIntegerArrayWrite(1, read, write, Endianness.Little)
@ -136,11 +136,11 @@ abstract class WritableCursorTests : CursorTests() {
@Test @Test
fun writeU16Array() { fun writeU16Array() {
val read: Cursor.(Int) -> IntArray = { n -> val read: Cursor.(Int) -> IntArray = { n ->
val arr = u16Array(n) val arr = uShortArray(n)
IntArray(n) { arr[it].toInt() } IntArray(n) { arr[it].toInt() }
} }
val write: WritableCursor.(IntArray) -> Unit = { a -> val write: WritableCursor.(IntArray) -> Unit = { a ->
writeU16Array(UShortArray(a.size) { a[it].toUShort() }) writeUShortArray(UShortArray(a.size) { a[it].toUShort() })
} }
testIntegerArrayWrite(2, read, write, Endianness.Little) testIntegerArrayWrite(2, read, write, Endianness.Little)
@ -150,11 +150,11 @@ abstract class WritableCursorTests : CursorTests() {
@Test @Test
fun writeU32Array() { fun writeU32Array() {
val read: Cursor.(Int) -> IntArray = { n -> val read: Cursor.(Int) -> IntArray = { n ->
val arr = u32Array(n) val arr = uIntArray(n)
IntArray(n) { arr[it].toInt() } IntArray(n) { arr[it].toInt() }
} }
val write: WritableCursor.(IntArray) -> Unit = { a -> val write: WritableCursor.(IntArray) -> Unit = { a ->
writeU32Array(UIntArray(a.size) { a[it].toUInt() }) writeUIntArray(UIntArray(a.size) { a[it].toUInt() })
} }
testIntegerArrayWrite(4, read, write, Endianness.Little) testIntegerArrayWrite(4, read, write, Endianness.Little)
@ -164,10 +164,10 @@ abstract class WritableCursorTests : CursorTests() {
@Test @Test
fun writeI32Array() { fun writeI32Array() {
val read: Cursor.(Int) -> IntArray = { n -> val read: Cursor.(Int) -> IntArray = { n ->
i32Array(n) intArray(n)
} }
val write: WritableCursor.(IntArray) -> Unit = { a -> val write: WritableCursor.(IntArray) -> Unit = { a ->
writeI32Array(a) writeIntArray(a)
} }
testIntegerArrayWrite(4, read, write, Endianness.Little) testIntegerArrayWrite(4, read, write, Endianness.Little)
@ -214,14 +214,14 @@ abstract class WritableCursorTests : CursorTests() {
cursor.seekStart(0) cursor.seekStart(0)
assertEquals(0, cursor.i8()) assertEquals(0, cursor.byte())
assertEquals(0, cursor.i8()) assertEquals(0, cursor.byte())
assertEquals(1, cursor.i8()) assertEquals(1, cursor.byte())
assertEquals(2, cursor.i8()) assertEquals(2, cursor.byte())
assertEquals(3, cursor.i8()) assertEquals(3, cursor.byte())
assertEquals(4, cursor.i8()) assertEquals(4, cursor.byte())
assertEquals(0, cursor.i8()) assertEquals(0, cursor.byte())
assertEquals(0, cursor.i8()) assertEquals(0, cursor.byte())
} }
@Test @Test
@ -233,14 +233,14 @@ abstract class WritableCursorTests : CursorTests() {
private fun write_seek_backwards_then_take(endianness: Endianness) { private fun write_seek_backwards_then_take(endianness: Endianness) {
val cursor = createCursor(ByteArray(16), endianness) val cursor = createCursor(ByteArray(16), endianness)
cursor.writeU32(1u).writeU32(2u).writeU32(3u).writeU32(4u) cursor.writeUInt(1u).writeUInt(2u).writeUInt(3u).writeUInt(4u)
cursor.seek(-8) cursor.seek(-8)
val newCursor = cursor.take(8) val newCursor = cursor.take(8)
assertEquals(16, cursor.position) assertEquals(16, cursor.position)
assertEquals(8, newCursor.size) assertEquals(8, newCursor.size)
assertEquals(0, newCursor.position) assertEquals(0, newCursor.position)
assertEquals(3u, newCursor.u32()) assertEquals(3u, newCursor.uInt())
assertEquals(4u, newCursor.u32()) assertEquals(4u, newCursor.uInt())
} }
} }

Binary file not shown.

View File

@ -30,37 +30,37 @@ actual class Buffer private constructor(
actual val capacity: Int actual val capacity: Int
get() = arrayBuffer.byteLength get() = arrayBuffer.byteLength
actual fun getU8(offset: Int): UByte { actual fun getUByte(offset: Int): UByte {
checkOffset(offset, 1) checkOffset(offset, 1)
return dataView.getUint8(offset).toUByte() return dataView.getUint8(offset).toUByte()
} }
actual fun getU16(offset: Int): UShort { actual fun getUShort(offset: Int): UShort {
checkOffset(offset, 2) checkOffset(offset, 2)
return dataView.getUint16(offset, littleEndian).toUShort() return dataView.getUint16(offset, littleEndian).toUShort()
} }
actual fun getU32(offset: Int): UInt { actual fun getUInt(offset: Int): UInt {
checkOffset(offset, 4) checkOffset(offset, 4)
return dataView.getUint32(offset, littleEndian).toUInt() return dataView.getUint32(offset, littleEndian).toUInt()
} }
actual fun getI8(offset: Int): Byte { actual fun getByte(offset: Int): Byte {
checkOffset(offset, 1) checkOffset(offset, 1)
return dataView.getInt8(offset) return dataView.getInt8(offset)
} }
actual fun getI16(offset: Int): Short { actual fun getShort(offset: Int): Short {
checkOffset(offset, 2) checkOffset(offset, 2)
return dataView.getInt16(offset, littleEndian) return dataView.getInt16(offset, littleEndian)
} }
actual fun getI32(offset: Int): Int { actual fun getInt(offset: Int): Int {
checkOffset(offset, 4) checkOffset(offset, 4)
return dataView.getInt32(offset, littleEndian) return dataView.getInt32(offset, littleEndian)
} }
actual fun getF32(offset: Int): Float { actual fun getFloat(offset: Int): Float {
checkOffset(offset, 4) checkOffset(offset, 4)
return dataView.getFloat32(offset, littleEndian) return dataView.getFloat32(offset, littleEndian)
} }
@ -74,7 +74,7 @@ actual class Buffer private constructor(
val len = maxByteLength / 2 val len = maxByteLength / 2
for (i in 0 until len) { for (i in 0 until len) {
val codePoint = getU16(offset + i * 2) val codePoint = getUShort(offset + i * 2)
if (nullTerminated && codePoint == ZERO_U16) { if (nullTerminated && codePoint == ZERO_U16) {
break break
@ -92,53 +92,53 @@ actual class Buffer private constructor(
) )
} }
actual fun setU8(offset: Int, value: UByte): Buffer { actual fun setUByte(offset: Int, value: UByte): Buffer {
checkOffset(offset, 1) checkOffset(offset, 1)
dataView.setUint8(offset, value.toByte()) dataView.setUint8(offset, value.toByte())
return this return this
} }
actual fun setU16(offset: Int, value: UShort): Buffer { actual fun setUShort(offset: Int, value: UShort): Buffer {
checkOffset(offset, 2) checkOffset(offset, 2)
dataView.setUint16(offset, value.toShort(), littleEndian) dataView.setUint16(offset, value.toShort(), littleEndian)
return this return this
} }
actual fun setU32(offset: Int, value: UInt): Buffer { actual fun setUInt(offset: Int, value: UInt): Buffer {
checkOffset(offset, 4) checkOffset(offset, 4)
dataView.setUint32(offset, value.toInt(), littleEndian) dataView.setUint32(offset, value.toInt(), littleEndian)
return this return this
} }
actual fun setI8(offset: Int, value: Byte): Buffer { actual fun setByte(offset: Int, value: Byte): Buffer {
checkOffset(offset, 1) checkOffset(offset, 1)
dataView.setInt8(offset, value) dataView.setInt8(offset, value)
return this return this
} }
actual fun setI16(offset: Int, value: Short): Buffer { actual fun setShort(offset: Int, value: Short): Buffer {
checkOffset(offset, 2) checkOffset(offset, 2)
dataView.setInt16(offset, value, littleEndian) dataView.setInt16(offset, value, littleEndian)
return this return this
} }
actual fun setI32(offset: Int, value: Int): Buffer { actual fun setInt(offset: Int, value: Int): Buffer {
checkOffset(offset, 4) checkOffset(offset, 4)
dataView.setInt32(offset, value, littleEndian) dataView.setInt32(offset, value, littleEndian)
return this return this
} }
actual fun setF32(offset: Int, value: Float): Buffer { actual fun setFloat(offset: Int, value: Float): Buffer {
checkOffset(offset, 4) checkOffset(offset, 4)
dataView.setFloat32(offset, value, littleEndian) dataView.setFloat32(offset, value, littleEndian)
return this return this
} }
actual fun zero(): Buffer = actual fun zero(): Buffer =
fill(0) fillByte(0)
actual fun fill(value: Byte): Buffer { actual fun fillByte(value: Byte): Buffer {
(Int8Array(arrayBuffer).asDynamic()).fill(value) (Int8Array(arrayBuffer, 0, size).asDynamic()).fill(value)
return this return this
} }
@ -156,16 +156,16 @@ actual class Buffer private constructor(
*/ */
private fun ensureCapacity(minNewSize: Int) { private fun ensureCapacity(minNewSize: Int) {
if (minNewSize > capacity) { if (minNewSize > capacity) {
var newSize = if (capacity == 0) minNewSize else capacity; var newSize = if (capacity == 0) minNewSize else capacity
do { do {
newSize *= 2; newSize *= 2
} while (newSize < minNewSize); } while (newSize < minNewSize)
val newBuffer = ArrayBuffer(newSize); val newBuffer = ArrayBuffer(newSize)
Uint8Array(newBuffer).set(Uint8Array(arrayBuffer, 0, size)); Uint8Array(newBuffer).set(Uint8Array(arrayBuffer, 0, size))
arrayBuffer = newBuffer; arrayBuffer = newBuffer
dataView = DataView(arrayBuffer); dataView = DataView(arrayBuffer)
} }
} }

View File

@ -35,56 +35,56 @@ class ArrayBufferCursor(
littleEndian = value == Endianness.Little littleEndian = value == Endianness.Little
} }
override fun u8(): UByte { override fun uByte(): UByte {
requireSize(1) requireSize(1)
val r = dv.getUint8(absolutePosition) val r = dv.getUint8(absolutePosition)
position++ position++
return r.toUByte() return r.toUByte()
} }
override fun u16(): UShort { override fun uShort(): UShort {
requireSize(2) requireSize(2)
val r = dv.getUint16(absolutePosition, littleEndian) val r = dv.getUint16(absolutePosition, littleEndian)
position += 2 position += 2
return r.toUShort() return r.toUShort()
} }
override fun u32(): UInt { override fun uInt(): UInt {
requireSize(4) requireSize(4)
val r = dv.getUint32(absolutePosition, littleEndian) val r = dv.getUint32(absolutePosition, littleEndian)
position += 4 position += 4
return r.toUInt() return r.toUInt()
} }
override fun i8(): Byte { override fun byte(): Byte {
requireSize(1) requireSize(1)
val r = dv.getInt8(absolutePosition) val r = dv.getInt8(absolutePosition)
position++ position++
return r return r
} }
override fun i16(): Short { override fun short(): Short {
requireSize(2) requireSize(2)
val r = dv.getInt16(absolutePosition, littleEndian) val r = dv.getInt16(absolutePosition, littleEndian)
position += 2 position += 2
return r return r
} }
override fun i32(): Int { override fun int(): Int {
requireSize(4) requireSize(4)
val r = dv.getInt32(absolutePosition, littleEndian) val r = dv.getInt32(absolutePosition, littleEndian)
position += 4 position += 4
return r return r
} }
override fun f32(): Float { override fun float(): Float {
requireSize(4) requireSize(4)
val r = dv.getFloat32(absolutePosition, littleEndian) val r = dv.getFloat32(absolutePosition, littleEndian)
position += 4 position += 4
return r return r
} }
override fun u8Array(n: Int): UByteArray { override fun uByteArray(n: Int): UByteArray {
requireSize(n) requireSize(n)
val array = UByteArray(n) val array = UByteArray(n)
@ -97,7 +97,7 @@ class ArrayBufferCursor(
return array return array
} }
override fun u16Array(n: Int): UShortArray { override fun uShortArray(n: Int): UShortArray {
requireSize(2 * n) requireSize(2 * n)
val array = UShortArray(n) val array = UShortArray(n)
@ -110,7 +110,7 @@ class ArrayBufferCursor(
return array return array
} }
override fun u32Array(n: Int): UIntArray { override fun uIntArray(n: Int): UIntArray {
requireSize(4 * n) requireSize(4 * n)
val array = UIntArray(n) val array = UIntArray(n)
@ -123,7 +123,7 @@ class ArrayBufferCursor(
return array return array
} }
override fun i32Array(n: Int): IntArray { override fun intArray(n: Int): IntArray {
requireSize(4 * n) requireSize(4 * n)
val array = IntArray(n) val array = IntArray(n)
@ -153,49 +153,49 @@ class ArrayBufferCursor(
return r return r
} }
override fun writeU8(value: UByte): WritableCursor { override fun writeUByte(value: UByte): WritableCursor {
requireSize(1) requireSize(1)
dv.setUint8(absolutePosition, value.toByte()) dv.setUint8(absolutePosition, value.toByte())
position++ position++
return this return this
} }
override fun writeU16(value: UShort): WritableCursor { override fun writeUShort(value: UShort): WritableCursor {
requireSize(2) requireSize(2)
dv.setUint16(absolutePosition, value.toShort(), littleEndian) dv.setUint16(absolutePosition, value.toShort(), littleEndian)
position += 2 position += 2
return this return this
} }
override fun writeU32(value: UInt): WritableCursor { override fun writeUInt(value: UInt): WritableCursor {
requireSize(4) requireSize(4)
dv.setUint32(absolutePosition, value.toInt(), littleEndian) dv.setUint32(absolutePosition, value.toInt(), littleEndian)
position += 4 position += 4
return this return this
} }
override fun writeI8(value: Byte): WritableCursor { override fun writeByte(value: Byte): WritableCursor {
requireSize(1) requireSize(1)
dv.setInt8(absolutePosition, value) dv.setInt8(absolutePosition, value)
position++ position++
return this return this
} }
override fun writeI16(value: Short): WritableCursor { override fun writeShort(value: Short): WritableCursor {
requireSize(2) requireSize(2)
dv.setInt16(absolutePosition, value, littleEndian) dv.setInt16(absolutePosition, value, littleEndian)
position += 2 position += 2
return this return this
} }
override fun writeI32(value: Int): WritableCursor { override fun writeInt(value: Int): WritableCursor {
requireSize(4) requireSize(4)
dv.setInt32(absolutePosition, value, littleEndian) dv.setInt32(absolutePosition, value, littleEndian)
position += 4 position += 4
return this return this
} }
override fun writeF32(value: Float): WritableCursor { override fun writeFloat(value: Float): WritableCursor {
requireSize(4) requireSize(4)
dv.setFloat32(absolutePosition, value, littleEndian) dv.setFloat32(absolutePosition, value, littleEndian)
position += 4 position += 4

View File

@ -12,7 +12,10 @@ actual fun asyncTest(block: suspend () -> Unit): dynamic = GlobalScope.promise {
actual suspend fun readFile(path: String): Cursor { actual suspend fun readFile(path: String): Cursor {
return window.fetch(path) return window.fetch(path)
.then { it.arrayBuffer() } .then {
require(it.ok) { """Couldn't load resource "$path".""" }
it.arrayBuffer()
}
.then { ArrayBufferCursor(it, Endianness.Little) } .then { ArrayBufferCursor(it, Endianness.Little) }
.await() .await()
} }

View File

@ -0,0 +1,188 @@
package world.phantasmal.lib.buffer
import world.phantasmal.lib.Endianness
import java.nio.ByteBuffer
import java.nio.ByteOrder
actual class Buffer private constructor(
private var buf: ByteBuffer,
size: Int,
endianness: Endianness,
) {
actual var size: Int = size
set(value) {
ensureCapacity(value)
field = value
}
actual var endianness: Endianness
get() = if (buf.order() == ByteOrder.LITTLE_ENDIAN) Endianness.Little else Endianness.Big
set(value) {
buf.order(
if (value == Endianness.Little) ByteOrder.LITTLE_ENDIAN else ByteOrder.BIG_ENDIAN
)
}
actual val capacity: Int
get() = buf.capacity()
init {
this.endianness = endianness
}
actual fun getUByte(offset: Int): UByte {
checkOffset(offset, 1)
return buf.get(offset).toUByte()
}
actual fun getUShort(offset: Int): UShort {
checkOffset(offset, 2)
return buf.getShort(offset).toUShort()
}
actual fun getUInt(offset: Int): UInt {
checkOffset(offset, 4)
return buf.getInt(offset).toUInt()
}
actual fun getByte(offset: Int): Byte {
checkOffset(offset, 1)
return buf.get(offset)
}
actual fun getShort(offset: Int): Short {
checkOffset(offset, 2)
return buf.getShort(offset)
}
actual fun getInt(offset: Int): Int {
checkOffset(offset, 4)
return buf.getInt(offset)
}
actual fun getFloat(offset: Int): Float {
checkOffset(offset, 4)
return buf.getFloat(offset)
}
actual fun getStringUtf16(
offset: Int,
maxByteLength: Int,
nullTerminated: Boolean,
): String =
buildString {
val len = maxByteLength / 2
for (i in 0 until len) {
val codePoint = buf.getChar(offset + i * 2)
if (nullTerminated && codePoint == '0') {
break
}
append(codePoint)
}
}
actual fun slice(offset: Int, size: Int): Buffer {
checkOffset(offset, size)
return fromByteArray(
buf.array().copyInto(ByteArray(size), 0, offset, (offset + size)),
endianness
)
}
actual fun setUByte(offset: Int, value: UByte): Buffer {
checkOffset(offset, 1)
buf.put(offset, value.toByte())
return this
}
actual fun setUShort(offset: Int, value: UShort): Buffer {
checkOffset(offset, 2)
buf.putShort(offset, value.toShort())
return this
}
actual fun setUInt(offset: Int, value: UInt): Buffer {
checkOffset(offset, 4)
buf.putInt(offset, value.toInt())
return this
}
actual fun setByte(offset: Int, value: Byte): Buffer {
checkOffset(offset, 1)
buf.put(offset, value)
return this
}
actual fun setShort(offset: Int, value: Short): Buffer {
checkOffset(offset, 2)
buf.putShort(offset, value)
return this
}
actual fun setInt(offset: Int, value: Int): Buffer {
checkOffset(offset, 4)
buf.putInt(offset, value)
return this
}
actual fun setFloat(offset: Int, value: Float): Buffer {
checkOffset(offset, 4)
buf.putFloat(offset, value)
return this
}
actual fun zero(): Buffer =
fillByte(0)
actual fun fillByte(value: Byte): Buffer {
for (i in 0 until size) {
buf.put(i, value)
}
return this
}
/**
* Checks whether we can read [size] bytes at [offset].
*/
private fun checkOffset(offset: Int, size: Int) {
require(offset >= 0 && offset + size <= this.size) {
"Offset $offset is out of bounds."
}
}
/**
* Reallocates the underlying ArrayBuffer if necessary.
*/
private fun ensureCapacity(minNewSize: Int) {
if (minNewSize > capacity) {
var newSize = if (capacity == 0) minNewSize else capacity
do {
newSize *= 2
} while (newSize < minNewSize)
val newBuf = ByteBuffer.allocate(newSize)
newBuf.put(buf.array())
buf = newBuf
}
}
actual companion object {
actual fun withCapacity(
initialCapacity: Int,
endianness: Endianness,
): Buffer =
Buffer(ByteBuffer.allocate(initialCapacity), size = 0, endianness)
actual fun withSize(initialSize: Int, endianness: Endianness): Buffer =
Buffer(ByteBuffer.allocate(initialSize), initialSize, endianness)
actual fun fromByteArray(array: ByteArray, endianness: Endianness): Buffer {
return Buffer(ByteBuffer.wrap(array), array.size, endianness)
}
}
}

View File

@ -0,0 +1,22 @@
@file:JvmName("TestUtilsJvm")
package world.phantasmal.lib.test
import kotlinx.coroutines.runBlocking
import world.phantasmal.lib.buffer.Buffer
import world.phantasmal.lib.cursor.Cursor
import world.phantasmal.lib.cursor.cursor
actual fun asyncTest(block: suspend () -> Unit) {
runBlocking { block() }
}
actual suspend fun readFile(path: String): Cursor {
val stream = {}::class.java.getResourceAsStream(path)
?: error("""Couldn't load resource "$path".""")
stream.use {
@Suppress("BlockingMethodInNonBlockingContext")
return Buffer.fromByteArray(it.readAllBytes()).cursor()
}
}

View File

@ -2,7 +2,7 @@ package world.phantasmal.observable.value
import world.phantasmal.core.disposable.Disposable import world.phantasmal.core.disposable.Disposable
import world.phantasmal.core.disposable.disposable import world.phantasmal.core.disposable.disposable
import world.phantasmal.core.fastCast import world.phantasmal.core.unsafeToNonNull
/** /**
* Starts observing its dependencies when the first observer on this val is registered. Stops * Starts observing its dependencies when the first observer on this val is registered. Stops
@ -25,7 +25,7 @@ abstract class DependentVal<T>(
_value = computeValue() _value = computeValue()
} }
return _value.fastCast() return _value.unsafeToNonNull()
} }
override fun observe(callNow: Boolean, observer: ValObserver<T>): Disposable { override fun observe(callNow: Boolean, observer: ValObserver<T>): Disposable {
@ -37,7 +37,7 @@ abstract class DependentVal<T>(
_value = computeValue() _value = computeValue()
if (_value != oldValue) { if (_value != oldValue) {
emit(oldValue.fastCast()) emit(oldValue.unsafeToNonNull())
} }
} }
) )

View File

@ -2,7 +2,7 @@ package world.phantasmal.observable.value
import world.phantasmal.core.disposable.Disposable import world.phantasmal.core.disposable.Disposable
import world.phantasmal.core.disposable.disposable import world.phantasmal.core.disposable.disposable
import world.phantasmal.core.fastCast import world.phantasmal.core.unsafeToNonNull
class FlatTransformedVal<T>( class FlatTransformedVal<T>(
dependencies: Iterable<Val<*>>, dependencies: Iterable<Val<*>>,
@ -16,7 +16,7 @@ class FlatTransformedVal<T>(
return if (hasNoObservers()) { return if (hasNoObservers()) {
super.value super.value
} else { } else {
computedVal.fastCast<Val<T>>().value computedVal.unsafeToNonNull<Val<T>>().value
} }
} }
@ -42,7 +42,7 @@ class FlatTransformedVal<T>(
if (hasObservers()) { if (hasObservers()) {
computedValObserver = computedVal.observe { (value) -> computedValObserver = computedVal.observe { (value) ->
val oldValue = _value.fastCast<T>() val oldValue = _value.unsafeToNonNull<T>()
_value = value _value = value
emit(oldValue) emit(oldValue)
} }

View File

@ -2,7 +2,7 @@ package world.phantasmal.observable.value.list
import world.phantasmal.core.disposable.Disposable import world.phantasmal.core.disposable.Disposable
import world.phantasmal.core.disposable.disposable import world.phantasmal.core.disposable.disposable
import world.phantasmal.core.fastCast import world.phantasmal.core.unsafeToNonNull
import world.phantasmal.observable.value.AbstractVal import world.phantasmal.observable.value.AbstractVal
import world.phantasmal.observable.value.ValObserver import world.phantasmal.observable.value.ValObserver
@ -19,7 +19,7 @@ class FoldedVal<T, R>(
return if (dependencyDisposable == null) { return if (dependencyDisposable == null) {
computeValue() computeValue()
} else { } else {
internalValue.fastCast() internalValue.unsafeToNonNull()
} }
} }
@ -32,7 +32,7 @@ class FoldedVal<T, R>(
dependencyDisposable = dependency.observe { dependencyDisposable = dependency.observe {
val oldValue = internalValue val oldValue = internalValue
internalValue = computeValue() internalValue = computeValue()
emit(oldValue.fastCast()) emit(oldValue.unsafeToNonNull())
} }
} }