diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/GlobalEventHandler.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/GlobalEventHandler.kt index 2d66cbc24..24a73bf1c 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/GlobalEventHandler.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/GlobalEventHandler.kt @@ -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() private val postWorldTick = WeakHashMap() +private val clientThreads = WeakHashSet() +private val serverThreads = WeakHashSet() + +private val serverCounter = AtomicInteger() +private var _server: MinecraftServer? = null +val isClient: Boolean by lazy { FMLLoader.getDist() == Dist.CLIENT } + +fun lazyPerServer(fn: (MinecraftServer) -> V): Lazy { + 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 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() } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/Ext.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/Ext.kt index c81c4c3bc..c0bde449d 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/capability/Ext.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/capability/Ext.kt @@ -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 { - val streams = ArrayList>() - streams.add(inventory.stream()) +fun Player.items(includeCosmetics: Boolean = true): Iterator { + val iterators = ArrayList>() + 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 * * Contains all items that player might see/access ([itemsStream] + open container's items) */ -fun Player.allItemsStream(includeCosmetics: Boolean = true): Stream { +fun Player.allItems(includeCosmetics: Boolean = true): Iterator { if (containerMenu == inventoryMenu || containerMenu == matteryPlayer?.exoPackMenu) { - return itemsStream(includeCosmetics) + return items(includeCosmetics) } - val seen = IdentityHashMap(containerMenu.slots.size) + val seen = ReferenceOpenHashSet(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 }) } /** diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/cos/CosmeticArmorCompat.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/cos/CosmeticArmorCompat.kt index d21277685..17cf76090 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/cos/CosmeticArmorCompat.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/cos/CosmeticArmorCompat.kt @@ -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 { +fun Player.cosmeticArmorStream(): Iterator { if (!isCosmeticArmorLoaded) { - return Stream.empty() + return emptyIterator() } return cosmeticArmorStreamImpl() } -private fun Player.cosmeticArmorStreamImpl(): Stream { - val manager = ModObjects.invMan ?: return Stream.empty() +private fun Player.cosmeticArmorStreamImpl(): Iterator { + val manager = ModObjects.invMan ?: return emptyIterator() val container = if (this !is ServerPlayer) { manager.getCosArmorInventoryClient(uuid) @@ -88,7 +90,7 @@ private fun Player.cosmeticArmorStreamImpl(): Stream { manager.getCosArmorInventory(uuid) } - return (container as Container).stream() + return (container as Container).iterator() } fun Player.cosmeticArmorAwareStream(): Stream { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/curios/CuriosCompat.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/curios/CuriosCompat.kt index 50802c1be..4904edad6 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/compat/curios/CuriosCompat.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/compat/curios/CuriosCompat.kt @@ -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> get() { return getCuriosSlotsImpl() } -private fun Player.curiosStreamImpl(includeCosmetics: Boolean): Stream { - val handler = getCapability(MatteryCapability.CURIOS_INVENTORY).orNull() ?: return Stream.empty() +private fun Player.curiosStreamImpl(includeCosmetics: Boolean): Iterator { + val handler = getCapability(MatteryCapability.CURIOS_INVENTORY).orNull() ?: return emptyIterator() - val result = ArrayList>() + val result = ArrayList>() 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 { - return Stream.empty() +fun Player.curiosStream(includeCosmetics: Boolean = true): Iterator { + return emptyIterator() if (!isCuriosLoaded) { - return Stream.empty() + return emptyIterator() } return curiosStreamImpl(includeCosmetics) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/container/ItemHandlerSpliterator.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/container/ItemHandlerSpliterator.kt index 25fc780fb..862c25e43 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/container/ItemHandlerSpliterator.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/container/ItemHandlerSpliterator.kt @@ -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 = Spliterators.iterator(spliterator()) +fun IItemHandler.iterator(): Iterator = Spliterators.iterator(spliterator()).filter { it.isNotEmpty } fun IItemHandler.spliterator(): Spliterator = ItemHandlerSpliterator(this) fun IItemHandler.awareSpliterator(): Spliterator = ItemHandlerAwareSpliterator(this) fun IItemHandler.stream(): Stream = StreamSupport.stream(spliterator(), false) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/ReferenceHashStrategy.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/ReferenceHashStrategy.kt new file mode 100644 index 000000000..76c362060 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/ReferenceHashStrategy.kt @@ -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 { + @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).refersTo(a.get() ?: return false) + } else { + return (a as Reference).refersTo(b) + } + } else if (b is Reference<*>) { + if (b.refersTo(null) || a == null) return false + return (b as Reference).refersTo(a) + } + + return a == b + } + + override fun hashCode(o: Any?): Int { + return o.hashCode() + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/StreamyIterators.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/StreamyIterators.kt index 8580dbb75..5711b8ec5 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/StreamyIterators.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/StreamyIterators.kt @@ -23,6 +23,7 @@ import java.util.stream.Collector */ class FilteringIterator(private val parent: Iterator, private val predicate: Predicate) : MutableIterator { private var foundValue: Any? = Companion + private var returned = false override fun hasNext(): Boolean { if (foundValue === Companion) { @@ -58,16 +59,14 @@ class FilteringIterator(private val parent: Iterator, 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).remove() - foundValue = Companion + returned = false } private companion object @@ -197,6 +196,10 @@ fun concatIterators(a: Iterator): MutableIterator { return a as MutableIterator } +fun concatIterators(iterators: Iterable>): MutableIterator { + return iterators.iterator().flatMap { it } +} + fun concatIterators(vararg iterators: Iterator): MutableIterator { return iterators.iterator().flatMap { it } } @@ -243,7 +246,7 @@ fun Iterator.reduce(identity: T, reducer: (T, T) -> T): T { } fun Iterator.reduce(identity: T, reducer: BinaryOperator): T = reduce(identity, reducer::apply) -fun Iterator.filterNotNull(): Iterator = filter { it != null } as Iterator +fun Iterator.filterNotNull(): MutableIterator = filter { it != null } as MutableIterator fun Iterator.any(predicate: Predicate): Boolean { for (value in this) { @@ -363,3 +366,7 @@ fun Iterator.maybe(): T? { else null } + +fun emptyIterator(): MutableIterator { + return ObjectIterators.emptyIterator() +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/WeakHashSet.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/WeakHashSet.kt index b00538f52..34c18d641 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/WeakHashSet.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/collect/WeakHashSet.kt @@ -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 : MutableSet { - private val backing = WeakHashMap() + private val queue = ReferenceQueue() + private val backing = ObjectOpenCustomHashSet(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): 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 { - return backing.keys.iterator() + purge() + return backing.iterator().map { (it as HashedWeakReference).get() }.filterNotNull() } override fun remove(element: E): Boolean { - return backing.remove(element) != null + purge() + return backing.remove(element) } override fun removeAll(elements: Collection): Boolean { - return backing.keys.removeAll(elements) + purge() + return backing.removeAll(elements) } override fun retainAll(elements: Collection): 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): Boolean { - return elements.all(::contains) + purge() + return backing.containsAll(elements) } override fun isEmpty(): Boolean { + purge() return backing.isEmpty() } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/math/Decimal.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/math/Decimal.kt index 3ed4e4750..8635e6610 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/math/Decimal.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/math/Decimal.kt @@ -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) } /** diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/AtomicallyInvalidatedLazy.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/AtomicallyInvalidatedLazy.kt new file mode 100644 index 000000000..239f96bce --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/AtomicallyInvalidatedLazy.kt @@ -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(private val invalidator: AtomicInteger, private val initializer: () -> V) : Lazy { + @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 +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/HashedWeakReference.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/HashedWeakReference.kt new file mode 100644 index 000000000..e73cd4c45 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/HashedWeakReference.kt @@ -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 : WeakReference { + constructor(value: T) : super(value) { + hash = value.hashCode() + } + + constructor(value: T, queue: ReferenceQueue) : super(value, queue) { + hash = value.hashCode() + } + + private val hash: Int + + override fun hashCode(): Int { + return hash + } + + override fun toString(): String { + return "HashedWeakReference[hash=$hash]" + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/data/condition/ItemInInventoryCondition.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/data/condition/ItemInInventoryCondition.kt index d4f088d3c..a0bf6ca5b 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/data/condition/ItemInInventoryCondition.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/data/condition/ItemInInventoryCondition.kt @@ -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 } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/item/QuantumBatteryItem.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/item/QuantumBatteryItem.kt index 4991374b6..d12384485 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/item/QuantumBatteryItem.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/item/QuantumBatteryItem.kt @@ -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?, - 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 - set(value) { - if (field != value) { + 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) { 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() + + 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() } - var passed: Decimal = passed - set(value) { - if (field != value) { - field = value - parent?.isDirty = true - } - } + return nbt + } - var received: Decimal = received - set(value) { - if (field != value) { - field = value - parent?.isDirty = true - } + 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)) } } + } + } + + val clientData = Object2ObjectOpenHashMap() + + 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 getCapability(cap: Capability, side: Direction?): LazyOptional { 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) { - return Decimal.ZERO - } - } + 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) { - return Decimal.ZERO - } - } + 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 + } else { + val newEnergy = (values.energy + howMuch.coerceAtMost(balanceValues.energyThroughput)).coerceAtMost(balanceValues.energyCapacity) + val diff = newEnergy - values.energy + + if (!simulate) { + values.energy = newEnergy + values.received += diff + } + + return diff } - - val newEnergy = (data.energy + howMuch.coerceAtMost(throughput!!)).coerceAtMost(capacity!!) - val diff = newEnergy - data.energy - - if (!simulate) { - data.energy = newEnergy - data.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() - } - - if (isClientThread()) { - val energy1 = clientPowerMap[data.index] - - if (energy1 != null) { - energy1.energy = value - } else { - data.energy = value - } - - return - } - - data.energy = value + updateValues() + values.energy = value } - val passed: Decimal - get() { - if (data.parent == null) { - determineQuantumLink() - } - - if (isClientThread()) { - return clientPowerMap[data.index]?.passed ?: data.passed - } - - return data.passed + val passed: Decimal get() { + updateValues() + return values.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? 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 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, - p_41424_: TooltipFlag - ) { - super.appendHoverText(itemStack, p_41422_, components, p_41424_) + override fun appendHoverText(itemStack: ItemStack, level: Level?, components: MutableList, 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).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(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, 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, 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).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) { 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 diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MItems.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MItems.kt index 8c004d6c5..42e6ab499 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MItems.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/registry/MItems.kt @@ -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() }