Compare commits

...

2 Commits

Author SHA1 Message Date
16f5f4c9c9
Improved syncher API, proper history graph class 2024-09-14 19:10:40 +07:00
a06fd6a31a
воцыа9гыаоу2п
ппи8ва729уц2улв
цыпцуле94пыва
ашпцувлцйа
аша94аца
в9н3р3
ццццццццццццццццццццццццццццвааупъма
аа
пм
м

мравркр
ук
ц
йке
пцу
рпыв
пр
варп
2024-09-14 18:55:09 +07:00
34 changed files with 1326 additions and 1165 deletions

View File

@ -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 }

View File

@ -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 ->

View File

@ -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()

View File

@ -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

View File

@ -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 ->

View File

@ -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)

View File

@ -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
}
// после раздумий, наиболее корректное место для обновления состояния профилированных
// хранилищ энергии и материи будет после всех остальных хуков на тики после тика сервера
// разумеется, такое решение может несколько снизить производительность сервера,

View File

@ -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

View File

@ -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,

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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()

View File

@ -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()
}
}

View File

@ -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
}
}
}

View File

@ -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
}
}

View File

@ -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()
}
}

View File

@ -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
)
}

View File

@ -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) {

View File

@ -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)

View File

@ -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 })

View File

@ -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 {

View File

@ -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)
}

View File

@ -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) {

View File

@ -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)
}
}

View File

@ -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) {

View File

@ -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
}
}

View File

@ -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 }

View File

@ -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()
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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)
}
}

View File

@ -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
}
}

View 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()
}
}