Make synchronized block entity use chunk grid to blazingly fast determine potential players to network stuff to
This commit is contained in:
parent
77aed4871f
commit
177a380bf3
@ -18,6 +18,7 @@ import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import ru.dbotthepony.mc.otm.android.AndroidResearchManager;
|
||||
import ru.dbotthepony.mc.otm.android.feature.NanobotsArmorFeature;
|
||||
import ru.dbotthepony.mc.otm.block.entity.SynchronizedBlockEntity;
|
||||
import ru.dbotthepony.mc.otm.block.entity.blackhole.ExplosionQueue;
|
||||
import ru.dbotthepony.mc.otm.capability.MatteryCapability;
|
||||
import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability;
|
||||
@ -135,6 +136,14 @@ public final class OverdriveThatMatters {
|
||||
EVENT_BUS.addListener(EventPriority.NORMAL, AndroidResearchManager.INSTANCE::reloadEvent);
|
||||
EVENT_BUS.addListener(EventPriority.NORMAL, AndroidResearchManager.INSTANCE::syncEvent);
|
||||
|
||||
EVENT_BUS.addListener(EventPriority.NORMAL, SynchronizedBlockEntity.Companion::onServerStopping);
|
||||
EVENT_BUS.addListener(EventPriority.NORMAL, SynchronizedBlockEntity.Companion::onServerStarting);
|
||||
EVENT_BUS.addListener(EventPriority.NORMAL, SynchronizedBlockEntity.Companion::onWatch);
|
||||
EVENT_BUS.addListener(EventPriority.NORMAL, SynchronizedBlockEntity.Companion::onForget);
|
||||
EVENT_BUS.addListener(EventPriority.NORMAL, SynchronizedBlockEntity.Companion::playerDisconnected);
|
||||
EVENT_BUS.addListener(EventPriority.NORMAL, SynchronizedBlockEntity.Companion::playerChangeDimensions);
|
||||
EVENT_BUS.addListener(EventPriority.NORMAL, SynchronizedBlockEntity.Companion::playerCloneEvent);
|
||||
|
||||
MatteryPlayerNetworkChannel.INSTANCE.register();
|
||||
MenuNetworkChannel.INSTANCE.register();
|
||||
WeaponNetworkChannel.INSTANCE.register();
|
||||
|
@ -1,9 +1,24 @@
|
||||
package ru.dbotthepony.mc.otm.block.entity
|
||||
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectAVLTreeMap
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectFunction
|
||||
import it.unimi.dsi.fastutil.objects.Object2ObjectAVLTreeMap
|
||||
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap
|
||||
import net.minecraft.core.BlockPos
|
||||
import net.minecraft.server.level.ServerLevel
|
||||
import net.minecraft.server.level.ServerPlayer
|
||||
import net.minecraft.world.level.ChunkPos
|
||||
import net.minecraft.world.level.Level
|
||||
import net.minecraft.world.level.block.entity.BlockEntity
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType
|
||||
import net.minecraft.world.level.block.state.BlockState
|
||||
import net.minecraftforge.event.TickEvent
|
||||
import net.minecraftforge.event.entity.player.PlayerEvent
|
||||
import net.minecraftforge.event.entity.player.PlayerEvent.PlayerChangedDimensionEvent
|
||||
import net.minecraftforge.event.level.ChunkWatchEvent
|
||||
import net.minecraftforge.event.server.ServerStartingEvent
|
||||
import net.minecraftforge.event.server.ServerStoppingEvent
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import ru.dbotthepony.mc.otm.core.component1
|
||||
import ru.dbotthepony.mc.otm.core.component2
|
||||
import ru.dbotthepony.mc.otm.core.component3
|
||||
@ -11,6 +26,58 @@ import ru.dbotthepony.mc.otm.core.position
|
||||
import ru.dbotthepony.mc.otm.network.BlockEntitySyncPacket
|
||||
import ru.dbotthepony.mc.otm.network.FieldSynchronizer
|
||||
import ru.dbotthepony.mc.otm.network.WorldNetworkChannel
|
||||
import java.lang.ref.WeakReference
|
||||
import java.util.LinkedList
|
||||
import java.util.WeakHashMap
|
||||
|
||||
private data class Subscribers(val blockEntities: LinkedList<WeakReference<BlockEntity>> = LinkedList(), val players: LinkedList<ServerPlayer> = LinkedList(), val level: WeakReference<Level>, val chunkPos: Long) {
|
||||
fun cleanUpBlocks() {
|
||||
val listIterator = blockEntities.listIterator()
|
||||
|
||||
for (block in listIterator) {
|
||||
if (block.get() == null) {
|
||||
listIterator.remove()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun subscribe(blockEntity: BlockEntity) {
|
||||
for (value in blockEntities) {
|
||||
if (value.get() === blockEntity) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
blockEntities.add(WeakReference(blockEntity))
|
||||
}
|
||||
|
||||
fun unsubscribe(blockEntity: BlockEntity): Boolean {
|
||||
val listIterator = blockEntities.listIterator()
|
||||
|
||||
for (value in listIterator) {
|
||||
if (value.get() === blockEntity) {
|
||||
listIterator.remove()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (players.isNotEmpty()) {
|
||||
return false
|
||||
}
|
||||
|
||||
cleanUpBlocks()
|
||||
return blockEntities.isEmpty()
|
||||
}
|
||||
|
||||
val isEmpty: Boolean get() {
|
||||
if (!players.isEmpty()) {
|
||||
return false
|
||||
}
|
||||
|
||||
cleanUpBlocks()
|
||||
return blockEntities.isEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
abstract class SynchronizedBlockEntity(p_155228_: BlockEntityType<*>, p_155229_: BlockPos, p_155230_: BlockState) : BlockEntity(p_155228_, p_155229_, p_155230_) {
|
||||
val synchronizer = FieldSynchronizer()
|
||||
@ -21,17 +88,46 @@ abstract class SynchronizedBlockEntity(p_155228_: BlockEntityType<*>, p_155229_:
|
||||
|
||||
open val synchronizationRadius: Double = 32.0
|
||||
|
||||
// TODO: Optimize, if required
|
||||
override fun setLevel(p_155231_: Level) {
|
||||
super.setLevel(p_155231_)
|
||||
unsubscribe()
|
||||
_subCache = null
|
||||
}
|
||||
|
||||
private var _subCache: Subscribers? = null
|
||||
|
||||
private fun unsubscribe() {
|
||||
val subCache = _subCache ?: return
|
||||
|
||||
if (subCache.unsubscribe(this)) {
|
||||
val level = subCache.level.get()
|
||||
|
||||
if (level != null) {
|
||||
playerMap.get(level)?.remove(subCache.chunkPos)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun subscribe(): LinkedList<ServerPlayer> {
|
||||
unsubscribe()
|
||||
val subs = playerMap.computeIfAbsent(level) { Long2ObjectAVLTreeMap() }.computeIfAbsent(ChunkPos(blockPos).toLong(), Long2ObjectFunction { Subscribers(level = WeakReference(level), chunkPos = ChunkPos(blockPos).toLong()) })
|
||||
subs.subscribe(this)
|
||||
_subCache = subs
|
||||
return subs.players
|
||||
}
|
||||
|
||||
private val playerSubscribers get() = _subCache?.players ?: subscribe()
|
||||
|
||||
fun synchronizeToPlayers() {
|
||||
if (synchronizationRadius <= 0.0 || synchronizer.isEmpty) {
|
||||
return
|
||||
}
|
||||
|
||||
check(level is ServerLevel) { "Invalid realm or Level is null" }
|
||||
|
||||
synchronizer.observe()
|
||||
|
||||
val server = level?.server ?: return
|
||||
|
||||
if (server.playerCount <= 0) {
|
||||
if (playerSubscribers.isEmpty()) {
|
||||
return
|
||||
}
|
||||
|
||||
@ -42,8 +138,8 @@ abstract class SynchronizedBlockEntity(p_155228_: BlockEntityType<*>, p_155229_:
|
||||
|
||||
val double = synchronizationRadius * synchronizationRadius
|
||||
|
||||
server.playerList.players.stream().filter {
|
||||
if (!it.isAlive || it.level != level) {
|
||||
playerSubscribers.stream().filter {
|
||||
if (!it.isAlive) {
|
||||
return@filter false
|
||||
}
|
||||
|
||||
@ -61,4 +157,82 @@ abstract class SynchronizedBlockEntity(p_155228_: BlockEntityType<*>, p_155229_:
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Why track player-tracked chunks?
|
||||
*
|
||||
* because minecraft itself doesn't track them, well,
|
||||
* in "section partitioning" way.
|
||||
*
|
||||
* just look at [net.minecraft.server.level.PlayerMap]
|
||||
*
|
||||
* the [net.minecraft.server.level.PlayerMap.getPlayers] straight ignores chunk position
|
||||
* and just fetches all players
|
||||
*
|
||||
* even if they did not ignore that argument, you still have to fetch player *list* though
|
||||
* [net.minecraft.server.level.ChunkMap.getPlayers], which is not something we want
|
||||
*/
|
||||
companion object {
|
||||
private val LOGGER = LogManager.getLogger()
|
||||
private val playerMap = WeakHashMap<Level, Long2ObjectAVLTreeMap<Subscribers>>()
|
||||
|
||||
fun onServerStopping(event: ServerStoppingEvent) {
|
||||
playerMap.clear()
|
||||
}
|
||||
|
||||
fun onServerStarting(event: ServerStartingEvent) {
|
||||
playerMap.clear()
|
||||
}
|
||||
|
||||
fun onWatch(event: ChunkWatchEvent.Watch) {
|
||||
playerMap.computeIfAbsent(event.level) { Long2ObjectAVLTreeMap() }.computeIfAbsent(event.pos.toLong(), Long2ObjectFunction { Subscribers(level = WeakReference(event.level), chunkPos = event.pos.toLong()) }).let {
|
||||
val (blocks, players) = it
|
||||
|
||||
if (event.player in players) {
|
||||
LOGGER.error("Player ${event.player} started 'watching' chunk ${event.chunk} more than once.")
|
||||
} else {
|
||||
players.add(event.player)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onForget(event: ChunkWatchEvent.UnWatch) {
|
||||
val subs = playerMap.get(event.level)?.get(event.pos.toLong()) ?: return LOGGER.error("Player ${event.player} stopped 'watching' chunk at ${event.pos} before starting to watch it/stopped 'watching' more than once.")
|
||||
|
||||
if (subs.players.remove(event.player)) {
|
||||
if (subs.isEmpty) {
|
||||
playerMap.get(event.level)?.remove(event.pos.toLong())
|
||||
}
|
||||
} else {
|
||||
LOGGER.error("Player ${event.player} stopped 'watching' chunk at ${event.pos} before starting to watch it/stopped 'watching' more than once.")
|
||||
}
|
||||
}
|
||||
|
||||
fun playerDisconnected(event: PlayerEvent.PlayerLoggedOutEvent) {
|
||||
for (tree in playerMap.values) {
|
||||
val iterator = tree.iterator()
|
||||
|
||||
for (entry in iterator) {
|
||||
val listIterator = entry.value.players.listIterator()
|
||||
|
||||
for (value in listIterator) {
|
||||
if (value === event.entity) {
|
||||
listIterator.remove()
|
||||
}
|
||||
}
|
||||
|
||||
if (entry.value.isEmpty) {
|
||||
iterator.remove()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun playerChangeDimensions(event: PlayerChangedDimensionEvent) {
|
||||
|
||||
}
|
||||
|
||||
fun playerCloneEvent(event: PlayerEvent.Clone) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user