Exopack arbitrary inventory slots charging

This commit is contained in:
DBotThePony 2023-07-09 14:45:13 +07:00
parent 344364520d
commit 37f4b77994
Signed by: DBot
GPG Key ID: DCC23B5715498507
13 changed files with 510 additions and 55 deletions

View File

@ -118,6 +118,7 @@ private fun sounds(provider: MatteryLanguageProvider) {
private fun misc(provider: MatteryLanguageProvider) {
with(provider.english) {
gui("help.slot_filters", "Hold CTRL to setup slot filters")
gui("help.slot_charging", "Hold ALT to switch slot charging")
misc("needs_no_power", "Requires no power to operate")

View File

@ -126,6 +126,7 @@ private fun sounds(provider: MatteryLanguageProvider) {
private fun misc(provider: MatteryLanguageProvider) {
with(provider.russian) {
gui("help.slot_filters", "Удерживайте CTRL для настройки фильтрации слотов")
gui("help.slot_charging", "Удерживайте ALT для переключения зарядки слотов")
misc("needs_no_power", "Не требует энергии для работы")

View File

@ -1,11 +1,14 @@
package ru.dbotthepony.mc.otm.capability
import it.unimi.dsi.fastutil.ints.IntAVLTreeSet
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap
import net.minecraft.ChatFormatting
import net.minecraft.commands.Commands
import net.minecraft.commands.arguments.EntityArgument
import net.minecraft.core.Direction
import net.minecraft.nbt.ByteTag
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.IntTag
import net.minecraft.nbt.ListTag
import net.minecraft.nbt.StringTag
import net.minecraft.network.chat.Component
@ -72,6 +75,7 @@ import ru.dbotthepony.mc.otm.client.minecraft
import ru.dbotthepony.mc.otm.config.AndroidConfig
import ru.dbotthepony.mc.otm.config.ExopackConfig
import ru.dbotthepony.mc.otm.container.MatteryContainer
import ru.dbotthepony.mc.otm.container.get
import ru.dbotthepony.mc.otm.container.iterator
import ru.dbotthepony.mc.otm.container.stream
import ru.dbotthepony.mc.otm.container.vanishCursedItems
@ -80,14 +84,18 @@ import ru.dbotthepony.mc.otm.core.collect.UUIDIntModifiersMap
import ru.dbotthepony.mc.otm.core.collect.filter
import ru.dbotthepony.mc.otm.core.math.Decimal
import ru.dbotthepony.mc.otm.core.math.minus
import ru.dbotthepony.mc.otm.core.nbt.getByteList
import ru.dbotthepony.mc.otm.core.nbt.getCompoundList
import ru.dbotthepony.mc.otm.core.nbt.getIntList
import ru.dbotthepony.mc.otm.core.nbt.getStringList
import ru.dbotthepony.mc.otm.core.nbt.set
import ru.dbotthepony.mc.otm.core.util.BooleanValueCodec
import ru.dbotthepony.mc.otm.core.util.IntValueCodec
import ru.dbotthepony.mc.otm.core.util.ItemValueCodec
import ru.dbotthepony.mc.otm.core.util.Savetables
import ru.dbotthepony.mc.otm.core.util.TickList
import ru.dbotthepony.mc.otm.core.util.UUIDValueCodec
import ru.dbotthepony.mc.otm.core.util.VarIntValueCodec
import ru.dbotthepony.mc.otm.menu.ExoPackInventoryMenu
import ru.dbotthepony.mc.otm.network.*
import ru.dbotthepony.mc.otm.network.synchronizer.FieldSynchronizer
@ -224,6 +232,27 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
synchronizer.Field(null, ItemValueCodec.nullable)
}
val regularSlotChargeFlag = immutableList(Inventory.INVENTORY_SIZE + 4) {
synchronizer.bool()
}
private fun slotChargeToDefault() {
// броня
regularSlotChargeFlag[36].boolean = true
regularSlotChargeFlag[37].boolean = true
regularSlotChargeFlag[38].boolean = true
regularSlotChargeFlag[39].boolean = true
}
init {
slotChargeToDefault()
}
val exoPackSlotsChargeFlag by synchronizer.Set(
codec = VarIntValueCodec,
backingSet = IntAVLTreeSet(),
)
/**
* Exopack container, which actually store items inside Exopack
*/
@ -808,6 +837,18 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
}
}
tag["regularSlotChargeFlag"] = ListTag().also {
for (flag in regularSlotChargeFlag) {
it.add(ByteTag.valueOf(flag.boolean))
}
}
tag["exoPackSlotsChargeFlag"] = ListTag().also {
for (value in exoPackSlotsChargeFlag) {
it.add(IntTag.valueOf(value))
}
}
return tag
}
@ -818,6 +859,14 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
filter.value = null
}
for (flag in regularSlotChargeFlag) {
flag.boolean = false
}
exoPackSlotsChargeFlag.clear()
slotChargeToDefault()
val regularSlotFilters = tag.getStringList("regularSlotFilters")
for (i in 0 until regularSlotFilters.size.coerceAtMost(this.regularSlotFilters.size)) {
@ -826,6 +875,16 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
this.regularSlotFilters[i].value = ForgeRegistries.ITEMS.getValue(ResourceLocation.tryParse(path) ?: continue) ?: Items.AIR
}
val regularSlotChargeFlag = tag.getByteList("regularSlotChargeFlag")
for (i in 0 until regularSlotChargeFlag.size.coerceAtMost(this.regularSlotChargeFlag.size)) {
this.regularSlotChargeFlag[i].boolean = regularSlotChargeFlag[i].asInt > 0
}
for (v in tag.getIntList("exoPackSlotsChargeFlag")) {
this.exoPackSlotsChargeFlag.add(v.asInt)
}
// iterations
deathLog.clear()
@ -965,9 +1024,10 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
if (isExoPackSmeltingInstalled)
smelters.forEach { it.think() }
if (!exoPackChargeSlots.isEmpty && exoPackEnergy.batteryLevel.isPositive) {
if (exoPackEnergy.batteryLevel.isPositive) {
var available = exoPackEnergy.extractEnergy(exoPackEnergy.batteryLevel, true)
if (!exoPackChargeSlots.isEmpty) {
for (item in exoPackChargeSlots) {
if (item.isNotEmpty) {
item.energy?.let {
@ -978,6 +1038,39 @@ class MatteryPlayerCapability(val ply: Player) : ICapabilityProvider, INBTSerial
}
}
}
if (available.isPositive) {
for ((i, flag) in regularSlotChargeFlag.withIndex()) {
if (flag.boolean) {
val item = ply.inventory[i]
if (item.isNotEmpty) {
item.energy?.let {
available -= exoPackEnergy.extractEnergy(it.receiveEnergy(available, false), false)
}
if (!available.isPositive) break
}
}
}
}
if (available.isPositive) {
for (slot in exoPackSlotsChargeFlag) {
if (slot in 0 until exoPackContainer.containerSize) {
val item = exoPackContainer[slot]
if (item.isNotEmpty) {
item.energy?.let {
available -= exoPackEnergy.extractEnergy(it.receiveEnergy(available, false), false)
}
if (!available.isPositive) break
}
}
}
}
}
}
}

View File

@ -21,6 +21,7 @@ fun Window.isKeyDown(key: Int) = InputConstants.isKeyDown(window, key)
val Window.isShiftDown get() = isKeyDown(InputConstants.KEY_LSHIFT) || isKeyDown(InputConstants.KEY_RSHIFT)
val Window.isCtrlDown get() = isKeyDown(InputConstants.KEY_RCONTROL) || isKeyDown(InputConstants.KEY_LCONTROL)
val Window.isAltDown get() = isKeyDown(InputConstants.KEY_RALT) || isKeyDown(InputConstants.KEY_LALT)
object ShiftPressedCond : BooleanSupplier {
override fun getAsBoolean(): Boolean {

View File

@ -13,7 +13,7 @@ import ru.dbotthepony.mc.otm.client.screen.panels.*
import ru.dbotthepony.mc.otm.client.screen.panels.button.LargeRectangleButtonPanel
import ru.dbotthepony.mc.otm.client.screen.panels.slot.AbstractSlotPanel
import ru.dbotthepony.mc.otm.client.screen.panels.slot.BatterySlotPanel
import ru.dbotthepony.mc.otm.client.screen.panels.slot.UserFilteredSlotPanel
import ru.dbotthepony.mc.otm.client.screen.panels.slot.InventorySlotPanel
import ru.dbotthepony.mc.otm.client.screen.panels.slot.SlotPanel
import ru.dbotthepony.mc.otm.client.screen.panels.util.BackgroundPanel
import ru.dbotthepony.mc.otm.client.screen.panels.util.DiscreteScrollBarPanel
@ -55,7 +55,7 @@ class ExoPackInventoryScreen(menu: ExoPackInventoryMenu) : MatteryScreen<ExoPack
override fun makeMainFrame(): FramePanel<MatteryScreen<*>> {
val frame = FramePanel(this, width = 200f, height = FRAME_BASE_HEIGHT + inventoryRows * AbstractSlotPanel.SIZE, title = this.title)
frame.makeHelpButton().addSlotFiltersHelp()
frame.makeHelpButton().addSlotFiltersHelp().tooltips.add(TranslatableComponent("otm.gui.help.slot_charging"))
val hotbarStrip = EditablePanel(this, frame, height = 18f)
hotbarStrip.dock = Dock.BOTTOM
@ -92,7 +92,7 @@ class ExoPackInventoryScreen(menu: ExoPackInventoryMenu) : MatteryScreen<ExoPack
mainInventoryLine.dock = Dock.BOTTOM
for (slot in menu.playerHotbarSlots) {
UserFilteredSlotPanel.of(this, hotbarStrip, slot).also {
InventorySlotPanel(this, hotbarStrip, slot).also {
it.dock = Dock.LEFT
}
}

View File

@ -1,41 +1,42 @@
package ru.dbotthepony.mc.otm.client.screen
import com.mojang.blaze3d.systems.RenderSystem
import com.mojang.blaze3d.vertex.PoseStack
import it.unimi.dsi.fastutil.ints.Int2ObjectFunction
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap
import net.minecraft.ChatFormatting
import net.minecraft.client.gui.Font
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen
import net.minecraft.client.renderer.entity.ItemRenderer
import net.minecraft.network.chat.Component
import net.minecraft.world.entity.player.Inventory
import net.minecraft.world.inventory.Slot
import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack
import net.minecraftforge.client.ForgeHooksClient
import net.minecraftforge.client.event.ContainerScreenEvent.Render.Background
import net.minecraftforge.client.event.ContainerScreenEvent.Render.Foreground
import net.minecraftforge.common.MinecraftForge
import org.lwjgl.opengl.GL11
import org.lwjgl.opengl.GL13
import ru.dbotthepony.mc.otm.config.ClientConfig
import ru.dbotthepony.mc.otm.client.moveMousePosScaled
import ru.dbotthepony.mc.otm.client.render.clearDepth
import ru.dbotthepony.mc.otm.client.render.translation
import ru.dbotthepony.mc.otm.client.screen.panels.*
import ru.dbotthepony.mc.otm.client.screen.panels.slot.AbstractSlotPanel
import ru.dbotthepony.mc.otm.client.screen.panels.slot.UserFilteredSlotPanel
import ru.dbotthepony.mc.otm.client.screen.panels.slot.InventorySlotPanel
import ru.dbotthepony.mc.otm.client.screen.panels.util.DiscreteScrollBarPanel
import ru.dbotthepony.mc.otm.client.screen.panels.util.HeightControls
import ru.dbotthepony.mc.otm.client.screen.panels.util.ScrollBarConstants
import ru.dbotthepony.mc.otm.config.ClientConfig
import ru.dbotthepony.mc.otm.core.TranslatableComponent
import ru.dbotthepony.mc.otm.core.math.component1
import ru.dbotthepony.mc.otm.core.math.component2
import ru.dbotthepony.mc.otm.core.math.component3
import ru.dbotthepony.mc.otm.core.math.integerDivisionDown
import ru.dbotthepony.mc.otm.menu.MatteryMenu
import java.util.Collections
import java.util.*
import kotlin.collections.ArrayDeque
import kotlin.collections.List
import kotlin.collections.MutableSet
import kotlin.collections.indices
import kotlin.collections.isNotEmpty
import kotlin.collections.withIndex
/**
* This class encapsulate most of logic for handling EditablePanel and it's children.
@ -151,12 +152,13 @@ abstract class MatteryScreen<T : MatteryMenu>(menu: T, inventory: Inventory, tit
if (menu.playerInventorySlots.isNotEmpty() && menu.autoCreateInventoryFrame) {
if (menu.playerExoSuitSlots.isEmpty()) {
inventoryFrame = FramePanel<MatteryScreen<*>>(this, null, 0f, 0f, INVENTORY_FRAME_WIDTH, INVENTORY_FRAME_HEIGHT, inventory.displayName).also(this::addPanel)
inventoryFrame!!.makeHelpButton().addSlotFiltersHelp().tooltips.add(TranslatableComponent("otm.gui.help.slot_charging"))
val hotbarStrip = EditablePanel(this, inventoryFrame, height = AbstractSlotPanel.SIZE)
hotbarStrip.dock = Dock.BOTTOM
for (slot in menu.playerHotbarSlots) {
UserFilteredSlotPanel.of(this, hotbarStrip, slot).also {
InventorySlotPanel(this, hotbarStrip, slot).also {
it.dock = Dock.LEFT
}
}
@ -174,6 +176,7 @@ abstract class MatteryScreen<T : MatteryMenu>(menu: T, inventory: Inventory, tit
}
} else {
inventoryFrame = FramePanel<MatteryScreen<*>>(this, null, 0f, 0f, INVENTORY_FRAME_WIDTH_EXTENDED, BASE_INVENTORY_FRAME_HEIGHT + AbstractSlotPanel.SIZE * inventoryRows, inventory.displayName).also(this::addPanel)
inventoryFrame!!.makeHelpButton().addSlotFiltersHelp().tooltips.add(TranslatableComponent("otm.gui.help.slot_charging"))
inventoryScrollbar = DiscreteScrollBarPanel(this, inventoryFrame, { integerDivisionDown(menu.playerCombinedInventorySlots.size, 9) }, {
_, old, new ->
@ -206,7 +209,7 @@ abstract class MatteryScreen<T : MatteryMenu>(menu: T, inventory: Inventory, tit
hotbarStrip.dock = Dock.BOTTOM
for (slot in menu.playerHotbarSlots) {
UserFilteredSlotPanel.of(this, hotbarStrip, slot).also {
InventorySlotPanel(this, hotbarStrip, slot).also {
it.dock = Dock.LEFT
}
}
@ -249,7 +252,7 @@ abstract class MatteryScreen<T : MatteryMenu>(menu: T, inventory: Inventory, tit
for (i in 0 .. (8).coerceAtMost(menu.playerCombinedInventorySlots.size - offset - 1)) {
val slot = menu.playerCombinedInventorySlots[offset + i]
object : UserFilteredSlotPanel<MatteryScreen<*>, Slot>(this@MatteryScreen, canvas, slot) {
object : InventorySlotPanel<MatteryScreen<*>, MatteryMenu.InventorySlot>(this@MatteryScreen, canvas, slot) {
init {
dock = Dock.LEFT
}

View File

@ -5,6 +5,7 @@ import ru.dbotthepony.mc.otm.client.minecraft
import ru.dbotthepony.mc.otm.client.screen.MatteryScreen
import ru.dbotthepony.mc.otm.client.screen.panels.slot.AbstractSlotPanel
import ru.dbotthepony.mc.otm.client.screen.panels.slot.FoldableSlotPanel
import ru.dbotthepony.mc.otm.client.screen.panels.slot.InventorySlotPanel
import ru.dbotthepony.mc.otm.client.screen.panels.slot.SlotPanel
import ru.dbotthepony.mc.otm.client.screen.panels.util.BackgroundPanel
import ru.dbotthepony.mc.otm.compat.cos.CosmeticToggleButton
@ -64,10 +65,10 @@ open class PlayerEquipmentPanel<S : MatteryScreen<*>>(
for ((slot, cosmeticSlot) in armorSlots) {
if (cosmeticSlot == null) {
SlotPanel(screen, armorSlotsStrip, slot).dock = Dock.TOP
InventorySlotPanel(screen, armorSlotsStrip, slot).dock = Dock.TOP
} else {
FoldableSlotPanel(screen, armorSlotsStrip,
SlotPanel(screen, null, slot).also { CosmeticToggleButton(screen, it, slot.type) },
InventorySlotPanel(screen, null, slot).also { CosmeticToggleButton(screen, it, slot.type) },
listOf(SlotPanel(screen, null, cosmeticSlot))).dock = Dock.TOP
}

View File

@ -0,0 +1,50 @@
package ru.dbotthepony.mc.otm.client.screen.panels.slot
import com.mojang.blaze3d.platform.InputConstants
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.world.item.Item
import ru.dbotthepony.mc.otm.client.isAltDown
import ru.dbotthepony.mc.otm.client.minecraft
import ru.dbotthepony.mc.otm.client.playGuiClickSound
import ru.dbotthepony.mc.otm.client.render.MatterySprite
import ru.dbotthepony.mc.otm.client.render.Widgets18
import ru.dbotthepony.mc.otm.client.screen.MatteryScreen
import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel
import ru.dbotthepony.mc.otm.menu.MatteryMenu
open class InventorySlotPanel<out S : MatteryScreen<*>, out T : MatteryMenu.InventorySlot>(
screen: S,
parent: EditablePanel<*>?,
slot: T,
x: Float = 0f,
y: Float = 0f,
noItemIcon: MatterySprite? = null
) : UserFilteredSlotPanel<S, T>(screen, parent, slot, x, y, SIZE, SIZE, noItemIcon) {
override var slotFilter: Item?
get() = slot.filter?.get()
set(value) { slot.filter?.accept(value) }
override fun renderSlotBackground(graphics: GuiGraphics, mouseX: Float, mouseY: Float, partialTick: Float) {
super.renderSlotBackground(graphics, mouseX, mouseY, partialTick)
if (slot.chargeFlag?.get() == true) {
Widgets18.BATTERY_SLOT_BACKGROUND.render(graphics, 0f, 0f, width, height)
}
}
override fun mouseClickedInner(x: Double, y: Double, button: Int): Boolean {
if (button == InputConstants.MOUSE_BUTTON_LEFT && minecraft.window.isAltDown) {
val chargeFlag = slot.chargeFlag
if (chargeFlag == null) {
return super.mouseClickedInner(x, y, button)
} else {
chargeFlag.accept(!chargeFlag.get())
playGuiClickSound()
return true
}
}
return super.mouseClickedInner(x, y, button)
}
}

View File

@ -262,20 +262,40 @@ abstract class MatteryMenu @JvmOverloads protected constructor(
return super.isSameInventory(other)
}
var chargeFlag: GetterSetter<Boolean>? = null
private set
init {
val mattery = ply.matteryPlayer
if (mattery != null) {
if (container === inventory && slotIndex in mattery.regularSlotFilters.indices) {
if (container === inventory) {
if (slotIndex in mattery.regularSlotFilters.indices)
filter = GetterSetter.of(
getter = { mattery.regularSlotFilters[slotIndex].value },
setter = { MatteryPlayerNetworkChannel.sendToServer(SetInventoryFilterPacket(SetInventoryFilterPacket.Type.INVENTORY, slotIndex, it)) }
)
if (slotIndex in mattery.regularSlotChargeFlag.indices) {
val input = booleanInput(true) { mattery.regularSlotChargeFlag[slotIndex].boolean = it }
chargeFlag = GetterSetter.of(
getter = { mattery.regularSlotChargeFlag[slotIndex].boolean },
setter = input::input
)
}
} else if (container === mattery.exoPackContainer) {
filter = GetterSetter.of(
getter = { mattery.exoPackContainer.getSlotFilter(slotIndex) },
setter = { MatteryPlayerNetworkChannel.sendToServer(SetInventoryFilterPacket(SetInventoryFilterPacket.Type.EXOPACK, slotIndex, it)) }
)
val input = booleanInput(true) { if (it) mattery.exoPackSlotsChargeFlag.add(slotIndex) else mattery.exoPackSlotsChargeFlag.remove(slotIndex) }
chargeFlag = GetterSetter.of(
getter = { slotIndex in mattery.exoPackSlotsChargeFlag },
setter = input::input
)
} else {
filter = null
}

View File

@ -1,5 +1,5 @@
package ru.dbotthepony.mc.otm.network.synchronizer
enum class MapAction {
enum class ChangesetAction {
CLEAR, ADD, REMOVE
}

View File

@ -349,6 +349,7 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
// use LinkedList because it is ensured memory is freed on LinkedList#clear
private val mapBacklogs = Reference2ObjectOpenHashMap<Map<*, *>, LinkedList<Pair<Any?, (DataOutputStream) -> Unit>>>()
private val setBacklogs = Reference2ObjectOpenHashMap<Set<*>, LinkedList<Pair<Any?, (DataOutputStream) -> Unit>>>()
var unused: Boolean = false
private set
@ -401,6 +402,24 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
})
}
internal fun <K, V> removeMapBacklog(map: Map<K, V>) {
mapBacklogs.remove(map)
}
internal fun <V> getSetBacklog(set: Set<V>): LinkedList<Pair<Any?, (DataOutputStream) -> Unit>> {
if (unused) {
return LinkedList()
}
return setBacklogs.computeIfAbsent(set, Reference2ObjectFunction {
LinkedList()
})
}
internal fun <V> removeSetBacklog(set: Set<V>) {
setBacklogs.remove(set)
}
fun collectNetworkPayload(): FastByteArrayOutputStream? {
if (unused || dirtyFields.isEmpty()) {
return null
@ -1527,6 +1546,246 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
}
}
inner class Set<E>(
private val codec: IStreamCodec<E>,
private val backingSet: MutableSet<E>,
private val callback: ((changes: Collection<SetChangeset<E>>) -> Unit)? = null,
) : AbstractField<MutableSet<E>>() {
private var isRemote = false
private var isDirty = false
private fun pushBacklog(element: E, action: (DataOutputStream) -> Unit) {
check(!isRemote) { "Field marked as remote" }
val pair = element to action
forEachEndpoint {
val list = it.getSetBacklog(this)
val iterator = list.listIterator()
for (value in iterator) {
if (value.first == element) {
iterator.remove()
}
}
list.addLast(pair)
}
}
override fun observe(): Boolean {
return false
}
override fun remove() {
if (!isRemoved) {
forEachEndpoint { it.removeSetBacklog(this) }
}
super.remove()
}
override fun markDirty() {
check(!isRemoved) { "Field was removed" }
notifyEndpoints(this)
isDirty = true
}
override fun markDirty(endpoint: Endpoint) {
super.markDirty(endpoint)
endpoint.getSetBacklog(this).let {
it.clear()
it.add(null to ClearBacklogEntry)
for (value in backingSet) {
it.add(value to { it.write(ChangesetAction.ADD.ordinal + 1); codec.write(it, value) })
}
}
}
override val value: MutableSet<E> = object : MutableSet<E> {
override fun add(element: E): Boolean {
if (backingSet.add(element)) {
if (!isRemote) {
markDirty()
pushBacklog(element) {
it.write(ChangesetAction.ADD.ordinal + 1)
codec.write(it, element)
}
}
return true
}
return false
}
override fun addAll(elements: Collection<E>): Boolean {
var any = false
elements.forEach { any = add(it) || any }
return any
}
override fun clear() {
if (backingSet.isNotEmpty()) {
backingSet.clear()
if (!isRemote) {
markDirty()
forEachEndpoint {
it.getSetBacklog(this@Set).let {
it.clear()
it.add(null to ClearBacklogEntry)
}
}
}
}
}
override fun iterator(): MutableIterator<E> {
return object : MutableIterator<E> {
private val parent = backingSet.iterator()
private var lastElement: Any? = Mark
override fun hasNext(): Boolean {
return parent.hasNext()
}
override fun next(): E {
return parent.next().also { lastElement = it }
}
override fun remove() {
parent.remove()
val lastElement = lastElement
if (lastElement !== Mark) {
this.lastElement = Mark
if (!isRemote) {
markDirty()
pushBacklog(lastElement as E) {
it.write(ChangesetAction.REMOVE.ordinal + 1)
codec.write(it, lastElement as E)
}
}
}
}
}
}
override fun remove(element: E): Boolean {
if (backingSet.remove(element)) {
if (!isRemote) {
markDirty()
pushBacklog(element) {
it.write(ChangesetAction.REMOVE.ordinal + 1)
codec.write(it, element)
}
}
return true
}
return false
}
override fun removeAll(elements: Collection<E>): Boolean {
var any = false
elements.forEach { any = remove(it) || any }
return any
}
override fun retainAll(elements: Collection<E>): Boolean {
var any = false
val iterator = iterator()
for (value in iterator) {
if (value !in elements) {
any = true
iterator.remove()
}
}
return any
}
override val size: Int
get() = backingSet.size
override fun contains(element: E): Boolean {
return element in backingSet
}
override fun containsAll(elements: Collection<E>): Boolean {
return backingSet.containsAll(elements)
}
override fun isEmpty(): Boolean {
return backingSet.isEmpty()
}
}
override fun write(stream: DataOutputStream, endpoint: Endpoint) {
val list = endpoint.getSetBacklog(this)
for (value in list) {
value.second.invoke(stream)
}
stream.write(0)
list.clear()
isDirty = false
}
override fun read(stream: DataInputStream) {
if (!isRemote) {
isRemote = true
forEachEndpoint { it.removeSetBacklog(this) }
}
isDirty = false
var action = stream.read()
val changeset = LinkedList<SetChangeset<E>>()
while (action != 0) {
if ((action - 1) !in ChangesetActionList.indices) {
throw IllegalArgumentException("Unknown changeset action with index ${action - 1}")
}
when (ChangesetActionList[action - 1]) {
ChangesetAction.CLEAR -> {
changeset.add(SetChangeset(ChangesetAction.CLEAR, null))
backingSet.clear()
}
ChangesetAction.ADD -> {
val read = codec.read(stream)
changeset.add(SetChangeset(ChangesetAction.ADD, read))
backingSet.add(read)
}
ChangesetAction.REMOVE -> {
val read = codec.read(stream)
changeset.add(SetChangeset(ChangesetAction.REMOVE, read))
backingSet.remove(read)
}
}
action = stream.read()
}
callback?.invoke(changeset)
}
}
inner class Map<K, V>(
private val keyCodec: IStreamCodec<K>,
private val valueCodec: IStreamCodec<V>,
@ -1545,17 +1804,19 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
}
private fun pushBacklog(key: Any?, value: (DataOutputStream) -> Unit) {
val pair = key to value
forEachEndpoint {
val list = it.getMapBacklog(this)
val iterator = list.listIterator()
for (pair in iterator) {
if (pair.first == key) {
for (e in iterator) {
if (e.first == key) {
iterator.remove()
}
}
list.addLast(key to value)
list.addLast(pair)
}
}
@ -1582,7 +1843,7 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
val valueCopy = valueCodec.copy(value)
pushBacklog(key) {
it.write(MapAction.ADD.ordinal + 1)
it.write(ChangesetAction.ADD.ordinal + 1)
keyCodec.write(it, key)
valueCodec.write(it, valueCopy)
}
@ -1631,7 +1892,7 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
val valueCopy = valueCodec.copy(value)
val action = { it: DataOutputStream ->
it.write(MapAction.ADD.ordinal + 1)
it.write(ChangesetAction.ADD.ordinal + 1)
keyCodec.write(it, key)
valueCodec.write(it, valueCopy)
}
@ -1660,7 +1921,7 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
val valueCopy = valueCodec.copy(value)
backlog.add(key to {
it.write(MapAction.ADD.ordinal + 1)
it.write(ChangesetAction.ADD.ordinal + 1)
keyCodec.write(it, key)
valueCodec.write(it, valueCopy)
})
@ -1707,7 +1968,7 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
pushBacklog(key) {
@Suppress("BlockingMethodInNonBlockingContext") // false positive
it.write(MapAction.ADD.ordinal + 1)
it.write(ChangesetAction.ADD.ordinal + 1)
keyCodec.write(it, key)
valueCodec.write(it, valueCopy)
}
@ -1735,7 +1996,7 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
pushBacklog(key) {
@Suppress("BlockingMethodInNonBlockingContext") // false positive
it.write(MapAction.REMOVE.ordinal + 1)
it.write(ChangesetAction.REMOVE.ordinal + 1)
keyCodec.write(it, keyCopy)
}
@ -1767,7 +2028,7 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
override fun read(stream: DataInputStream) {
if (!isRemote) {
isRemote = true
clearBacklog()
forEachEndpoint { it.removeMapBacklog(this) }
observingBackingMap?.clear()
}
@ -1777,27 +2038,27 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
var readAction = stream.read() - 1
while (readAction != -1) {
if (readAction >= MapActionList.size) {
if (readAction >= ChangesetActionList.size) {
throw IndexOutOfBoundsException("Unknown map action with ID $readAction")
}
when (MapActionList[readAction]) {
MapAction.CLEAR -> {
when (ChangesetActionList[readAction]) {
ChangesetAction.CLEAR -> {
backingMap.clear()
changeset.add(ClearMapChangeset)
}
MapAction.ADD -> {
ChangesetAction.ADD -> {
val key = keyCodec.read(stream)
val value = valueCodec.read(stream)
backingMap[key] = value
changeset.add(MapChangeset(MapAction.ADD, key, value))
changeset.add(MapChangeset(ChangesetAction.ADD, key, value))
}
MapAction.REMOVE -> {
ChangesetAction.REMOVE -> {
val key = keyCodec.read(stream)
backingMap.remove(key)
changeset.add(MapChangeset(MapAction.REMOVE, key, null))
changeset.add(MapChangeset(ChangesetAction.REMOVE, key, null))
}
}
@ -1860,9 +2121,11 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
return i
}
private object Mark
companion object {
private val ClearBacklogEntry = { stream: DataOutputStream -> stream.write(MapAction.CLEAR.ordinal + 1) }
private val MapActionList = MapAction.values()
private val ClearMapChangeset = MapChangeset(MapAction.CLEAR, null, null)
private val ClearBacklogEntry = { stream: DataOutputStream -> stream.write(ChangesetAction.CLEAR.ordinal + 1) }
private val ChangesetActionList = ChangesetAction.values()
private val ClearMapChangeset = MapChangeset(ChangesetAction.CLEAR, null, null)
}
}

View File

@ -1,23 +1,23 @@
package ru.dbotthepony.mc.otm.network.synchronizer
data class MapChangeset<out K, out V>(
val action: MapAction,
val action: ChangesetAction,
val key: K?,
val value: V?
) {
inline fun map(add: (K, V) -> Unit, remove: (K) -> Unit) {
when (action) {
MapAction.ADD -> add.invoke(key!!, value!!)
MapAction.REMOVE -> remove.invoke(key!!)
ChangesetAction.ADD -> add.invoke(key!!, value!!)
ChangesetAction.REMOVE -> remove.invoke(key!!)
else -> {}
}
}
inline fun map(add: (K, V) -> Unit, remove: (K) -> Unit, clear: () -> Unit) {
when (action) {
MapAction.CLEAR -> clear.invoke()
MapAction.ADD -> add.invoke(key!!, value!!)
MapAction.REMOVE -> remove.invoke(key!!)
ChangesetAction.CLEAR -> clear.invoke()
ChangesetAction.ADD -> add.invoke(key!!, value!!)
ChangesetAction.REMOVE -> remove.invoke(key!!)
}
}
}

View File

@ -0,0 +1,22 @@
package ru.dbotthepony.mc.otm.network.synchronizer
data class SetChangeset<out V>(
val action: ChangesetAction,
val value: V?
) {
inline fun map(add: (V) -> Unit, remove: (V) -> Unit) {
when (action) {
ChangesetAction.ADD -> add.invoke(value!!)
ChangesetAction.REMOVE -> remove.invoke(value!!)
else -> {}
}
}
inline fun map(add: (V) -> Unit, remove: (V) -> Unit, clear: () -> Unit) {
when (action) {
ChangesetAction.CLEAR -> clear.invoke()
ChangesetAction.ADD -> add.invoke(value!!)
ChangesetAction.REMOVE -> remove.invoke(value!!)
}
}
}