Update chunk map subscribers implementation

This commit is contained in:
DBotThePony 2023-08-17 13:58:01 +07:00
parent 90348d5789
commit 66c356bd41
Signed by: DBot
GPG Key ID: DCC23B5715498507
5 changed files with 106 additions and 206 deletions

View File

@ -15,7 +15,6 @@ import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.NotNull;
import ru.dbotthepony.mc.otm.android.AndroidResearchManager; import ru.dbotthepony.mc.otm.android.AndroidResearchManager;
import ru.dbotthepony.mc.otm.android.feature.EnderTeleporterFeature; import ru.dbotthepony.mc.otm.android.feature.EnderTeleporterFeature;
import ru.dbotthepony.mc.otm.block.entity.MatteryBlockEntity; import ru.dbotthepony.mc.otm.block.entity.MatteryBlockEntity;
@ -47,7 +46,6 @@ import ru.dbotthepony.mc.otm.config.MachinesConfig;
import ru.dbotthepony.mc.otm.config.ServerCompatConfig; import ru.dbotthepony.mc.otm.config.ServerCompatConfig;
import ru.dbotthepony.mc.otm.config.ServerConfig; import ru.dbotthepony.mc.otm.config.ServerConfig;
import ru.dbotthepony.mc.otm.config.ToolsConfig; import ru.dbotthepony.mc.otm.config.ToolsConfig;
import ru.dbotthepony.mc.otm.core.math.Decimal;
import ru.dbotthepony.mc.otm.item.tool.ExplosiveHammerItem; import ru.dbotthepony.mc.otm.item.tool.ExplosiveHammerItem;
import ru.dbotthepony.mc.otm.item.armor.TritaniumArmorItem; import ru.dbotthepony.mc.otm.item.armor.TritaniumArmorItem;
import ru.dbotthepony.mc.otm.item.QuantumBatteryItem; import ru.dbotthepony.mc.otm.item.QuantumBatteryItem;
@ -56,14 +54,12 @@ import ru.dbotthepony.mc.otm.item.PortableCondensationDriveItem;
import ru.dbotthepony.mc.otm.matter.MatterManager; import ru.dbotthepony.mc.otm.matter.MatterManager;
import ru.dbotthepony.mc.otm.network.*; import ru.dbotthepony.mc.otm.network.*;
import ru.dbotthepony.mc.otm.registry.*; import ru.dbotthepony.mc.otm.registry.*;
import ru.dbotthepony.mc.otm.storage.*;
import ru.dbotthepony.mc.otm.triggers.KillAsAndroidTrigger; import ru.dbotthepony.mc.otm.triggers.KillAsAndroidTrigger;
import top.theillusivec4.curios.api.CuriosApi; import top.theillusivec4.curios.api.CuriosApi;
import static net.minecraftforge.common.MinecraftForge.EVENT_BUS; import static net.minecraftforge.common.MinecraftForge.EVENT_BUS;
import javax.annotation.ParametersAreNonnullByDefault; import javax.annotation.ParametersAreNonnullByDefault;
import java.util.Objects;
// The value here should match an entry in the META-INF/mods.toml file // The value here should match an entry in the META-INF/mods.toml file
@Mod(OverdriveThatMatters.MOD_ID) @Mod(OverdriveThatMatters.MOD_ID)
@ -147,7 +143,7 @@ public final class OverdriveThatMatters {
EVENT_BUS.addListener(EventPriority.HIGHEST, GlobalEventHandlerKt::onServerStopped); EVENT_BUS.addListener(EventPriority.HIGHEST, GlobalEventHandlerKt::onServerStopped);
EVENT_BUS.addListener(EventPriority.HIGHEST, GlobalEventHandlerKt::onServerStopping); EVENT_BUS.addListener(EventPriority.HIGHEST, GlobalEventHandlerKt::onServerStopping);
EVENT_BUS.addListener(EventPriority.HIGHEST, GlobalEventHandlerKt::onServerStarting); EVENT_BUS.addListener(EventPriority.HIGHEST, GlobalEventHandlerKt::onServerStarting);
EVENT_BUS.addListener(EventPriority.LOWEST, GlobalEventHandlerKt::onWorldTick); EVENT_BUS.addListener(EventPriority.LOWEST, GlobalEventHandlerKt::onLevelTick);
EVENT_BUS.addListener(EventPriority.LOWEST, GlobalEventHandlerKt::onServerTick); EVENT_BUS.addListener(EventPriority.LOWEST, GlobalEventHandlerKt::onServerTick);
EVENT_BUS.addListener(EventPriority.NORMAL, MatteryPlayerCapability.Companion::onPlayerTick); EVENT_BUS.addListener(EventPriority.NORMAL, MatteryPlayerCapability.Companion::onPlayerTick);
@ -180,7 +176,6 @@ public final class OverdriveThatMatters {
EVENT_BUS.addListener(EventPriority.NORMAL, MatteryBlockEntity.Companion::onWatch); EVENT_BUS.addListener(EventPriority.NORMAL, MatteryBlockEntity.Companion::onWatch);
EVENT_BUS.addListener(EventPriority.NORMAL, MatteryBlockEntity.Companion::onForget); EVENT_BUS.addListener(EventPriority.NORMAL, MatteryBlockEntity.Companion::onForget);
EVENT_BUS.addListener(EventPriority.NORMAL, MatteryBlockEntity.Companion::playerDisconnected); EVENT_BUS.addListener(EventPriority.NORMAL, MatteryBlockEntity.Companion::playerDisconnected);
EVENT_BUS.addListener(EventPriority.LOWEST, MatteryBlockEntity.Companion::postLevelTick);
EVENT_BUS.addListener(EventPriority.LOWEST, KillAsAndroidTrigger.INSTANCE::onKill); EVENT_BUS.addListener(EventPriority.LOWEST, KillAsAndroidTrigger.INSTANCE::onKill);

View File

@ -15,6 +15,7 @@ import net.minecraftforge.event.server.ServerStoppedEvent
import net.minecraftforge.event.server.ServerStoppingEvent import net.minecraftforge.event.server.ServerStoppingEvent
import net.minecraftforge.fml.loading.FMLLoader import net.minecraftforge.fml.loading.FMLLoader
import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.LogManager
import ru.dbotthepony.mc.otm.block.entity.MatteryBlockEntity
import ru.dbotthepony.mc.otm.capability.AbstractProfiledStorage import ru.dbotthepony.mc.otm.capability.AbstractProfiledStorage
import ru.dbotthepony.mc.otm.client.minecraft import ru.dbotthepony.mc.otm.client.minecraft
import ru.dbotthepony.mc.otm.core.collect.WeakHashSet import ru.dbotthepony.mc.otm.core.collect.WeakHashSet
@ -165,7 +166,7 @@ fun onServerTick(event: ServerTickEvent) {
} }
} }
fun onWorldTick(event: LevelTickEvent) { fun onLevelTick(event: LevelTickEvent) {
if (event.phase === TickEvent.Phase.START) { if (event.phase === TickEvent.Phase.START) {
preWorldTick[event.level]?.tick() preWorldTick[event.level]?.tick()
@ -176,6 +177,7 @@ fun onWorldTick(event: LevelTickEvent) {
} }
} else { } else {
postWorldTick[event.level]?.tick() postWorldTick[event.level]?.tick()
MatteryBlockEntity.postLevelTick(event)
} }
} }

