This commit is contained in:
2025-09-22 13:02:35 +02:00
parent c32b20e5b3
commit 5b95c4dd64
41 changed files with 998 additions and 152 deletions

View File

@@ -11,7 +11,7 @@ repositories {
}
dependencies {
implementation("me.alex_s168:blitz:0.24.1")
implementation("me.alex_s168:blitz:0.25")
}
```

View File

@@ -2,10 +2,11 @@ plugins {
kotlin("jvm") version "1.9.21"
application
`maven-publish`
id("org.jetbrains.dokka") version "2.0.0"
}
group = "me.alex_s168"
version = "0.24.1"
version = "0.25"
repositories {
mavenCentral()

View File

@@ -1,5 +1,7 @@
package blitz
import blitz.collections.RefVec
fun interface DeferScope {
fun defer(block: () -> Unit)
}
@@ -28,9 +30,9 @@ interface ExecutionScope: DeferScope {
}
fun <R> resourceScoped(block: ExecutionScope.() -> R): R {
val defer = mutableListOf<() -> Unit>()
val onError = mutableListOf<() -> Unit>()
val scope = ExecutionScope.create(defer::add, onError::add) {
val defer = RefVec<() -> Unit>()
val onError = RefVec<() -> Unit>()
val scope = ExecutionScope.create(defer::pushBack, onError::pushBack) {
throw Exception("Manual error triggered")
}
val ex: Exception

View File

@@ -1,14 +1,19 @@
package blitz
import blitz.annotations.DontAccessManually
@Suppress("UNCHECKED_CAST", "NOTHING_TO_INLINE")
class Either<A: Any, B: Any> private constructor(
aIn: A?, bIn: B?
) {
/** DO NOT SET MANUALLY!!! */
@JvmField
@DontAccessManually
var a: A? = aIn
/** DO NOT SET MANUALLY!!! */
@JvmField
@DontAccessManually
var b: B? = bIn
override fun equals(other: Any?): Boolean =
@@ -42,7 +47,10 @@ class Either<A: Any, B: Any> private constructor(
}
}
@Suppress("NOTHING_TO_INLINE")
inline fun <A: Any, B: Any> Either<A, B>.isA() = a != null
@Suppress("NOTHING_TO_INLINE")
inline fun <A: Any, B: Any> Either<A, B>.isB() = b != null
inline fun <A: Any, B: Any> Either<A, B>.getAOr(prov: Provider<A>): A =

View File

@@ -3,17 +3,30 @@ package blitz
import java.nio.ByteBuffer
import java.nio.ByteOrder
enum class Endian {
LITTLE,
BIG
;
@JvmInline
value class Endian private constructor(val little: Boolean) {
companion object {
@Deprecated("Renamed", replaceWith = ReplaceWith("Little"))
val LITTLE = Endian(little = true)
@JvmStatic
val Little = Endian(little = true)
infix fun encodeLittle(little: ByteArray) =
if (this == BIG) little.reversedArray()
@Deprecated("Renamed", replaceWith = ReplaceWith("Big"))
val BIG = Endian(little = false)
@JvmStatic
val Big = Endian(little = false)
}
inline val big get() = !little
@Suppress("NOTHING_TO_INLINE")
inline infix fun encodeLittle(little: ByteArray) =
if (this.big) little.reversedArray()
else little
fun toNIO(): ByteOrder =
if (this == LITTLE) ByteOrder.LITTLE_ENDIAN
@Suppress("NOTHING_TO_INLINE")
inline fun toNIO(): ByteOrder =
if (this.little) ByteOrder.LITTLE_ENDIAN
else ByteOrder.BIG_ENDIAN
}
@@ -22,8 +35,12 @@ fun ByteBuffer.order(endian: Endian): ByteBuffer =
fun Long.toBytes(endian: Endian) =
endian encodeLittle
toInt().toBytes(Endian.LITTLE) +
toInt().shr(32).toBytes(Endian.LITTLE)
toInt().toBytes(Endian.Little) +
toInt().shr(32).toBytes(Endian.Little)
@Suppress("NOTHING_TO_INLINE")
inline fun ULong.toBytes(endian: Endian) =
toLong().toBytes(endian)
fun Int.toBytes(endian: Endian) =
endian encodeLittle byteArrayOf(
@@ -33,19 +50,22 @@ fun Int.toBytes(endian: Endian) =
this.shr(24).and(0xFF).toByte()
)
fun UInt.toBytes(endian: Endian) =
@Suppress("NOTHING_TO_INLINE")
inline fun UInt.toBytes(endian: Endian) =
toInt().toBytes(endian)
fun Short.toBytes(endian: Endian) =
endian encodeLittle toInt().toBytes(Endian.LITTLE).copyOf(2)
endian encodeLittle toInt().toBytes(Endian.Little).copyOf(2)
fun UShort.toBytes(endian: Endian) =
@Suppress("NOTHING_TO_INLINE")
inline fun UShort.toBytes(endian: Endian) =
toShort().toBytes(endian)
fun Byte.toBytes() =
byteArrayOf(this)
fun UByte.toBytes() =
@Suppress("NOTHING_TO_INLINE")
inline fun UByte.toBytes() =
toByte().toBytes()
// TODO: no cheat
@@ -68,8 +88,10 @@ fun ByteArray.toLong(endian: Endian) =
fun ByteArray.toULong(endian: Endian) =
toLong(endian).toULong()
fun ByteArray.toByte() =
@Suppress("NOTHING_TO_INLINE")
inline fun ByteArray.toByte() =
this[0]
fun ByteArray.toUByte() =
@Suppress("NOTHING_TO_INLINE")
inline fun ByteArray.toUByte() =
this[0].toUByte()

View File

@@ -1,13 +0,0 @@
package blitz
@Deprecated(
level = DeprecationLevel.ERROR,
message = "Will be removed in the future!",
replaceWith = ReplaceWith(
"Terminal.warn",
"blitz.term.Terminal"
)
)
fun warn(msg: String) {
System.err.println(msg)
}

View File

@@ -1,87 +0,0 @@
package blitz
import blitz.collections.selfInitializingSequence
class OperationChain<I, O> private constructor(
private val impl: Impl = Impl()
) {
private var until = 0
private class Impl {
val seqe = mutableListOf<(Sequence<Any?>) -> Sequence<Any?>>()
var finalized = false
fun add(op: Operator<*, *>) {
seqe += { seq: Sequence<Any?> ->
seq.map(op as Operator<Any?, Any?>)
}
}
fun addFlat(op: Operator<*, Sequence<*>>) {
seqe += { seq: Sequence<Any?> ->
seq.flatMap(op as Operator<Any?, Sequence<Any?>>)
}
}
}
fun <NO> map(op: Operator<O, NO>): OperationChain<I, NO> =
OperationChain<I, NO>(impl.also { it.add(op) })
.also { it.until = this.until + 1 }
fun <NO> flatMap(op: Operator<O, Sequence<NO>>): OperationChain<I, NO> =
OperationChain<I, NO>(impl.also { it.addFlat(op) })
.also { it.until = this.until + 1 }
fun <NO> map(op: OperationChain<O, NO>): OperationChain<I, NO> {
if (!op.impl.finalized)
throw Exception("Can not map un-finalized operation chain onto operation chain!")
return flatMap(op::process)
}
fun <NO> modifier(op: Operator<Sequence<O>, Sequence<NO>>): OperationChain<I, NO> =
OperationChain<I, NO>(impl.also { it.seqe.add(op as (Sequence<Any?>) -> Sequence<Any?>) })
.also { it.until = this.until + 1 }
fun finalize(): OperationChain<I, O> {
if (impl.finalized)
throw Exception("Can't finalize a finalized OperationChain!")
impl.finalized = true
return this
}
fun process(v: I): Sequence<O> =
selfInitializingSequence {
var seq = sequenceOf<Any?>(v)
impl.seqe
.asSequence()
.take(until)
.forEach { op ->
seq = op(seq)
}
seq as Sequence<O>
}
fun processAll(v: Sequence<I>): Sequence<O> =
v.flatMap { process(it) }
companion object {
internal fun <I> create(): OperationChain<I, I> =
OperationChain()
}
}
fun <T, O> Sequence<T>.map(chain: OperationChain<T, O>): Sequence<O> =
chain.processAll(this)
fun <I> chain(): OperationChain<I, I> =
OperationChain.create()
fun <I, O> OperationChain<I, O>.chunked(size: Int): OperationChain<I, List<O>> =
modifier { it.chunked(size) }
fun <I, O, R> OperationChain<I, O>.chunked(size: Int, transform: (List<O>) -> R): OperationChain<I, R> =
modifier { it.chunked(size, transform) }
fun <I, O> OperationChain<I, O>.filter(predicate: (O) -> Boolean): OperationChain<I, O> =
modifier { it.filter(predicate) }

View File

@@ -5,6 +5,7 @@ data class SwitchCase<C, T: Any, R>(
val then: (T) -> R,
)
@Suppress("NOTHING_TO_INLINE")
inline infix fun <C, T: Any, R> ((C)->Pair<Boolean, T?>).case(noinline then: (T) -> R) =
SwitchCase(this, then)

View File

@@ -1,5 +1,3 @@
package blitz
typealias Provider<T> = () -> T
typealias Operator<I, O> = (I) -> O
typealias Provider<T> = () -> T

View File

@@ -1,7 +1,7 @@
package blitz
fun unreachable(): Nothing =
error("this should be unreachable")
throw UnsupportedOperationException("this should be unreachable")
inline fun <reified R> Any?.cast(): R? =
this?.let { if (it is R) it else null }
this?.let { it as? R }

View File

@@ -0,0 +1,4 @@
package blitz.annotations
@Retention(AnnotationRetention.SOURCE)
annotation class DontAccessManually

View File

@@ -0,0 +1,25 @@
package blitz.annotations
/**
* The target class is mutable.
* Mutable methods should be annotated with [Mutate]
*
* @see Immutable
*/
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.SOURCE)
annotation class Mutable
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.SOURCE)
annotation class Mutate
/**
* The target class has no mutability that can be detected from outside.
* It can have for example a mutable cache, but it should not affect the values of future reads.
*
* @see Mutable
*/
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.SOURCE)
annotation class Immutable

View File

@@ -135,7 +135,7 @@ class ByteVec(private val initCap: Int = 0): Vec<Byte>, ByteBatchSequence {
array[index] = value
}
override fun idx(value: Byte): Int =
override fun indexOf(value: Byte): Int =
array.indexOf(value)
companion object {

View File

@@ -135,7 +135,7 @@ class CharVec(private val initCap: Int = 0): Vec<Char>, BatchSequence<Char> {
array[index] = value
}
override fun idx(value: Char): Int =
override fun indexOf(value: Char): Int =
array.indexOf(value)
companion object {

View File

@@ -3,6 +3,8 @@ package blitz.collections
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract
// TODO: rewrite to math package
@JvmInline
@OptIn(ExperimentalUnsignedTypes::class)
value class Dense16x16BoolMap(

View File

@@ -132,7 +132,7 @@ class IntVec(private val initCap: Int = 0): Vec<Int>, BatchSequence<Int> {
array[index] = value
}
override fun idx(value: Int): Int =
override fun indexOf(value: Int): Int =
array.indexOf(value)
companion object {

View File

@@ -0,0 +1,3 @@
package blitz.collections
typealias Iter<T> = ((T) -> Unit) -> Unit

View File

@@ -33,6 +33,7 @@ fun <T> generateIterator(fn: () -> T?): Iterator<T> =
/**
* Can't explain this function. Look at the source of [blitz.parse.tokenize] as an example
*/
@Deprecated("tf is this")
fun <T, R> Iterator<T>.funnyMap(fn: (UnGettableIterator<T>) -> R?): Iterator<R> {
val iter = asUnGettable()
return generateIterator { fn(iter) }
@@ -50,4 +51,17 @@ fun <T> Iterator<T>.collect(to: Vec<T>): Vec<T> {
to.pushBack(it)
}
return to
}
inline fun <T> Iterator<T>.collect(crossinline consumer: (T) -> Unit) {
forEachRemaining {
consumer(it)
}
}
fun <T> Iter<T>.collect(to: Vec<T>): Vec<T> {
this {
to.pushBack(it)
}
return to
}

View File

@@ -132,7 +132,7 @@ class LongVec(private val initCap: Int = 0): Vec<Long>, BatchSequence<Long> {
array[index] = value
}
override fun idx(value: Long): Int =
override fun indexOf(value: Long): Int =
array.indexOf(value)
companion object {

View File

@@ -1,8 +1,12 @@
package blitz.collections
import blitz.annotations.Mutable
import blitz.str.MutMultiLineString
import kotlin.math.min
// TODO: rewrite to Array2o
@Mutable
class Matrix<T>(
val width: Int,
val height: Int,

View File

@@ -1,6 +1,6 @@
package blitz.collections
@Suppress("UNCHECKED_CAST")
@Suppress("UNCHECKED_CAST", "NOTHING_TO_INLINE")
class RefVec<T>(private val initCap: Int = 0): Vec<T> {
override var size = 0
@JvmField var _cap = initCap
@@ -23,7 +23,7 @@ class RefVec<T>(private val initCap: Int = 0): Vec<T> {
inline fun copyIntoArray(arr: Array<Any?>, destOff: Int = 0, startOff: Int = 0) =
_array?.copyInto(arr, destOff, startOff, size)
override inline fun copy(): RefVec<T> =
override fun copy(): RefVec<T> =
RefVec<T>(size).also {
it._array?.let { copyIntoArray(it) }
}
@@ -101,7 +101,7 @@ class RefVec<T>(private val initCap: Int = 0): Vec<T> {
inline fun <R> map(fn: (T) -> R): MutableList<R> =
MutableList(size) { fn(this[it]) }
override fun idx(value: T): Int =
override fun indexOf(value: T): Int =
_array?.indexOf(value) ?: -1
companion object {

View File

@@ -132,7 +132,7 @@ class ShortVec(private val initCap: Int = 0): Vec<Short>, BatchSequence<Short> {
array[index] = value
}
override fun idx(value: Short): Int =
override fun indexOf(value: Short): Int =
array.indexOf(value)
companion object {

View File

@@ -1,5 +1,6 @@
package blitz.collections
@Deprecated("Results in unreadable code. Use custom tree instead")
class Tree<T> {
var root: Node<T>? = null
@@ -27,6 +28,7 @@ class Tree<T> {
}
}
@Deprecated("Results in unreadable code. Use custom tree instead")
class Node<T>(
var value: T? = null,
var children: MutableList<Node<T>> = mutableListOf()
@@ -74,6 +76,7 @@ class Node<T>(
}
}
@Deprecated("Results in unreadable code. Use custom tree instead")
interface TreeTraverser<T> {
fun process(node: Node<T>): Boolean /* true if continue, false if stop */

View File

@@ -38,7 +38,7 @@ interface Vec<T>: IndexableSequence<T> {
fun clear()
fun idx(value: T): Int {
fun indexOf(value: T): Int {
for (i in 0 until size) {
if (this[i] == value)
return i

View File

@@ -0,0 +1,57 @@
package blitz.math
data class Aabb3f(
@JvmField val min: Vec3f,
@JvmField val max: Vec3f,
) {
inline val height get() = max.y - min.y
inline val width get() = max.x - min.x
inline val depth get() = max.z - min.z
companion object {
fun from(other: Aabb3s) =
Aabb3f(
Vec3f(other.min.x.toFloat(), other.min.y.toFloat(), other.min.z.toFloat()),
Vec3f(other.max.x.toFloat(), other.max.y.toFloat(), other.max.z.toFloat())
)
}
/**
* A vector representing the size of the box. (width, height, depth)
*/
val size: Vec3f get() =
Vec3f(width, height, depth)
operator fun contains(point: Vec3f) =
point.x >= min.x && point.x <= max.x &&
point.y >= min.y && point.y <= max.y &&
point.z >= min.z && point.z <= max.z
operator fun rangeTo(other: Aabb3f): Aabb3f =
Aabb3f(
min = Vec3f(
minOf(min.x, other.min.x),
minOf(min.y, other.min.y),
minOf(min.z, other.min.z)
),
max = Vec3f(
maxOf(max.x, other.max.x),
maxOf(max.y, other.max.y),
maxOf(max.z, other.max.z)
)
)
operator fun rangeTo(other: Vec3f) =
Aabb3f(
min = Vec3f(
minOf(min.x, other.x),
minOf(min.y, other.y),
minOf(min.z, other.z)
),
max = Vec3f(
maxOf(max.x, other.x),
maxOf(max.y, other.y),
maxOf(max.z, other.z)
)
)
}

View File

@@ -0,0 +1,90 @@
package blitz.math
import blitz.Endian
import blitz.annotations.Immutable
import blitz.toBytes
import blitz.toLong
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
@Immutable
@Suppress("NOTHING_TO_INLINE")
@OptIn(ExperimentalContracts::class)
class Aabb3s private constructor(
val min: Vec3s,
val max: Vec3s
) {
inline fun copy(min: Vec3s = this.min, max: Vec3s = this.max) =
of(min, max)
override fun equals(other: Any?): Boolean =
other is Aabb3s && other.min == min && other.max == max
override fun hashCode(): Int =
31 * min.hashCode() + max.hashCode()
override fun toString(): String =
"($min, $max)"
operator fun contains(point: Vec3s): Boolean =
point.x in min.x..max.x && point.y in min.y..max.y && point.z in min.z..max.z
operator fun contains(other: Aabb3s): Boolean =
contains(other.min) && contains(other.max)
operator fun rangeTo(include: Vec3s): Aabb3s {
val actualMin = Vec3s(minOf(min.x, include.x), minOf(min.y, include.y), minOf(min.z, include.z))
val actualMax = Vec3s(maxOf(max.x, include.x), maxOf(max.y, include.y), maxOf(max.z, include.z))
return Aabb3s(actualMin, actualMax)
}
operator fun rangeTo(include: Aabb3s): Aabb3s {
val actualMin = Vec3s(minOf(min.x, include.min.x), minOf(min.y, include.min.y), minOf(min.z, include.min.z))
val actualMax = Vec3s(maxOf(max.x, include.max.x), maxOf(max.y, include.max.y), maxOf(max.z, include.max.z))
return Aabb3s(actualMin, actualMax)
}
operator fun plus(off: Vec3s) =
Aabb3s(min + off, max + off)
operator fun minus(off: Vec3s) =
Aabb3s(min - off, max - off)
inline fun iter(consumer: (Vec3s) -> Unit) {
for (y in min.y..max.y) {
for (x in min.x..max.x) {
for (z in min.z..max.z) {
consumer(Vec3s(x, y, z))
}
}
}
}
inline fun serialize(put: (ByteArray) -> Unit) {
contract {
callsInPlace(put, InvocationKind.AT_LEAST_ONCE)
}
min.packed.toBytes(Endian.Little).let(put)
max.packed.toBytes(Endian.Little).let(put)
}
companion object {
fun of(a: Vec3s, b: Vec3s): Aabb3s =
Aabb3s(a, a)..b
@OptIn(ExperimentalContracts::class)
inline fun deserialize(get: (Int) -> ByteArray): Aabb3s {
contract {
callsInPlace(get, InvocationKind.AT_LEAST_ONCE)
}
val min = Vec3s.from(get(8).toLong(Endian.LITTLE).toULong())
val max = Vec3s.from(get(8).toLong(Endian.LITTLE).toULong())
return of(min, max)
}
}
}

View File

@@ -0,0 +1,51 @@
package blitz.math
@JvmInline
value class Anglef(
val radians: Float
) {
val degrees get() =
radians * 180f / Math.PI.toFloat()
val gradians get() =
radians * 200f / Math.PI.toFloat()
fun normalized() =
Anglef(radians % (2 * Math.PI.toFloat()))
operator fun plus(other: Anglef) =
Anglef(radians + other.radians)
operator fun minus(other: Anglef) =
Anglef(radians - other.radians)
operator fun times(other: Anglef) =
Anglef(radians * other.radians)
operator fun div(other: Anglef) =
Anglef(radians / other.radians)
operator fun times(other: Float) =
Anglef(radians * other)
operator fun div(other: Float) =
Anglef(radians / other)
operator fun unaryMinus() =
Anglef(-radians)
override fun toString(): String {
return "$degrees°"
}
companion object {
fun fromRadians(radians: Float) =
Anglef(radians)
fun fromDegrees(degrees: Float) =
Anglef(degrees * Math.PI.toFloat() / 180f)
fun fromGradians(gradians: Float) =
Anglef(gradians * Math.PI.toFloat() / 200f)
}
}

View File

@@ -0,0 +1,41 @@
package blitz.math
import blitz.annotations.Immutable
@Immutable
@JvmInline
@Suppress("NOTHING_TO_INLINE")
value class U3(val value: UByte) {
override fun toString() =
value.toString()
inline fun toU4() = U4(value)
}
@Immutable
@JvmInline
@Suppress("NOTHING_TO_INLINE")
value class U4(val value: UByte) {
override fun toString() =
value.toString()
inline fun toU3() = U3(value and 0b111u)
}
/** two U4 values packed => UByte */
@Immutable
@JvmInline
@Suppress("NOTHING_TO_INLINE")
value class Vec2u4(val packed: UByte) {
inline val x : U4 get() = U4((packed.toUInt() shr 4).toUByte())
inline val y : U4 get() = U4(packed and 0b111u)
constructor(x: U4, y: U4): this((x.value.toUInt() shl 4 or
y.value.toUInt()).toUByte())
override fun toString() = "($x, $y)"
inline fun component1() = x
inline fun component2() = y
}

View File

@@ -0,0 +1,29 @@
package blitz.math
import blitz.unreachable
/** only 6 directions -> only needs 3 bits */
@JvmInline
value class Direction(
val idx: U3
) {
companion object {
val UP = Direction(U3(0u))
val DOWN = Direction(U3(1u))
val NORTH = Direction(U3(2u))
val EAST = Direction(U3(3u))
val SOUTH = Direction(U3(4u))
val WEST = Direction(U3(5u))
}
override fun toString() =
when (this) {
UP -> "Up"
DOWN -> "Down"
NORTH -> "North"
EAST -> "East"
SOUTH -> "South"
WEST -> "West"
else -> unreachable()
}
}

View File

@@ -3,8 +3,14 @@ package blitz.math
infix fun Pair<Int, Int>.lerpi(t: Double): Double =
(1.0f - t) * first + second * t
infix fun Pair<Int, Int>.lerpi(t: Float): Float =
(1.0f - t) * first + second * t
infix fun Pair<Double, Double>.lerpd(t: Double): Double =
(1.0f - t) * first + second * t
infix fun Pair<Float, Float>.lerpf(t: Double): Double =
(1.0f - t) * first + second * t
infix fun Pair<Float, Float>.lerpf(t: Float): Float =
(1.0f - t) * first + second * t

View File

@@ -0,0 +1,146 @@
package blitz.math
import blitz.annotations.Mutable
import blitz.annotations.Mutate
import blitz.test.annotations.Test
import blitz.test.util.requireEqual
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract
/**
* use this only for small-ish voxel shapes: 16x16, 32x16, etc
*
* size needs to be known at construction
*/
@Mutable
@JvmInline
@OptIn(ExperimentalContracts::class)
@Suppress("NOTHING_TO_INLINE")
value class PackedVoxelShape3i(
val packed: LongArray,
) {
inline val sizes4x4x4 get() =
Vec3s.from(packed[0].toULong())
val sizes get() =
Vec3s(sizes4x4x4.x * 4.toShort(), sizes4x4x4.y * 4.toShort(), sizes4x4x4.z * 4.toShort())
inline fun index4x4x4(x: Int, y: Int, z: Int) =
1 + x + sizes4x4x4.x.toInt() * (y + sizes4x4x4.y.toInt() * z)
operator fun get(x: Int, y: Int, z: Int): Boolean {
require(Vec3s(x, y, z).allComponents(sizes) { a, sz -> a < sz })
return VoxelShape4x4x4i(packed[index4x4x4(x / 4, y / 4, z / 4)])[x % 4, y % 4, z % 4]
}
inline operator fun get(idx: Vec3s) =
get(idx.x.toInt(), idx.y.toInt(), idx.z.toInt())
@Mutate
operator fun set(x: Int, y: Int, z: Int, value: Boolean) {
require(Vec3s(x, y, z).allComponents(sizes) { a, sz -> a < sz }) { "index out of bounds: ($x, $y, $z) for $sizes" }
val idx = index4x4x4(x / 4, y / 4, z / 4)
packed[idx] = VoxelShape4x4x4i(packed[idx]).set(x % 4, y % 4, z % 4, value).packed
}
@Mutate
inline operator fun set(idx: Vec3s, value: Boolean) =
set(idx.x.toInt(), idx.y.toInt(), idx.z.toInt(), value)
inline fun iterChunks(consumer: (Vec3s, VoxelShape4x4x4i) -> Unit) {
contract {
callsInPlace(consumer)
}
sizes4x4x4.asSizeIterate {
consumer(it,
VoxelShape4x4x4i(
packed[index4x4x4(it.x.toInt(), it.y.toInt(), it.z.toInt())]))
}
}
inline fun iterSetVoxels(consumer: (Vec3s) -> Unit) {
contract {
callsInPlace(consumer)
}
iterChunks { cp, chunk ->
chunk.iterSetVoxels { off ->
consumer(cp * 4 + off)
}
}
}
fun layerEmpty(y: Int): Boolean {
val cy = y / 4
for (cx in 0..<sizes4x4x4.x) {
for (cz in 0..<sizes4x4x4.z) {
val chunk = VoxelShape4x4x4i(packed[index4x4x4(cx, cy, cz)])
if (chunk.empty) continue
val layer = chunk[y % 4]
if (!layer.empty) return false
}
}
return true
}
inline fun iterNonEmptyLayers(consumer: (Short) -> Unit) {
contract {
callsInPlace(consumer)
}
for (y in 0..<sizes4x4x4.y * 4) {
if (!layerEmpty(y)) {
consumer(y.toShort())
}
}
}
inline fun copy() = PackedVoxelShape3i(packed.copyOf())
override fun toString(): String {
val out = StringBuilder()
iterNonEmptyLayers { y ->
out.append("layer $y:\n")
for (x in 0..<sizes.x) {
out.append(" ")
for (z in 0..<sizes.z) {
out.append(if (this[x, y.toInt(), z]) '#' else '.')
}
out.append('\n')
}
}
return out.toString()
}
fun aabb(): Aabb3s? {
var out: Aabb3s? = null
iterChunks { cp, chunk ->
chunk.aabb()?.let {
val x = it + cp * 4
out = out?.let { it..x } ?: x
}
}
return out
}
companion object {
fun of(size: Vec3s): PackedVoxelShape3i {
val sizes4x4x4 = size.ceilDiv(4)
val obj = PackedVoxelShape3i(LongArray(sizes4x4x4.x.toInt() * sizes4x4x4.y.toInt() * sizes4x4x4.z.toInt() + 1))
obj.packed[0] = sizes4x4x4.packed.toLong()
return obj
}
}
object _tests {
@Test
fun aabbCorrect() {
val voxels = of(Vec3s(8, 6, 8))
voxels[3,2,1] = true
voxels[6,2,2] = true
voxels[7,4,2] = true
requireEqual(voxels.aabb(), Aabb3s.of(Vec3s(3, 2, 1), Vec3s(7, 4, 2)))
}
}
}

View File

@@ -0,0 +1,98 @@
package blitz.math
import blitz.Endian
import blitz.annotations.Immutable
import blitz.toBytes
import blitz.toInt
import blitz.unreachable
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
@Immutable
@OptIn(ExperimentalContracts::class)
@Suppress("NOTHING_TO_INLINE")
data class Vec3f(
@JvmField val x: Float,
@JvmField val y: Float,
@JvmField val z: Float
) {
inline fun serialize(put: (ByteArray) -> Unit) {
contract {
callsInPlace(put, InvocationKind.AT_LEAST_ONCE)
}
x.toRawBits().toBytes(Endian.Little).let(put)
y.toRawBits().toBytes(Endian.Little).let(put)
z.toRawBits().toBytes(Endian.Little).let(put)
}
companion object {
inline fun from(s: Vec3s) =
Vec3f(s.x.toFloat(), s.y.toFloat(), s.z.toFloat())
val Zero = Vec3f(0f, 0f, 0f)
@OptIn(ExperimentalContracts::class)
inline fun deserialize(get: (Int) -> ByteArray): Vec3f {
contract {
callsInPlace(get, InvocationKind.AT_LEAST_ONCE)
}
return Vec3f(
get(4).toInt(Endian.Little).let(Float::fromBits),
get(4).toInt(Endian.Little).let(Float::fromBits),
get(4).toInt(Endian.Little).let(Float::fromBits),
)
}
}
override fun toString() =
"($x, $y, $z)"
fun rotate(direction: Direction): Vec3f =
when (direction) {
Direction.DOWN -> Vec3f(x, -z, y)
Direction.UP -> Vec3f(x, z, -y)
Direction.NORTH -> Vec3f(x, y, z)
Direction.SOUTH -> Vec3f(-x, y, -z)
Direction.WEST -> Vec3f(z, y, -x)
Direction.EAST -> Vec3f(-z, y, x)
else -> unreachable()
}
inline operator fun plus(r: Vec3f): Vec3f =
Vec3f(this.x + r.x, this.y + r.y, this.z + r.z)
inline operator fun minus(r: Vec3f): Vec3f =
Vec3f(this.x - r.x, this.y - r.y, this.z - r.z)
inline operator fun times(r: Vec3f): Vec3f =
Vec3f(this.x * r.x, this.y * r.y, this.z * r.z)
inline operator fun div(r: Vec3f): Vec3f =
Vec3f(this.x / r.x, this.y / r.y, this.z / r.z)
inline infix fun dot(r: Vec3f) =
this.x * r.x + this.y * r.y + this.z * r.z
inline fun product() = x * y * z
inline operator fun unaryMinus() =
Vec3f(-x, -y, -z)
inline fun allComponents(fn: (Float) -> Boolean) =
fn(x) && fn(y) && fn(z)
inline fun anyComponents(fn: (Float) -> Boolean) =
fn(x) || fn(y) || fn(z)
inline fun allComponents(other: Vec3f, fn: (Float, Float) -> Boolean) =
fn(x, other.x) && fn(y, other.y) && fn(z, other.z)
inline fun anyComponent(other: Vec3f, fn: (Float, Float) -> Boolean) =
fn(x, other.x) || fn(y, other.y) || fn(z, other.z)
}

View File

@@ -0,0 +1,136 @@
package blitz.math
import blitz.annotations.Immutable
import blitz.unreachable
import blitz.test.annotations.Test
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract
@Immutable
@JvmInline
@OptIn(ExperimentalContracts::class)
@Suppress("NOTHING_TO_INLINE")
value class Vec3s private constructor(val backing: Vec4s) {
inline val x: Short get() = backing.x
inline val y: Short get() = backing.y
inline val z: Short get() = backing.z
inline val packed: ULong get() = backing.packed
companion object {
fun from(xyz: Vec4s) =
Vec3s(Vec4s(xyz.packed and 0xFFFF_FFFF_FFFF_0000uL))
inline fun from(packed: ULong) =
from(Vec4s(packed))
inline fun from(v: Vec3f) =
Vec3s(v.x.toInt(), v.y.toInt(), v.z.toInt())
@JvmStatic
private fun Int.toShortThrow() = toShort().also {
if (it.toInt() != this) throw IllegalArgumentException("Value $this cannot be represented as Short")
}
val Zero = Vec3s(0, 0, 0)
}
@Suppress("UNUSED")
object _tests {
@Test
fun fromPackedCorrect() {
require(from(Vec4s(1,2,3,4).packed) == Vec3s(1,2,3))
}
@Test
fun serialFormatStable() {
require(Vec4s(1,2,3,4).packed == 0x0001_0002_0003_0004uL)
require(Vec4s(0x0001_0002_0003_0004uL) == Vec4s(1,2,3,4))
}
}
constructor(x: Short, y: Short, z: Short): this(Vec4s(x, y, z, 0))
constructor(x: Int, y: Int, z: Int): this(Vec4s(x.toShortThrow(), y.toShortThrow(), z.toShortThrow(), 0))
override fun toString() = "($x, $y, $z)"
inline operator fun component1() = x
inline operator fun component2() = y
inline operator fun component3() = z
inline fun asSizeIterate(consumer: (Vec3s) -> Unit) {
contract {
callsInPlace(consumer)
}
for (z in 0..<z) {
for (x in 0..<x) {
for (y in 0..<y) {
consumer(Vec3s(x, y, z))
}
}
}
}
fun rotate(direction: Direction): Vec3s =
when (direction) {
Direction.DOWN -> Vec3s(x, (-z).toShort(), y)
Direction.UP -> Vec3s(x, z, (-y).toShort())
Direction.NORTH -> Vec3s(x, y, z)
Direction.SOUTH -> Vec3s((-x).toShort(), y, (-z).toShort())
Direction.WEST -> Vec3s(z, y, (-x).toShort())
Direction.EAST -> Vec3s((-z).toShort(), y, x)
else -> unreachable()
}
inline operator fun plus(r: Vec3s): Vec3s =
Vec3s(this.x + r.x, this.y + r.y, this.z + r.z)
inline operator fun minus(r: Vec3s): Vec3s =
Vec3s(this.x - r.x, this.y - r.y, this.z - r.z)
inline operator fun times(r: Vec3s): Vec3s =
Vec3s(this.x * r.x, this.y * r.y, this.z * r.z)
inline operator fun plus(off: Short): Vec3s =
Vec3s(this.x + off, this.y + off, this.z + off)
inline operator fun minus(off: Short): Vec3s =
Vec3s(this.x - off, this.y - off, this.z - off)
operator fun times(value: Float): Vec3s =
Vec3s((x * value).toInt().toShortThrow(), (y * value).toInt().toShortThrow(), (z * value).toInt().toShortThrow())
operator fun times(value: Short): Vec3s =
Vec3s(x * value, y * value, z * value)
operator fun div(value: Float): Vec3s =
Vec3s((x / value).toInt().toShortThrow(), (y / value).toInt().toShortThrow(), (z / value).toInt().toShortThrow())
operator fun div(value: Short): Vec3s =
Vec3s(x / value, y / value, z / value)
fun ceilDiv(value: Short): Vec3s =
Vec3s(
if (x >= 0) (x / value) + 1 else (x / value),
if (y >= 0) (y / value) + 1 else (y / value),
if (z >= 0) (z / value) + 1 else (z / value)
)
inline fun product() = x.toInt() * y.toInt() * z.toInt()
inline operator fun unaryMinus() =
Vec3s(-x, -y, -z)
inline fun allComponents(fn: (Short) -> Boolean) =
fn(x) && fn(y) && fn(z)
inline fun anyComponents(fn: (Short) -> Boolean) =
fn(x) || fn(y) || fn(z)
inline fun allComponents(other: Vec3s, fn: (Short, Short) -> Boolean) =
fn(x, other.x) && fn(y, other.y) && fn(z, other.z)
inline fun anyComponent(other: Vec3s, fn: (Short, Short) -> Boolean) =
fn(x, other.x) || fn(y, other.y) || fn(z, other.z)
}

View File

@@ -0,0 +1,26 @@
package blitz.math
import blitz.annotations.Immutable
@Immutable
@JvmInline
@Suppress("NOTHING_TO_INLINE")
value class Vec4s(val packed: ULong) {
inline val x: Short get() = (packed shr 48).toUShort().toShort()
inline val y: Short get() = (packed shr 32).toUShort().toShort()
inline val z: Short get() = (packed shr 16).toUShort().toShort()
inline val w: Short get() = packed.toUShort().toShort()
constructor(x: Short, y: Short, z: Short, w: Short): this(
(x.toUShort().toULong() shl 48) or
(y.toUShort().toULong() shl 32) or
(z.toUShort().toULong() shl 16) or
w.toUShort().toULong())
override fun toString() = "($x, $y, $z, $w)"
inline operator fun component1() = x
inline operator fun component2() = y
inline operator fun component3() = z
inline operator fun component4() = w
}

View File

@@ -0,0 +1,40 @@
package blitz.math
import blitz.annotations.Immutable
import kotlin.experimental.and
import kotlin.experimental.or
@Immutable
@JvmInline
@Suppress("NOTHING_TO_INLINE")
value class VoxelShape4x4i(
val packed: Short,
) {
fun bitIndexFor(x: Int, y: Int): Int {
require(x in 0..3 && y in 0..3) { "index out of bounds: ($x, $y) for 4x4" }
return 4 * y + x
}
inline operator fun get(x: Int, y: Int): Boolean =
(packed.toUShort().toInt() and (1 shl bitIndexFor(x, y))) != 0
inline fun set(x: Int, y: Int, value: Boolean): VoxelShape4x4i {
val mask = 1 shl bitIndexFor(x, y)
return VoxelShape4x4i((if (value)
packed.toUShort().toInt() or mask
else
packed.toUShort().toInt() and mask.inv()).toShort())
}
inline operator fun plus(other: VoxelShape4x4i) =
VoxelShape4x4i(packed or other.packed)
inline operator fun times(other: VoxelShape4x4i) =
VoxelShape4x4i(packed and other.packed)
inline val empty get() = packed.toInt() == 0
companion object {
val Air = VoxelShape4x4i(0)
}
}

View File

@@ -0,0 +1,93 @@
package blitz.math
import blitz.annotations.Immutable
import blitz.test.annotations.Test
import blitz.test.util.requireEqual
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract
@Immutable
@JvmInline
@OptIn(ExperimentalContracts::class)
@Suppress("NOTHING_TO_INLINE")
value class VoxelShape4x4x4i(
val packed: Long
) {
operator fun get(y: Int): VoxelShape4x4i {
require(y in 0..3) { "layer index out of bounds: y=$y (expected 0..3)" }
val shift = 16 * y
return VoxelShape4x4i(((packed ushr shift) and 0xFFFFL).toShort())
}
fun set(y: Int, value: VoxelShape4x4i): VoxelShape4x4x4i {
require(y in 0..3) { "layer index out of bounds: y=$y (expected 0..3)" }
val shift = 16 * y
val clearMask = (0xFFFFL shl shift).inv()
val slice = ((value.packed.toUShort().toLong() and 0xFFFFL) shl shift)
return VoxelShape4x4x4i((packed and clearMask) or slice)
}
inline operator fun plus(other: VoxelShape4x4x4i) =
VoxelShape4x4x4i(packed or other.packed)
inline operator fun times(other: VoxelShape4x4x4i) =
VoxelShape4x4x4i(packed and other.packed)
inline operator fun get(x: Int, y: Int, z: Int): Boolean =
this[y][x, z]
inline fun set(x: Int, y: Int, z: Int, value: Boolean) =
set(y, this[y].set(x, z, value))
inline val empty get() = packed == 0L
inline fun iterSetVoxels(consumer: (Vec3s) -> Unit) {
contract {
callsInPlace(consumer)
}
if (empty) return
for (y in 0..<4) {
for (x in 0..<4) {
for (z in 0..<4) {
if (this[x, y, z]) {
consumer(Vec3s(x, y, z))
}
}
}
}
}
fun aabb(): Aabb3s? {
if (empty) return null
val minBit = packed.countTrailingZeroBits()
val maxBit = 63 - packed.countLeadingZeroBits()
return Aabb3s.of(
bitToPos(minBit),
bitToPos(maxBit)
)
}
companion object {
val AIR = VoxelShape4x4x4i(0)
private fun bitToPos(bit: Int): Vec3s {
// bit = x + 4 * z + 16 * y
val pos2 = bit % 16
val x = pos2 % 4
val z = pos2 / 4
return Vec3s(x.toShort(), (bit / 16).toShort(), z.toShort())
}
}
object _test {
@Test
fun aabbCorrect() {
val x = AIR
.set(2, 3, 1, true)
.set(3, 3, 1, true)
requireEqual(x.aabb(), Aabb3s.of(Vec3s(2, 3, 1), Vec3s(3, 3, 1)))
}
}
}

View File

@@ -3,10 +3,12 @@ package blitz.parse
import blitz.collections.RefVec
import blitz.collections.contents
import blitz.parse.comb2.*
import blitz.test.annotations.Test
import blitz.test.util.requireEqual
import blitz.unreachable
@Suppress("NOTHING_TO_INLINE")
object JSON {
val jsonBool: Parser<Char, Element> = choose {
it(mapValue(seq("true".toList())) { Element.newBool(true) })
it(mapValue(seq("false".toList())) { Element.newBool(false) })
@@ -116,31 +118,72 @@ object JSON {
inline fun Element.uncheckedAsObj(): Map<String, Element> =
_boxed as Map<String, Element>
inline fun Element.asNum(): Double {
fun Element.asNum(): Double {
require(kind == Element.NUM) { "Element is not a Number" }
return _num
}
inline fun Element.asBool(): Boolean {
fun Element.asBool(): Boolean {
require(kind == Element.BOOL) { "Element is not a Boolean" }
return _bool
}
inline fun Element.asArr(): RefVec<Element> {
fun Element.asArr(): RefVec<Element> {
require(kind == Element.ARR) { "Element is not an Array" }
return _boxed as RefVec<Element>
}
inline fun Element.asStr(): String {
fun Element.asStr(): String {
require(kind == Element.STR) { "Element is not a String" }
return _boxed as String
}
inline fun Element.asObj(): Map<String, Element> {
fun Element.asObj(): Map<String, Element> {
require(kind == Element.OBJ) { "Element is not an Object" }
return _boxed as Map<String, Element>
}
fun parse(string: String): ParseResult<Element> =
jsonElement.run(string.toList())
object _tests {
@Test
fun parseJsonNumber() {
parse("-1.351").assertA().asNum()
.requireEqual(-1.351)
}
@Test
fun parseJsonBool() {
parse("true").assertA().asBool()
.requireEqual(true)
parse("false").assertA().asBool()
.requireEqual(false)
}
@Test
fun parseJsonNull() {
parse("null").assertA().kind
.requireEqual(Element.NULL)
}
@Test
fun parseJsonStr() {
parse("\"Hello\\\n\\\"aworld\"").assertA().asStr()
.requireEqual("Hello\n\"aworld")
}
@Test
fun parseJsonArr() {
parse("[1, 2, 3,\n 4]").assertA().asArr()
.map { it.asNum() }.contents.requireEqual(listOf(1.0,2.0,3.0,4.0).contents)
}
@Test
fun parseJsonObj() {
val obj = parse("{\"a\": 1, \"b\": 2}").assertA().asObj()
obj.map { it.value.asNum() }.contents.requireEqual(listOf(1.0,2.0).contents)
obj.map { it.key }.contents.requireEqual(listOf("a","b").contents)
}
}
}

View File

@@ -77,17 +77,6 @@ object Terminal {
System.err.println(encodeString(str, *modes))
}
@Deprecated(
"Use errln instead!",
ReplaceWith(
"errln(str, *modes)",
"blitz.term.Terminal.errln"
)
)
fun warn(str: String, vararg modes: AnsiiMode) {
errln(str, *modes)
}
object Cursor {
fun savePos() {
print(escape + "7")

View File

@@ -0,0 +1,5 @@
package blitz.test.annotations
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS)
@Retention(AnnotationRetention.SOURCE)
annotation class Test

View File

@@ -0,0 +1,7 @@
package blitz.test.util
fun requireEqual(a: Any?, b: Any?) =
require(a == b) { "Expected $a to be equal to $b"}
fun <T> T.requireEqual(other: T) =
requireEqual(this, other)

View File

@@ -4,6 +4,8 @@ import blitz.collections.I2HashMapKey
import blitz.collections.contents
import kotlin.test.Test
// TODO: port tests to sanetest
class Maps {
@Test
fun i2hashmap() {