Revisit quantum battery code and clean it up

This commit is contained in:
DBotThePony 2023-08-16 18:15:14 +07:00
parent 8e2c1f25dc
commit 8e03b4363d
Signed by: DBot
GPG Key ID: DCC23B5715498507
14 changed files with 439 additions and 366 deletions

View File

@ -17,18 +17,37 @@ import net.minecraftforge.fml.loading.FMLLoader
import org.apache.logging.log4j.LogManager
import ru.dbotthepony.mc.otm.capability.AbstractProfiledStorage
import ru.dbotthepony.mc.otm.client.minecraft
import ru.dbotthepony.mc.otm.core.collect.WeakHashSet
import ru.dbotthepony.mc.otm.core.util.AtomicallyInvalidatedLazy
import ru.dbotthepony.mc.otm.core.util.IConditionalTickable
import ru.dbotthepony.mc.otm.core.util.ITickable
import ru.dbotthepony.mc.otm.core.util.TickList
import ru.dbotthepony.mc.otm.graph.GraphNodeList
import ru.dbotthepony.mc.otm.network.MatteryNetworkChannel
import java.util.*
import java.util.concurrent.atomic.AtomicInteger
private val preServerTick = TickList()
private val postServerTick = TickList()
private val preWorldTick = WeakHashMap<Level, TickList>()
private val postWorldTick = WeakHashMap<Level, TickList>()
private val clientThreads = WeakHashSet<Thread>()
private val serverThreads = WeakHashSet<Thread>()
private val serverCounter = AtomicInteger()
private var _server: MinecraftServer? = null
val isClient: Boolean by lazy { FMLLoader.getDist() == Dist.CLIENT }
fun <V> lazyPerServer(fn: (MinecraftServer) -> V): Lazy<V> {
return AtomicallyInvalidatedLazy(serverCounter) {
if (!SERVER_IS_LIVE)
throw IllegalStateException("No server is running")
fn.invoke(_server!!)
}
}
fun onceServerPre(inTicks: Int, callback: Runnable): TickList.Timer? {
if (!SERVER_IS_LIVE) {
LOGGER.error("Refusing to add timer $callback in ticks $inTicks while server is dying", IllegalStateException("Server is stopping"))
@ -47,12 +66,6 @@ fun onceServer(inTicks: Int, callback: Runnable): TickList.Timer? {
return postServerTick.Timer(inTicks, callback)
}
private var _server: MinecraftServer? = null
private var _serverThread: Thread? = null
private var _clientThread: Thread? = null
val isClient: Boolean by lazy { FMLLoader.getDist() == Dist.CLIENT }
private val isPausedImpl: Boolean get() {
val server = _server
@ -64,7 +77,7 @@ private val isPausedImpl: Boolean get() {
}
val isPaused: Boolean get() {
if (_clientThread === null) {
if (clientThreads.isEmpty()) {
return false
}
@ -72,11 +85,7 @@ val isPaused: Boolean get() {
}
fun recordClientThread() {
if (_clientThread != null) {
throw IllegalStateException("Already have client channel")
}
_clientThread = Thread.currentThread()
clientThreads.add(Thread.currentThread())
}
fun runIfClient(lambda: () -> Unit) {
@ -119,11 +128,11 @@ fun <V> runOnClient(value: V, lambda: () -> V): V {
}
fun isServerThread(): Boolean {
return Thread.currentThread() === _serverThread
return Thread.currentThread() in serverThreads
}
fun isClientThread(): Boolean {
return Thread.currentThread() === _clientThread
return Thread.currentThread() in clientThreads
}
val MINECRAFT_SERVER: MinecraftServer
@ -146,6 +155,7 @@ private val LOGGER = LogManager.getLogger()
fun onServerTick(event: ServerTickEvent) {
if (event.phase === TickEvent.Phase.START) {
preServerTick.tick()
serverThreads.add(Thread.currentThread())
} else {
postServerTick.tick()
// чтоб не плодить кучу подписчиков, вызовем напрямую отсюда
@ -158,6 +168,12 @@ fun onServerTick(event: ServerTickEvent) {
fun onWorldTick(event: LevelTickEvent) {
if (event.phase === TickEvent.Phase.START) {
preWorldTick[event.level]?.tick()
if (event.side.isClient) {
clientThreads.add(Thread.currentThread())
} else if (event.side.isServer) {
serverThreads.add(Thread.currentThread())
}
} else {
postWorldTick[event.level]?.tick()
}
@ -254,13 +270,15 @@ fun onServerStarting(event: ServerAboutToStartEvent) {
clear()
SERVER_IS_LIVE = true
_server = event.server
_serverThread = Thread.currentThread()
serverThreads.add(Thread.currentThread())
serverCounter.incrementAndGet()
MatteryNetworkChannel.onServerStarting()
}
fun onServerStopping(event: ServerStoppingEvent) {
clear()
SERVER_IS_LIVE = false
serverCounter.incrementAndGet()
MatteryNetworkChannel.onServerStopping()
}
@ -273,6 +291,6 @@ fun onServerStopped(event: ServerStoppedEvent) {
}
_server = null
_serverThread = null
serverCounter.incrementAndGet()
MatteryNetworkChannel.onServerStopped()
}

View File

@ -1,6 +1,7 @@
package ru.dbotthepony.mc.otm.capability
import com.google.common.collect.Streams
import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet
import net.minecraft.ChatFormatting
import net.minecraft.core.Direction
import net.minecraft.network.chat.Component
@ -30,6 +31,9 @@ import ru.dbotthepony.mc.otm.container.iterator
import ru.dbotthepony.mc.otm.container.stream
import ru.dbotthepony.mc.otm.core.collect.AwareItemStack
import ru.dbotthepony.mc.otm.core.collect.ContainerItemStackEntry
import ru.dbotthepony.mc.otm.core.collect.concatIterators
import ru.dbotthepony.mc.otm.core.collect.filter
import ru.dbotthepony.mc.otm.core.collect.map
import ru.dbotthepony.mc.otm.core.isNotEmpty
import ru.dbotthepony.mc.otm.core.math.Decimal
import ru.dbotthepony.mc.otm.core.orNull
@ -219,25 +223,25 @@ fun ICapabilityProvider.getMatteryEnergySided(side: Direction? = null): LazyOpti
*
* Contains all items that player might carry
*/
fun Player.itemsStream(includeCosmetics: Boolean = true): Stream<out ItemStack> {
val streams = ArrayList<Stream<out ItemStack>>()
streams.add(inventory.stream())
fun Player.items(includeCosmetics: Boolean = true): Iterator<ItemStack> {
val iterators = ArrayList<Iterator<ItemStack>>()
iterators.add(inventory.iterator())
matteryPlayer?.let {
if (it.hasExopack) {
streams.add(it.exopackContainer.stream())
iterators.add(it.exopackContainer.iterator())
}
}
if (isCuriosLoaded) {
streams.add(curiosStream(includeCosmetics))
iterators.add(curiosStream(includeCosmetics))
}
if (includeCosmetics && isCosmeticArmorLoaded) {
streams.add(cosmeticArmorStream())
iterators.add(cosmeticArmorStream())
}
return streams.stream().flatMap { it }
return concatIterators(iterators)
}
/**
@ -245,20 +249,20 @@ fun Player.itemsStream(includeCosmetics: Boolean = true): Stream<out ItemStack>
*
* Contains all items that player might see/access ([itemsStream] + open container's items)
*/
fun Player.allItemsStream(includeCosmetics: Boolean = true): Stream<out ItemStack> {
fun Player.allItems(includeCosmetics: Boolean = true): Iterator<ItemStack> {
if (containerMenu == inventoryMenu || containerMenu == matteryPlayer?.exoPackMenu) {
return itemsStream(includeCosmetics)
return items(includeCosmetics)
}
val seen = IdentityHashMap<ItemStack, Unit>(containerMenu.slots.size)
val seen = ReferenceOpenHashSet<ItemStack>(containerMenu.slots.size)
for (slot in containerMenu.slots) {
seen[slot.item] = Unit
seen.add(slot.item)
}
return Streams.concat(
itemsStream(includeCosmetics).filter { it.isEmpty || it !in seen },
containerMenu.slots.stream().map { it.item })
return concatIterators(
items(includeCosmetics).filter { it.isNotEmpty && it !in seen },
containerMenu.slots.iterator().map { it.item })
}
/**

View File

@ -24,8 +24,10 @@ import ru.dbotthepony.mc.otm.client.screen.panels.button.RectangleButtonPanel
import ru.dbotthepony.mc.otm.compat.cos.CosmeticToggleButton.Companion.BUTTON_ACTIVE
import ru.dbotthepony.mc.otm.compat.cos.CosmeticToggleButton.Companion.BUTTON_INACTIVE
import ru.dbotthepony.mc.otm.container.awareStream
import ru.dbotthepony.mc.otm.container.iterator
import ru.dbotthepony.mc.otm.container.stream
import ru.dbotthepony.mc.otm.core.collect.AwareItemStack
import ru.dbotthepony.mc.otm.core.collect.emptyIterator
import ru.dbotthepony.mc.otm.menu.MatterySlot
import java.util.stream.Stream
@ -71,16 +73,16 @@ private class CosmeticSlot(container: Container, private val slot: EquipmentSlot
}
}
fun Player.cosmeticArmorStream(): Stream<out ItemStack> {
fun Player.cosmeticArmorStream(): Iterator<ItemStack> {
if (!isCosmeticArmorLoaded) {
return Stream.empty()
return emptyIterator()
}
return cosmeticArmorStreamImpl()
}
private fun Player.cosmeticArmorStreamImpl(): Stream<out ItemStack> {
val manager = ModObjects.invMan ?: return Stream.empty()
private fun Player.cosmeticArmorStreamImpl(): Iterator<ItemStack> {
val manager = ModObjects.invMan ?: return emptyIterator()
val container = if (this !is ServerPlayer) {
manager.getCosArmorInventoryClient(uuid)
@ -88,7 +90,7 @@ private fun Player.cosmeticArmorStreamImpl(): Stream<out ItemStack> {
manager.getCosArmorInventory(uuid)
}
return (container as Container).stream()
return (container as Container).iterator()
}
fun Player.cosmeticArmorAwareStream(): Stream<out AwareItemStack> {

View File

@ -11,8 +11,11 @@ import net.minecraftforge.network.PacketDistributor
import ru.dbotthepony.mc.otm.capability.MatteryCapability
import ru.dbotthepony.mc.otm.capability.matteryPlayer
import ru.dbotthepony.mc.otm.container.awareStream
import ru.dbotthepony.mc.otm.container.iterator
import ru.dbotthepony.mc.otm.container.stream
import ru.dbotthepony.mc.otm.core.collect.AwareItemStack
import ru.dbotthepony.mc.otm.core.collect.concatIterators
import ru.dbotthepony.mc.otm.core.collect.emptyIterator
import ru.dbotthepony.mc.otm.core.orNull
import ru.dbotthepony.mc.otm.menu.PlayerSlot
import top.theillusivec4.curios.api.CuriosApi
@ -85,27 +88,27 @@ val Player.curiosSlots: List<PlayerSlot<Slot, Slot>> get() {
return getCuriosSlotsImpl()
}
private fun Player.curiosStreamImpl(includeCosmetics: Boolean): Stream<out ItemStack> {
val handler = getCapability(MatteryCapability.CURIOS_INVENTORY).orNull() ?: return Stream.empty()
private fun Player.curiosStreamImpl(includeCosmetics: Boolean): Iterator<ItemStack> {
val handler = getCapability(MatteryCapability.CURIOS_INVENTORY).orNull() ?: return emptyIterator()
val result = ArrayList<Stream<out ItemStack>>()
val result = ArrayList<Iterator<ItemStack>>()
for ((identifier, curio) in handler.curios) {
result.add(curio.stacks.stream())
result.add(curio.stacks.iterator())
if (includeCosmetics && curio.hasCosmetic()) {
result.add(curio.cosmeticStacks.stream())
result.add(curio.cosmeticStacks.iterator())
}
}
return result.stream().flatMap { it }
return concatIterators(result)
}
fun Player.curiosStream(includeCosmetics: Boolean = true): Stream<out ItemStack> {
return Stream.empty()
fun Player.curiosStream(includeCosmetics: Boolean = true): Iterator<ItemStack> {
return emptyIterator()
if (!isCuriosLoaded) {
return Stream.empty()
return emptyIterator()
}
return curiosStreamImpl(includeCosmetics)

View File

@ -6,6 +6,8 @@ import net.minecraft.world.item.ItemStack
import net.minecraftforge.items.IItemHandler
import ru.dbotthepony.mc.otm.core.collect.AwareItemStack
import ru.dbotthepony.mc.otm.core.collect.ItemHandlerItemStackEntry
import ru.dbotthepony.mc.otm.core.collect.filter
import ru.dbotthepony.mc.otm.core.isNotEmpty
import java.util.*
import java.util.stream.Stream
import java.util.stream.StreamSupport
@ -48,7 +50,7 @@ class ItemHandlerAwareSpliterator(private val handler: IItemHandler, offset: Int
}
}
fun IItemHandler.iterator(): Iterator<ItemStack> = Spliterators.iterator(spliterator())
fun IItemHandler.iterator(): Iterator<ItemStack> = Spliterators.iterator(spliterator()).filter { it.isNotEmpty }
fun IItemHandler.spliterator(): Spliterator<out ItemStack> = ItemHandlerSpliterator(this)
fun IItemHandler.awareSpliterator(): Spliterator<out AwareItemStack> = ItemHandlerAwareSpliterator(this)
fun IItemHandler.stream(): Stream<out ItemStack> = StreamSupport.stream(spliterator(), false)

View File

@ -0,0 +1,31 @@
package ru.dbotthepony.mc.otm.core.collect
import it.unimi.dsi.fastutil.Hash
import java.lang.ref.Reference
object ReferenceHashStrategy : Hash.Strategy<Any?> {
@Suppress("UNCHECKED_CAST")
override fun equals(a: Any?, b: Any?): Boolean {
if (a === b) return true
if (a is Reference<*>) {
if (a.refersTo(null) || b == null) return false
if (b is Reference<*>) {
if (b.refersTo(null)) return false
return (b as Reference<Any>).refersTo(a.get() ?: return false)
} else {
return (a as Reference<Any>).refersTo(b)
}
} else if (b is Reference<*>) {
if (b.refersTo(null) || a == null) return false
return (b as Reference<Any>).refersTo(a)
}
return a == b
}
override fun hashCode(o: Any?): Int {
return o.hashCode()
}
}

View File

@ -23,6 +23,7 @@ import java.util.stream.Collector
*/
class FilteringIterator<T>(private val parent: Iterator<T>, private val predicate: Predicate<in T>) : MutableIterator<T> {
private var foundValue: Any? = Companion
private var returned = false
override fun hasNext(): Boolean {
if (foundValue === Companion) {
@ -58,16 +59,14 @@ class FilteringIterator<T>(private val parent: Iterator<T>, private val predicat
}
this.foundValue = Companion
returned = true
return foundValue as T
}
override fun remove() {
if (foundValue === Companion) {
throw NoSuchElementException()
}
if (!returned) throw NoSuchElementException()
(parent as MutableIterator<T>).remove()
foundValue = Companion
returned = false
}
private companion object
@ -197,6 +196,10 @@ fun <T> concatIterators(a: Iterator<T>): MutableIterator<T> {
return a as MutableIterator<T>
}
fun <T> concatIterators(iterators: Iterable<Iterator<T>>): MutableIterator<T> {
return iterators.iterator().flatMap { it }
}
fun <T> concatIterators(vararg iterators: Iterator<T>): MutableIterator<T> {
return iterators.iterator().flatMap { it }
}
@ -243,7 +246,7 @@ fun <T> Iterator<T>.reduce(identity: T, reducer: (T, T) -> T): T {
}
fun <T> Iterator<T>.reduce(identity: T, reducer: BinaryOperator<T>): T = reduce(identity, reducer::apply)
fun <T> Iterator<T?>.filterNotNull(): Iterator<T> = filter { it != null } as Iterator<T>
fun <T> Iterator<T?>.filterNotNull(): MutableIterator<T> = filter { it != null } as MutableIterator<T>
fun <T> Iterator<T>.any(predicate: Predicate<in T>): Boolean {
for (value in this) {
@ -363,3 +366,7 @@ fun <T> Iterator<T>.maybe(): T? {
else
null
}
fun <T> emptyIterator(): MutableIterator<T> {
return ObjectIterators.emptyIterator()
}

View File

@ -1,53 +1,76 @@
package ru.dbotthepony.mc.otm.core.collect
import java.util.WeakHashMap
import it.unimi.dsi.fastutil.objects.ObjectOpenCustomHashSet
import ru.dbotthepony.mc.otm.core.util.HashedWeakReference
import java.lang.ref.ReferenceQueue
/**
* Wrapper around [WeakHashMap] to behave like set
*/
class WeakHashSet<E : Any> : MutableSet<E> {
private val backing = WeakHashMap<E, Unit>()
private val queue = ReferenceQueue<E>()
private val backing = ObjectOpenCustomHashSet<Any>(ReferenceHashStrategy)
private fun purge() {
var next = queue.poll()
while (next != null) {
backing.remove(next)
next = queue.poll()
}
}
override fun add(element: E): Boolean {
return backing.put(element, Unit) == null
purge()
if (element in backing) return false
return backing.add(HashedWeakReference(element, queue))
}
override fun addAll(elements: Collection<E>): Boolean {
return elements.count(::add) > 0
var any = false
elements.forEach { any = add(it) || any }
return any
}
override fun clear() {
backing.clear()
while (queue.poll() != null) {}
}
override fun iterator(): MutableIterator<E> {
return backing.keys.iterator()
purge()
return backing.iterator().map { (it as HashedWeakReference<E>).get() }.filterNotNull()
}
override fun remove(element: E): Boolean {
return backing.remove(element) != null
purge()
return backing.remove(element)
}
override fun removeAll(elements: Collection<E>): Boolean {
return backing.keys.removeAll(elements)
purge()
return backing.removeAll(elements)
}
override fun retainAll(elements: Collection<E>): Boolean {
return backing.keys.retainAll(elements)
purge()
return backing.retainAll(elements)
}
override val size: Int
get() = backing.size
override val size: Int get() {
purge()
return backing.size
}
override fun contains(element: E): Boolean {
return backing.get(element) === Unit
purge()
return backing.contains(element)
}
override fun containsAll(elements: Collection<E>): Boolean {
return elements.all(::contains)
purge()
return backing.containsAll(elements)
}
override fun isEmpty(): Boolean {
purge()
return backing.isEmpty()
}
}

View File

@ -665,7 +665,7 @@ class Decimal private constructor(val mag: BigInteger, marker: Nothing?) : Numbe
@JvmStatic
fun read(buff: FriendlyByteBuf): Decimal {
return Decimal(BigInteger(buff.readByteArray()))
return Decimal(BigInteger(buff.readByteArray()), null)
}
/**

View File

@ -0,0 +1,42 @@
package ru.dbotthepony.mc.otm.core.util
import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.locks.ReentrantLock
class AtomicallyInvalidatedLazy<V>(private val invalidator: AtomicInteger, private val initializer: () -> V) : Lazy<V> {
@Volatile
private var thisCounter = -1
@Volatile
private var stored: Any? = Companion
private val lock = ReentrantLock()
override val value: V get() {
if (thisCounter != invalidator.get()) {
lock.lock()
this.stored = Companion
thisCounter = invalidator.get()
lock.unlock()
}
var stored = stored
if (stored !== Companion)
return stored as V
lock.lock()
try {
stored = initializer.invoke()
this.stored = stored
return stored
} finally {
lock.unlock()
}
}
override fun isInitialized(): Boolean {
return stored !== Companion && thisCounter == invalidator.get()
}
private companion object
}

View File

@ -0,0 +1,28 @@
package ru.dbotthepony.mc.otm.core.util
import java.lang.ref.ReferenceQueue
import java.lang.ref.WeakReference
/**
* [WeakReference], but with [hashCode] overridden with hash of referent object
*/
@Suppress("EqualsOrHashCode")
class HashedWeakReference<T : Any> : WeakReference<T> {
constructor(value: T) : super(value) {
hash = value.hashCode()
}
constructor(value: T, queue: ReferenceQueue<T>) : super(value, queue) {
hash = value.hashCode()
}
private val hash: Int
override fun hashCode(): Int {
return hash
}
override fun toString(): String {
return "HashedWeakReference[hash=$hash]"
}
}

View File

@ -7,7 +7,8 @@ import net.minecraft.world.level.storage.loot.LootContext
import net.minecraft.world.level.storage.loot.parameters.LootContextParams
import net.minecraft.world.level.storage.loot.predicates.LootItemCondition
import net.minecraft.world.level.storage.loot.predicates.LootItemConditionType
import ru.dbotthepony.mc.otm.capability.itemsStream
import ru.dbotthepony.mc.otm.capability.items
import ru.dbotthepony.mc.otm.core.collect.filter
import ru.dbotthepony.mc.otm.data.Codec2Serializer
import ru.dbotthepony.mc.otm.data.get
import ru.dbotthepony.mc.otm.registry.MLootItemConditions
@ -19,7 +20,7 @@ data class ItemInInventoryCondition(
val matchCosmetics: Boolean = true,
) : LootItemCondition, LootItemCondition.Builder {
override fun test(t: LootContext): Boolean {
val matches = t[LootContextParams.LAST_DAMAGE_PLAYER]?.itemsStream(matchCosmetics)?.filter {
val matches = t[LootContextParams.LAST_DAMAGE_PLAYER]?.items(matchCosmetics)?.filter {
if (it.isEmpty) {
return@filter false
}

View File

@ -1,17 +1,18 @@
package ru.dbotthepony.mc.otm.item
import it.unimi.dsi.fastutil.ints.Int2ObjectFunction
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap
import it.unimi.dsi.fastutil.ints.IntOpenHashSet
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
import net.minecraft.ChatFormatting
import net.minecraft.core.Direction
import net.minecraft.nbt.ByteArrayTag
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.Tag
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.network.chat.Component
import net.minecraft.world.item.*
import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Rarity
import net.minecraft.world.item.TooltipFlag
import net.minecraft.world.level.Level
import net.minecraft.world.level.saveddata.SavedData
import net.minecraftforge.client.event.ClientPlayerNetworkEvent
import net.minecraftforge.common.capabilities.Capability
import net.minecraftforge.common.capabilities.ForgeCapabilities
@ -21,63 +22,140 @@ import net.minecraftforge.event.TickEvent
import net.minecraftforge.event.TickEvent.ServerTickEvent
import net.minecraftforge.network.NetworkEvent
import net.minecraftforge.registries.ForgeRegistries
import net.minecraftforge.registries.ForgeRegistry
import ru.dbotthepony.mc.otm.*
import ru.dbotthepony.mc.otm.capability.*
import ru.dbotthepony.mc.otm.capability.FlowDirection
import ru.dbotthepony.mc.otm.capability.MatteryCapability
import ru.dbotthepony.mc.otm.capability.allItems
import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage
import ru.dbotthepony.mc.otm.capability.energy.getBarColor
import ru.dbotthepony.mc.otm.capability.energy.getBarWidth
import ru.dbotthepony.mc.otm.capability.isMekanismLoaded
import ru.dbotthepony.mc.otm.capability.matteryEnergy
import ru.dbotthepony.mc.otm.compat.mekanism.Mattery2MekanismEnergyWrapper
import ru.dbotthepony.mc.otm.config.EnergyBalanceValues
import ru.dbotthepony.mc.otm.core.*
import ru.dbotthepony.mc.otm.core.TranslatableComponent
import ru.dbotthepony.mc.otm.core.collect.filter
import ru.dbotthepony.mc.otm.core.getID
import ru.dbotthepony.mc.otm.core.getValue
import ru.dbotthepony.mc.otm.core.isNotEmpty
import ru.dbotthepony.mc.otm.core.math.Decimal
import ru.dbotthepony.mc.otm.core.math.getDecimal
import ru.dbotthepony.mc.otm.core.math.readDecimal
import ru.dbotthepony.mc.otm.core.math.set
import ru.dbotthepony.mc.otm.core.math.writeDecimal
import ru.dbotthepony.mc.otm.core.nbt.getUUIDSafe
import ru.dbotthepony.mc.otm.core.nbt.set
import ru.dbotthepony.mc.otm.core.orThrow
import ru.dbotthepony.mc.otm.core.tagNotNull
import ru.dbotthepony.mc.otm.core.util.formatPower
import ru.dbotthepony.mc.otm.isClientThread
import ru.dbotthepony.mc.otm.isServerThread
import ru.dbotthepony.mc.otm.lazyPerServer
import ru.dbotthepony.mc.otm.network.GenericNetworkChannel
import ru.dbotthepony.mc.otm.network.MatteryPacket
import ru.dbotthepony.mc.otm.network.packetHandled
import ru.dbotthepony.mc.otm.saveddata.SavedCountingMap
import java.util.*
import java.util.function.Function
import java.util.function.Supplier
import kotlin.collections.MutableList
import kotlin.collections.component1
import kotlin.collections.component2
import kotlin.collections.forEach
import kotlin.collections.iterator
import kotlin.collections.set
class QuantumBatteryItem : Item {
class Data(
val parent: SavedCountingMap<Data>?,
val index: Int = -1,
energy: Decimal = Decimal.ZERO,
passed: Decimal = Decimal.ZERO,
received: Decimal = Decimal.ZERO,
) {
constructor(
energy: Decimal = Decimal.ZERO,
passed: Decimal = Decimal.ZERO,
received: Decimal = Decimal.ZERO,
) : this(null, -1, energy, passed, received)
class QuantumBatteryItem(val savedataID: String, val balanceValues: EnergyBalanceValues?) : Item(Properties().stacksTo(1).rarity(if (balanceValues == null) Rarity.EPIC else Rarity.UNCOMMON)) {
val isCreative = balanceValues == null
var energy: Decimal = energy
interface IValues {
val uuid: UUID
var energy: Decimal
var passed: Decimal
var received: Decimal
val isServer: Boolean
fun serialize(): CompoundTag {
return CompoundTag().also {
it["energy"] = energy
it["passed"] = passed
it["received"] = received
}
}
fun deserialize(nbt: CompoundTag) {
energy = nbt.getDecimal("energy")
passed = nbt.getDecimal("passed")
received = nbt.getDecimal("received")
}
}
class UnboundValues(override val uuid: UUID = UUID.randomUUID()) : IValues {
override var energy: Decimal = Decimal.ZERO
override var passed: Decimal = Decimal.ZERO
override var received: Decimal = Decimal.ZERO
override val isServer: Boolean
get() = false
}
class Data() : SavedData() {
constructor(nbt: CompoundTag) : this() {
load(nbt)
}
inner class Values(override val uuid: UUID) : IValues {
override var energy = Decimal.ZERO
set(value) {
if (field != value) {
field = value
parent?.isDirty = true
isDirty = true
}
override var passed = Decimal.ZERO
set(value) {
field = value
isDirty = true
}
override var received = Decimal.ZERO
set(value) {
field = value
isDirty = true
}
override val isServer: Boolean
get() = true
}
private val data = Object2ObjectOpenHashMap<UUID, Values>()
fun values(uuid: UUID): Values {
return data.computeIfAbsent(uuid, Function { Values(uuid) })
}
fun values(): Values {
return values(UUID.randomUUID())
}
override fun save(nbt: CompoundTag): CompoundTag {
for ((key, values) in data) {
nbt[key.toString()] = values.serialize()
}
return nbt
}
fun load(nbt: CompoundTag) {
data.clear()
for (key in nbt.allKeys) {
val id = UUID.fromString(key)
data[id] = Values(id).also { it.deserialize(nbt.getCompound(key)) }
}
}
}
var passed: Decimal = passed
set(value) {
if (field != value) {
field = value
parent?.isDirty = true
}
}
val clientData = Object2ObjectOpenHashMap<UUID, IValues>()
var received: Decimal = received
set(value) {
if (field != value) {
field = value
parent?.isDirty = true
}
}
val serverData: Data by lazyPerServer {
it.overworld().dataStorage.computeIfAbsent(::Data, ::Data, "otm_$savedataID")
}
private inner class Power(private val stack: ItemStack) : IMatteryEnergyStorage, ICapabilityProvider {
@ -87,7 +165,18 @@ class QuantumBatteryItem : Item {
override val energyFlow: FlowDirection
get() = FlowDirection.BI_DIRECTIONAL
var data = Data()
var values: IValues = UnboundValues()
fun updateValues() {
if (!values.isServer && isServerThread()) {
values = serverData.values(stack.tag?.getUUIDSafe("id") ?: UUID.randomUUID().also { stack.tagNotNull["id"] = it })
} else if (isClientThread()) {
val id = stack.tag?.getUUIDSafe("id") ?: return
if (values.uuid != id)
values = clientData.computeIfAbsent(id, Function { UnboundValues(it) })
}
}
override fun <T : Any?> getCapability(cap: Capability<T>, side: Direction?): LazyOptional<T> {
if (cap == ForgeCapabilities.ENERGY || cap == MatteryCapability.ENERGY) {
@ -100,253 +189,107 @@ class QuantumBatteryItem : Item {
}
override fun extractEnergy(howMuch: Decimal, simulate: Boolean): Decimal {
if (howMuch.isNegative) {
if (!howMuch.isPositive)
return Decimal.ZERO
}
if (data.parent == null && isServerThread()) {
determineQuantumLink()
updateValues()
if (data.parent == null) {
if (!values.isServer && !simulate)
return Decimal.ZERO
}
}
if (isCreative) {
val newEnergy = (data.energy - howMuch).moreThanZero()
val diff = data.energy - newEnergy
if (balanceValues == null) {
val newEnergy = (values.energy - howMuch).moreThanZero()
val diff = values.energy - newEnergy
if (!simulate) {
data.energy = newEnergy
data.passed += diff
values.energy = newEnergy
values.passed += diff
}
return diff
} else {
val newEnergy = (values.energy - howMuch.coerceAtMost(balanceValues.energyThroughput)).moreThanZero()
val diff = values.energy - newEnergy
if (!simulate) {
values.energy = newEnergy
values.passed += diff
}
return diff
}
val newEnergy = (data.energy - howMuch.coerceAtMost(throughput!!)).moreThanZero()
val diff = data.energy - newEnergy
if (!simulate) {
data.energy = newEnergy
data.passed += diff
}
return diff
}
override fun receiveEnergy(howMuch: Decimal, simulate: Boolean): Decimal {
if (howMuch.isNegative) {
if (!howMuch.isPositive)
return Decimal.ZERO
}
if (data.parent == null && isServerThread()) {
determineQuantumLink()
updateValues()
if (data.parent == null) {
if (!values.isServer && !simulate)
return Decimal.ZERO
}
}
if (isCreative) {
if (balanceValues == null) {
if (!simulate) {
data.energy += howMuch
data.received += howMuch
values.energy += howMuch
values.received += howMuch
}
return howMuch
}
if (data.energy >= capacity!!) {
} else if (values.energy >= balanceValues.energyCapacity) {
return Decimal.ZERO
}
val newEnergy = (data.energy + howMuch.coerceAtMost(throughput!!)).coerceAtMost(capacity!!)
val diff = newEnergy - data.energy
} else {
val newEnergy = (values.energy + howMuch.coerceAtMost(balanceValues.energyThroughput)).coerceAtMost(balanceValues.energyCapacity)
val diff = newEnergy - values.energy
if (!simulate) {
data.energy = newEnergy
data.received += diff
values.energy = newEnergy
values.received += diff
}
return diff
}
}
override var batteryLevel: Decimal
get() {
if (data.parent == null) {
determineQuantumLink()
}
if (isClientThread()) {
return clientPowerMap[data.index]?.energy ?: data.energy
}
return data.energy
updateValues()
return values.energy
}
set(value) {
if (data.parent == null) {
determineQuantumLink()
updateValues()
values.energy = value
}
if (isClientThread()) {
val energy1 = clientPowerMap[data.index]
if (energy1 != null) {
energy1.energy = value
} else {
data.energy = value
val passed: Decimal get() {
updateValues()
return values.passed
}
return
}
data.energy = value
}
val passed: Decimal
get() {
if (data.parent == null) {
determineQuantumLink()
}
if (isClientThread()) {
return clientPowerMap[data.index]?.passed ?: data.passed
}
return data.passed
}
val received: Decimal
get() {
if (data.parent == null) {
determineQuantumLink()
}
if (isClientThread()) {
return clientPowerMap[data.index]?.received ?: data.received
}
return data.received
val received: Decimal get() {
updateValues()
return values.received
}
override val maxBatteryLevel: Decimal
get() = capacity ?: (batteryLevel + Decimal.LONG_MAX_VALUE)
get() = balanceValues?.energyCapacity ?: (batteryLevel + Decimal.LONG_MAX_VALUE)
override val missingPower: Decimal
get() = if (isCreative) Decimal.LONG_MAX_VALUE else super.missingPower
private fun determineQuantumLink() {
if (data.parent == null && isServerThread()) {
val existing = stack.tag?.getInt("link_id")
if (existing == null) {
val old = data
data = saveData!!.factorize()
data.energy = old.energy
stack.tagNotNull["link_id"] = data.index
} else {
data = saveData?.computeIfAbsent(existing) ?: Data(null, existing, data.energy, data.passed, data.received)
}
} else if (!isServerThread()) {
// client ?
val existing = stack.tag?.getInt("link_id") ?: return
if (existing != data.index) {
data = Data(data.parent, existing, data.energy, data.passed, data.received)
}
}
}
fun determineQuantumLinkWeak() {
if (data.parent == null && isServerThread()) {
val existing = stack.tag?.getInt("link_id")
if (existing != null) {
data = saveData?.computeIfAbsent(existing) ?: Data(null, existing, data.energy, data.passed, data.received)
}
} else if (!isServerThread()) {
val existing = stack.tag?.getInt("link_id") ?: return
if (existing != data.index) {
data = Data(data.parent, existing, data.energy, data.passed, data.received)
}
}
}
}
val isCreative: Boolean
private val _capacity: () -> Decimal?
private val _throughput: () -> Decimal?
val capacity get() = _capacity.invoke()
val throughput get() = _throughput.invoke()
val saveDataID: String
val saveData: SavedCountingMap<Data>? get() {
if (isServerThread()) {
return MINECRAFT_SERVER.overworld().dataStorage.computeIfAbsent({
SavedCountingMap(Companion::storeValue, Companion::loadValue, ::Data).load(it)
}, {
SavedCountingMap(Companion::storeValue, Companion::loadValue, ::Data)
}, saveDataID) ?: throw NullPointerException("Unable to get save data for $this in ${MINECRAFT_SERVER.overworld()}")
}
return null
}
data class ClientData(
var energy: Decimal = Decimal.ZERO,
var passed: Decimal = Decimal.ZERO,
var received: Decimal = Decimal.ZERO,
)
val clientPowerMap: Int2ObjectOpenHashMap<ClientData> by lazy {
check(isClient) { "Invalid side" }
Int2ObjectOpenHashMap()
}
constructor(saveDataID: String) : super(Properties().stacksTo(1).rarity(Rarity.EPIC)) {
isCreative = true
_capacity = { null }
_throughput = { null }
this.saveDataID = "otm_$saveDataID".intern()
}
constructor(saveDataID: String, capacity: Decimal, io: Decimal) : super(Properties().stacksTo(1)) {
isCreative = false
_capacity = { capacity }
_throughput = { io }
this.saveDataID = "otm_$saveDataID".intern()
}
constructor(saveDataID: String, values: EnergyBalanceValues) : super(Properties().stacksTo(1)) {
isCreative = false
_capacity = values::energyCapacity
_throughput = values::energyThroughput
this.saveDataID = "otm_$saveDataID".intern()
}
override fun isBarVisible(p_150899_: ItemStack): Boolean {
if (isCreative)
return false
if (isCreative) return false
return p_150899_.matteryEnergy != null
}
override fun getBarWidth(p_150900_: ItemStack): Int {
if (isCreative)
return 13
if (isCreative) return 13
return p_150900_.matteryEnergy?.getBarWidth() ?: super.getBarWidth(p_150900_)
}
override fun getBarColor(p_150901_: ItemStack): Int {
if (isCreative)
return 0
if (isCreative) return 0
return p_150901_.matteryEnergy?.getBarColor() ?: super.getBarColor(p_150901_)
}
@ -354,29 +297,19 @@ class QuantumBatteryItem : Item {
return Power(stack)
}
override fun appendHoverText(
itemStack: ItemStack,
p_41422_: Level?,
components: MutableList<Component>,
p_41424_: TooltipFlag
) {
super.appendHoverText(itemStack, p_41422_, components, p_41424_)
override fun appendHoverText(itemStack: ItemStack, level: Level?, components: MutableList<Component>, flags: TooltipFlag) {
super.appendHoverText(itemStack, level, components, flags)
val power = itemStack.getCapability(MatteryCapability.ENERGY).orThrow() as Power
power.updateValues()
components.add(TranslatableComponent("otm.item.quantum_description").withStyle(ChatFormatting.DARK_GRAY))
if (isCreative) {
if (balanceValues == null) {
components.add(TranslatableComponent("otm.item.quantum_battery.creative_power", power.batteryLevel.formatPower()).withStyle(ChatFormatting.GRAY))
} else {
components.add(TranslatableComponent("otm.item.power.storage", power.batteryLevel.formatPower(), capacity!!.formatPower()).withStyle(ChatFormatting.GRAY))
components.add(
TranslatableComponent(
"otm.item.power.throughput",
throughput!!.formatPower(),
throughput!!.formatPower()
).withStyle(ChatFormatting.GRAY))
components.add(TranslatableComponent("otm.item.power.storage", power.batteryLevel.formatPower(), balanceValues.energyCapacity.formatPower()).withStyle(ChatFormatting.GRAY))
components.add(TranslatableComponent("otm.item.power.throughput_mono", balanceValues.energyThroughput.formatPower()).withStyle(ChatFormatting.GRAY))
}
components.add(TranslatableComponent("otm.item.power.passed", power.passed.formatPower()).withStyle(ChatFormatting.GRAY))
@ -387,18 +320,18 @@ class QuantumBatteryItem : Item {
components.add(TranslatableComponent("otm.item.quantum_battery.creative2").withStyle(ChatFormatting.DARK_GRAY))
}
components.add(TranslatableComponent("otm.item.quantum_link_id", power.data.index).withStyle(ChatFormatting.DARK_GRAY))
components.add(TranslatableComponent("otm.item.quantum_link_id", power.values.uuid).withStyle(ChatFormatting.DARK_GRAY))
}
companion object {
fun clientDisconnect(event: ClientPlayerNetworkEvent.LoggingOut) {
ForgeRegistries.ITEMS.values.stream().forEach { if (it is QuantumBatteryItem) it.clientPowerMap.clear() }
ForgeRegistries.ITEMS.values.forEach { if (it is QuantumBatteryItem) it.clientData.clear() }
}
fun readPacket(buff: FriendlyByteBuf): ChargePacket {
return ChargePacket(
(ForgeRegistries.ITEMS as ForgeRegistry<Item>).getValue(buff.readInt()) as QuantumBatteryItem,
buff.readInt(),
ForgeRegistries.ITEMS.getValue(buff.readInt()) as QuantumBatteryItem,
buff.readUUID(),
buff.readDecimal(),
buff.readDecimal(),
buff.readDecimal(),
@ -408,54 +341,33 @@ class QuantumBatteryItem : Item {
fun tick(event: ServerTickEvent) {
if (event.phase == TickEvent.Phase.END) {
for (ply in event.server.playerList.players) {
val networkedChannels = IntOpenHashSet(0)
val networkedChannels = ObjectOpenHashSet<UUID>(0)
for (item in ply.allItemsStream().filter { !it.isEmpty && it.item is QuantumBatteryItem }) {
for (item in ply.allItems().filter { it.isNotEmpty && it.item is QuantumBatteryItem }) {
val power = item.getCapability(MatteryCapability.ENERGY).orThrow() as Power
power.determineQuantumLinkWeak()
if (power.data.index < 0) {
continue
}
if (networkedChannels.add(power.data.index)) {
GenericNetworkChannel.send(ply, ChargePacket(item.item as QuantumBatteryItem, power.data.index, power.batteryLevel, power.data.passed, power.data.received))
power.updateValues()
if (power.values.isServer && networkedChannels.add(power.values.uuid)) {
GenericNetworkChannel.send(ply, ChargePacket(item.item as QuantumBatteryItem, power.values.uuid, power.batteryLevel, power.passed, power.received))
}
}
}
}
}
private fun loadValue(parent: SavedCountingMap<Data>, tag: Tag, index: Int): Data {
if (tag is ByteArrayTag) {
return Data(parent, index, Decimal.deserializeNBT(tag))
} else if (tag is CompoundTag) {
return Data(parent, index, Decimal.deserializeNBT(tag["energy"]), Decimal.deserializeNBT(tag["passed"]), Decimal.deserializeNBT(tag["received"]))
} else {
return Data(parent, index)
}
}
private fun storeValue(parent: SavedCountingMap<Data>, value: Data, index: Int): CompoundTag {
return CompoundTag().also {
it["energy"] = value.energy.serializeNBT()
it["passed"] = value.passed.serializeNBT()
it["received"] = value.received.serializeNBT()
}
}
}
class ChargePacket(
val type: QuantumBatteryItem,
val channel: Int,
val energy: Decimal,
val passed: Decimal,
val received: Decimal,
) : MatteryPacket {
override val uuid: UUID,
override var energy: Decimal,
override var passed: Decimal,
override var received: Decimal,
) : MatteryPacket, IValues {
override val isServer: Boolean
get() = false
override fun write(buff: FriendlyByteBuf) {
buff.writeInt((ForgeRegistries.ITEMS as ForgeRegistry<Item>).getID(type))
buff.writeInt(channel)
buff.writeInt(ForgeRegistries.ITEMS.getID(type))
buff.writeUUID(uuid)
buff.writeDecimal(energy)
buff.writeDecimal(passed)
buff.writeDecimal(received)
@ -463,7 +375,7 @@ class QuantumBatteryItem : Item {
override fun play(context: Supplier<NetworkEvent.Context>) {
context.packetHandled = true
val data = type.clientPowerMap.computeIfAbsent(channel, Int2ObjectFunction { ClientData() })
val data = type.clientData.computeIfAbsent(uuid, Function { UnboundValues(it) })
data.energy = energy
data.passed = passed
data.received = received

View File

@ -351,7 +351,7 @@ object MItems {
val QUANTUM_BATTERY: QuantumBatteryItem by registry.register(MNames.QUANTUM_BATTERY) { QuantumBatteryItem(MNames.QUANTUM_BATTERY, ItemsConfig.Batteries.QUANTUM_BATTERY) }
val QUANTUM_CAPACITOR: QuantumBatteryItem by registry.register(MNames.QUANTUM_CAPACITOR) { QuantumBatteryItem(MNames.QUANTUM_CAPACITOR, ItemsConfig.Batteries.QUANTUM_CAPACITOR) }
val QUANTUM_BATTERY_CREATIVE: QuantumBatteryItem by registry.register(MNames.QUANTUM_BATTERY_CREATIVE) { QuantumBatteryItem(MNames.QUANTUM_BATTERY_CREATIVE) }
val QUANTUM_BATTERY_CREATIVE: QuantumBatteryItem by registry.register(MNames.QUANTUM_BATTERY_CREATIVE) { QuantumBatteryItem(MNames.QUANTUM_BATTERY_CREATIVE, null) }
val ZPM_BATTERY: ZPMItem by registry.register(MNames.ZPM_BATTERY) { ZPMItem() }
val PROCEDURAL_BATTERY: ProceduralBatteryItem by registry.register(MNames.PROCEDURAL_BATTERY) { ProceduralBatteryItem() }