From c3018ca1a64d57dda614a639b04b4e52d2a85616 Mon Sep 17 00:00:00 2001 From: Linnea Gräf Date: Thu, 15 Feb 2024 00:03:49 +0100 Subject: Add more const utilities --- .../at/hannibal2/skyhanni/utils/const/Const.kt | 65 ++++++++++++++++++++- .../skyhanni/utils/const/ConstItemStack.kt | 39 +++++++++++-- .../at/hannibal2/skyhanni/utils/const/ConstNBT.kt | 67 ++++++++++++++++++++++ .../at/hannibal2/skyhanni/utils/const/Unconst.kt | 14 +++++ 4 files changed, 178 insertions(+), 7 deletions(-) create mode 100644 src/main/java/at/hannibal2/skyhanni/utils/const/ConstNBT.kt create mode 100644 src/main/java/at/hannibal2/skyhanni/utils/const/Unconst.kt diff --git a/src/main/java/at/hannibal2/skyhanni/utils/const/Const.kt b/src/main/java/at/hannibal2/skyhanni/utils/const/Const.kt index 8da2498e0..30d8ef0c6 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/const/Const.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/const/Const.kt @@ -16,8 +16,9 @@ package at.hannibal2.skyhanni.utils.const value class Const private constructor( /** * Unsafely access the underlying object. Callers of this method promise not to modify the returned instance, or to - * leak this instance to any other codepaths which modify the instance. Whenever possible callers should wrap - * objects they return which offer a view into this object into a [Const] of its own. + * leak this instance or references/views into this instance to any other codepaths which modify it. + * Whenever possible callers should wrap objects they return which offer a view into this object into a [Const] of + * its own. */ @PublishedApi internal val unsafeMutable: T, @@ -28,11 +29,69 @@ value class Const private constructor( * internally. This should ideally be done by every instance of [value] being wrapped in a [Const] (and other * references to be discarded as quickly as possible). */ - fun fromOwned(value: T): Const { + fun newUnchecked(value: T): Const { return Const(value) } } + + /** + * Map the contained object into a new [Const]. + */ + inline fun unsafeMap( + /** + * This lambda needs to hold the same guarantees for its parameter like for access to [unsafeMutable]. + * It can however assume that the return value will never be modified by other code. + */ + mapper: (T) -> U + ): Const { + return unsafeMutable + .let(mapper) + .let(::newUnchecked) + } + + /** + * Flat map the contained object into a new [Const]. Behaves like [unsafeMap] but allowing the [mapper] to return a [Const] + */ + inline fun unsafeFlatMap( + /** + * This lambda needs to hold the same guarantees for its parameter like for access to [unsafeMutable]. + */ + mapper: (T) -> Const + ): Const { + return unsafeMutable + .let(mapper) + } } +/** + * Flatten two nested [Const] into just one. + */ +fun Const>.flatten(): Const = unsafeFlatMap { it } + +/** + * Lift nullability out of a [Const] to allow for easier `?.` operations. + */ +fun Const.liftNull(): Const? = unsafeMutable?.let(Const.Companion::newUnchecked) + +inline fun Const.tryCast(): Const? = (unsafeMutable as? U)?.let(Const.Companion::newUnchecked) +/** + * List a list out of a const. This is legal since [List] does not allow for mutating unless it's elements are individually + * mutable as long. The caller may never cast the returned instance to [MutableList]. + */ +fun Const>.liftList(): List> { + @Suppress("UNCHECKED_CAST") + // This cast is valid since List and List> are always the same at runtime, guaranteed by `@JvmInline` + return unsafeMutable as List> +} +/** + * Lift const out of a [List]. The caller must guarantee that the list instance is never modified. This means it was + * either constructed directly as a [List] or if it originally comes from a [MutableList], mutations operations on this + * instance are never used. + */ +fun List>.unliftList(): Const> { + @Suppress("UNCHECKED_CAST") + // This cast is valid since List and List> are always the same at runtime, guaranteed by `@JvmInline` + return Const.newUnchecked(this as List) +} diff --git a/src/main/java/at/hannibal2/skyhanni/utils/const/ConstItemStack.kt b/src/main/java/at/hannibal2/skyhanni/utils/const/ConstItemStack.kt index 5e9484ffd..176ea5e47 100644 --- a/src/main/java/at/hannibal2/skyhanni/utils/const/ConstItemStack.kt +++ b/src/main/java/at/hannibal2/skyhanni/utils/const/ConstItemStack.kt @@ -3,7 +3,9 @@ package at.hannibal2.skyhanni.utils.const import net.minecraft.item.EnumDyeColor import net.minecraft.item.Item import net.minecraft.item.ItemStack +import net.minecraft.nbt.NBTBase import net.minecraft.nbt.NBTTagCompound +import net.minecraft.nbt.NBTTagString /** * Access a shallow copy of the underlying item stack. Callers of this method promise not to modify the nbt data of @@ -26,17 +28,17 @@ fun Const.getOwnedDeepCopy(): ItemStack { /** * Returns the [ItemStack.stackSize] */ -inline val Const.stackSize: Int get() = unsafeMutable.stackSize +inline val Const.stackSize: Int get() = unsafeMap(ItemStack::stackSize).unconst /** * Returns the [item type](ItemStack.item) */ -inline val Const.itemType: Item get() = unsafeMutable.item +inline val Const.itemType: Item get() = unsafeMap(ItemStack::getItem).unconst /** * Returns the [damage or metadata](ItemStack.metadata) */ -inline val Const.damage: Int get() = unsafeMutable.metadata +inline val Const.damage: Int get() = unsafeMap(ItemStack::getItemDamage).unconst /** * Interprets the [damage] of this item as a [color](EnumDyeColor). This is only valid for some [item types](itemType), @@ -44,5 +46,34 @@ inline val Const.damage: Int get() = unsafeMutable.metadata */ inline val Const.color: EnumDyeColor get() = EnumDyeColor.byDyeDamage(damage) +/** + * Access the underlying NBT data of this item stack in a [Const]. + */ inline val Const.nbt: Const? - get() = unsafeMutable.tagCompound?.let(Const.Companion::fromOwned) + get() = unsafeMap(ItemStack::getTagCompound).liftNull() + +/** + * Access the NBT data relating to display data. + */ +inline val Const.displayNbt: Const? + get() = nbt?.getTagCompound("display") + +/** + * Get the display name of this item stack according to its NBT. + */ +fun Const.getDisplayName(): String? = + displayNbt?.getString("Name")?.getString() + +/** + * Get the lore of this item stack according to its NBT. + * @throws NullPointerException if `display.Lore[*]` is not a string in the NBT + */ +fun Const.getLore(): List? = + displayNbt + ?.getList("Lore") + ?.intoList() + ?.liftList() + ?.map { it.tryCast()!!.getString() } + + + diff --git a/src/main/java/at/hannibal2/skyhanni/utils/const/ConstNBT.kt b/src/main/java/at/hannibal2/skyhanni/utils/const/ConstNBT.kt new file mode 100644 index 000000000..1725747f8 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/utils/const/ConstNBT.kt @@ -0,0 +1,67 @@ +package at.hannibal2.skyhanni.utils.const + +import net.minecraft.nbt.NBTBase +import net.minecraft.nbt.NBTTagCompound +import net.minecraft.nbt.NBTTagInt +import net.minecraft.nbt.NBTTagList +import net.minecraft.nbt.NBTTagLong +import net.minecraft.nbt.NBTTagString + + +/** + * This method indexes into an NBTTagCompound searching for a tag. + * The caller must guarantee that the [T]s type identifier is [tag]. + * This means for any `instance` of [NBTBase], `instance.getId() == tag` iff `instance is T`. + */ +@PublishedApi +internal fun Const.getAndCast(name: String, tag: Int): Const? = + unsafeMap { + if (it.hasKey(name, tag)) + @Suppress("UNCHECKED_CAST") + it.getTag(name) as T + else + null + }.liftNull() + +private val TAG_COMPOUND_KEY = NBTTagCompound().id.toInt() +private val TAG_COMPOUND_STRING = NBTTagString().id.toInt() +private val TAG_COMPOUND_INT = NBTTagInt(0).id.toInt() +private val TAG_COMPOUND_LONG = NBTTagLong(0L).id.toInt() +private val TAG_COMPOUND_LIST = NBTTagList().id.toInt() + +fun Const.getTagCompound(name: String): Const? = + getAndCast(name, TAG_COMPOUND_KEY) + +fun Const.getString(name: String): Const? = + getAndCast(name, TAG_COMPOUND_STRING) + +fun Const.getInt(name: String): Const? = + getAndCast(name, TAG_COMPOUND_INT) + +fun Const.getLong(name: String): Const? = + getAndCast(name, TAG_COMPOUND_LONG) + +fun Const.getList(name: String): Const? = + getAndCast(name, TAG_COMPOUND_LIST) + +val Const.size: Int + get() = unsafeMap(NBTTagList::tagCount).unconst + +fun Const.getTag(i: Int): Const { + return unsafeMap { it.get(i) } +} + +inline fun Const.intoList(): Const> { + return unsafeMap { + val build = mutableListOf() + for (i in 0...getString(): String = unsafeMap(NBTTagString::getString).unconst +fun Const.getInt(): Int = unsafeMap(NBTTagInt::getInt).unconst +fun Const.getLong(): Long = unsafeMap(NBTTagLong::getLong).unconst + diff --git a/src/main/java/at/hannibal2/skyhanni/utils/const/Unconst.kt b/src/main/java/at/hannibal2/skyhanni/utils/const/Unconst.kt new file mode 100644 index 000000000..5a2f2ce21 --- /dev/null +++ b/src/main/java/at/hannibal2/skyhanni/utils/const/Unconst.kt @@ -0,0 +1,14 @@ +/** + * This file contains safe dereferences for [Const.unsafeMutable]. By convention `unconst` wrappers should only available + * for types without interior mutability. + */ + +package at.hannibal2.skyhanni.utils.const + +import net.minecraft.item.Item + + +inline val Const.unconst: String get() = unsafeMutable +inline val Const.unconst: Int get() = unsafeMutable +inline val Const.unconst: Long get() = unsafeMutable +inline val Const.unconst: Item get() = unsafeMutable -- cgit