Compare commits
2 Commits
fdf8e47e5e
...
16f5f4c9c9
Author | SHA1 | Date | |
---|---|---|---|
16f5f4c9c9 | |||
a06fd6a31a |
@ -3,6 +3,7 @@
|
||||
|
||||
package ru.dbotthepony.mc.otm
|
||||
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder
|
||||
import net.minecraft.client.server.IntegratedServer
|
||||
import net.minecraft.core.HolderLookup
|
||||
import net.minecraft.server.MinecraftServer
|
||||
@ -27,6 +28,7 @@ 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 java.lang.ref.Cleaner
|
||||
import java.util.*
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
@ -38,6 +40,8 @@ private val postWorldTick = WeakHashMap<Level, TickList>()
|
||||
private val clientThreads = WeakHashSet<Thread>()
|
||||
private val serverThreads = WeakHashSet<Thread>()
|
||||
|
||||
val OTM_CLEANER: Cleaner = Cleaner.create(ThreadFactoryBuilder().setNameFormat("OverdriveThatMatters-Cleaner-%d").build())
|
||||
|
||||
private val serverCounter = AtomicInteger()
|
||||
private var _server: MinecraftServer? = null
|
||||
val isClient: Boolean by lazy { FMLLoader.getDist() == Dist.CLIENT }
|
||||
|
@ -2,10 +2,9 @@ package ru.dbotthepony.mc.otm.android
|
||||
|
||||
import net.minecraft.core.HolderLookup
|
||||
import net.minecraft.nbt.CompoundTag
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
import net.neoforged.neoforge.common.util.INBTSerializable
|
||||
import net.neoforged.neoforge.event.entity.living.LivingIncomingDamageEvent
|
||||
import ru.dbotthepony.mc.otm.network.DelegateSyncher
|
||||
import ru.dbotthepony.mc.otm.network.syncher.Syncher
|
||||
import ru.dbotthepony.kommons.util.getValue
|
||||
import ru.dbotthepony.kommons.util.setValue
|
||||
import ru.dbotthepony.mc.otm.capability.MatteryPlayer
|
||||
@ -13,7 +12,7 @@ import ru.dbotthepony.mc.otm.core.nbt.set
|
||||
|
||||
abstract class AndroidFeature(val type: AndroidFeatureType<*>, val android: MatteryPlayer) : INBTSerializable<CompoundTag> {
|
||||
val ply get() = android.ply
|
||||
val syncher = DelegateSyncher()
|
||||
val syncher = Syncher()
|
||||
val syncherRemote = syncher.Remote()
|
||||
|
||||
open var level by syncher.int(setter = setter@{ field, value ->
|
||||
|
@ -10,7 +10,7 @@ import net.minecraft.world.entity.player.Player
|
||||
import net.neoforged.bus.api.Event
|
||||
import net.neoforged.neoforge.common.NeoForge
|
||||
import net.neoforged.neoforge.common.util.INBTSerializable
|
||||
import ru.dbotthepony.mc.otm.network.DelegateSyncher
|
||||
import ru.dbotthepony.mc.otm.network.syncher.Syncher
|
||||
import ru.dbotthepony.kommons.util.getValue
|
||||
import ru.dbotthepony.kommons.util.setValue
|
||||
import ru.dbotthepony.mc.otm.OverdriveThatMatters
|
||||
@ -48,7 +48,7 @@ class AndroidResearch(val type: AndroidResearchType, val capability: MatteryPlay
|
||||
|
||||
val ply: Player get() = capability.ply
|
||||
|
||||
val syncher = DelegateSyncher()
|
||||
val syncher = Syncher()
|
||||
val syncherRemote = syncher.Remote()
|
||||
|
||||
var isResearched by syncher.boolean()
|
||||
|
@ -37,7 +37,6 @@ import net.neoforged.neoforge.event.server.ServerStoppingEvent
|
||||
import net.neoforged.neoforge.event.tick.LevelTickEvent
|
||||
import net.neoforged.neoforge.network.PacketDistributor
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import ru.dbotthepony.mc.otm.network.DelegateSyncher
|
||||
import ru.dbotthepony.kommons.util.Listenable
|
||||
import ru.dbotthepony.mc.otm.SERVER_IS_LIVE
|
||||
import ru.dbotthepony.mc.otm.block.INeighbourChangeListener
|
||||
@ -54,6 +53,7 @@ import ru.dbotthepony.mc.otm.core.util.Savetables
|
||||
import ru.dbotthepony.mc.otm.core.util.TickList
|
||||
import ru.dbotthepony.mc.otm.core.util.countingLazy
|
||||
import ru.dbotthepony.mc.otm.network.BlockEntitySyncPacket
|
||||
import ru.dbotthepony.mc.otm.network.syncher.Syncher
|
||||
import ru.dbotthepony.mc.otm.onceServer
|
||||
import ru.dbotthepony.mc.otm.registry.MBlocks
|
||||
import ru.dbotthepony.mc.otm.sometimeServer
|
||||
@ -369,8 +369,8 @@ abstract class MatteryBlockEntity(p_155228_: BlockEntityType<*>, p_155229_: Bloc
|
||||
}
|
||||
}
|
||||
|
||||
val syncher = DelegateSyncher()
|
||||
private val synchers = Object2ObjectArrayMap<ServerPlayer, DelegateSyncher.Remote>()
|
||||
val syncher = Syncher()
|
||||
private val synchers = Object2ObjectArrayMap<ServerPlayer, Syncher.Remote>()
|
||||
|
||||
override fun setLevel(level: Level) {
|
||||
val old = this.level
|
||||
|
@ -3,7 +3,7 @@ package ru.dbotthepony.mc.otm.block.entity
|
||||
import net.minecraft.core.HolderLookup
|
||||
import net.minecraft.nbt.CompoundTag
|
||||
import net.neoforged.neoforge.common.util.INBTSerializable
|
||||
import ru.dbotthepony.mc.otm.network.DelegateSyncher
|
||||
import ru.dbotthepony.mc.otm.network.syncher.Syncher
|
||||
import ru.dbotthepony.kommons.util.Listenable
|
||||
import ru.dbotthepony.kommons.util.getValue
|
||||
import ru.dbotthepony.kommons.util.setValue
|
||||
@ -79,7 +79,7 @@ class RedstoneControl(private val valueChanges: (new: Boolean, old: Boolean) ->
|
||||
}
|
||||
|
||||
class SynchronizedRedstoneControl(
|
||||
synchronizer: DelegateSyncher,
|
||||
synchronizer: Syncher,
|
||||
private val valueChanges: (new: Boolean, old: Boolean) -> Unit,
|
||||
) : AbstractRedstoneControl() {
|
||||
override var redstoneSetting: RedstoneSetting by synchronizer.enum(RedstoneSetting.LOW, setter = { access, value ->
|
||||
|
@ -27,7 +27,7 @@ import ru.dbotthepony.mc.otm.network.wrap
|
||||
import ru.dbotthepony.mc.otm.registry.MBlockEntities
|
||||
|
||||
class FluidTankBlockEntity(blockPos: BlockPos, blockState: BlockState) : MatteryDeviceBlockEntity(MBlockEntities.FLUID_TANK, blockPos, blockState) {
|
||||
val fluid = BlockMatteryFluidHandler(ItemsConfig::FLUID_TANK_CAPACITY, syncher.Slot(ListenableDelegate.SmartBox(FluidStack.EMPTY, setter = { access, value ->
|
||||
val fluid = BlockMatteryFluidHandler(ItemsConfig::FLUID_TANK_CAPACITY, syncher.add(ListenableDelegate.SmartBox(FluidStack.EMPTY, setter = { access, value ->
|
||||
access.accept(value)
|
||||
level?.lightEngine?.checkBlock(blockPos)
|
||||
|
||||
|
@ -1,23 +1,22 @@
|
||||
package ru.dbotthepony.mc.otm.capability
|
||||
|
||||
import com.google.common.collect.ImmutableList
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList
|
||||
import net.minecraft.core.HolderLookup
|
||||
import net.minecraft.nbt.CompoundTag
|
||||
import net.minecraft.nbt.ListTag
|
||||
import net.minecraft.nbt.NumericTag
|
||||
import net.neoforged.neoforge.common.util.INBTSerializable
|
||||
import ru.dbotthepony.mc.otm.core.forValidRefs
|
||||
import ru.dbotthepony.mc.otm.core.DecimalHistoryGraph
|
||||
import ru.dbotthepony.mc.otm.core.math.Decimal
|
||||
import ru.dbotthepony.mc.otm.core.nbt.map
|
||||
import ru.dbotthepony.mc.otm.core.nbt.set
|
||||
import java.lang.ref.WeakReference
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
abstract class AbstractProfiledStorage<out P>(val parent: P) : INBTSerializable<CompoundTag?> {
|
||||
private val historyReceiveInternal = ArrayList<Decimal>(HISTORY_SIZE)
|
||||
private val historyTransferInternal = ArrayList<Decimal>(HISTORY_SIZE)
|
||||
val received = DecimalHistoryGraph(ticks = HISTORY_SIZE)
|
||||
val transferred = DecimalHistoryGraph(ticks = HISTORY_SIZE)
|
||||
|
||||
var receivedThisTick = Decimal.ZERO
|
||||
private set
|
||||
var transferredThisTick = Decimal.ZERO
|
||||
private set
|
||||
|
||||
private var isTicking = false
|
||||
|
||||
@ -28,31 +27,9 @@ abstract class AbstractProfiledStorage<out P>(val parent: P) : INBTSerializable<
|
||||
}
|
||||
}
|
||||
|
||||
val historyReceive: List<Decimal> = Collections.unmodifiableList(historyReceiveInternal)
|
||||
val historyTransfer: List<Decimal> = Collections.unmodifiableList(historyTransferInternal)
|
||||
|
||||
val lastTickReceive
|
||||
get() = historyReceiveInternal[tick]
|
||||
|
||||
val lastTickTransfer
|
||||
get() = historyTransferInternal[tick]
|
||||
|
||||
private var thisTickReceive = Decimal.ZERO
|
||||
private var thisTickTransfer = Decimal.ZERO
|
||||
|
||||
var tick = 0
|
||||
private set
|
||||
|
||||
init {
|
||||
for (i in 0 until HISTORY_SIZE) {
|
||||
historyReceiveInternal.add(Decimal.ZERO)
|
||||
historyTransferInternal.add(Decimal.ZERO)
|
||||
}
|
||||
}
|
||||
|
||||
protected fun recordTransfer(value: Decimal, simulate: Boolean): Decimal {
|
||||
if (!simulate) {
|
||||
thisTickTransfer += value
|
||||
transferredThisTick += value
|
||||
if (!value.isZero) startTicking()
|
||||
}
|
||||
|
||||
@ -61,7 +38,7 @@ abstract class AbstractProfiledStorage<out P>(val parent: P) : INBTSerializable<
|
||||
|
||||
protected fun recordReceive(value: Decimal, simulate: Boolean): Decimal {
|
||||
if (!simulate) {
|
||||
thisTickReceive += value
|
||||
receivedThisTick += value
|
||||
if (!value.isZero) startTicking()
|
||||
}
|
||||
|
||||
@ -69,14 +46,13 @@ abstract class AbstractProfiledStorage<out P>(val parent: P) : INBTSerializable<
|
||||
}
|
||||
|
||||
private fun tick(): Boolean {
|
||||
tick = (tick + 1) % HISTORY_SIZE
|
||||
received.add(receivedThisTick)
|
||||
transferred.add(transferredThisTick)
|
||||
|
||||
historyReceiveInternal[tick] = thisTickReceive
|
||||
historyTransferInternal[tick] = thisTickTransfer
|
||||
isTicking = !thisTickReceive.isZero || !thisTickTransfer.isZero || historyReceiveInternal.any { !it.isZero } || historyTransferInternal.any { !it.isZero }
|
||||
isTicking = !receivedThisTick.isZero || !transferredThisTick.isZero || received.any { !it.isZero } || transferred.any { !it.isZero }
|
||||
|
||||
thisTickReceive = Decimal.ZERO
|
||||
thisTickTransfer = Decimal.ZERO
|
||||
transferredThisTick = Decimal.ZERO
|
||||
receivedThisTick = Decimal.ZERO
|
||||
|
||||
return isTicking
|
||||
}
|
||||
@ -84,48 +60,26 @@ abstract class AbstractProfiledStorage<out P>(val parent: P) : INBTSerializable<
|
||||
val savedata: INBTSerializable<CompoundTag?> = object : INBTSerializable<CompoundTag?> {
|
||||
override fun serializeNBT(registry: HolderLookup.Provider): CompoundTag {
|
||||
return CompoundTag().also { tag ->
|
||||
tag["historyReceive"] = ListTag().also {
|
||||
for (value in historyReceiveInternal) {
|
||||
it.add(value.serializeNBT())
|
||||
}
|
||||
}
|
||||
|
||||
tag["historyTransfer"] = ListTag().also {
|
||||
for (value in historyTransferInternal) {
|
||||
it.add(value.serializeNBT())
|
||||
}
|
||||
}
|
||||
|
||||
tag["historyTick"] = tick
|
||||
tag["historyReceive"] = received.serializeNBT(registry)
|
||||
tag["historyTransfer"] = transferred.serializeNBT(registry)
|
||||
}
|
||||
}
|
||||
|
||||
override fun deserializeNBT(registry: HolderLookup.Provider, nbt: CompoundTag?) {
|
||||
tick = 0
|
||||
thisTickReceive = Decimal.ZERO
|
||||
thisTickTransfer = Decimal.ZERO
|
||||
transferredThisTick = Decimal.ZERO
|
||||
receivedThisTick = Decimal.ZERO
|
||||
|
||||
for (i in 0 until HISTORY_SIZE) {
|
||||
historyReceiveInternal[i] = Decimal.ZERO
|
||||
historyTransferInternal[i] = Decimal.ZERO
|
||||
}
|
||||
received.clear()
|
||||
transferred.clear()
|
||||
|
||||
nbt ?: return
|
||||
|
||||
nbt.map("historyTick") { it: NumericTag ->
|
||||
tick = it.asInt
|
||||
nbt.map("historyReceive") { it: CompoundTag ->
|
||||
received.deserializeNBT(registry, it)
|
||||
}
|
||||
|
||||
nbt.map("historyReceive") { it: ListTag ->
|
||||
for (i in 0 until HISTORY_SIZE.coerceAtMost(it.size)) {
|
||||
historyReceiveInternal[i] = Decimal.deserializeNBT(it[i])
|
||||
}
|
||||
}
|
||||
|
||||
nbt.map("historyTransfer") { it: ListTag ->
|
||||
for (i in 0 until HISTORY_SIZE.coerceAtMost(it.size)) {
|
||||
historyTransferInternal[i] = Decimal.deserializeNBT(it[i])
|
||||
}
|
||||
nbt.map("historyTransfer") { it: CompoundTag ->
|
||||
transferred.deserializeNBT(registry, it)
|
||||
}
|
||||
|
||||
startTicking()
|
||||
@ -150,14 +104,6 @@ abstract class AbstractProfiledStorage<out P>(val parent: P) : INBTSerializable<
|
||||
return tag
|
||||
}
|
||||
|
||||
val weightedTransfer: Decimal get() {
|
||||
return calcWeightedAverage(historyTransfer, tick)
|
||||
}
|
||||
|
||||
val weightedReceive: Decimal get() {
|
||||
return calcWeightedAverage(historyReceive, tick)
|
||||
}
|
||||
|
||||
override fun deserializeNBT(registry: HolderLookup.Provider, nbt: CompoundTag?) {
|
||||
if (parent is INBTSerializable<*>) {
|
||||
(parent as INBTSerializable<CompoundTag?>).deserializeNBT(registry, nbt)
|
||||
@ -167,48 +113,9 @@ abstract class AbstractProfiledStorage<out P>(val parent: P) : INBTSerializable<
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val HISTORY_SIZE = 20
|
||||
const val HISTORY_SIZE = 20 * 5
|
||||
private val storages = ObjectArrayList<AbstractProfiledStorage<*>>()
|
||||
|
||||
val HISTORY_WEIGHTERS: ImmutableList<Decimal> = ImmutableList.of(
|
||||
Decimal("0.313335967"),
|
||||
Decimal("0.146176397"),
|
||||
Decimal("0.09357867"),
|
||||
Decimal("0.068193701"),
|
||||
Decimal("0.053351084"),
|
||||
Decimal("0.043655993"),
|
||||
Decimal("0.036847023"),
|
||||
Decimal("0.031813486"),
|
||||
Decimal("0.027947534"),
|
||||
Decimal("0.024889161"),
|
||||
Decimal("0.02241188"),
|
||||
Decimal("0.020366241"),
|
||||
Decimal("0.018649731"),
|
||||
Decimal("0.017189744"),
|
||||
Decimal("0.015933452"),
|
||||
Decimal("0.014841516"),
|
||||
Decimal("0.013884059"),
|
||||
Decimal("0.013037985"),
|
||||
Decimal("0.012285173"),
|
||||
Decimal("0.011611204"),
|
||||
)
|
||||
|
||||
fun calcWeightedAverage(inputs: List<Decimal>, pointer: Int): Decimal {
|
||||
require(inputs.size == HISTORY_WEIGHTERS.size) { "Expected list of size ${HISTORY_WEIGHTERS.size}, got ${inputs.size}" }
|
||||
require(pointer in 0 until HISTORY_WEIGHTERS.size) { "Invalid pointer position: $pointer" }
|
||||
|
||||
var result = Decimal.ZERO
|
||||
var i = 0
|
||||
|
||||
for (i2 in pointer downTo 0)
|
||||
result += inputs[i2] * HISTORY_WEIGHTERS[i++]
|
||||
|
||||
for (i2 in HISTORY_WEIGHTERS.size - 1 downTo pointer + 1)
|
||||
result += inputs[i2] * HISTORY_WEIGHTERS[i++]
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// после раздумий, наиболее корректное место для обновления состояния профилированных
|
||||
// хранилищ энергии и материи будет после всех остальных хуков на тики после тика сервера
|
||||
// разумеется, такое решение может несколько снизить производительность сервера,
|
||||
|
@ -59,7 +59,7 @@ import org.apache.logging.log4j.LogManager
|
||||
import org.joml.Vector4f
|
||||
import ru.dbotthepony.kommons.collect.ListenableMap
|
||||
import ru.dbotthepony.kommons.collect.ListenableSet
|
||||
import ru.dbotthepony.mc.otm.network.DelegateSyncher
|
||||
import ru.dbotthepony.mc.otm.network.syncher.Syncher
|
||||
import ru.dbotthepony.kommons.util.ListenableDelegate
|
||||
import ru.dbotthepony.kommons.util.getValue
|
||||
import ru.dbotthepony.kommons.util.setValue
|
||||
@ -80,6 +80,7 @@ import ru.dbotthepony.mc.otm.capability.energy.ProfiledEnergyStorage
|
||||
import ru.dbotthepony.mc.otm.capability.energy.extractEnergyExact
|
||||
import ru.dbotthepony.mc.otm.capability.energy.receiveEnergyExact
|
||||
import ru.dbotthepony.mc.otm.client.minecraft
|
||||
import ru.dbotthepony.mc.otm.client.onceClient
|
||||
import ru.dbotthepony.mc.otm.config.AndroidConfig
|
||||
import ru.dbotthepony.mc.otm.config.ExopackConfig
|
||||
import ru.dbotthepony.mc.otm.container.CombinedContainer
|
||||
@ -122,6 +123,7 @@ import ru.dbotthepony.mc.otm.triggers.ExopackGainedSmeltingTrigger
|
||||
import ru.dbotthepony.mc.otm.triggers.ExopackObtainedTrigger
|
||||
import ru.dbotthepony.mc.otm.triggers.ExopackSlotsExpandedTrigger
|
||||
import ru.dbotthepony.mc.otm.triggers.MatteryInventoryChangeTrigger
|
||||
import java.io.Closeable
|
||||
import java.util.*
|
||||
import java.util.stream.Stream
|
||||
import kotlin.collections.ArrayDeque
|
||||
@ -182,7 +184,7 @@ class MatteryPlayer(val ply: Player) {
|
||||
* any differences in field order/types/etc between client and server
|
||||
* will break *everything*
|
||||
*/
|
||||
val syncher = DelegateSyncher()
|
||||
val syncher = Syncher()
|
||||
|
||||
/**
|
||||
* Remote for "this" player
|
||||
@ -196,10 +198,10 @@ class MatteryPlayer(val ply: Player) {
|
||||
* any differences in field order/types/etc between client and server
|
||||
* will break *everything*
|
||||
*/
|
||||
val publicSyncher = DelegateSyncher()
|
||||
val publicSyncher = Syncher()
|
||||
|
||||
val publicSyncherRemote = publicSyncher.Remote()
|
||||
val remoteSynchers = Object2ObjectArrayMap<ServerPlayer, DelegateSyncher.Remote>()
|
||||
val remoteSynchers = Object2ObjectArrayMap<ServerPlayer, Syncher.Remote>()
|
||||
|
||||
/**
|
||||
* For data to be stored to and loaded from NBT automatically
|
||||
@ -242,8 +244,8 @@ class MatteryPlayer(val ply: Player) {
|
||||
exopackSlotModifier.recompute()
|
||||
}
|
||||
|
||||
private val exopackSlotModifierMap = syncher.MapSlot(
|
||||
ListenableMap<UUID, Int>().also { it.addListener(Runnable { _recomputeModifiers() }) },
|
||||
private val exopackSlotModifierMap = syncher.map(
|
||||
backing = ListenableMap<UUID, Int>().also { it.addListener(Runnable { _recomputeModifiers() }) },
|
||||
keyCodec = StreamCodecs.UUID,
|
||||
valueCodec = StreamCodecs.VAR_INT,
|
||||
)
|
||||
@ -265,9 +267,9 @@ class MatteryPlayer(val ply: Player) {
|
||||
syncher.add(null, StreamCodecs.ITEM_TYPE.nullable())
|
||||
}
|
||||
|
||||
val slotsChargeFlag = syncher.SetSlot(
|
||||
ListenableSet(IntAVLTreeSet()),
|
||||
StreamCodecs.VAR_INT,
|
||||
val slotsChargeFlag = syncher.set(
|
||||
backing = ListenableSet(IntAVLTreeSet()),
|
||||
codec = StreamCodecs.VAR_INT,
|
||||
).delegate
|
||||
|
||||
private fun slotChargeToDefault() {
|
||||
@ -282,13 +284,15 @@ class MatteryPlayer(val ply: Player) {
|
||||
slotChargeToDefault()
|
||||
}
|
||||
|
||||
private var exopackContainerSynchedFilters: List<Closeable> = listOf()
|
||||
|
||||
/**
|
||||
* Exopack container, which actually store items inside Exopack
|
||||
*/
|
||||
var exopackContainer: MatteryContainer = PlayerMatteryContainer(0)
|
||||
private set(value) {
|
||||
_exoPackMenu = null
|
||||
field.removeFilterSynchronizer()
|
||||
exopackContainerSynchedFilters.forEach { it.close() }
|
||||
|
||||
@Suppress("SENSELESS_COMPARISON") // false positive - fields of player can easily be nulls, despite annotations saying otherwise
|
||||
if (ply.containerMenu != null && (ply !is ServerPlayer || ply.connection != null)) {
|
||||
@ -303,7 +307,7 @@ class MatteryPlayer(val ply: Player) {
|
||||
}
|
||||
|
||||
value.deserializeNBT(ply.level().registryAccess(), field.serializeNBT(ply.level().registryAccess()))
|
||||
value.addFilterSynchronizer(syncher)
|
||||
exopackContainerSynchedFilters = value.synchableFilters.map { syncher.add0(it) }
|
||||
field = value
|
||||
|
||||
_combinedInventory = null
|
||||
|
@ -8,7 +8,7 @@ import net.minecraft.world.item.ItemStack
|
||||
import net.minecraft.world.ticks.ContainerSingleItem
|
||||
import net.neoforged.neoforge.capabilities.Capabilities
|
||||
import net.neoforged.neoforge.common.util.INBTSerializable
|
||||
import ru.dbotthepony.mc.otm.network.DelegateSyncher
|
||||
import ru.dbotthepony.mc.otm.network.syncher.Syncher
|
||||
import ru.dbotthepony.kommons.util.getValue
|
||||
import ru.dbotthepony.kommons.util.setValue
|
||||
import ru.dbotthepony.mc.otm.capability.FlowDirection
|
||||
@ -23,7 +23,7 @@ import ru.dbotthepony.mc.otm.triggers.ExopackBatterySlotTrigger
|
||||
|
||||
class BatteryBackedEnergyStorage(
|
||||
private val ply: Player,
|
||||
synchronizer: DelegateSyncher,
|
||||
synchronizer: Syncher,
|
||||
initialCharge: Decimal,
|
||||
maxCharge: Decimal,
|
||||
val isAndroid: Boolean,
|
||||
|
@ -43,8 +43,8 @@ object MatterStorageProvider : IBlockComponentProvider, IServerDataProvider<Bloc
|
||||
// profiledData.putDecimal("lastTickReceive", it.lastTickReceive)
|
||||
// profiledData.putDecimal("lastTickTransfer", it.lastTickTransfer)
|
||||
|
||||
profiledData.putDecimal("weightedReceive", it.weightedReceive)
|
||||
profiledData.putDecimal("weightedTransfer", it.weightedTransfer)
|
||||
profiledData.putDecimal("weightedReceive", it.received.calcWeightedAverage())
|
||||
profiledData.putDecimal("weightedTransfer", it.transferred.calcWeightedAverage())
|
||||
|
||||
matterData.put("profiledData", profiledData)
|
||||
}
|
||||
|
@ -37,8 +37,8 @@ object MatteryEnergyProvider : IBlockComponentProvider, IServerDataProvider<Bloc
|
||||
// profiledData.putDecimal("lastTickReceive", it.lastTickReceive)
|
||||
// profiledData.putDecimal("lastTickTransfer", it.lastTickTransfer)
|
||||
|
||||
profiledData.putDecimal("weightedReceive", it.weightedReceive)
|
||||
profiledData.putDecimal("weightedTransfer", it.weightedTransfer)
|
||||
profiledData.putDecimal("weightedReceive", it.received.calcWeightedAverage())
|
||||
profiledData.putDecimal("weightedTransfer", it.transferred.calcWeightedAverage())
|
||||
|
||||
energyData.put("profiledData", profiledData)
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ import net.minecraft.nbt.CompoundTag
|
||||
import net.minecraft.nbt.ListTag
|
||||
import net.minecraft.nbt.NbtOps
|
||||
import net.minecraft.nbt.Tag
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
import net.minecraft.world.Container
|
||||
import net.minecraft.world.entity.player.Player
|
||||
import net.minecraft.world.entity.player.StackedContents
|
||||
@ -24,7 +25,8 @@ import net.minecraft.world.item.Items
|
||||
import net.neoforged.neoforge.common.util.INBTSerializable
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import ru.dbotthepony.kommons.collect.ListenableMap
|
||||
import ru.dbotthepony.mc.otm.network.DelegateSyncher
|
||||
import ru.dbotthepony.kommons.util.Delegate
|
||||
import ru.dbotthepony.mc.otm.network.syncher.Syncher
|
||||
import ru.dbotthepony.mc.otm.container.util.IContainerSlot
|
||||
import ru.dbotthepony.mc.otm.core.addSorted
|
||||
import ru.dbotthepony.mc.otm.core.collect.any
|
||||
@ -33,11 +35,15 @@ import ru.dbotthepony.mc.otm.core.collect.emptyIterator
|
||||
import ru.dbotthepony.mc.otm.core.collect.filter
|
||||
import ru.dbotthepony.mc.otm.core.collect.map
|
||||
import ru.dbotthepony.mc.otm.core.collect.toList
|
||||
import ru.dbotthepony.mc.otm.core.immutableList
|
||||
import ru.dbotthepony.mc.otm.core.isNotEmpty
|
||||
import ru.dbotthepony.mc.otm.core.nbt.set
|
||||
import ru.dbotthepony.mc.otm.core.registryName
|
||||
import ru.dbotthepony.mc.otm.data.minRange
|
||||
import ru.dbotthepony.mc.otm.network.StreamCodecs
|
||||
import ru.dbotthepony.mc.otm.network.syncher.IRemoteState
|
||||
import ru.dbotthepony.mc.otm.network.syncher.ISynchable
|
||||
import ru.dbotthepony.mc.otm.network.syncher.SynchableObservedDelegate
|
||||
import java.util.*
|
||||
import java.util.function.Consumer
|
||||
import java.util.function.Predicate
|
||||
@ -80,7 +86,6 @@ open class MatteryContainer(var listener: ContainerListener, private val size: I
|
||||
|
||||
private val trackedSlots: Array<ItemStack> = Array(size) { ItemStack.EMPTY }
|
||||
private val filters: Array<Item?> = arrayOfNulls(size)
|
||||
private var filterSynchronizer: DelegateSyncher.MapSlot<Int, Item>? = null
|
||||
|
||||
var changeset = 0
|
||||
private set
|
||||
@ -111,60 +116,18 @@ open class MatteryContainer(var listener: ContainerListener, private val size: I
|
||||
|
||||
fun clearSlotFilters() {
|
||||
Arrays.fill(filters, null)
|
||||
filterSynchronizer?.delegate?.clear()
|
||||
}
|
||||
|
||||
fun removeFilterSynchronizer() {
|
||||
filterSynchronizer?.remove()
|
||||
filterSynchronizer = null
|
||||
}
|
||||
|
||||
fun addFilterSynchronizer(synchronizer: DelegateSyncher): DelegateSyncher.MapSlot<Int, Item> {
|
||||
check(filterSynchronizer?.delegate == null) { "Already has filter synchronizer" }
|
||||
|
||||
val field = synchronizer.MapSlot(
|
||||
ListenableMap<Int, Item>(Int2ObjectOpenHashMap()),
|
||||
keyCodec = StreamCodecs.VAR_INT,
|
||||
valueCodec = StreamCodecs.ITEM_TYPE,
|
||||
)
|
||||
|
||||
for ((i, filter) in filters.withIndex()) {
|
||||
if (filter != null) {
|
||||
field.delegate[i] = filter
|
||||
val synchableFilters by lazy {
|
||||
immutableList<ISynchable> {
|
||||
for (i in 0 until size) {
|
||||
accept(SynchableObservedDelegate(Delegate.Of({ filters[i] }, { filters[i] = it }), StreamCodecs.ITEM_TYPE_NULLABLE))
|
||||
}
|
||||
}
|
||||
|
||||
field.addListener(object : ListenableMap.MapListener<Int, Item> {
|
||||
override fun onClear() {
|
||||
Arrays.fill(filters, null)
|
||||
}
|
||||
|
||||
override fun onValueAdded(key: Int, value: Item) {
|
||||
filters[key] = value
|
||||
}
|
||||
|
||||
override fun onValueRemoved(key: Int, value: Item) {
|
||||
filters[key] = null
|
||||
}
|
||||
})
|
||||
|
||||
filterSynchronizer = field
|
||||
return field
|
||||
}
|
||||
|
||||
final override fun setSlotFilter(slot: Int, filter: Item?): Boolean {
|
||||
if (filters[slot] !== filter) {
|
||||
filters[slot] = filter
|
||||
|
||||
filterSynchronizer?.delegate?.let {
|
||||
if (filter == null) {
|
||||
it.remove(slot)
|
||||
} else {
|
||||
it[slot] = filter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
filters[slot] = filter
|
||||
return true
|
||||
}
|
||||
|
||||
@ -230,8 +193,6 @@ open class MatteryContainer(var listener: ContainerListener, private val size: I
|
||||
nonEmptyFlags.clear()
|
||||
nonEmptyIndices = IntArrayList()
|
||||
|
||||
filterSynchronizer?.delegate?.clear()
|
||||
|
||||
if (tag != null) {
|
||||
SerializedState.CODEC.parse(registries.createSerializationContext(NbtOps.INSTANCE), tag)
|
||||
.resultOrPartial { LOGGER.error("Error deserializing container: $it") }
|
||||
@ -258,11 +219,9 @@ open class MatteryContainer(var listener: ContainerListener, private val size: I
|
||||
}
|
||||
}
|
||||
|
||||
val map = filterSynchronizer?.delegate
|
||||
|
||||
for ((item, index) in it.filters) {
|
||||
filters[index] = item
|
||||
map?.put(index, item)
|
||||
if (index in 0 until size)
|
||||
filters[index] = item
|
||||
}
|
||||
|
||||
setChanged()
|
||||
|
@ -0,0 +1,200 @@
|
||||
package ru.dbotthepony.mc.otm.core
|
||||
|
||||
import com.mojang.serialization.Codec
|
||||
import com.mojang.serialization.codecs.RecordCodecBuilder
|
||||
import net.minecraft.core.HolderLookup
|
||||
import net.minecraft.nbt.CompoundTag
|
||||
import net.minecraft.nbt.NbtOps
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
import net.neoforged.neoforge.common.util.INBTSerializable
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import ru.dbotthepony.mc.otm.network.MatteryStreamCodec
|
||||
import ru.dbotthepony.mc.otm.network.syncher.IRemoteState
|
||||
import ru.dbotthepony.mc.otm.network.syncher.ISynchable
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
|
||||
abstract class AbstractHistoryGraph<V : Any>(
|
||||
/**
|
||||
* How many measurements one graph value contains
|
||||
*/
|
||||
val resolution: Int,
|
||||
|
||||
/**
|
||||
* Limit on how many graph values to store
|
||||
*/
|
||||
val width: Int = 100,
|
||||
) : Iterable<V>, INBTSerializable<CompoundTag>, ISynchable {
|
||||
constructor(ticks: Int) : this(1, ticks)
|
||||
|
||||
init {
|
||||
require(resolution >= 1) { "Invalid resolution: $resolution" }
|
||||
require(width >= 1) { "Invalid width: $width" }
|
||||
}
|
||||
|
||||
// can't believe i've become lazy enough to use codecs for this
|
||||
private data class SData<V : Any>(val accumulated: List<V>, val values: List<V>)
|
||||
|
||||
protected abstract fun calculateAverage(input: List<V>): V
|
||||
protected abstract fun sum(a: V, b: V): V
|
||||
protected abstract fun identity(): V
|
||||
|
||||
abstract val codec: Codec<V>
|
||||
abstract val streamCodec: MatteryStreamCodec<RegistryFriendlyByteBuf, V>
|
||||
|
||||
private val remotes = CopyOnWriteArrayList<RemoteState>()
|
||||
|
||||
private inner class RemoteState(val listener: Runnable) : IRemoteState {
|
||||
init {
|
||||
remotes.add(this)
|
||||
}
|
||||
|
||||
private var shouldFullyNetwork = false
|
||||
private val networkValues = ArrayList<V>()
|
||||
|
||||
override fun write(stream: RegistryFriendlyByteBuf) {
|
||||
stream.writeBoolean(shouldFullyNetwork)
|
||||
|
||||
if (shouldFullyNetwork) {
|
||||
shouldFullyNetwork = false
|
||||
stream.writeVarInt(values.size)
|
||||
values.forEach { streamCodec.encode(stream, it) }
|
||||
} else {
|
||||
stream.writeVarInt(networkValues.size)
|
||||
networkValues.forEach { streamCodec.encode(stream, it) }
|
||||
networkValues.clear()
|
||||
}
|
||||
}
|
||||
|
||||
fun add(value: V) {
|
||||
if (shouldFullyNetwork) {
|
||||
return
|
||||
} else if (networkValues.size + 1 >= (width / 2).coerceIn(20, 100)) {
|
||||
shouldFullyNetwork = true
|
||||
networkValues.clear()
|
||||
listener.run()
|
||||
} else {
|
||||
networkValues.add(value)
|
||||
listener.run()
|
||||
}
|
||||
}
|
||||
|
||||
override fun invalidate() {
|
||||
networkValues.clear()
|
||||
shouldFullyNetwork = true
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
remotes.remove(this)
|
||||
}
|
||||
}
|
||||
|
||||
override fun read(stream: RegistryFriendlyByteBuf) {
|
||||
if (stream.readBoolean()) {
|
||||
values.clear()
|
||||
|
||||
for (i in 0 until stream.readVarInt()) {
|
||||
values.add(streamCodec.decode(stream))
|
||||
}
|
||||
} else {
|
||||
for (i in 0 until stream.readVarInt()) {
|
||||
values.addFirst(streamCodec.decode(stream))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun createRemoteState(listener: Runnable): IRemoteState {
|
||||
return RemoteState(listener)
|
||||
}
|
||||
|
||||
fun calculateAverage(): V {
|
||||
if (values.isEmpty()) {
|
||||
return identity()
|
||||
}
|
||||
|
||||
return calculateAverage(values)
|
||||
}
|
||||
|
||||
private val accumulator = ArrayList<V>(this.resolution)
|
||||
private val values = ArrayDeque<V>()
|
||||
|
||||
init {
|
||||
values.fill(identity())
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates sum of values provided by calls to [add] and writes it to graph history or graph value accumulator
|
||||
*/
|
||||
fun add(value: V) {
|
||||
accumulator.add(value)
|
||||
|
||||
if (accumulator.size >= resolution) {
|
||||
val calc = calculateAverage(accumulator)
|
||||
values.addFirst(calc)
|
||||
remotes.forEach { it.add(calc) }
|
||||
accumulator.clear()
|
||||
while (values.size >= width) values.removeLast()
|
||||
}
|
||||
}
|
||||
|
||||
val size: Int
|
||||
get() = values.size
|
||||
|
||||
operator fun get(index: Int): V {
|
||||
return values.getOrNull(index) ?: identity()
|
||||
}
|
||||
|
||||
fun getRaw(index: Int): V {
|
||||
return values[index]
|
||||
}
|
||||
|
||||
private fun codec(): Codec<SData<V>> {
|
||||
val list = Codec.list(codec)
|
||||
|
||||
return RecordCodecBuilder.create {
|
||||
it.group(
|
||||
list.fieldOf("accumulator").forGetter { it.accumulated },
|
||||
list.fieldOf("values").forGetter { it.values },
|
||||
).apply(it, ::SData)
|
||||
}
|
||||
}
|
||||
|
||||
override fun serializeNBT(registry: HolderLookup.Provider): CompoundTag {
|
||||
return codec().encodeStart(registry.createSerializationContext(NbtOps.INSTANCE), SData(accumulator, values)).orThrow as CompoundTag
|
||||
}
|
||||
|
||||
override fun deserializeNBT(registry: HolderLookup.Provider, nbt: CompoundTag) {
|
||||
this.accumulator.clear()
|
||||
this.values.clear()
|
||||
|
||||
val (accumulator, values) = codec().decode(registry.createSerializationContext(NbtOps.INSTANCE), nbt)
|
||||
.map { it.first }
|
||||
.resultOrPartial { LOGGER.error("Errors during deserializing history graph: $it") }
|
||||
.orElseGet { SData(listOf(), listOf()) }
|
||||
|
||||
this.accumulator.addAll(accumulator)
|
||||
this.values.addAll(values)
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
accumulator.clear()
|
||||
values.fill(identity())
|
||||
}
|
||||
|
||||
final override fun iterator(): Iterator<V> {
|
||||
return object : Iterator<V> {
|
||||
private var index = 0
|
||||
|
||||
override fun hasNext(): Boolean {
|
||||
return index < width
|
||||
}
|
||||
|
||||
override fun next(): V {
|
||||
return this@AbstractHistoryGraph[index++]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val LOGGER = LogManager.getLogger()
|
||||
}
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
package ru.dbotthepony.mc.otm.core
|
||||
|
||||
import com.google.common.collect.ImmutableList
|
||||
import com.mojang.serialization.Codec
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
import ru.dbotthepony.mc.otm.core.collect.reduce
|
||||
import ru.dbotthepony.mc.otm.core.math.Decimal
|
||||
import ru.dbotthepony.mc.otm.data.DecimalCodec
|
||||
import ru.dbotthepony.mc.otm.network.MatteryStreamCodec
|
||||
import kotlin.math.pow
|
||||
|
||||
class DecimalHistoryGraph : AbstractHistoryGraph<Decimal> {
|
||||
constructor(resolution: Int, width: Int) : super(resolution, width)
|
||||
constructor(ticks: Int) : this(1, ticks)
|
||||
|
||||
override fun calculateAverage(input: List<Decimal>): Decimal {
|
||||
return input.iterator().reduce(Decimal.ZERO, Decimal::plus) / Decimal(input.size)
|
||||
}
|
||||
|
||||
override fun sum(a: Decimal, b: Decimal): Decimal {
|
||||
return a + b
|
||||
}
|
||||
|
||||
override fun identity(): Decimal {
|
||||
return Decimal.ZERO
|
||||
}
|
||||
|
||||
override val codec: Codec<Decimal>
|
||||
get() = DecimalCodec
|
||||
override val streamCodec: MatteryStreamCodec<RegistryFriendlyByteBuf, Decimal>
|
||||
get() = DecimalCodec.NETWORK
|
||||
|
||||
fun calcWeightedAverage(): Decimal {
|
||||
return calcWeightedAverage(this::get)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val HISTORY_WEIGHTERS: ImmutableList<Decimal> = immutableList {
|
||||
/*for (i in 0 until 20) {
|
||||
accept(Decimal(0.5.pow(i + 1)))
|
||||
}*/
|
||||
|
||||
val average = Decimal(1) / Decimal(20)
|
||||
|
||||
for (i in 0 until 20) {
|
||||
accept(average)
|
||||
}
|
||||
}
|
||||
|
||||
fun calcWeightedAverage(getter: (Int) -> Decimal): Decimal {
|
||||
var result = Decimal.ZERO
|
||||
|
||||
for (i in 0 until 20) {
|
||||
result += getter(i) * HISTORY_WEIGHTERS[i]
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
package ru.dbotthepony.mc.otm.core.collect
|
||||
|
||||
import net.minecraft.nbt.ListTag
|
||||
import net.minecraft.nbt.Tag
|
||||
import java.util.function.BiConsumer
|
||||
import java.util.function.BinaryOperator
|
||||
import java.util.function.Function
|
||||
import java.util.function.Supplier
|
||||
import java.util.stream.Collector
|
||||
import java.util.stream.Collector.Characteristics
|
||||
|
||||
object ListTagCollector : Collector<Tag, ListTag, ListTag> {
|
||||
override fun supplier(): Supplier<ListTag> {
|
||||
return Supplier { ListTag() }
|
||||
}
|
||||
|
||||
override fun accumulator(): BiConsumer<ListTag, Tag> {
|
||||
return BiConsumer { t, u -> t.add(u) }
|
||||
}
|
||||
|
||||
override fun combiner(): BinaryOperator<ListTag> {
|
||||
return BinaryOperator { t, u ->
|
||||
if (t.size >= u.size) {
|
||||
t.addAll(u)
|
||||
t
|
||||
} else {
|
||||
u.addAll(t)
|
||||
u
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun finisher(): Function<ListTag, ListTag> {
|
||||
return Function.identity()
|
||||
}
|
||||
|
||||
private val chars = setOf(Characteristics.IDENTITY_FINISH)
|
||||
|
||||
override fun characteristics(): Set<Characteristics> {
|
||||
return chars
|
||||
}
|
||||
}
|
@ -4,6 +4,7 @@ import net.minecraft.core.HolderLookup
|
||||
import net.minecraft.nbt.CompoundTag
|
||||
import net.minecraft.nbt.ListTag
|
||||
import net.neoforged.neoforge.common.util.INBTSerializable
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import ru.dbotthepony.mc.otm.core.nbt.contains
|
||||
import java.util.UUID
|
||||
|
||||
@ -33,15 +34,14 @@ class UUIDIntModifiersMap(private val observer: (Int) -> Unit, private val backi
|
||||
}
|
||||
|
||||
operator fun set(key: UUID, value: Int): Boolean {
|
||||
val old = backingMap.put(key, value)
|
||||
|
||||
if (old == value) {
|
||||
return false
|
||||
}
|
||||
|
||||
ignoreRecompute = true
|
||||
|
||||
try {
|
||||
val old = backingMap.put(key, value)
|
||||
|
||||
if (old == value)
|
||||
return false
|
||||
|
||||
this.value += value - (old ?: 0)
|
||||
observer.invoke(this.value)
|
||||
return true
|
||||
@ -113,7 +113,13 @@ class UUIDIntModifiersMap(private val observer: (Int) -> Unit, private val backi
|
||||
|
||||
if (value.contains("key", "value")) {
|
||||
val int = value.getInt("value")
|
||||
backingMap.put(value.getUUID("key"), int)
|
||||
val previous = backingMap.put(value.getUUID("key"), int)
|
||||
|
||||
if (previous != null) {
|
||||
LOGGER.warn("Duplicate modifier in UUID Int Modifier Map with UUID ${value.getUUID("key")}")
|
||||
this.value -= previous
|
||||
}
|
||||
|
||||
this.value += int
|
||||
}
|
||||
}
|
||||
@ -125,4 +131,8 @@ class UUIDIntModifiersMap(private val observer: (Int) -> Unit, private val backi
|
||||
ignoreRecompute = false
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val LOGGER = LogManager.getLogger()
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,9 @@
|
||||
package ru.dbotthepony.mc.otm.core.util
|
||||
|
||||
import it.unimi.dsi.fastutil.chars.CharArrayList
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList
|
||||
import net.minecraft.ChatFormatting
|
||||
import net.minecraft.network.chat.Component
|
||||
import net.minecraft.network.chat.MutableComponent
|
||||
import ru.dbotthepony.kommons.util.value
|
||||
import ru.dbotthepony.mc.otm.capability.AbstractProfiledStorage
|
||||
import ru.dbotthepony.mc.otm.core.TextComponent
|
||||
import ru.dbotthepony.mc.otm.core.TranslatableComponent
|
||||
import ru.dbotthepony.mc.otm.core.math.Decimal
|
||||
@ -283,22 +280,20 @@ fun formatHistory(
|
||||
}
|
||||
|
||||
if (verbose.asBoolean) {
|
||||
addLine(widget.lastTickReceive, widget.lastTickTransfer)
|
||||
|
||||
val values = IntArrayList()
|
||||
values.addAll(widget.tick downTo 0)
|
||||
values.addAll(AbstractProfiledStorage.HISTORY_SIZE - 1 downTo widget.tick + 1)
|
||||
|
||||
for (i in values.intIterator()) {
|
||||
addLine(widget.historyReceive[i].value, widget.historyTransfer[i].value)
|
||||
}
|
||||
addLine(widget.receivedThisTick, widget.transferredThisTick)
|
||||
}
|
||||
|
||||
addLine(
|
||||
widget.weightedReceive,
|
||||
widget.weightedTransfer
|
||||
widget.received.calcWeightedAverage(),
|
||||
widget.transferred.calcWeightedAverage()
|
||||
)
|
||||
|
||||
if (verbose.asBoolean) {
|
||||
for (i in 0 until 20) {
|
||||
addLine(widget.received[i], widget.transferred[i])
|
||||
}
|
||||
}
|
||||
|
||||
val maxWidthDeltaNumber = lines.maxOf { it.delta.formatted.length }
|
||||
val maxWidthInNumber = lines.maxOf { it.incoming.formatted.length }
|
||||
val maxWidthOutNumber = lines.maxOf { it.outgoing.formatted.length }
|
||||
@ -310,7 +305,7 @@ fun formatHistory(
|
||||
fun Part.format(widthNumbers: Int, widthSi: Int): MutableComponent {
|
||||
return TranslatableComponent(
|
||||
prefix.formatLocaleKey,
|
||||
resplice("-".repeat(max(widthNumbers - formatted.length, 0)) + formatted, decimals) + "-".repeat(max(widthSi - prefix.length, 0)),
|
||||
resplice("_".repeat(max(widthNumbers - formatted.length, 0)) + formatted, decimals) + "_".repeat(max(widthSi - prefix.length, 0)),
|
||||
suffix
|
||||
)
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import net.minecraft.network.FriendlyByteBuf
|
||||
import net.minecraft.network.codec.StreamCodec
|
||||
import ru.dbotthepony.mc.otm.core.math.Decimal
|
||||
import ru.dbotthepony.mc.otm.core.math.Decimal.Companion.fromByteArray
|
||||
import ru.dbotthepony.mc.otm.network.wrap
|
||||
import java.nio.ByteBuffer
|
||||
|
||||
object DecimalCodec : Codec<Decimal> {
|
||||
@ -21,7 +22,7 @@ object DecimalCodec : Codec<Decimal> {
|
||||
override fun encode(buf: FriendlyByteBuf, p_320396_: Decimal) {
|
||||
buf.writeByteArray(p_320396_.toByteArray())
|
||||
}
|
||||
}
|
||||
}.wrap()
|
||||
|
||||
override fun <T : Any> encode(input: Decimal, ops: DynamicOps<T>, prefix: T): DataResult<T> {
|
||||
if (ops === NbtOps.INSTANCE) {
|
||||
|
@ -14,7 +14,6 @@ import net.minecraft.network.protocol.common.custom.CustomPacketPayload
|
||||
import net.minecraft.resources.ResourceLocation
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.world.Container
|
||||
import net.minecraft.world.SimpleContainer
|
||||
import net.minecraft.world.entity.player.Inventory
|
||||
import net.minecraft.world.entity.player.Player
|
||||
import net.minecraft.world.inventory.AbstractContainerMenu
|
||||
@ -32,26 +31,18 @@ import ru.dbotthepony.kommons.util.Delegate
|
||||
import ru.dbotthepony.kommons.util.getValue
|
||||
import ru.dbotthepony.kommons.util.setValue
|
||||
import ru.dbotthepony.mc.otm.OverdriveThatMatters
|
||||
import ru.dbotthepony.mc.otm.capability.IMatteryUpgrade
|
||||
import ru.dbotthepony.mc.otm.capability.MatteryCapability
|
||||
import ru.dbotthepony.mc.otm.capability.UpgradeType
|
||||
import ru.dbotthepony.mc.otm.capability.energy.ProfiledEnergyStorage
|
||||
import ru.dbotthepony.mc.otm.capability.matteryPlayer
|
||||
import ru.dbotthepony.mc.otm.compat.cos.cosmeticArmorSlots
|
||||
import ru.dbotthepony.mc.otm.compat.curios.curiosSlots
|
||||
import ru.dbotthepony.mc.otm.compat.curios.isCurioSlot
|
||||
import ru.dbotthepony.mc.otm.container.IMatteryContainer
|
||||
import ru.dbotthepony.mc.otm.container.UpgradeContainer
|
||||
import ru.dbotthepony.mc.otm.container.computeSortedIndices
|
||||
import ru.dbotthepony.mc.otm.container.sortWithIndices
|
||||
import ru.dbotthepony.mc.otm.core.ResourceLocation
|
||||
import ru.dbotthepony.mc.otm.core.collect.ConditionalEnumSet
|
||||
import ru.dbotthepony.mc.otm.core.collect.ConditionalSet
|
||||
import ru.dbotthepony.mc.otm.core.immutableList
|
||||
import ru.dbotthepony.mc.otm.core.math.Decimal
|
||||
import ru.dbotthepony.mc.otm.menu.input.InstantBooleanInput
|
||||
import ru.dbotthepony.mc.otm.menu.widget.ProfiledLevelGaugeWidget
|
||||
import ru.dbotthepony.mc.otm.network.DelegateSyncher
|
||||
import ru.dbotthepony.mc.otm.network.MatteryStreamCodec
|
||||
import ru.dbotthepony.mc.otm.network.MenuDataPacket
|
||||
import ru.dbotthepony.mc.otm.network.SetCarriedPacket
|
||||
@ -60,13 +51,12 @@ import ru.dbotthepony.mc.otm.network.decode
|
||||
import ru.dbotthepony.mc.otm.network.encode
|
||||
import ru.dbotthepony.mc.otm.network.nullable
|
||||
import ru.dbotthepony.mc.otm.network.readByteListUnbounded
|
||||
import ru.dbotthepony.mc.otm.network.syncher.Syncher
|
||||
import ru.dbotthepony.mc.otm.network.wrap
|
||||
import ru.dbotthepony.mc.otm.network.writeByteListUnbounded
|
||||
import java.util.*
|
||||
import java.util.function.BooleanSupplier
|
||||
import java.util.function.Consumer
|
||||
import java.util.function.DoubleSupplier
|
||||
import java.util.function.IntSupplier
|
||||
import java.util.function.Predicate
|
||||
|
||||
data class PlayerSlot<A : Slot, B : Slot>(val functional: A, val cosmetic: B? = null)
|
||||
@ -85,7 +75,7 @@ abstract class MatteryMenu(
|
||||
/**
|
||||
* Server->Client synchronizer
|
||||
*/
|
||||
val mSynchronizer = DelegateSyncher()
|
||||
val mSynchronizer = Syncher()
|
||||
val synchronizerRemote = mSynchronizer.Remote()
|
||||
val player: Player get() = inventory.player
|
||||
|
||||
@ -133,7 +123,7 @@ abstract class MatteryMenu(
|
||||
/**
|
||||
* Client->Server input
|
||||
*/
|
||||
inner class PlayerInput<V>(val codec: MatteryStreamCodec<in RegistryFriendlyByteBuf, V>, allowSpectators: Boolean = false, val handler: (V) -> Unit) : Consumer<V>, Predicate<Player> {
|
||||
inner class PlayerInput<V>(val codec: MatteryStreamCodec<RegistryFriendlyByteBuf, V>, allowSpectators: Boolean = false, val handler: (V) -> Unit) : Consumer<V>, Predicate<Player> {
|
||||
val id = playerInputs.size
|
||||
var allowSpectators by mSynchronizer.boolean(allowSpectators)
|
||||
|
||||
@ -209,7 +199,7 @@ abstract class MatteryMenu(
|
||||
|
||||
val exopackChargeSlots: List<MatterySlot> = Collections.unmodifiableList(_exopackChargeSlots)
|
||||
|
||||
val exopackPowerLevel = ProfiledLevelGaugeWidget<ProfiledEnergyStorage<*>>(mSynchronizer)
|
||||
val exopackPowerLevel = ProfiledLevelGaugeWidget<ProfiledEnergyStorage<*>>(mSynchronizer, player.matteryPlayer.exopackEnergy)
|
||||
|
||||
var sortInventoryInput: SortInput? = null
|
||||
private set
|
||||
@ -326,8 +316,6 @@ abstract class MatteryMenu(
|
||||
|
||||
for (i in 0 until mattery.exopackChargeSlots.containerSize)
|
||||
_exopackChargeSlots.add(ChargeSlot(mattery.exopackChargeSlots, i).also { mapQuickMoveToExternal(it); mapQuickMoveToInventory(it); addSlot(it) })
|
||||
|
||||
exopackPowerLevel.with(mattery.exopackEnergy)
|
||||
}
|
||||
|
||||
sortInventoryInput = SortInput(mattery.inventoryAndExopackNoHotbar, playerSortSettings)
|
||||
|
@ -48,7 +48,7 @@ class PainterMenu(
|
||||
val inputContainer = MatteryContainer(::rescan, 1)
|
||||
val outputContainer = MatteryContainer(1)
|
||||
private var lastRecipe: RecipeHolder<out AbstractPainterRecipe>? = null
|
||||
var selectedRecipe by mSynchronizer.Slot(ListenableDelegate.Box(null), StreamCodecs.RESOURCE_LOCATION.nullable()).also { it.addListener(Runnable { rescan() }) }
|
||||
var selectedRecipe by mSynchronizer.add(ListenableDelegate.Box(null), StreamCodecs.RESOURCE_LOCATION.nullable()).also { it.addListener(Runnable { rescan() }) }
|
||||
|
||||
val isBulk = BooleanInputWithFeedback(this, tile?.let { it::isBulk })
|
||||
|
||||
|
@ -44,7 +44,7 @@ class DriveViewerMenu(
|
||||
|
||||
private val powered: PoweredVirtualComponent<ItemStorageStack>?
|
||||
private var lastDrive: IMatteryDrive<ItemStorageStack>? = null
|
||||
val profiledEnergy = ProfiledLevelGaugeWidget<ProfiledEnergyStorage<*>>(this, energyWidget)
|
||||
val profiledEnergy = ProfiledLevelGaugeWidget<ProfiledEnergyStorage<*>>(this, tile?.energy, energyWidget)
|
||||
val energyConfig = EnergyConfigPlayerInput(this, tile?.energyConfig)
|
||||
|
||||
val settings = object : DriveViewerBlockEntity.ISettings {
|
||||
@ -62,7 +62,6 @@ class DriveViewerMenu(
|
||||
|
||||
init {
|
||||
if (tile != null) {
|
||||
profiledEnergy.with(tile.energy)
|
||||
powered = PoweredVirtualComponent(StorageStack.ITEMS, tile::energy)
|
||||
this.networkedItemView.component = powered
|
||||
} else {
|
||||
|
@ -2,7 +2,7 @@ package ru.dbotthepony.mc.otm.menu.widget
|
||||
|
||||
import net.neoforged.neoforge.fluids.FluidStack
|
||||
import net.neoforged.neoforge.fluids.capability.IFluidHandler
|
||||
import ru.dbotthepony.mc.otm.network.DelegateSyncher
|
||||
import ru.dbotthepony.mc.otm.network.syncher.Syncher
|
||||
import ru.dbotthepony.kommons.util.getValue
|
||||
import ru.dbotthepony.mc.otm.container.get
|
||||
import ru.dbotthepony.mc.otm.menu.MatteryMenu
|
||||
@ -10,7 +10,7 @@ import ru.dbotthepony.mc.otm.network.wrap
|
||||
import java.util.function.IntSupplier
|
||||
import java.util.function.Supplier
|
||||
|
||||
class FluidGaugeWidget(synchronizer: DelegateSyncher) {
|
||||
class FluidGaugeWidget(synchronizer: Syncher) {
|
||||
constructor(menu: MatteryMenu) : this(menu.mSynchronizer)
|
||||
|
||||
var maxCapacitySupplier = IntSupplier { 0 }
|
||||
@ -27,7 +27,7 @@ class FluidGaugeWidget(synchronizer: DelegateSyncher) {
|
||||
return (fluid.amount.toFloat() / maxCapacity.toFloat()).coerceIn(0f, 1f)
|
||||
}
|
||||
|
||||
constructor(synchronizer: DelegateSyncher, fluid: IFluidHandler?, tank: Int = 0) : this(synchronizer) {
|
||||
constructor(synchronizer: Syncher, fluid: IFluidHandler?, tank: Int = 0) : this(synchronizer) {
|
||||
if (fluid != null) {
|
||||
with(fluid, tank)
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
package ru.dbotthepony.mc.otm.menu.widget
|
||||
|
||||
import ru.dbotthepony.mc.otm.network.DelegateSyncher
|
||||
import ru.dbotthepony.kommons.util.getValue
|
||||
import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage
|
||||
import ru.dbotthepony.mc.otm.capability.matter.IMatterStorage
|
||||
@ -8,9 +7,10 @@ import ru.dbotthepony.mc.otm.capability.matter.IPatternStorage
|
||||
import ru.dbotthepony.mc.otm.core.math.Decimal
|
||||
import ru.dbotthepony.mc.otm.menu.MatteryMenu
|
||||
import ru.dbotthepony.mc.otm.network.StreamCodecs
|
||||
import ru.dbotthepony.mc.otm.network.syncher.Syncher
|
||||
|
||||
@Suppress("unused")
|
||||
class LevelGaugeWidget(synchronizer: DelegateSyncher) {
|
||||
class LevelGaugeWidget(synchronizer: Syncher) {
|
||||
constructor(menu: MatteryMenu) : this(menu.mSynchronizer)
|
||||
|
||||
var levelProvider = { Decimal.ONE }
|
||||
@ -56,7 +56,7 @@ class LevelGaugeWidget(synchronizer: DelegateSyncher) {
|
||||
}
|
||||
|
||||
constructor(
|
||||
synchronizer: DelegateSyncher,
|
||||
synchronizer: Syncher,
|
||||
power: IMatteryEnergyStorage?
|
||||
) : this(synchronizer) {
|
||||
if (power != null) {
|
||||
@ -65,7 +65,7 @@ class LevelGaugeWidget(synchronizer: DelegateSyncher) {
|
||||
}
|
||||
|
||||
constructor(
|
||||
synchronizer: DelegateSyncher,
|
||||
synchronizer: Syncher,
|
||||
matter: IMatterStorage?
|
||||
) : this(synchronizer) {
|
||||
if (matter != null) {
|
||||
@ -74,7 +74,7 @@ class LevelGaugeWidget(synchronizer: DelegateSyncher) {
|
||||
}
|
||||
|
||||
constructor(
|
||||
synchronizer: DelegateSyncher,
|
||||
synchronizer: Syncher,
|
||||
patterns: IPatternStorage?
|
||||
) : this(synchronizer) {
|
||||
if (patterns != null) {
|
||||
@ -83,7 +83,7 @@ class LevelGaugeWidget(synchronizer: DelegateSyncher) {
|
||||
}
|
||||
|
||||
constructor(
|
||||
synchronizer: DelegateSyncher,
|
||||
synchronizer: Syncher,
|
||||
level: () -> Decimal,
|
||||
maxLevel: () -> Decimal,
|
||||
) : this(synchronizer) {
|
||||
|
@ -1,64 +1,40 @@
|
||||
package ru.dbotthepony.mc.otm.menu.widget
|
||||
|
||||
import ru.dbotthepony.mc.otm.network.DelegateSyncher
|
||||
import ru.dbotthepony.kommons.util.getValue
|
||||
import ru.dbotthepony.mc.otm.capability.AbstractProfiledStorage
|
||||
import ru.dbotthepony.mc.otm.capability.AbstractProfiledStorage.Companion.HISTORY_SIZE
|
||||
import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage
|
||||
import ru.dbotthepony.mc.otm.capability.matter.IMatterStorage
|
||||
import ru.dbotthepony.mc.otm.core.collect.SupplierList
|
||||
import ru.dbotthepony.mc.otm.core.immutableList
|
||||
import ru.dbotthepony.mc.otm.core.DecimalHistoryGraph
|
||||
import ru.dbotthepony.mc.otm.core.math.Decimal
|
||||
import ru.dbotthepony.mc.otm.menu.MatteryMenu
|
||||
import ru.dbotthepony.mc.otm.menu.MatteryPoweredMenu
|
||||
import ru.dbotthepony.mc.otm.network.StreamCodecs
|
||||
import java.util.function.IntSupplier
|
||||
import ru.dbotthepony.mc.otm.network.syncher.Syncher
|
||||
|
||||
class ProfiledLevelGaugeWidget<P : AbstractProfiledStorage<*>>(synchronizer: DelegateSyncher, val gauge: LevelGaugeWidget = LevelGaugeWidget(synchronizer)) {
|
||||
var parent: P? = null
|
||||
private set
|
||||
class ProfiledLevelGaugeWidget<P : AbstractProfiledStorage<*>>(
|
||||
synchronizer: Syncher,
|
||||
val storage: P?,
|
||||
val gauge: LevelGaugeWidget = LevelGaugeWidget(synchronizer)
|
||||
) {
|
||||
constructor(
|
||||
menu: MatteryMenu,
|
||||
storage: P?,
|
||||
gauge: LevelGaugeWidget = LevelGaugeWidget(menu.mSynchronizer)
|
||||
) : this(menu.mSynchronizer, storage, gauge)
|
||||
|
||||
val historyReceive = immutableList(AbstractProfiledStorage.HISTORY_SIZE) {
|
||||
synchronizer.computed({ parent?.historyReceive?.get(it) ?: Decimal.ZERO }, StreamCodecs.DECIMAL)
|
||||
}
|
||||
val received = storage?.received ?: DecimalHistoryGraph(ticks = HISTORY_SIZE)
|
||||
val transferred = storage?.transferred ?: DecimalHistoryGraph(ticks = HISTORY_SIZE)
|
||||
|
||||
val historyTransfer = immutableList(AbstractProfiledStorage.HISTORY_SIZE) {
|
||||
synchronizer.computed({ parent?.historyTransfer?.get(it) ?: Decimal.ZERO }, StreamCodecs.DECIMAL)
|
||||
}
|
||||
val receivedThisTick by synchronizer.computed({ storage?.receivedThisTick ?: Decimal.ZERO }, StreamCodecs.DECIMAL)
|
||||
val transferredThisTick by synchronizer.computed({ storage?.transferredThisTick ?: Decimal.ZERO }, StreamCodecs.DECIMAL)
|
||||
|
||||
val directHistoryReceive = SupplierList(historyReceive)
|
||||
val directHistoryTransfer = SupplierList(historyTransfer)
|
||||
|
||||
val tick by synchronizer.computedInt(IntSupplier { parent?.tick ?: 0 })
|
||||
val lastTickReceive by synchronizer.computed({ parent?.lastTickReceive ?: Decimal.ZERO }, StreamCodecs.DECIMAL)
|
||||
val lastTickTransfer by synchronizer.computed({ parent?.lastTickTransfer ?: Decimal.ZERO }, StreamCodecs.DECIMAL)
|
||||
|
||||
constructor(synchronizer: DelegateSyncher, storage: P?, gauge: LevelGaugeWidget = LevelGaugeWidget(synchronizer)) : this(synchronizer, gauge = gauge) {
|
||||
if (storage != null) {
|
||||
with(storage)
|
||||
}
|
||||
}
|
||||
|
||||
constructor(menu: MatteryMenu, storage: P?, gauge: LevelGaugeWidget = LevelGaugeWidget(menu)) : this(menu.mSynchronizer, storage, gauge = gauge)
|
||||
constructor(menu: MatteryMenu, gauge: LevelGaugeWidget = LevelGaugeWidget(menu)) : this(menu.mSynchronizer, gauge = gauge)
|
||||
|
||||
constructor(menu: MatteryPoweredMenu, storage: P?) : this(menu.mSynchronizer, storage, menu.energyWidget)
|
||||
constructor(menu: MatteryPoweredMenu) : this(menu.mSynchronizer, menu.energyWidget)
|
||||
|
||||
fun with(storage: P): ProfiledLevelGaugeWidget<P> {
|
||||
init {
|
||||
if (storage is IMatterStorage)
|
||||
gauge.with(storage)
|
||||
else if (storage is IMatteryEnergyStorage)
|
||||
gauge.with(storage)
|
||||
|
||||
parent = storage
|
||||
return this
|
||||
}
|
||||
|
||||
val weightedTransfer: Decimal get() {
|
||||
return AbstractProfiledStorage.calcWeightedAverage(directHistoryTransfer, tick)
|
||||
}
|
||||
|
||||
val weightedReceive: Decimal get() {
|
||||
return AbstractProfiledStorage.calcWeightedAverage(directHistoryReceive, tick)
|
||||
synchronizer.add(received)
|
||||
synchronizer.add(transferred)
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
package ru.dbotthepony.mc.otm.menu.widget
|
||||
|
||||
import ru.dbotthepony.mc.otm.network.DelegateSyncher
|
||||
import ru.dbotthepony.mc.otm.network.syncher.Syncher
|
||||
import ru.dbotthepony.kommons.util.getValue
|
||||
import ru.dbotthepony.mc.otm.block.entity.MachineJobEventLoop
|
||||
import ru.dbotthepony.mc.otm.block.entity.MatteryWorkerBlockEntity
|
||||
@ -9,7 +9,7 @@ import ru.dbotthepony.mc.otm.menu.MatteryMenu
|
||||
import java.util.function.BooleanSupplier
|
||||
|
||||
@Suppress("unused")
|
||||
class ProgressGaugeWidget(synchronizer: DelegateSyncher) {
|
||||
class ProgressGaugeWidget(synchronizer: Syncher) {
|
||||
constructor(menu: MatteryMenu) : this(menu.mSynchronizer)
|
||||
|
||||
var progressSupplier: FloatSupplier = FloatSupplier { 0f }
|
||||
@ -44,14 +44,14 @@ class ProgressGaugeWidget(synchronizer: DelegateSyncher) {
|
||||
}
|
||||
|
||||
constructor(
|
||||
synchronizer: DelegateSyncher,
|
||||
synchronizer: Syncher,
|
||||
progress: FloatSupplier
|
||||
) : this(synchronizer) {
|
||||
this.progressSupplier = progress
|
||||
}
|
||||
|
||||
constructor(
|
||||
synchronizer: DelegateSyncher,
|
||||
synchronizer: Syncher,
|
||||
blockEntity: MatteryWorkerBlockEntity<*>?
|
||||
) : this(synchronizer) {
|
||||
if (blockEntity != null) {
|
||||
@ -60,7 +60,7 @@ class ProgressGaugeWidget(synchronizer: DelegateSyncher) {
|
||||
}
|
||||
|
||||
constructor(
|
||||
synchronizer: DelegateSyncher,
|
||||
synchronizer: Syncher,
|
||||
job: MachineJobEventLoop<*>?
|
||||
) : this(synchronizer) {
|
||||
if (job != null) {
|
||||
|
@ -1,857 +0,0 @@
|
||||
package ru.dbotthepony.mc.otm.network
|
||||
|
||||
import io.netty.buffer.ByteBufAllocator
|
||||
import it.unimi.dsi.fastutil.bytes.ByteArrayList
|
||||
import it.unimi.dsi.fastutil.ints.IntAVLTreeSet
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap
|
||||
import it.unimi.dsi.fastutil.objects.ObjectAVLTreeSet
|
||||
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
|
||||
import net.minecraft.core.RegistryAccess
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
import net.minecraft.world.item.ItemStack
|
||||
import net.neoforged.neoforge.network.connection.ConnectionType
|
||||
import ru.dbotthepony.kommons.collect.ListenableMap
|
||||
import ru.dbotthepony.kommons.collect.ListenableSet
|
||||
import ru.dbotthepony.kommons.util.Delegate
|
||||
import ru.dbotthepony.kommons.util.DelegateGetter
|
||||
import ru.dbotthepony.kommons.util.DelegateSetter
|
||||
import ru.dbotthepony.kommons.util.KOptional
|
||||
import ru.dbotthepony.kommons.util.Listenable
|
||||
import ru.dbotthepony.kommons.util.ListenableDelegate
|
||||
import ru.dbotthepony.kommons.util.Observer
|
||||
import ru.dbotthepony.kommons.util.ValueObserver
|
||||
import ru.dbotthepony.mc.otm.core.math.Decimal
|
||||
import java.io.Closeable
|
||||
import java.util.*
|
||||
import java.util.concurrent.ConcurrentLinkedQueue
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
import java.util.function.BooleanSupplier
|
||||
import java.util.function.Consumer
|
||||
import java.util.function.DoubleSupplier
|
||||
import java.util.function.IntSupplier
|
||||
import java.util.function.LongSupplier
|
||||
import java.util.function.Supplier
|
||||
import kotlin.concurrent.withLock
|
||||
|
||||
/**
|
||||
* Universal, one-to-many delegate/value synchronizer.
|
||||
*
|
||||
* Values and delegates can be attached using [add], or by constructing subclassed directly.
|
||||
* Delta changes are tracked by [DelegateSyncher.Remote] instances.
|
||||
*
|
||||
* In general, this class is not meant to be _structurally_ concurrently mutated by different threads,
|
||||
* to avoid structure corruption a lock is employed.
|
||||
*
|
||||
* Attached delegates can be safely mutated by multiple threads concurrently
|
||||
* (attached [ListenableDelegate] can call [Listenable.addListener] callback from different threads).
|
||||
*/
|
||||
@Suppress("UNCHECKED_CAST", "UNUSED")
|
||||
class DelegateSyncher : Observer {
|
||||
private val lock = ReentrantLock()
|
||||
private val slots = ArrayList<AbstractSlot?>()
|
||||
private val gaps = IntAVLTreeSet()
|
||||
private val observers = ArrayList<AbstractSlot>()
|
||||
private val remotes = ArrayList<Remote>()
|
||||
private var isRemote = false
|
||||
|
||||
override fun observe(): Boolean {
|
||||
var any = false
|
||||
observers.forEach { any = it.observe() || any }
|
||||
return any
|
||||
}
|
||||
|
||||
fun read(stream: RegistryFriendlyByteBuf) {
|
||||
isRemote = true
|
||||
|
||||
var readID = stream.readVarInt()
|
||||
|
||||
while (readID > 0) {
|
||||
val slot = slots.getOrNull(readID - 1) ?: throw IndexOutOfBoundsException("Unknown networked slot ${readID - 1}!")
|
||||
slot.read(stream)
|
||||
readID = stream.readVarInt()
|
||||
}
|
||||
}
|
||||
|
||||
fun read(registry: RegistryAccess, bytes: ByteArrayList) {
|
||||
return decodePayload(registry, bytes) {
|
||||
read(it)
|
||||
}
|
||||
}
|
||||
|
||||
abstract inner class AbstractSlot : Closeable, Observer, Comparable<AbstractSlot> {
|
||||
val id: Int
|
||||
|
||||
protected val isRemoved = AtomicBoolean()
|
||||
|
||||
fun remove() {
|
||||
if (isRemoved.compareAndSet(false, true)) {
|
||||
lock.withLock {
|
||||
slots[id] = null
|
||||
gaps.add(id)
|
||||
|
||||
remoteSlots.forEach {
|
||||
it.remove()
|
||||
}
|
||||
|
||||
remoteSlots.clear()
|
||||
remove0()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final override fun close() {
|
||||
return remove()
|
||||
}
|
||||
|
||||
final override fun compareTo(other: AbstractSlot): Int {
|
||||
return id.compareTo(other.id)
|
||||
}
|
||||
|
||||
protected abstract fun remove0()
|
||||
internal abstract fun read(stream: RegistryFriendlyByteBuf)
|
||||
internal abstract fun write(stream: RegistryFriendlyByteBuf)
|
||||
|
||||
internal val remoteSlots = CopyOnWriteArrayList<Remote.RemoteSlot>()
|
||||
|
||||
protected fun markDirty() {
|
||||
if (!isRemote && !isRemoved.get()) {
|
||||
remoteSlots.forEach {
|
||||
it.markDirty()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
lock.withLock {
|
||||
if (gaps.isNotEmpty()) {
|
||||
val gap = gaps.firstInt()
|
||||
gaps.remove(gap)
|
||||
id = gap
|
||||
check(slots[id] == null) { "Expected slot $id to be empty" }
|
||||
slots[id] = this
|
||||
} else {
|
||||
id = slots.size
|
||||
slots.add(this)
|
||||
}
|
||||
|
||||
remotes.forEach {
|
||||
it.addSlot(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inner class Slot<V>(val delegate: ListenableDelegate<V>, val codec: MatteryStreamCodec<in RegistryFriendlyByteBuf, V>) : AbstractSlot(), ListenableDelegate<V> {
|
||||
constructor(delegate: Supplier<V>, codec: MatteryStreamCodec<in RegistryFriendlyByteBuf, V>) : this(ListenableDelegate.CustomShadow(delegate, codec::compare, codec::copy), codec)
|
||||
|
||||
private val l = delegate.addListener(Consumer {
|
||||
markDirty()
|
||||
listeners.accept(it)
|
||||
})
|
||||
|
||||
init {
|
||||
if (delegate is ListenableDelegate.AbstractShadow) {
|
||||
observers.add(this)
|
||||
}
|
||||
}
|
||||
|
||||
private val listeners = Listenable.Impl<V>()
|
||||
|
||||
override fun remove0() {
|
||||
l.remove()
|
||||
listeners.clear()
|
||||
}
|
||||
|
||||
override fun read(stream: RegistryFriendlyByteBuf) {
|
||||
check(!isRemoved.get()) { "This network slot was removed" }
|
||||
delegate.accept(codec.decode(stream))
|
||||
}
|
||||
|
||||
override fun write(stream: RegistryFriendlyByteBuf) {
|
||||
check(!isRemoved.get()) { "This network slot was removed" }
|
||||
codec.encode(stream, delegate.get())
|
||||
}
|
||||
|
||||
override fun get(): V {
|
||||
check(!isRemoved.get()) { "This network slot was removed" }
|
||||
return delegate.get()
|
||||
}
|
||||
|
||||
override fun accept(t: V) {
|
||||
check(!isRemoved.get()) { "This network slot was removed" }
|
||||
delegate.accept(t)
|
||||
}
|
||||
|
||||
override fun addListener(listener: Consumer<V>): Listenable.L {
|
||||
check(!isRemoved.get()) { "This network slot was removed" }
|
||||
return listeners.addListener(listener)
|
||||
}
|
||||
|
||||
override fun observe(): Boolean {
|
||||
check(!isRemoved.get()) { "This network slot was removed" }
|
||||
|
||||
if (delegate is ListenableDelegate.AbstractShadow) {
|
||||
return delegate.observe()
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
inner class ObservedSlot<V>(val delegate: Delegate<V>, val codec: MatteryStreamCodec<in RegistryFriendlyByteBuf, V>) : AbstractSlot(), ListenableDelegate<V>, ValueObserver<V> {
|
||||
private val listeners = Listenable.Impl<V>()
|
||||
private var observed: Any? = Mark
|
||||
private val observeLock = ReentrantLock()
|
||||
|
||||
init {
|
||||
observers.add(this)
|
||||
}
|
||||
|
||||
override fun remove0() {
|
||||
listeners.clear()
|
||||
}
|
||||
|
||||
override fun read(stream: RegistryFriendlyByteBuf) {
|
||||
check(!isRemoved.get()) { "This network slot was removed" }
|
||||
val read = codec.decode(stream)
|
||||
observed = codec.copy(read)
|
||||
delegate.accept(read)
|
||||
}
|
||||
|
||||
override fun write(stream: RegistryFriendlyByteBuf) {
|
||||
check(!isRemoved.get()) { "This network slot was removed" }
|
||||
codec.encode(stream, delegate.get())
|
||||
}
|
||||
|
||||
override fun observe(): Boolean {
|
||||
val get = delegate.get()
|
||||
|
||||
if (observed === Mark || !codec.compare(get, observed as V)) {
|
||||
observeLock.withLock {
|
||||
if (observed === Mark || !codec.compare(get, observed as V)) {
|
||||
observed = codec.copy(get)
|
||||
markDirty()
|
||||
listeners.accept(get)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
override fun getAndObserve(): Pair<V, Boolean> {
|
||||
val get = delegate.get()
|
||||
|
||||
if (observed === Mark || !codec.compare(get, observed as V)) {
|
||||
observeLock.withLock {
|
||||
if (observed === Mark || !codec.compare(get, observed as V)) {
|
||||
observed = codec.copy(get)
|
||||
markDirty()
|
||||
listeners.accept(get)
|
||||
}
|
||||
|
||||
return get to true
|
||||
}
|
||||
}
|
||||
|
||||
return get to false
|
||||
}
|
||||
|
||||
override fun get(): V {
|
||||
check(!isRemoved.get()) { "This network slot was removed" }
|
||||
return delegate.get()
|
||||
}
|
||||
|
||||
override fun accept(t: V) {
|
||||
check(!isRemoved.get()) { "This network slot was removed" }
|
||||
delegate.accept(t)
|
||||
}
|
||||
|
||||
override fun addListener(listener: Consumer<V>): Listenable.L {
|
||||
check(!isRemoved.get()) { "This network slot was removed" }
|
||||
return listeners.addListener(listener)
|
||||
}
|
||||
}
|
||||
|
||||
private object Mark
|
||||
|
||||
internal data class SetAction<V>(val value: KOptional<V>, val action: Int)
|
||||
|
||||
inner class SetSlot<V>(val delegate: ListenableSet<V>, val codec: MatteryStreamCodec<in RegistryFriendlyByteBuf, V>) : AbstractSlot() {
|
||||
private val listener = object : ListenableSet.SetListener<V> {
|
||||
override fun onClear() {
|
||||
remoteSlots.forEach {
|
||||
(it as Remote.RemoteSetSlot<V>).changelist.clear()
|
||||
|
||||
synchronized(it.changelistLock) {
|
||||
it.changelist.add(SetAction(KOptional(), CLEAR))
|
||||
}
|
||||
|
||||
listeners.forEach {
|
||||
it.listener.onClear()
|
||||
}
|
||||
|
||||
it.markDirty()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onValueAdded(element: V) {
|
||||
remoteSlots.forEach {
|
||||
it as Remote.RemoteSetSlot<V>
|
||||
|
||||
synchronized(it.changelistLock) {
|
||||
it.changelist.removeIf { it.value.valueEquals(element) }
|
||||
it.changelist.add(SetAction(KOptional(codec.copy(element)), ADD))
|
||||
}
|
||||
|
||||
listeners.forEach {
|
||||
it.listener.onValueAdded(element)
|
||||
}
|
||||
|
||||
it.markDirty()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onValueRemoved(element: V) {
|
||||
remoteSlots.forEach {
|
||||
it as Remote.RemoteSetSlot<V>
|
||||
|
||||
synchronized(it.changelistLock) {
|
||||
it.changelist.removeIf { it.value.valueEquals(element) }
|
||||
it.changelist.add(SetAction(KOptional(codec.copy(element)), REMOVE))
|
||||
}
|
||||
|
||||
listeners.forEach {
|
||||
it.listener.onValueRemoved(element)
|
||||
}
|
||||
|
||||
it.markDirty()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val l = delegate.addListener(listener)
|
||||
private val listeners = CopyOnWriteArrayList<Listener>()
|
||||
|
||||
override fun remove0() {
|
||||
l.remove()
|
||||
listeners.clear()
|
||||
}
|
||||
|
||||
private inner class Listener(val listener: ListenableSet.SetListener<V>): Listenable.L {
|
||||
private var isRemoved = false
|
||||
|
||||
init {
|
||||
listeners.add(this)
|
||||
}
|
||||
|
||||
override fun remove() {
|
||||
if (!isRemoved) {
|
||||
isRemoved = true
|
||||
listeners.remove(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun addListener(listener: ListenableSet.SetListener<V>): Listenable.L {
|
||||
check(!isRemoved.get()) { "This network slot was removed" }
|
||||
return Listener(listener)
|
||||
}
|
||||
|
||||
fun addListener(listener: Runnable): Listenable.L {
|
||||
check(!isRemoved.get()) { "This network slot was removed" }
|
||||
return Listener(ListenableSet.RunnableAdapter(listener))
|
||||
}
|
||||
|
||||
override fun read(stream: RegistryFriendlyByteBuf) {
|
||||
check(!isRemoved.get()) { "This network slot was removed" }
|
||||
var action = stream.readByte().toInt()
|
||||
|
||||
while (true) {
|
||||
when (action) {
|
||||
ADD -> delegate.add(codec.decode(stream))
|
||||
REMOVE -> delegate.remove(codec.decode(stream))
|
||||
CLEAR -> delegate.clear()
|
||||
else -> break
|
||||
}
|
||||
|
||||
action = stream.readByte().toInt()
|
||||
}
|
||||
}
|
||||
|
||||
override fun write(stream: RegistryFriendlyByteBuf) {
|
||||
check(!isRemoved.get()) { "This network slot was removed" }
|
||||
throw RuntimeException("unreachable code")
|
||||
}
|
||||
|
||||
override fun observe(): Boolean {
|
||||
check(!isRemoved.get()) { "This network slot was removed" }
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
internal data class MapAction<K, V>(val key: KOptional<K>, val value: KOptional<V>, val action: Int)
|
||||
|
||||
inner class MapSlot<K, V>(val delegate: ListenableMap<K, V>, val keyCodec: MatteryStreamCodec<in RegistryFriendlyByteBuf, K>, val valueCodec: MatteryStreamCodec<in RegistryFriendlyByteBuf, V>) : AbstractSlot() {
|
||||
private val listener = object : ListenableMap.MapListener<K, V> {
|
||||
override fun onClear() {
|
||||
remoteSlots.forEach {
|
||||
it as Remote.RemoteMapSlot<K, V>
|
||||
|
||||
synchronized(it.changelistLock) {
|
||||
it.changelist.clear()
|
||||
it.changelist.add(MapAction(KOptional(), KOptional(), CLEAR))
|
||||
}
|
||||
|
||||
listeners.forEach {
|
||||
it.listener.onClear()
|
||||
}
|
||||
|
||||
it.markDirty()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onValueAdded(key: K, value: V) {
|
||||
remoteSlots.forEach {
|
||||
it as Remote.RemoteMapSlot<K, V>
|
||||
|
||||
synchronized(it.changelistLock) {
|
||||
it.changelist.removeIf { it.key.valueEquals(key) }
|
||||
it.changelist.add(MapAction(KOptional(keyCodec.copy(key)), KOptional(valueCodec.copy(value)), ADD))
|
||||
}
|
||||
|
||||
listeners.forEach {
|
||||
it.listener.onValueAdded(key, value)
|
||||
}
|
||||
|
||||
it.markDirty()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onValueRemoved(key: K, value: V) {
|
||||
remoteSlots.forEach {
|
||||
it as Remote.RemoteMapSlot<K, V>
|
||||
|
||||
synchronized(it.changelistLock) {
|
||||
it.changelist.removeIf { it.key.valueEquals(key) }
|
||||
it.changelist.add(MapAction(KOptional(keyCodec.copy(key)), KOptional(), REMOVE))
|
||||
}
|
||||
|
||||
listeners.forEach {
|
||||
it.listener.onValueRemoved(key, value)
|
||||
}
|
||||
|
||||
it.markDirty()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val l = delegate.addListener(listener)
|
||||
private val listeners = CopyOnWriteArrayList<Listener>()
|
||||
|
||||
override fun remove0() {
|
||||
l.remove()
|
||||
listeners.clear()
|
||||
}
|
||||
|
||||
private inner class Listener(val listener: ListenableMap.MapListener<K, V>): Listenable.L {
|
||||
private var isRemoved = false
|
||||
|
||||
init {
|
||||
listeners.add(this)
|
||||
}
|
||||
|
||||
override fun remove() {
|
||||
if (!isRemoved) {
|
||||
isRemoved = true
|
||||
listeners.remove(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun addListener(listener: ListenableMap.MapListener<K, V>): Listenable.L {
|
||||
check(!isRemoved.get()) { "This network slot was removed" }
|
||||
return Listener(listener)
|
||||
}
|
||||
|
||||
fun addListener(listener: Runnable): Listenable.L {
|
||||
check(!isRemoved.get()) { "This network slot was removed" }
|
||||
return Listener(ListenableMap.RunnableAdapter(listener))
|
||||
}
|
||||
|
||||
override fun read(stream: RegistryFriendlyByteBuf) {
|
||||
check(!isRemoved.get()) { "This network slot was removed" }
|
||||
var action = stream.readByte().toInt()
|
||||
|
||||
while (true) {
|
||||
when (action) {
|
||||
ADD -> delegate.put(keyCodec.decode(stream), valueCodec.decode(stream))
|
||||
REMOVE -> delegate.remove(keyCodec.decode(stream))
|
||||
CLEAR -> delegate.clear()
|
||||
else -> break
|
||||
}
|
||||
|
||||
action = stream.readByte().toInt()
|
||||
}
|
||||
}
|
||||
|
||||
override fun write(stream: RegistryFriendlyByteBuf) {
|
||||
check(!isRemoved.get()) { "This network slot was removed" }
|
||||
throw RuntimeException("unreachable code")
|
||||
}
|
||||
|
||||
override fun observe(): Boolean {
|
||||
check(!isRemoved.get()) { "This network slot was removed" }
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
fun <V> add(value: V, codec: MatteryStreamCodec<in RegistryFriendlyByteBuf, V>, setter: DelegateSetter<V> = DelegateSetter.passthrough(), getter: DelegateGetter<V> = DelegateGetter.passthrough()): Slot<V> {
|
||||
return Slot(ListenableDelegate.maskSmart(value, getter, setter), codec)
|
||||
}
|
||||
|
||||
fun <V> add(delegate: ListenableDelegate<V>, codec: MatteryStreamCodec<in RegistryFriendlyByteBuf, V>): Slot<V> {
|
||||
return Slot(delegate, codec)
|
||||
}
|
||||
|
||||
fun <V> add(delegate: Delegate<V>, codec: MatteryStreamCodec<in RegistryFriendlyByteBuf, V>): ObservedSlot<V> {
|
||||
return ObservedSlot(delegate, codec)
|
||||
}
|
||||
|
||||
fun <V> add(delegate: Supplier<V>, codec: MatteryStreamCodec<in RegistryFriendlyByteBuf, V>): Slot<V> {
|
||||
return computed(delegate, codec)
|
||||
}
|
||||
|
||||
fun <E> add(delegate: ListenableSet<E>, codec: MatteryStreamCodec<in RegistryFriendlyByteBuf, E>): SetSlot<E> {
|
||||
return SetSlot(delegate, codec)
|
||||
}
|
||||
|
||||
fun <K, V> add(delegate: ListenableMap<K, V>, keyCodec: MatteryStreamCodec<in RegistryFriendlyByteBuf, K>, valueCodec: MatteryStreamCodec<in RegistryFriendlyByteBuf, V>): MapSlot<K, V> {
|
||||
return MapSlot(delegate, keyCodec, valueCodec)
|
||||
}
|
||||
|
||||
fun computedByte(delegate: Supplier<Byte>) = Slot(ListenableDelegate.Shadow(delegate), StreamCodecs.BYTE)
|
||||
fun computedShort(delegate: Supplier<Short>) = Slot(ListenableDelegate.Shadow(delegate), StreamCodecs.SHORT)
|
||||
fun computedInt(delegate: Supplier<Int>) = Slot(ListenableDelegate.Shadow(delegate), StreamCodecs.INT)
|
||||
fun computedLong(delegate: Supplier<Long>) = Slot(ListenableDelegate.Shadow(delegate), StreamCodecs.VAR_LONG)
|
||||
fun computedFloat(delegate: Supplier<Float>) = Slot(ListenableDelegate.Shadow(delegate), StreamCodecs.FLOAT)
|
||||
fun computedDouble(delegate: Supplier<Double>) = Slot(ListenableDelegate.Shadow(delegate), StreamCodecs.DOUBLE)
|
||||
fun computedBoolean(delegate: Supplier<Boolean>) = Slot(ListenableDelegate.Shadow(delegate), StreamCodecs.BOOLEAN)
|
||||
fun computedString(delegate: Supplier<String>) = Slot(ListenableDelegate.Shadow(delegate), StreamCodecs.STRING)
|
||||
fun computedUUID(delegate: Supplier<UUID>) = Slot(ListenableDelegate.Shadow(delegate), StreamCodecs.UUID)
|
||||
fun <V> computed(delegate: Supplier<V>, codec: MatteryStreamCodec<in RegistryFriendlyByteBuf, V>) = Slot(ListenableDelegate.CustomShadow(delegate, codec::compare, codec::copy), codec)
|
||||
|
||||
fun computedInt(delegate: IntSupplier) = Slot(ListenableDelegate.Shadow(delegate::getAsInt), StreamCodecs.VAR_INT)
|
||||
fun computedLong(delegate: LongSupplier) = Slot(ListenableDelegate.Shadow(delegate::getAsLong), StreamCodecs.VAR_LONG)
|
||||
fun computedDouble(delegate: DoubleSupplier) = Slot(ListenableDelegate.Shadow(delegate::getAsDouble), StreamCodecs.DOUBLE)
|
||||
fun computedBoolean(delegate: BooleanSupplier) = Slot(ListenableDelegate.Shadow(delegate::getAsBoolean), StreamCodecs.BOOLEAN)
|
||||
|
||||
@JvmName("vbyte")
|
||||
@JvmOverloads
|
||||
fun byte(value: Byte = 0, setter: DelegateSetter<Byte> = DelegateSetter.passthrough(), getter: DelegateGetter<Byte> = DelegateGetter.passthrough()): Slot<Byte> {
|
||||
return add(ListenableDelegate.maskSmart(value, getter, setter), StreamCodecs.BYTE)
|
||||
}
|
||||
|
||||
@JvmName("vshort")
|
||||
@JvmOverloads
|
||||
fun short(value: Short = 0, setter: DelegateSetter<Short> = DelegateSetter.passthrough(), getter: DelegateGetter<Short> = DelegateGetter.passthrough()): Slot<Short> {
|
||||
return add(ListenableDelegate.maskSmart(value, getter, setter), StreamCodecs.SHORT)
|
||||
}
|
||||
|
||||
@JvmName("vint")
|
||||
@JvmOverloads
|
||||
fun int(value: Int = 0, setter: DelegateSetter<Int> = DelegateSetter.passthrough(), getter: DelegateGetter<Int> = DelegateGetter.passthrough()): Slot<Int> {
|
||||
return add(ListenableDelegate.maskSmart(value, getter, setter), StreamCodecs.VAR_INT)
|
||||
}
|
||||
|
||||
@JvmName("vlong")
|
||||
@JvmOverloads
|
||||
fun long(value: Long = 0L, setter: DelegateSetter<Long> = DelegateSetter.passthrough(), getter: DelegateGetter<Long> = DelegateGetter.passthrough()): Slot<Long> {
|
||||
return add(ListenableDelegate.maskSmart(value, getter, setter), StreamCodecs.VAR_LONG)
|
||||
}
|
||||
|
||||
@JvmName("vfloat")
|
||||
@JvmOverloads
|
||||
fun float(value: Float = 0f, setter: DelegateSetter<Float> = DelegateSetter.passthrough(), getter: DelegateGetter<Float> = DelegateGetter.passthrough()): Slot<Float> {
|
||||
return add(ListenableDelegate.maskSmart(value, getter, setter), StreamCodecs.FLOAT)
|
||||
}
|
||||
|
||||
@JvmName("vdouble")
|
||||
@JvmOverloads
|
||||
fun double(value: Double = 0.0, setter: DelegateSetter<Double> = DelegateSetter.passthrough(), getter: DelegateGetter<Double> = DelegateGetter.passthrough()): Slot<Double> {
|
||||
return add(ListenableDelegate.maskSmart(value, getter, setter), StreamCodecs.DOUBLE)
|
||||
}
|
||||
|
||||
@JvmName("vboolean")
|
||||
@JvmOverloads
|
||||
fun boolean(value: Boolean = false, setter: DelegateSetter<Boolean> = DelegateSetter.passthrough(), getter: DelegateGetter<Boolean> = DelegateGetter.passthrough()): Slot<Boolean> {
|
||||
return add(ListenableDelegate.maskSmart(value, getter, setter), StreamCodecs.BOOLEAN)
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
fun string(value: String, setter: DelegateSetter<String> = DelegateSetter.passthrough(), getter: DelegateGetter<String> = DelegateGetter.passthrough()): Slot<String> {
|
||||
return add(ListenableDelegate.maskSmart(value, getter, setter), StreamCodecs.STRING)
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
fun uuid(value: UUID, setter: DelegateSetter<UUID> = DelegateSetter.passthrough(), getter: DelegateGetter<UUID> = DelegateGetter.passthrough()): Slot<UUID> {
|
||||
return add(ListenableDelegate.maskSmart(value, getter, setter), StreamCodecs.UUID)
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
fun <E> set(codec: MatteryStreamCodec<in RegistryFriendlyByteBuf, E>, backing: MutableSet<E> = ObjectOpenHashSet()): SetSlot<E> {
|
||||
return add(ListenableSet(backing), codec)
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
fun <K, V> map(keyCodec: MatteryStreamCodec<in RegistryFriendlyByteBuf, K>, valueCodec: MatteryStreamCodec<in RegistryFriendlyByteBuf, V>, backing: MutableMap<K, V> = Object2ObjectOpenHashMap()): MapSlot<K, V> {
|
||||
return add(ListenableMap(backing), keyCodec, valueCodec)
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
fun <E : Enum<E>> enum(value: E, setter: DelegateSetter<E> = DelegateSetter.passthrough(), getter: DelegateGetter<E> = DelegateGetter.passthrough()): Slot<E> {
|
||||
return add(ListenableDelegate.maskSmart(value, getter, setter), MatteryStreamCodec.Enum(value::class.java))
|
||||
}
|
||||
|
||||
fun decimal(value: Decimal = Decimal.ZERO, setter: DelegateSetter<Decimal> = DelegateSetter.passthrough(), getter: DelegateGetter<Decimal> = DelegateGetter.passthrough()): DelegateSyncher.Slot<Decimal> {
|
||||
return add(ListenableDelegate.maskSmart(value, getter, setter), StreamCodecs.DECIMAL)
|
||||
}
|
||||
|
||||
fun computedDecimal(delegate: Supplier<Decimal>): DelegateSyncher.Slot<Decimal> {
|
||||
return add(delegate, StreamCodecs.DECIMAL)
|
||||
}
|
||||
|
||||
fun item(value: ItemStack = ItemStack.EMPTY, setter: DelegateSetter<ItemStack> = DelegateSetter.passthrough(), getter: DelegateGetter<ItemStack> = DelegateGetter.passthrough()): DelegateSyncher.Slot<ItemStack> {
|
||||
return add(ListenableDelegate.maskSmart(value, getter, setter), ItemStack.OPTIONAL_STREAM_CODEC.wrap())
|
||||
}
|
||||
|
||||
fun computedItem(delegate: Supplier<ItemStack>): DelegateSyncher.Slot<ItemStack> {
|
||||
return computed(delegate, ItemStack.OPTIONAL_STREAM_CODEC.wrap())
|
||||
}
|
||||
|
||||
fun observedItem(value: ItemStack = ItemStack.EMPTY, setter: DelegateSetter<ItemStack> = DelegateSetter.passthrough(), getter: DelegateGetter<ItemStack> = DelegateGetter.passthrough()): DelegateSyncher.ObservedSlot<ItemStack> {
|
||||
return add(Delegate.maskSmart(value, getter, setter), ItemStack.OPTIONAL_STREAM_CODEC.wrap())
|
||||
}
|
||||
|
||||
/**
|
||||
* Remotes must be [close]d when no longer in use, otherwise they will
|
||||
* leak memory and decrease performance of syncher.
|
||||
*
|
||||
* To get changes to be networked to remote client, [write] should be called.
|
||||
*/
|
||||
inner class Remote : Closeable {
|
||||
@Volatile
|
||||
private var isRemoved = false
|
||||
internal val dirty = ConcurrentLinkedQueue<RemoteSlot>()
|
||||
private val remoteSlots = CopyOnWriteArrayList<RemoteSlot>()
|
||||
|
||||
internal open inner class RemoteSlot(val parent: AbstractSlot) : Comparable<RemoteSlot> {
|
||||
private val isDirty = AtomicBoolean()
|
||||
|
||||
final override fun compareTo(other: RemoteSlot): Int {
|
||||
return parent.compareTo(other.parent)
|
||||
}
|
||||
|
||||
init {
|
||||
remoteSlots.add(this)
|
||||
dirty.add(this)
|
||||
}
|
||||
|
||||
fun markDirty() {
|
||||
if (!isRemoved && isDirty.compareAndSet(false, true)) {
|
||||
dirty.add(this)
|
||||
}
|
||||
}
|
||||
|
||||
fun markClean() {
|
||||
isDirty.set(false)
|
||||
}
|
||||
|
||||
open fun invalidate() {
|
||||
markDirty()
|
||||
}
|
||||
|
||||
open fun remove() {
|
||||
isDirty.set(true)
|
||||
remoteSlots.remove(this)
|
||||
dirty.remove(this)
|
||||
}
|
||||
|
||||
open fun write(stream: RegistryFriendlyByteBuf) {
|
||||
parent.write(stream)
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return parent.hashCode()
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
return this === other || other is Remote.RemoteSlot && other.parent == parent
|
||||
}
|
||||
}
|
||||
|
||||
internal inner class RemoteSetSlot<V>(parent: SetSlot<V>) : RemoteSlot(parent) {
|
||||
val changelist = ArrayList<SetAction<V>>()
|
||||
val changelistLock = Any()
|
||||
|
||||
override fun remove() {
|
||||
super.remove()
|
||||
changelist.clear()
|
||||
}
|
||||
|
||||
override fun invalidate() {
|
||||
synchronized(changelistLock) {
|
||||
changelist.clear()
|
||||
changelist.add(SetAction(KOptional(), CLEAR))
|
||||
|
||||
parent as SetSlot<V>
|
||||
|
||||
for (v in parent.delegate) {
|
||||
changelist.add(SetAction(KOptional(parent.codec.copy(v)), ADD))
|
||||
}
|
||||
}
|
||||
|
||||
super.invalidate()
|
||||
}
|
||||
|
||||
override fun write(stream: RegistryFriendlyByteBuf) {
|
||||
synchronized(changelistLock) {
|
||||
for (change in changelist) {
|
||||
stream.writeByte(change.action)
|
||||
change.value.ifPresent { (parent as SetSlot<V>).codec.encode(stream, it) }
|
||||
}
|
||||
|
||||
changelist.clear()
|
||||
}
|
||||
|
||||
stream.writeByte(0)
|
||||
}
|
||||
}
|
||||
|
||||
internal inner class RemoteMapSlot<K, V>(parent: MapSlot<K, V>) : RemoteSlot(parent) {
|
||||
val changelist = ArrayList<MapAction<K, V>>()
|
||||
val changelistLock = Any()
|
||||
|
||||
override fun remove() {
|
||||
super.remove()
|
||||
changelist.clear()
|
||||
}
|
||||
|
||||
override fun invalidate() {
|
||||
synchronized(changelistLock) {
|
||||
changelist.clear()
|
||||
changelist.add(MapAction(KOptional(), KOptional(), CLEAR))
|
||||
|
||||
parent as MapSlot<K, V>
|
||||
|
||||
for ((k, v) in parent.delegate) {
|
||||
changelist.add(MapAction(KOptional(parent.keyCodec.copy(k)), KOptional(parent.valueCodec.copy(v)), ADD))
|
||||
}
|
||||
}
|
||||
|
||||
super.invalidate()
|
||||
}
|
||||
|
||||
override fun write(stream: RegistryFriendlyByteBuf) {
|
||||
synchronized(changelistLock) {
|
||||
for (change in changelist) {
|
||||
stream.writeByte(change.action)
|
||||
change.key.ifPresent { (parent as MapSlot<K, V>).keyCodec.encode(stream, it) }
|
||||
change.value.ifPresent { (parent as MapSlot<K, V>).valueCodec.encode(stream, it) }
|
||||
}
|
||||
|
||||
changelist.clear()
|
||||
}
|
||||
|
||||
stream.writeByte(0)
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
lock.withLock {
|
||||
slots.forEach {
|
||||
if (it != null) {
|
||||
addSlot(it)
|
||||
}
|
||||
}
|
||||
|
||||
remotes.add(this)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun addSlot(slot: AbstractSlot) {
|
||||
if (slot is SetSlot<*>) {
|
||||
slot.remoteSlots.add(RemoteSetSlot(slot))
|
||||
} else if (slot is MapSlot<*, *>) {
|
||||
slot.remoteSlots.add(RemoteMapSlot(slot))
|
||||
} else {
|
||||
slot.remoteSlots.add(RemoteSlot(slot))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns null if this remote is clean.
|
||||
*
|
||||
* [DelegateSyncher.observe] is not called automatically for performance
|
||||
* reasons, you must call it manually.
|
||||
*/
|
||||
fun write(registry: RegistryAccess): ByteArrayList? {
|
||||
if (dirty.isNotEmpty()) {
|
||||
val sorted = ObjectAVLTreeSet<RemoteSlot>()
|
||||
var next = dirty.poll()
|
||||
|
||||
while (next != null) {
|
||||
sorted.add(next)
|
||||
next.markClean()
|
||||
next = dirty.poll()
|
||||
}
|
||||
|
||||
return encodePayload(registry) {
|
||||
for (slot in sorted) {
|
||||
it.writeVarInt(slot.parent.id + 1)
|
||||
slot.write(it)
|
||||
}
|
||||
|
||||
it.writeByte(0)
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks all networked slots dirty
|
||||
*/
|
||||
fun invalidate() {
|
||||
remoteSlots.forEach { it.invalidate() }
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
if (!isRemoved) {
|
||||
lock.withLock {
|
||||
if (!isRemoved) {
|
||||
remoteSlots.forEach {
|
||||
it.remove()
|
||||
it.parent.remoteSlots.remove(it)
|
||||
}
|
||||
|
||||
remotes.remove(this)
|
||||
}
|
||||
}
|
||||
|
||||
dirty.clear()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val END = 0
|
||||
private const val CLEAR = 1
|
||||
private const val ADD = 2
|
||||
private const val REMOVE = 3
|
||||
}
|
||||
}
|
@ -12,7 +12,7 @@ import java.io.DataInputStream
|
||||
import java.io.DataOutputStream
|
||||
import java.util.NoSuchElementException
|
||||
|
||||
interface MatteryStreamCodec<S : ByteBuf, V> : StreamCodec<S, V> {
|
||||
interface MatteryStreamCodec<in S : ByteBuf, V> : StreamCodec<@UnsafeVariance S, V> {
|
||||
fun copy(value: V): V {
|
||||
return value
|
||||
}
|
||||
@ -25,9 +25,9 @@ interface MatteryStreamCodec<S : ByteBuf, V> : StreamCodec<S, V> {
|
||||
override fun decode(stream: S): V
|
||||
override fun encode(stream: S, value: V)
|
||||
|
||||
class Wrapper<S : ByteBuf, V>(parent: StreamCodec<S, V>) : MatteryStreamCodec<S, V>, StreamCodec<S, V> by parent
|
||||
class Wrapper<in S : ByteBuf, V>(parent: StreamCodec<S, V>) : MatteryStreamCodec<S, V>, StreamCodec<@UnsafeVariance S, V> by parent
|
||||
|
||||
class Nullable<S : ByteBuf, V>(val parent: MatteryStreamCodec<S, V>) : MatteryStreamCodec<S, V?> {
|
||||
class Nullable<in S : ByteBuf, V>(val parent: MatteryStreamCodec<S, V>) : MatteryStreamCodec<S, V?> {
|
||||
override fun decode(stream: S): V? {
|
||||
return if (!stream.readBoolean()) null else parent.decode(stream)
|
||||
}
|
||||
@ -52,7 +52,7 @@ interface MatteryStreamCodec<S : ByteBuf, V> : StreamCodec<S, V> {
|
||||
}
|
||||
}
|
||||
|
||||
class Optional<S : ByteBuf, V : Any>(val parent: MatteryStreamCodec<S, V>) : MatteryStreamCodec<S, java.util.Optional<V>> {
|
||||
class Optional<in S : ByteBuf, V : Any>(val parent: MatteryStreamCodec<S, V>) : MatteryStreamCodec<S, java.util.Optional<V>> {
|
||||
override fun decode(stream: S): java.util.Optional<V> {
|
||||
return if (!stream.readBoolean()) java.util.Optional.empty() else java.util.Optional.of(parent.decode(stream))
|
||||
}
|
||||
@ -77,7 +77,7 @@ interface MatteryStreamCodec<S : ByteBuf, V> : StreamCodec<S, V> {
|
||||
}
|
||||
}
|
||||
|
||||
class Collection<S : FriendlyByteBuf, E, C : MutableCollection<E>>(val elementCodec: MatteryStreamCodec<in S, E>, val collectionFactory: (Int) -> C) : MatteryStreamCodec<S, C> {
|
||||
class Collection<in S : FriendlyByteBuf, E, C : MutableCollection<E>>(val elementCodec: MatteryStreamCodec<S, E>, val collectionFactory: (Int) -> C) : MatteryStreamCodec<S, C> {
|
||||
override fun decode(stream: S): C {
|
||||
val size = stream.readVarInt()
|
||||
|
||||
@ -106,7 +106,7 @@ interface MatteryStreamCodec<S : ByteBuf, V> : StreamCodec<S, V> {
|
||||
}
|
||||
}
|
||||
|
||||
class Enum<S : FriendlyByteBuf, V : kotlin.Enum<V>>(clazz: Class<out V>) : MatteryStreamCodec<S, V> {
|
||||
class Enum<in S : FriendlyByteBuf, V : kotlin.Enum<V>>(clazz: Class<out V>) : MatteryStreamCodec<S, V> {
|
||||
val clazz = searchClass(clazz)
|
||||
val values: List<V> = listOf(*this.clazz.enumConstants!!)
|
||||
val valuesMap = values.associateBy { it.name }
|
||||
|
@ -0,0 +1,10 @@
|
||||
package ru.dbotthepony.mc.otm.network.syncher
|
||||
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
import java.io.Closeable
|
||||
|
||||
interface IRemoteState : Closeable {
|
||||
fun write(stream: RegistryFriendlyByteBuf)
|
||||
|
||||
fun invalidate()
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package ru.dbotthepony.mc.otm.network.syncher
|
||||
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
import ru.dbotthepony.kommons.util.Observer
|
||||
|
||||
interface ISynchable : Observer {
|
||||
fun read(stream: RegistryFriendlyByteBuf)
|
||||
|
||||
/**
|
||||
* Provided [listener] is to be considered thread-safe, and can be called at any time
|
||||
*
|
||||
* Created [IRemoteState] is implied to have [IRemoteState.invalidate] called implicitly internally
|
||||
*/
|
||||
fun createRemoteState(listener: Runnable): IRemoteState
|
||||
|
||||
val shouldBeObserved: Boolean
|
||||
get() = false
|
||||
|
||||
override fun observe(): Boolean {
|
||||
// no op
|
||||
return false
|
||||
}
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
package ru.dbotthepony.mc.otm.network.syncher
|
||||
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
import ru.dbotthepony.kommons.util.Listenable
|
||||
import ru.dbotthepony.kommons.util.ListenableDelegate
|
||||
import ru.dbotthepony.kommons.util.Observer
|
||||
import ru.dbotthepony.mc.otm.network.MatteryStreamCodec
|
||||
import java.util.function.Consumer
|
||||
import java.util.function.Supplier
|
||||
|
||||
class SynchableDelegate<V>(val delegate: ListenableDelegate<V>, val codec: MatteryStreamCodec<RegistryFriendlyByteBuf, V>) : ISynchable, ListenableDelegate<V>, Observer {
|
||||
constructor(delegate: Supplier<V>, codec: MatteryStreamCodec<RegistryFriendlyByteBuf, V>) : this(ListenableDelegate.CustomShadow(delegate, codec::compare, codec::copy), codec)
|
||||
|
||||
private val listeners = Listenable.Impl<V>()
|
||||
private val l = delegate.addListener(listeners)
|
||||
|
||||
override fun read(stream: RegistryFriendlyByteBuf) {
|
||||
delegate.accept(codec.decode(stream))
|
||||
}
|
||||
|
||||
inner class Remote(listener: Runnable) : IRemoteState {
|
||||
private val l = listeners.addListener(listener)
|
||||
|
||||
override fun write(stream: RegistryFriendlyByteBuf) {
|
||||
codec.encode(stream, delegate.get())
|
||||
}
|
||||
|
||||
override fun invalidate() {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
l.remove()
|
||||
}
|
||||
}
|
||||
|
||||
override fun createRemoteState(listener: Runnable): IRemoteState {
|
||||
return Remote(listener)
|
||||
}
|
||||
|
||||
override fun accept(t: V) {
|
||||
delegate.accept(t)
|
||||
}
|
||||
|
||||
override fun addListener(listener: Consumer<V>): Listenable.L {
|
||||
return listeners.addListener(listener)
|
||||
}
|
||||
|
||||
override fun get(): V {
|
||||
return delegate.get()
|
||||
}
|
||||
|
||||
override val shouldBeObserved: Boolean
|
||||
get() = delegate is ListenableDelegate.AbstractShadow
|
||||
|
||||
override fun observe(): Boolean {
|
||||
if (delegate is ListenableDelegate.AbstractShadow) {
|
||||
return delegate.observe()
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
@ -0,0 +1,122 @@
|
||||
package ru.dbotthepony.mc.otm.network.syncher
|
||||
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
import ru.dbotthepony.kommons.collect.ListenableMap
|
||||
import ru.dbotthepony.kommons.util.KOptional
|
||||
import ru.dbotthepony.mc.otm.network.MatteryStreamCodec
|
||||
import java.util.ArrayList
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
|
||||
class SynchableMap<K, V>(
|
||||
val delegate: ListenableMap<K, V>,
|
||||
val keyCodec: MatteryStreamCodec<RegistryFriendlyByteBuf, K>,
|
||||
val valueCodec: MatteryStreamCodec<RegistryFriendlyByteBuf, V>
|
||||
) : ISynchable {
|
||||
override fun read(stream: RegistryFriendlyByteBuf) {
|
||||
var action = stream.readByte().toInt()
|
||||
|
||||
while (true) {
|
||||
when (action) {
|
||||
ADD -> delegate.put(keyCodec.decode(stream), valueCodec.decode(stream))
|
||||
REMOVE -> delegate.remove(keyCodec.decode(stream))
|
||||
CLEAR -> delegate.clear()
|
||||
else -> break
|
||||
}
|
||||
|
||||
action = stream.readByte().toInt()
|
||||
}
|
||||
}
|
||||
|
||||
private val remoteSlots = CopyOnWriteArrayList<RemoteState>()
|
||||
private data class MapAction<K, V>(val key: KOptional<K>, val value: KOptional<V>, val action: Int)
|
||||
|
||||
private inner class RemoteState(val listener: Runnable) : IRemoteState, ListenableMap.MapListener<K, V> {
|
||||
private val changelist = ArrayList<MapAction<K, V>>()
|
||||
private val changelistLock = Any()
|
||||
private val l = delegate.addListener(this)
|
||||
|
||||
init {
|
||||
remoteSlots.add(this)
|
||||
}
|
||||
|
||||
override fun write(stream: RegistryFriendlyByteBuf) {
|
||||
synchronized(changelistLock) {
|
||||
for (change in changelist) {
|
||||
stream.writeByte(change.action)
|
||||
change.key.ifPresent { keyCodec.encode(stream, it) }
|
||||
change.value.ifPresent { valueCodec.encode(stream, it) }
|
||||
}
|
||||
|
||||
changelist.clear()
|
||||
}
|
||||
|
||||
stream.writeByte(END)
|
||||
}
|
||||
|
||||
override fun invalidate() {
|
||||
synchronized(changelistLock) {
|
||||
changelist.clear()
|
||||
changelist.add(MapAction(KOptional(), KOptional(), CLEAR))
|
||||
|
||||
for ((k, v) in delegate) {
|
||||
changelist.add(MapAction(KOptional(keyCodec.copy(k)), KOptional(valueCodec.copy(v)), ADD))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
l.remove()
|
||||
remoteSlots.remove(this)
|
||||
|
||||
synchronized(changelistLock) {
|
||||
changelist.clear()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onClear() {
|
||||
synchronized(changelistLock) {
|
||||
changelist.clear()
|
||||
changelist.add(MapAction(KOptional(), KOptional(), CLEAR))
|
||||
}
|
||||
|
||||
listener.run()
|
||||
}
|
||||
|
||||
override fun onValueAdded(key: K, value: V) {
|
||||
synchronized(changelistLock) {
|
||||
changelist.removeIf { it.key.valueEquals(key) }
|
||||
changelist.add(MapAction(KOptional(keyCodec.copy(key)), KOptional(valueCodec.copy(value)), ADD))
|
||||
}
|
||||
|
||||
listener.run()
|
||||
}
|
||||
|
||||
override fun onValueRemoved(key: K, value: V) {
|
||||
synchronized(changelistLock) {
|
||||
changelist.removeIf { it.key.valueEquals(key) }
|
||||
changelist.add(MapAction(KOptional(keyCodec.copy(key)), KOptional(), REMOVE))
|
||||
}
|
||||
|
||||
listener.run()
|
||||
}
|
||||
|
||||
init {
|
||||
invalidate()
|
||||
}
|
||||
}
|
||||
|
||||
override fun createRemoteState(listener: Runnable): IRemoteState {
|
||||
return RemoteState(listener)
|
||||
}
|
||||
|
||||
override fun observe(): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val END = 0
|
||||
private const val CLEAR = 1
|
||||
private const val ADD = 2
|
||||
private const val REMOVE = 3
|
||||
}
|
||||
}
|
@ -0,0 +1,94 @@
|
||||
package ru.dbotthepony.mc.otm.network.syncher
|
||||
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
import ru.dbotthepony.kommons.util.Delegate
|
||||
import ru.dbotthepony.kommons.util.Listenable
|
||||
import ru.dbotthepony.kommons.util.ListenableDelegate
|
||||
import ru.dbotthepony.kommons.util.ValueObserver
|
||||
import ru.dbotthepony.mc.otm.network.MatteryStreamCodec
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
import java.util.function.Consumer
|
||||
import kotlin.concurrent.withLock
|
||||
|
||||
class SynchableObservedDelegate<V>(val delegate: Delegate<V>, val codec: MatteryStreamCodec<RegistryFriendlyByteBuf, V>) : ISynchable, ListenableDelegate<V>, ValueObserver<V> {
|
||||
private object Mark
|
||||
|
||||
private val listeners = Listenable.Impl<V>()
|
||||
private var observed: Any? = Mark
|
||||
private val observeLock = ReentrantLock()
|
||||
|
||||
override fun read(stream: RegistryFriendlyByteBuf) {
|
||||
val read = codec.decode(stream)
|
||||
observed = codec.copy(read)
|
||||
delegate.accept(read)
|
||||
}
|
||||
|
||||
override fun observe(): Boolean {
|
||||
val get = delegate.get()
|
||||
|
||||
if (observed === Mark || !codec.compare(get, observed as V)) {
|
||||
observeLock.withLock {
|
||||
if (observed === Mark || !codec.compare(get, observed as V)) {
|
||||
observed = codec.copy(get)
|
||||
listeners.accept(get)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
override fun getAndObserve(): Pair<V, Boolean> {
|
||||
val get = delegate.get()
|
||||
|
||||
if (observed === Mark || !codec.compare(get, observed as V)) {
|
||||
observeLock.withLock {
|
||||
if (observed === Mark || !codec.compare(get, observed as V)) {
|
||||
observed = codec.copy(get)
|
||||
listeners.accept(get)
|
||||
}
|
||||
|
||||
return get to true
|
||||
}
|
||||
}
|
||||
|
||||
return get to false
|
||||
}
|
||||
|
||||
override fun get(): V {
|
||||
return delegate.get()
|
||||
}
|
||||
|
||||
override fun accept(t: V) {
|
||||
delegate.accept(t)
|
||||
}
|
||||
|
||||
override val shouldBeObserved: Boolean
|
||||
get() = true
|
||||
|
||||
override fun addListener(listener: Consumer<V>): Listenable.L {
|
||||
return listeners.addListener(listener)
|
||||
}
|
||||
|
||||
private inner class RemoteState(listener: Runnable) : IRemoteState {
|
||||
private val l = listeners.addListener(listener)
|
||||
|
||||
override fun write(stream: RegistryFriendlyByteBuf) {
|
||||
codec.encode(stream, delegate.get())
|
||||
}
|
||||
|
||||
override fun invalidate() {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
l.remove()
|
||||
}
|
||||
}
|
||||
|
||||
override fun createRemoteState(listener: Runnable): IRemoteState {
|
||||
return RemoteState(listener)
|
||||
}
|
||||
}
|
@ -0,0 +1,119 @@
|
||||
package ru.dbotthepony.mc.otm.network.syncher
|
||||
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
import ru.dbotthepony.kommons.collect.ListenableSet
|
||||
import ru.dbotthepony.kommons.util.KOptional
|
||||
import ru.dbotthepony.kommons.util.Listenable
|
||||
import ru.dbotthepony.mc.otm.network.MatteryStreamCodec
|
||||
import java.util.ArrayList
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
|
||||
class SynchableSet<V>(val delegate: ListenableSet<V>, val codec: MatteryStreamCodec<RegistryFriendlyByteBuf, V>) : ISynchable {
|
||||
private data class SetAction<V>(val value: KOptional<V>, val action: Int)
|
||||
private val remoteSlots = CopyOnWriteArrayList<RemoteState>()
|
||||
|
||||
private inner class RemoteState(val listener: Runnable) : IRemoteState, ListenableSet.SetListener<V> {
|
||||
private val changelist = ArrayList<SetAction<V>>()
|
||||
private val changelistLock = Any()
|
||||
private val l = delegate.addListener(this)
|
||||
|
||||
init {
|
||||
remoteSlots.add(this)
|
||||
}
|
||||
|
||||
override fun write(stream: RegistryFriendlyByteBuf) {
|
||||
synchronized(changelistLock) {
|
||||
for (change in changelist) {
|
||||
stream.writeByte(change.action)
|
||||
change.value.ifPresent { codec.encode(stream, it) }
|
||||
}
|
||||
|
||||
changelist.clear()
|
||||
}
|
||||
|
||||
stream.writeByte(END)
|
||||
}
|
||||
|
||||
override fun invalidate() {
|
||||
synchronized(changelistLock) {
|
||||
changelist.clear()
|
||||
changelist.add(SetAction(KOptional(), CLEAR))
|
||||
|
||||
for (v in delegate) {
|
||||
changelist.add(SetAction(KOptional(codec.copy(v)), ADD))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
remoteSlots.remove(this)
|
||||
l.remove()
|
||||
|
||||
synchronized(changelistLock) {
|
||||
changelist.clear()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onClear() {
|
||||
changelist.clear()
|
||||
|
||||
synchronized(changelistLock) {
|
||||
changelist.add(SetAction(KOptional(), CLEAR))
|
||||
}
|
||||
|
||||
listener.run()
|
||||
}
|
||||
|
||||
override fun onValueAdded(element: V) {
|
||||
synchronized(changelistLock) {
|
||||
changelist.removeIf { it.value.valueEquals(element) }
|
||||
changelist.add(SetAction(KOptional(codec.copy(element)), ADD))
|
||||
}
|
||||
|
||||
listener.run()
|
||||
}
|
||||
|
||||
override fun onValueRemoved(element: V) {
|
||||
synchronized(changelistLock) {
|
||||
changelist.removeIf { it.value.valueEquals(element) }
|
||||
changelist.add(SetAction(KOptional(codec.copy(element)), REMOVE))
|
||||
}
|
||||
|
||||
listener.run()
|
||||
}
|
||||
|
||||
init {
|
||||
invalidate()
|
||||
}
|
||||
}
|
||||
|
||||
override fun createRemoteState(listener: Runnable): IRemoteState {
|
||||
return RemoteState(listener)
|
||||
}
|
||||
|
||||
override fun observe(): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun read(stream: RegistryFriendlyByteBuf) {
|
||||
var action = stream.readByte().toInt()
|
||||
|
||||
while (true) {
|
||||
when (action) {
|
||||
ADD -> delegate.add(codec.decode(stream))
|
||||
REMOVE -> delegate.remove(codec.decode(stream))
|
||||
CLEAR -> delegate.clear()
|
||||
else -> break
|
||||
}
|
||||
|
||||
action = stream.readByte().toInt()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val END = 0
|
||||
private const val CLEAR = 1
|
||||
private const val ADD = 2
|
||||
private const val REMOVE = 3
|
||||
}
|
||||
}
|
443
src/main/kotlin/ru/dbotthepony/mc/otm/network/syncher/Syncher.kt
Normal file
443
src/main/kotlin/ru/dbotthepony/mc/otm/network/syncher/Syncher.kt
Normal file
@ -0,0 +1,443 @@
|
||||
package ru.dbotthepony.mc.otm.network.syncher
|
||||
|
||||
import it.unimi.dsi.fastutil.bytes.ByteArrayList
|
||||
import it.unimi.dsi.fastutil.ints.IntAVLTreeSet
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap
|
||||
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
|
||||
import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet
|
||||
import net.minecraft.core.RegistryAccess
|
||||
import net.minecraft.network.RegistryFriendlyByteBuf
|
||||
import net.minecraft.world.item.ItemStack
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import ru.dbotthepony.kommons.collect.ListenableMap
|
||||
import ru.dbotthepony.kommons.collect.ListenableSet
|
||||
import ru.dbotthepony.kommons.util.Delegate
|
||||
import ru.dbotthepony.kommons.util.DelegateGetter
|
||||
import ru.dbotthepony.kommons.util.DelegateSetter
|
||||
import ru.dbotthepony.kommons.util.Listenable
|
||||
import ru.dbotthepony.kommons.util.ListenableDelegate
|
||||
import ru.dbotthepony.kommons.util.Observer
|
||||
import ru.dbotthepony.mc.otm.OTM_CLEANER
|
||||
import ru.dbotthepony.mc.otm.core.math.Decimal
|
||||
import ru.dbotthepony.mc.otm.network.MatteryStreamCodec
|
||||
import ru.dbotthepony.mc.otm.network.StreamCodecs
|
||||
import ru.dbotthepony.mc.otm.network.decodePayload
|
||||
import ru.dbotthepony.mc.otm.network.encodePayload
|
||||
import ru.dbotthepony.mc.otm.network.wrap
|
||||
import java.io.Closeable
|
||||
import java.lang.ref.Cleaner.Cleanable
|
||||
import java.lang.ref.WeakReference
|
||||
import java.util.*
|
||||
import java.util.concurrent.ConcurrentLinkedQueue
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
import java.util.function.BooleanSupplier
|
||||
import java.util.function.DoubleSupplier
|
||||
import java.util.function.IntSupplier
|
||||
import java.util.function.LongSupplier
|
||||
import java.util.function.Supplier
|
||||
import kotlin.collections.ArrayList
|
||||
import kotlin.concurrent.withLock
|
||||
|
||||
/**
|
||||
* Universal, one-to-many delegate/value synchronizer.
|
||||
*
|
||||
* Values and delegates can be attached using [add], or by constructing subclassed directly.
|
||||
* Delta changes are tracked by [Syncher.Remote] instances.
|
||||
*
|
||||
* In general, this class is not meant to be _structurally_ concurrently mutated by different threads,
|
||||
* to avoid structure corruption a lock is employed.
|
||||
*
|
||||
* Attached delegates can be safely mutated by multiple threads concurrently
|
||||
* (attached [ListenableDelegate] can call [Listenable.addListener] callback from different threads).
|
||||
*/
|
||||
@Suppress("UNUSED")
|
||||
class Syncher : Observer, ISynchable {
|
||||
private val lock = ReentrantLock()
|
||||
private val slots = ArrayList<Slot?>()
|
||||
private val gaps = IntAVLTreeSet()
|
||||
private val observers = ArrayList<Slot>()
|
||||
private val remotes = ArrayList<Remote>()
|
||||
private var isRemote = false
|
||||
|
||||
override fun observe(): Boolean {
|
||||
var any = false
|
||||
observers.forEach { any = it.observe() || any }
|
||||
return any
|
||||
}
|
||||
|
||||
override val shouldBeObserved: Boolean
|
||||
get() = true
|
||||
|
||||
override fun createRemoteState(listener: Runnable): IRemoteState {
|
||||
return Remote(listener)
|
||||
}
|
||||
|
||||
override fun read(stream: RegistryFriendlyByteBuf) {
|
||||
isRemote = true
|
||||
|
||||
var readID = stream.readVarInt()
|
||||
|
||||
while (readID > 0) {
|
||||
val slot = lock.withLock { slots.getOrNull(readID - 1) ?: throw IndexOutOfBoundsException("Unknown networked slot ${readID - 1}!") }
|
||||
slot.synchable.read(stream)
|
||||
readID = stream.readVarInt()
|
||||
}
|
||||
}
|
||||
|
||||
fun read(registry: RegistryAccess, bytes: ByteArrayList) {
|
||||
return decodePayload(registry, bytes) {
|
||||
read(it)
|
||||
}
|
||||
}
|
||||
|
||||
internal inner class Slot(val synchable: ISynchable) : Closeable, Observer {
|
||||
val id: Int
|
||||
|
||||
private val isRemoved = AtomicBoolean()
|
||||
val remoteSlots = CopyOnWriteArrayList<Remote.RemoteSlot>()
|
||||
|
||||
override fun close() {
|
||||
if (isRemoved.compareAndSet(false, true)) {
|
||||
lock.withLock {
|
||||
slots[id] = null
|
||||
gaps.add(id)
|
||||
|
||||
observers.remove(this)
|
||||
remoteSlots.forEach { it.remove() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun observe(): Boolean {
|
||||
return synchable.observe()
|
||||
}
|
||||
|
||||
private fun markDirty() {
|
||||
if (!isRemote && !isRemoved.get()) {
|
||||
remoteSlots.forEach {
|
||||
it.markDirty()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
lock.withLock {
|
||||
if (synchable.shouldBeObserved) {
|
||||
observers.add(this)
|
||||
}
|
||||
|
||||
if (gaps.isNotEmpty()) {
|
||||
val gap = gaps.firstInt()
|
||||
gaps.remove(gap)
|
||||
id = gap
|
||||
check(slots[id] == null) { "Expected slot $id to be empty" }
|
||||
slots[id] = this
|
||||
} else {
|
||||
id = slots.size
|
||||
slots.add(this)
|
||||
}
|
||||
|
||||
remotes.forEach {
|
||||
it.addSlot(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun add0(synchable: ISynchable): Closeable {
|
||||
return Slot(synchable)
|
||||
}
|
||||
|
||||
fun <T : ISynchable> add(synchable: T): T {
|
||||
Slot(synchable)
|
||||
return synchable
|
||||
}
|
||||
|
||||
fun <V> add(value: V, codec: MatteryStreamCodec<RegistryFriendlyByteBuf, V>, setter: DelegateSetter<V> = DelegateSetter.passthrough(), getter: DelegateGetter<V> = DelegateGetter.passthrough()): SynchableDelegate<V> {
|
||||
return add(SynchableDelegate(ListenableDelegate.maskSmart(value, getter, setter), codec))
|
||||
}
|
||||
|
||||
fun <V> add(delegate: ListenableDelegate<V>, codec: MatteryStreamCodec<RegistryFriendlyByteBuf, V>): SynchableDelegate<V> {
|
||||
return add(SynchableDelegate(delegate, codec))
|
||||
}
|
||||
|
||||
fun <V> add(delegate: Delegate<V>, codec: MatteryStreamCodec<RegistryFriendlyByteBuf, V>): SynchableObservedDelegate<V> {
|
||||
return add(SynchableObservedDelegate(delegate, codec))
|
||||
}
|
||||
|
||||
fun <V> add(delegate: Supplier<V>, codec: MatteryStreamCodec<RegistryFriendlyByteBuf, V>): SynchableDelegate<V> {
|
||||
return computed(delegate, codec)
|
||||
}
|
||||
|
||||
fun <E> add(delegate: ListenableSet<E>, codec: MatteryStreamCodec<RegistryFriendlyByteBuf, E>): SynchableSet<E> {
|
||||
return add(SynchableSet(delegate, codec))
|
||||
}
|
||||
|
||||
fun <K, V> add(delegate: ListenableMap<K, V>, keyCodec: MatteryStreamCodec<RegistryFriendlyByteBuf, K>, valueCodec: MatteryStreamCodec<RegistryFriendlyByteBuf, V>): SynchableMap<K, V> {
|
||||
return add(SynchableMap(delegate, keyCodec, valueCodec))
|
||||
}
|
||||
|
||||
fun computedByte(delegate: Supplier<Byte>) = add(SynchableDelegate(ListenableDelegate.Shadow(delegate), StreamCodecs.BYTE))
|
||||
fun computedShort(delegate: Supplier<Short>) = add(SynchableDelegate(ListenableDelegate.Shadow(delegate), StreamCodecs.SHORT))
|
||||
fun computedInt(delegate: Supplier<Int>) = add(SynchableDelegate(ListenableDelegate.Shadow(delegate), StreamCodecs.INT))
|
||||
fun computedLong(delegate: Supplier<Long>) = add(SynchableDelegate(ListenableDelegate.Shadow(delegate), StreamCodecs.VAR_LONG))
|
||||
fun computedFloat(delegate: Supplier<Float>) = add(SynchableDelegate(ListenableDelegate.Shadow(delegate), StreamCodecs.FLOAT))
|
||||
fun computedDouble(delegate: Supplier<Double>) = add(SynchableDelegate(ListenableDelegate.Shadow(delegate), StreamCodecs.DOUBLE))
|
||||
fun computedBoolean(delegate: Supplier<Boolean>) = add(SynchableDelegate(ListenableDelegate.Shadow(delegate), StreamCodecs.BOOLEAN))
|
||||
fun computedString(delegate: Supplier<String>) = add(SynchableDelegate(ListenableDelegate.Shadow(delegate), StreamCodecs.STRING))
|
||||
fun computedUUID(delegate: Supplier<UUID>) = add(SynchableDelegate(ListenableDelegate.Shadow(delegate), StreamCodecs.UUID))
|
||||
fun <V> computed(delegate: Supplier<V>, codec: MatteryStreamCodec<RegistryFriendlyByteBuf, V>) = add(SynchableDelegate(ListenableDelegate.CustomShadow(delegate, codec::compare, codec::copy), codec))
|
||||
|
||||
fun computedInt(delegate: IntSupplier) = add(SynchableDelegate(ListenableDelegate.Shadow(delegate::getAsInt), StreamCodecs.VAR_INT))
|
||||
fun computedLong(delegate: LongSupplier) = add(SynchableDelegate(ListenableDelegate.Shadow(delegate::getAsLong), StreamCodecs.VAR_LONG))
|
||||
fun computedDouble(delegate: DoubleSupplier) = add(SynchableDelegate(ListenableDelegate.Shadow(delegate::getAsDouble), StreamCodecs.DOUBLE))
|
||||
fun computedBoolean(delegate: BooleanSupplier) = add(SynchableDelegate(ListenableDelegate.Shadow(delegate::getAsBoolean), StreamCodecs.BOOLEAN))
|
||||
|
||||
@JvmName("vbyte")
|
||||
@JvmOverloads
|
||||
fun byte(value: Byte = 0, setter: DelegateSetter<Byte> = DelegateSetter.passthrough(), getter: DelegateGetter<Byte> = DelegateGetter.passthrough()): SynchableDelegate<Byte> {
|
||||
return add(ListenableDelegate.maskSmart(value, getter, setter), StreamCodecs.BYTE)
|
||||
}
|
||||
|
||||
@JvmName("vshort")
|
||||
@JvmOverloads
|
||||
fun short(value: Short = 0, setter: DelegateSetter<Short> = DelegateSetter.passthrough(), getter: DelegateGetter<Short> = DelegateGetter.passthrough()): SynchableDelegate<Short> {
|
||||
return add(ListenableDelegate.maskSmart(value, getter, setter), StreamCodecs.SHORT)
|
||||
}
|
||||
|
||||
@JvmName("vint")
|
||||
@JvmOverloads
|
||||
fun int(value: Int = 0, setter: DelegateSetter<Int> = DelegateSetter.passthrough(), getter: DelegateGetter<Int> = DelegateGetter.passthrough()): SynchableDelegate<Int> {
|
||||
return add(ListenableDelegate.maskSmart(value, getter, setter), StreamCodecs.VAR_INT)
|
||||
}
|
||||
|
||||
@JvmName("vlong")
|
||||
@JvmOverloads
|
||||
fun long(value: Long = 0L, setter: DelegateSetter<Long> = DelegateSetter.passthrough(), getter: DelegateGetter<Long> = DelegateGetter.passthrough()): SynchableDelegate<Long> {
|
||||
return add(ListenableDelegate.maskSmart(value, getter, setter), StreamCodecs.VAR_LONG)
|
||||
}
|
||||
|
||||
@JvmName("vfloat")
|
||||
@JvmOverloads
|
||||
fun float(value: Float = 0f, setter: DelegateSetter<Float> = DelegateSetter.passthrough(), getter: DelegateGetter<Float> = DelegateGetter.passthrough()): SynchableDelegate<Float> {
|
||||
return add(ListenableDelegate.maskSmart(value, getter, setter), StreamCodecs.FLOAT)
|
||||
}
|
||||
|
||||
@JvmName("vdouble")
|
||||
@JvmOverloads
|
||||
fun double(value: Double = 0.0, setter: DelegateSetter<Double> = DelegateSetter.passthrough(), getter: DelegateGetter<Double> = DelegateGetter.passthrough()): SynchableDelegate<Double> {
|
||||
return add(ListenableDelegate.maskSmart(value, getter, setter), StreamCodecs.DOUBLE)
|
||||
}
|
||||
|
||||
@JvmName("vboolean")
|
||||
@JvmOverloads
|
||||
fun boolean(value: Boolean = false, setter: DelegateSetter<Boolean> = DelegateSetter.passthrough(), getter: DelegateGetter<Boolean> = DelegateGetter.passthrough()): SynchableDelegate<Boolean> {
|
||||
return add(ListenableDelegate.maskSmart(value, getter, setter), StreamCodecs.BOOLEAN)
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
fun string(value: String, setter: DelegateSetter<String> = DelegateSetter.passthrough(), getter: DelegateGetter<String> = DelegateGetter.passthrough()): SynchableDelegate<String> {
|
||||
return add(ListenableDelegate.maskSmart(value, getter, setter), StreamCodecs.STRING)
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
fun uuid(value: UUID, setter: DelegateSetter<UUID> = DelegateSetter.passthrough(), getter: DelegateGetter<UUID> = DelegateGetter.passthrough()): SynchableDelegate<UUID> {
|
||||
return add(ListenableDelegate.maskSmart(value, getter, setter), StreamCodecs.UUID)
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
fun <E> set(codec: MatteryStreamCodec<RegistryFriendlyByteBuf, E>, backing: MutableSet<E> = ObjectOpenHashSet()): SynchableSet<E> {
|
||||
return add(ListenableSet(backing), codec)
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
fun <K, V> map(keyCodec: MatteryStreamCodec<RegistryFriendlyByteBuf, K>, valueCodec: MatteryStreamCodec<RegistryFriendlyByteBuf, V>, backing: MutableMap<K, V> = Object2ObjectOpenHashMap()): SynchableMap<K, V> {
|
||||
return add(ListenableMap(backing), keyCodec, valueCodec)
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
fun <E : Enum<E>> enum(value: E, setter: DelegateSetter<E> = DelegateSetter.passthrough(), getter: DelegateGetter<E> = DelegateGetter.passthrough()): SynchableDelegate<E> {
|
||||
return add(ListenableDelegate.maskSmart(value, getter, setter), MatteryStreamCodec.Enum(value::class.java))
|
||||
}
|
||||
|
||||
fun decimal(value: Decimal = Decimal.ZERO, setter: DelegateSetter<Decimal> = DelegateSetter.passthrough(), getter: DelegateGetter<Decimal> = DelegateGetter.passthrough()): SynchableDelegate<Decimal> {
|
||||
return add(ListenableDelegate.maskSmart(value, getter, setter), StreamCodecs.DECIMAL)
|
||||
}
|
||||
|
||||
fun computedDecimal(delegate: Supplier<Decimal>): SynchableDelegate<Decimal> {
|
||||
return add(delegate, StreamCodecs.DECIMAL)
|
||||
}
|
||||
|
||||
fun item(value: ItemStack = ItemStack.EMPTY, setter: DelegateSetter<ItemStack> = DelegateSetter.passthrough(), getter: DelegateGetter<ItemStack> = DelegateGetter.passthrough()): SynchableDelegate<ItemStack> {
|
||||
return add(ListenableDelegate.maskSmart(value, getter, setter), ItemStack.OPTIONAL_STREAM_CODEC.wrap())
|
||||
}
|
||||
|
||||
fun computedItem(delegate: Supplier<ItemStack>): SynchableDelegate<ItemStack> {
|
||||
return computed(delegate, ItemStack.OPTIONAL_STREAM_CODEC.wrap())
|
||||
}
|
||||
|
||||
fun observedItem(value: ItemStack = ItemStack.EMPTY, setter: DelegateSetter<ItemStack> = DelegateSetter.passthrough(), getter: DelegateGetter<ItemStack> = DelegateGetter.passthrough()): SynchableObservedDelegate<ItemStack> {
|
||||
return add(Delegate.maskSmart(value, getter, setter), ItemStack.OPTIONAL_STREAM_CODEC.wrap())
|
||||
}
|
||||
|
||||
/**
|
||||
* Remotes must be [close]d when no longer in use, otherwise they will
|
||||
* leak memory and decrease performance of syncher.
|
||||
*
|
||||
* To get changes to be networked to remote client, [write] should be called.
|
||||
*/
|
||||
inner class Remote(private val listener: Runnable) : Closeable, IRemoteState {
|
||||
constructor() : this(Runnable { })
|
||||
|
||||
@Volatile
|
||||
private var isRemoved = false
|
||||
internal val dirty = ConcurrentLinkedQueue<RemoteSlot>()
|
||||
private val remoteSlots = CopyOnWriteArrayList<RemoteSlot>()
|
||||
|
||||
internal inner class RemoteSlot(val slot: Slot) {
|
||||
private val isDirty = AtomicBoolean()
|
||||
|
||||
init {
|
||||
remoteSlots.add(this)
|
||||
dirty.add(this)
|
||||
}
|
||||
|
||||
fun markDirty() {
|
||||
if (!isRemoved && isDirty.compareAndSet(false, true)) {
|
||||
dirty.add(this)
|
||||
listener.run()
|
||||
}
|
||||
}
|
||||
|
||||
private val state: IRemoteState
|
||||
private val cleanable: Cleanable
|
||||
|
||||
init {
|
||||
val weakSelf = WeakReference(this)
|
||||
val state = slot.synchable.createRemoteState(Runnable {
|
||||
weakSelf.get()?.markDirty()
|
||||
})
|
||||
|
||||
// !!!
|
||||
// hOLY SHIT
|
||||
val weakState = WeakReference(state)
|
||||
cleanable = OTM_CLEANER.register(this) { weakState.get()?.close() }
|
||||
this.state = state
|
||||
}
|
||||
|
||||
fun markClean() {
|
||||
isDirty.set(false)
|
||||
}
|
||||
|
||||
fun invalidate() {
|
||||
state.invalidate()
|
||||
markDirty()
|
||||
}
|
||||
|
||||
fun write(stream: RegistryFriendlyByteBuf) {
|
||||
state.write(stream)
|
||||
}
|
||||
|
||||
fun remove() {
|
||||
isDirty.set(true)
|
||||
remoteSlots.remove(this)
|
||||
dirty.remove(this)
|
||||
cleanable.clean()
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return slot.hashCode()
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
return this === other || other is RemoteSlot && other.slot == slot
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
lock.withLock {
|
||||
slots.forEach {
|
||||
if (it != null) {
|
||||
addSlot(it)
|
||||
}
|
||||
}
|
||||
|
||||
remotes.add(this)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun addSlot(slot: Slot) {
|
||||
slot.remoteSlots.add(RemoteSlot(slot))
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns null if this remote is clean.
|
||||
*
|
||||
* [Syncher.observe] is not called automatically for performance
|
||||
* reasons, you must call it manually.
|
||||
*/
|
||||
fun write(registry: RegistryAccess): ByteArrayList? {
|
||||
if (dirty.isNotEmpty()) {
|
||||
return encodePayload(registry) {
|
||||
write(it)
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks all networked slots dirty
|
||||
*/
|
||||
override fun invalidate() {
|
||||
remoteSlots.forEach { it.invalidate() }
|
||||
}
|
||||
|
||||
override fun write(stream: RegistryFriendlyByteBuf) {
|
||||
val seen = ReferenceOpenHashSet<RemoteSlot>()
|
||||
val sorted = ArrayList<RemoteSlot>()
|
||||
var next = dirty.poll()
|
||||
|
||||
while (next != null) {
|
||||
val status = seen.add(next)
|
||||
next.markClean()
|
||||
|
||||
if (status) {
|
||||
sorted.add(next)
|
||||
}
|
||||
|
||||
next = dirty.poll()
|
||||
}
|
||||
|
||||
for (slot in sorted) {
|
||||
stream.writeVarInt(slot.slot.id + 1)
|
||||
slot.write(stream)
|
||||
}
|
||||
|
||||
stream.writeByte(0)
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
if (!isRemoved) {
|
||||
lock.withLock {
|
||||
if (!isRemoved) {
|
||||
remoteSlots.forEach {
|
||||
it.remove()
|
||||
it.slot.remoteSlots.remove(it)
|
||||
}
|
||||
|
||||
remotes.remove(this)
|
||||
}
|
||||
}
|
||||
|
||||
dirty.clear()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val LOGGER = LogManager.getLogger()
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user