View File

@ -5,6 +5,7 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap
import it.unimi.dsi.fastutil.longs.Long2ObjectFunction import it.unimi.dsi.fastutil.longs.Long2ObjectFunction
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap
import it.unimi.dsi.fastutil.objects.ObjectArraySet import it.unimi.dsi.fastutil.objects.ObjectArraySet
import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap
import mekanism.api.energy.IStrictEnergyHandler import mekanism.api.energy.IStrictEnergyHandler
import net.minecraft.core.BlockPos import net.minecraft.core.BlockPos
@ -31,7 +32,6 @@ import net.minecraftforge.common.capabilities.ForgeCapabilities
import net.minecraftforge.common.util.INBTSerializable import net.minecraftforge.common.util.INBTSerializable
import net.minecraftforge.common.util.LazyOptional import net.minecraftforge.common.util.LazyOptional
import net.minecraftforge.energy.IEnergyStorage import net.minecraftforge.energy.IEnergyStorage
import net.minecraftforge.event.TickEvent
import net.minecraftforge.event.TickEvent.LevelTickEvent import net.minecraftforge.event.TickEvent.LevelTickEvent
import net.minecraftforge.event.entity.player.PlayerEvent import net.minecraftforge.event.entity.player.PlayerEvent
import net.minecraftforge.event.level.ChunkWatchEvent import net.minecraftforge.event.level.ChunkWatchEvent
@ -46,7 +46,6 @@ import ru.dbotthepony.mc.otm.compat.mekanism.Mattery2MekanismEnergyWrapper
import ru.dbotthepony.mc.otm.compat.mekanism.Mekanism2MatteryEnergyWrapper import ru.dbotthepony.mc.otm.compat.mekanism.Mekanism2MatteryEnergyWrapper
import ru.dbotthepony.mc.otm.core.ISubscriptable import ru.dbotthepony.mc.otm.core.ISubscriptable
import ru.dbotthepony.mc.otm.core.collect.WeakHashSet import ru.dbotthepony.mc.otm.core.collect.WeakHashSet
import ru.dbotthepony.mc.otm.core.forValidRefs
import ru.dbotthepony.mc.otm.core.get import ru.dbotthepony.mc.otm.core.get
import ru.dbotthepony.mc.otm.core.ifPresentK import ru.dbotthepony.mc.otm.core.ifPresentK
import ru.dbotthepony.mc.otm.core.immutableList import ru.dbotthepony.mc.otm.core.immutableList
@ -69,7 +68,6 @@ import java.util.function.Predicate
import java.util.function.Supplier import java.util.function.Supplier
import java.util.stream.Stream import java.util.stream.Stream
import kotlin.NoSuchElementException import kotlin.NoSuchElementException
import kotlin.collections.ArrayList
import kotlin.properties.ReadWriteProperty import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty import kotlin.reflect.KProperty
import kotlin.reflect.KProperty0 import kotlin.reflect.KProperty0
@ -120,11 +118,10 @@ abstract class MatteryBlockEntity(p_155228_: BlockEntityType<*>, p_155229_: Bloc
*/ */
protected val savetablesLevel = Savetables() protected val savetablesLevel = Savetables()
private var tickedOnce = false
open fun tick() { open fun tick() {
tickList.tick() tickList.tick()
tickedOnce = true
if (synchronizer.isNotEmpty)
synchronizeToPlayers(false) synchronizeToPlayers(false)
} }
@ -537,10 +534,12 @@ abstract class MatteryBlockEntity(p_155228_: BlockEntityType<*>, p_155229_: Bloc
} }
val synchronizer = FieldSynchronizer { val synchronizer = FieldSynchronizer {
if (isSynchronizing || tickedOnce) if (isSynchronizing || tickList.ticks != 0)
return@FieldSynchronizer return@FieldSynchronizer
if (!isRemoved && level?.isClientSide == false && (_subCache == null || (_subCache ?: throw ConcurrentModificationException()).players.isNotEmpty())) { if (isRemoved) markSynchronizerClean()
if (level?.isClientSide == false && (_subCache == null || (_subCache ?: throw ConcurrentModificationException()).players.isNotEmpty())) {
ru.dbotthepony.mc.otm.onceServer { ru.dbotthepony.mc.otm.onceServer {
synchronizeToPlayers(true) synchronizeToPlayers(true)
} }
@ -599,6 +598,8 @@ abstract class MatteryBlockEntity(p_155228_: BlockEntityType<*>, p_155229_: Bloc
} }
private var _subCache: ChunkSubscribers? = null private var _subCache: ChunkSubscribers? = null
private val subscription get() = _subCache ?: subscribe()
private var playerListUpdated = false
private fun unsubscribe() { private fun unsubscribe() {
val subCache = _subCache ?: return val subCache = _subCache ?: return
@ -611,7 +612,7 @@ abstract class MatteryBlockEntity(p_155228_: BlockEntityType<*>, p_155229_: Bloc
} }
} }
lastSubscriptionChangeset = -1 playerListUpdated = false
} }
private fun subscribe(): ChunkSubscribers { private fun subscribe(): ChunkSubscribers {
@ -621,41 +622,24 @@ abstract class MatteryBlockEntity(p_155228_: BlockEntityType<*>, p_155229_: Bloc
val subs = playerMap val subs = playerMap
.computeIfAbsent(level) { Long2ObjectOpenHashMap() } .computeIfAbsent(level) { Long2ObjectOpenHashMap() }
.computeIfAbsent(ChunkPos(blockPos).toLong(), Long2ObjectFunction { ChunkSubscribers(level = WeakReference(level), chunkPos = ChunkPos(blockPos).toLong()) }) .computeIfAbsent(ChunkPos(blockPos).toLong(), Long2ObjectFunction { ChunkSubscribers(level, ChunkPos(blockPos).toLong()) })
subs.subscribe(this) subs.subscribe(this)
_subCache = subs _subCache = subs
playerListUpdated = true
return subs return subs
} }
private val subscription get() = _subCache ?: subscribe() private fun synchronizeToPlayers(calledBySynchronizer: Boolean) {
private var lastSubscriptionChangeset = -1
fun synchronizeToPlayers() {
synchronizeToPlayers(false)
}
protected fun synchronizeToPlayers(calledBySynchronizer: Boolean) {
isSynchronizing = true isSynchronizing = true
try { try {
if (synchronizer.isEmpty || isRemoved) {
if (calledBySynchronizer) synchronizer.markClean()
return
}
check(level is ServerLevel) { "Invalid realm or Level is null" } check(level is ServerLevel) { "Invalid realm or Level is null" }
synchronizer.observe() synchronizer.observe()
val subscription = subscription val subscription = subscription
if (subscription.players.isEmpty() || lastSubscriptionChangeset == subscription.changeset && !synchronizer.hasChanges) { if (subscription.players.isNotEmpty() && (playerListUpdated || synchronizer.isDirty)) {
if (calledBySynchronizer) synchronizer.markClean() playerListUpdated = false
return
}
lastSubscriptionChangeset = subscription.changeset
for (player in subscription.players) { for (player in subscription.players) {
val payload = synchronizer.computeEndpointFor(player).collectNetworkPayload() val payload = synchronizer.computeEndpointFor(player).collectNetworkPayload()
@ -666,126 +650,78 @@ abstract class MatteryBlockEntity(p_155228_: BlockEntityType<*>, p_155229_: Bloc
} }
synchronizer.markClean() synchronizer.markClean()
} else if (calledBySynchronizer) {
synchronizer.markClean()
}
} finally { } finally {
isSynchronizing = false isSynchronizing = false
} }
} }
private data class ChunkSubscribers( private class ChunkSubscribers(level: ServerLevel, val chunkPos: Long) {
val blockEntities: ArrayList<WeakReference<MatteryBlockEntity>> = ArrayList(0), val level = WeakReference(level)
val players: ArrayList<ServerPlayer> = ArrayList(1), val blockEntities = WeakHashSet<MatteryBlockEntity>(linked = true, initialCapacity = 0)
val observingBlockEntities: ArrayList<WeakReference<MatteryBlockEntity>> = ArrayList(0), val players = ObjectLinkedOpenHashSet<ServerPlayer>(0)
val level: WeakReference<ServerLevel>, val blockEntitiesWithObservers = WeakHashSet<MatteryBlockEntity>(linked = true, initialCapacity = 0)
val chunkPos: Long,
var changeset: Int = 0
) {
fun cleanUpBlocks() {
val listIterator = blockEntities.listIterator()
for (block in listIterator) { operator fun component1() = blockEntities
if (block.get() == null) { operator fun component2() = players
listIterator.remove()
}
}
}
val hasObservers: Boolean get() { val hasObservers: Boolean get() {
val listIterator = blockEntities.listIterator() return blockEntities.any { it.synchronizer.hasObservers }
for (value in listIterator) {
val ref = value.get()
if (ref == null) {
listIterator.remove()
} else if (ref.synchronizer.hasObservers) {
return true
} }
fun subscribe(player: ServerPlayer) {
if (players.add(player)) {
blockEntities.forEach { it.playerListUpdated = true }
onceServer(10) {
if (!player.hasDisconnected() && player in players) {
blockEntities.forEach {
it.synchronizeToPlayers(false)
}
}
}
}
}
fun unsubscribe(player: ServerPlayer): Boolean {
if (players.remove(player)) {
blockEntities.forEach {
it.synchronizer.removeEndpointFor(player)
}
return true
} }
return false return false
} }
fun subscribe(blockEntity: MatteryBlockEntity) { fun subscribe(blockEntity: MatteryBlockEntity) {
val iterator = blockEntities.listIterator() if (!blockEntities.add(blockEntity)) return
for (value in iterator) {
val ref = value.get()
if (ref === blockEntity) {
return
} else if (ref == null) {
iterator.remove()
}
}
val bref = WeakReference(blockEntity)
blockEntities.add(bref)
changeset++
onceServer { onceServer {
blockEntity.synchronizeToPlayers() blockEntity.synchronizeToPlayers(false)
} }
if (blockEntity.synchronizer.hasObservers) { if (blockEntity.synchronizer.hasObservers && blockEntity.tickList.ticks == 0) {
observingBlockEntities.add(bref) blockEntitiesWithObservers.add(blockEntity)
val listing = tickingMap.computeIfAbsent(level.get() ?: throw NullPointerException("Level got GCd!")) { ArrayList(1) }
val tickerIterator = listing.listIterator()
for (value in tickerIterator) { tickingMap
val ref = value.get() .computeIfAbsent(level.get() ?: throw NullPointerException("Level got GCd!")) { WeakHashSet(linked = true, initialCapacity = 2) }
.add(this)
if (ref === this) {
return
} else if (ref == null) {
tickerIterator.remove()
}
}
listing.add(WeakReference(this))
} }
} }
fun unsubscribe(blockEntity: MatteryBlockEntity): Boolean { fun unsubscribe(blockEntity: MatteryBlockEntity): Boolean {
val listIterator = blockEntities.listIterator() blockEntities.remove(blockEntity)
blockEntitiesWithObservers.remove(blockEntity)
for (value in listIterator) { return players.isEmpty() && blockEntities.isEmpty()
if (value.get() === blockEntity) {
listIterator.remove()
changeset++
break
}
}
if (blockEntity.synchronizer.hasObservers) {
val tickerIterator = observingBlockEntities.listIterator()
for (value in tickerIterator) {
val ref = value.get()
if (ref === blockEntity) {
tickerIterator.remove()
break
} else if (ref == null) {
tickerIterator.remove()
}
}
}
if (players.isNotEmpty()) {
return false
}
cleanUpBlocks()
return blockEntities.isEmpty()
} }
val isEmpty: Boolean get() { val isEmpty: Boolean get() {
if (players.isNotEmpty()) { return players.isEmpty() && blockEntities.isEmpty()
return false
}
cleanUpBlocks()
return blockEntities.isEmpty()
} }
} }
@ -813,7 +749,7 @@ abstract class MatteryBlockEntity(p_155228_: BlockEntityType<*>, p_155229_: Bloc
const val LOOT_TABLE_SEED_KEY = RandomizableContainerBlockEntity.LOOT_TABLE_SEED_TAG const val LOOT_TABLE_SEED_KEY = RandomizableContainerBlockEntity.LOOT_TABLE_SEED_TAG
private val playerMap = WeakHashMap<ServerLevel, Long2ObjectOpenHashMap<ChunkSubscribers>>() private val playerMap = WeakHashMap<ServerLevel, Long2ObjectOpenHashMap<ChunkSubscribers>>()
private val tickingMap = WeakHashMap<ServerLevel, ArrayList<WeakReference<ChunkSubscribers>>>() private val tickingMap = WeakHashMap<ServerLevel, WeakHashSet<ChunkSubscribers>>()
private val vec2Dir = Int2ObjectOpenHashMap<Direction>() private val vec2Dir = Int2ObjectOpenHashMap<Direction>()
@ -860,7 +796,7 @@ abstract class MatteryBlockEntity(p_155228_: BlockEntityType<*>, p_155229_: Bloc
} }
init { init {
for (dir in Direction.values()) { for (dir in Direction.entries) {
vec2Dir[vecKey(dir.normal)] = dir vec2Dir[vecKey(dir.normal)] = dir
} }
} }
@ -877,87 +813,47 @@ abstract class MatteryBlockEntity(p_155228_: BlockEntityType<*>, p_155229_: Bloc
} }
fun postLevelTick(event: LevelTickEvent) { fun postLevelTick(event: LevelTickEvent) {
if (event.phase == TickEvent.Phase.END) {
val level = event.level as? ServerLevel ?: return val level = event.level as? ServerLevel ?: return
val listing = tickingMap[level] ?: return val ticking = tickingMap[level] ?: return
val listIterator = listing.listIterator()
var hitAnyObservers = false
for (value in listIterator) { ticking.removeIf {
val ref = value.get() val shouldRemove = it.blockEntitiesWithObservers.isEmpty()
if (ref == null) { if (!shouldRemove && it.players.isNotEmpty()) {
listIterator.remove() it.blockEntitiesWithObservers.forEach {
} else if (ref.players.isNotEmpty()) { it.synchronizeToPlayers(false)
var hitObservers = false
ref.observingBlockEntities.forValidRefs {
hitObservers = true
hitAnyObservers = true
it.synchronizeToPlayers()
}
if (!hitObservers) {
listIterator.remove()
} }
} }
shouldRemove
} }
if (!hitAnyObservers) { if (ticking.isEmpty()) {
tickingMap.remove(level) tickingMap.remove(level)
} }
} }
}
fun onWatch(event: ChunkWatchEvent.Watch) { fun onWatch(event: ChunkWatchEvent.Watch) {
playerMap playerMap
.computeIfAbsent(event.level) { Long2ObjectOpenHashMap() } .computeIfAbsent(event.level) { Long2ObjectOpenHashMap() }
.computeIfAbsent(event.pos.toLong(), Long2ObjectFunction { ChunkSubscribers(level = WeakReference(event.level), chunkPos = event.pos.toLong()) }) .computeIfAbsent(event.pos.toLong(), Long2ObjectFunction { ChunkSubscribers(event.level, it) })
.let { .subscribe(event.player)
val (blocks, players) = it
if (event.player !in players) {
players.add(event.player)
it.changeset++
onceServer(10) {
if (!event.player.hasDisconnected() && event.player in players) {
blocks.forValidRefs {
it.synchronizeToPlayers(false)
}
}
}
}
}
} }
fun onForget(event: ChunkWatchEvent.UnWatch) { fun onForget(event: ChunkWatchEvent.UnWatch) {
val subs = playerMap.get(event.level)?.get(event.pos.toLong()) ?: return val levelMap = playerMap[event.level] ?: return
val subs = levelMap.get(event.pos.toLong()) ?: return
if (subs.players.remove(event.player)) { if (subs.unsubscribe(event.player) && subs.isEmpty) {
if (subs.isEmpty) { levelMap.remove(event.pos.toLong())
playerMap.get(event.level)?.remove(event.pos.toLong())
} else {
subs.changeset++
subs.blockEntities.forValidRefs {
it.synchronizer.removeEndpointFor(event.player)
}
}
} }
} }
fun playerDisconnected(event: PlayerEvent.PlayerLoggedOutEvent) { fun playerDisconnected(event: PlayerEvent.PlayerLoggedOutEvent) {
for (tree in playerMap.values) { for (tree in playerMap.values) {
val iterator = tree.iterator() tree.values.removeIf {
it.unsubscribe(event.entity as ServerPlayer)
for (entry in iterator) { it.isEmpty
if (entry.value.players.remove(event.entity)) {
entry.value.changeset++
if (entry.value.isEmpty) {
iterator.remove()
}
}
} }
} }
} }

View File

@ -1,12 +1,14 @@
package ru.dbotthepony.mc.otm.core.collect package ru.dbotthepony.mc.otm.core.collect
import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenCustomHashSet
import it.unimi.dsi.fastutil.objects.ObjectOpenCustomHashSet import it.unimi.dsi.fastutil.objects.ObjectOpenCustomHashSet
import it.unimi.dsi.fastutil.objects.ObjectSet
import ru.dbotthepony.mc.otm.core.util.HashedWeakReference import ru.dbotthepony.mc.otm.core.util.HashedWeakReference
import java.lang.ref.ReferenceQueue import java.lang.ref.ReferenceQueue
class WeakHashSet<E : Any> : MutableSet<E> { class WeakHashSet<E : Any>(initialCapacity: Int = 16, loadFactor: Float = 0.75f, linked: Boolean = false) : MutableSet<E> {
private val queue = ReferenceQueue<E>() private val queue = ReferenceQueue<E>()
private val backing = ObjectOpenCustomHashSet<Any>(ReferenceHashStrategy) private val backing: ObjectSet<Any> = if (linked) ObjectLinkedOpenCustomHashSet(initialCapacity, loadFactor, ReferenceHashStrategy) else ObjectOpenCustomHashSet(initialCapacity, loadFactor, ReferenceHashStrategy)
private fun purge() { private fun purge() {
var next = queue.poll() var next = queue.poll()

View File

@ -66,10 +66,13 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
private var nextFieldID = 0 private var nextFieldID = 0
val hasObservers: Boolean get() = observers.isNotEmpty() val hasObservers: Boolean get() = observers.isNotEmpty()
val isEmpty: Boolean get() = fields.isEmpty()
val isNotEmpty: Boolean get() = fields.isNotEmpty()
var hasChanges: Boolean = false var isEmpty: Boolean = true
private set
val isNotEmpty: Boolean get() = !isEmpty
var isDirty: Boolean = false
private set(value) { private set(value) {
if (value != field) { if (value != field) {
field = value field = value
@ -85,7 +88,7 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
} }
fun markClean() { fun markClean() {
hasChanges = false isDirty = false
} }
fun computedByte(getter: () -> Byte) = ComputedField(getter, ByteValueCodec) fun computedByte(getter: () -> Byte) = ComputedField(getter, ByteValueCodec)
@ -301,7 +304,7 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
private var nextEndpointsCleanup = secondTime private var nextEndpointsCleanup = secondTime
private fun notifyEndpoints(dirtyField: AbstractField<*>) { private fun notifyEndpoints(dirtyField: AbstractField<*>) {
hasChanges = true isDirty = true
forEachEndpoint { forEachEndpoint {
it.addDirtyField(dirtyField) it.addDirtyField(dirtyField)
@ -480,6 +483,7 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
} else { } else {
fields.add(this) fields.add(this)
id = fields.size id = fields.size
isEmpty = false
} }
} }
@ -496,6 +500,7 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
freeSlots++ freeSlots++
fields[id - 1] = null fields[id - 1] = null
observers.remove(this) observers.remove(this)
isEmpty = fields.all { it == null }
while (fields[fields.size - 1] == null) { while (fields[fields.size - 1] == null) {
fields.removeAt(fields.size - 1) fields.removeAt(fields.size - 1)
@ -1994,7 +1999,7 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
} }
lmarkDirty() lmarkDirty()
hasChanges = true this@FieldSynchronizer.isDirty = true
} }
override fun onValueAdded(key: K, value: V) { override fun onValueAdded(key: K, value: V) {