Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import com.gtocore.api.gui.ktflexible.progressBar
import com.gtocore.api.gui.ktflexible.textBlock
import com.gtocore.common.machine.multiblock.part.ae.MEPatternContentSortMachine.MODE.FLUID
import com.gtocore.common.machine.multiblock.part.ae.MEPatternContentSortMachine.MODE.ITEM
import com.gtocore.utils.AEKeySubstitutionMap
import com.gtocore.utils.AEPatternRefresher

import net.minecraft.network.chat.Component
import net.minecraft.server.TickTask
Expand All @@ -16,8 +18,6 @@ import appeng.api.networking.IManagedGridNode
import appeng.api.stacks.AEFluidKey
import appeng.api.stacks.AEItemKey
import appeng.api.stacks.AEKey
import appeng.blockentity.crafting.PatternProviderBlockEntity
import appeng.me.Grid
import com.gregtechceu.gtceu.api.blockentity.MetaMachineBlockEntity
import com.gregtechceu.gtceu.api.gui.fancy.FancyMachineUIWidget
import com.gregtechceu.gtceu.api.gui.fancy.IFancyUIProvider
Expand All @@ -26,7 +26,6 @@ import com.gregtechceu.gtceu.api.machine.TickableSubscription
import com.gregtechceu.gtceu.api.machine.feature.IFancyUIMachine
import com.gregtechceu.gtceu.integration.ae2.machine.feature.IGridConnectedMachine
import com.gregtechceu.gtceu.integration.ae2.machine.trait.GridNodeHolder
import com.gtolib.api.ae2.IExpandedGrid
import com.gtolib.api.annotation.DataGeneratorScanned
import com.gtolib.api.annotation.language.RegisterLanguage
import com.gtolib.api.gui.ktflexible.button
Expand All @@ -39,15 +38,10 @@ import com.lowdragmc.lowdraglib.misc.ItemTransferList
import com.lowdragmc.lowdraglib.syncdata.IContentChangeAware
import com.lowdragmc.lowdraglib.syncdata.annotation.DescSynced
import com.lowdragmc.lowdraglib.syncdata.annotation.Persisted
import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap
import it.unimi.dsi.fastutil.objects.Object2ObjectMap
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap
import it.unimi.dsi.fastutil.objects.ObjectArrayList
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*

import java.util.concurrent.ConcurrentHashMap

@DataGeneratorScanned
class MEPatternContentSortMachine(holder: MetaMachineBlockEntity) :
MetaMachine(holder),
Expand Down Expand Up @@ -95,146 +89,95 @@ class MEPatternContentSortMachine(holder: MetaMachineBlockEntity) :
class FlowData(val finished: Int, val total: Int)
abstract fun applyRefresh()
abstract fun receiveFlowData(flowData: FlowData)
abstract fun collectFlowRunner(): List<Runnable>
abstract fun buildCoroutine(run: List<Runnable>): CoroutineScope
}
private inner class InternalLogic : InternalLogicTemplate() {
var lastFlowData: FlowData? = null
var uniqueScope: CoroutineScope? = null
var isRefreshing: Boolean = false
override fun applyRefresh() {
val runnables = collectFlowRunner()
uniqueScope?.cancel()
uniqueScope = buildCoroutine(runnables)
}
val grid = gridNodeHolder.mainNode?.grid ?: return
uniqueScope = CoroutineScope(Dispatchers.Default)

override fun receiveFlowData(flowData: FlowData) {
lastFlowData = flowData
}

override fun collectFlowRunner(): List<Runnable> {
val runnable = mutableListOf<Runnable>()
if (gridNodeHolder.mainNode?.isActive == false) return runnable
val grid = gridNodeHolder.mainNode?.grid ?: return runnable
grid.getActiveMachines(PatternProviderBlockEntity::class.java).forEach {
runnable.add { it.logic.updatePatterns() }
}
if (grid is Grid && grid is IExpandedGrid) {
val machinesPart = mutableListOf<MEPatternPartMachineKt<*>>()
grid.machines.forEach { _, node ->
if (node.isActive) {
val logicHost = node.owner
if (logicHost is MEPatternPartMachineKt<*>) {
machinesPart.plusAssign(logicHost)
}
}
AEPatternRefresher.refreshAllPatternsAsync(grid)
.onStart {
isRefreshing = true
// 初始化进度条
receiveFlowData(FlowData(0, 1))
}
machinesPart.forEach { s ->
runnable.add {
(0 until s.maxPatternCount).forEach {
if (!s.internalPatternInventory.getStackInSlot(it).isEmpty) {
s.onPatternChange(it)
}
}
.onEach { progress ->
receiveFlowData(FlowData(progress.completed, progress.total))
}
.onCompletion {
isRefreshing = false
lastFlowData?.let {
receiveFlowData(FlowData(it.total, it.total))
}
}
}
return runnable
.launchIn(uniqueScope!!)
}

override fun buildCoroutine(run: List<Runnable>): CoroutineScope {
val scope = CoroutineScope(Dispatchers.Default)
flow {
val total = run.size
val block = 5
run.chunked(block).forEachIndexed { blockIndex, runnables ->
runnables.forEach { it.run() }
val finished = ((blockIndex + 1) * block).coerceAtMost(total)
emit(FlowData(finished, total))
delay(100)
}
}.onEach {
receiveFlowData(it)
}.onStart {
isRefreshing = true
}.onCompletion {
isRefreshing = false
}.launchIn(scope)
return scope
override fun receiveFlowData(flowData: FlowData) {
lastFlowData = flowData
}
}

val externalLogic by lazy { ExternalLogic() }
abstract class ExternalLogicTemplate {
abstract fun getAEKeyReplaced(stack: AEKey): AEKey
}
inner class ExternalLogic : ExternalLogicTemplate() {
private val lastAccessTime = Object2LongOpenHashMap<AEKey>()

// 物品->首选物品映射表
val itemToPreferred: ConcurrentHashMap<AEKey, AEKey> = ConcurrentHashMap()

// 物品->物品排序从高到低映射表
val itemToSort: Object2ObjectMap<AEKey, ObjectArrayList<AEKey>> = Object2ObjectOpenHashMap()

override fun getAEKeyReplaced(stack: AEKey): AEKey {
itemToPreferred[stack]?.let {
lastAccessTime[stack] = System.currentTimeMillis()
return it
}
val sortStacks = itemToSort[stack]
if (sortStacks != null && sortStacks.isNotEmpty()) {
val preferred = sortStacks.first()
itemToPreferred[stack] = preferred
lastAccessTime[stack] = System.currentTimeMillis()
return preferred
}
@Volatile
private var substitutionMap = AEKeySubstitutionMap.EMPTY

itemToPreferred[stack] = stack
lastAccessTime[stack] = System.currentTimeMillis()
return stack
}
// TODO 使用PatternContentAccessTerminalPart获取(grid.getMachines(PatternContentAccessTerminalPart.class))
override fun getAEKeyReplaced(stack: AEKey): AEKey = substitutionMap.getSubstitution(stack)

fun fullyRefresh() {
itemToPreferred.clear()
itemToSort.clear()
val priorityGroups = mutableListOf<List<AEKey>>()

itemTransferList.transfers.forEach { transfer ->
transfer as MyItemStackTransfer
val sortStacks = ObjectArrayList<AEKey>()
val priorityGroup = ObjectArrayList<AEKey>()
val itemStacks = (0 until transfer.slots)
.mapNotNull { transfer.getStackInSlot(it) }
.filterNot { it.isEmpty }

var currentMode = ITEM
// 只要行内有一个流体容器,整行都按流体处理
for (stack in itemStacks) {
transfer.mode = ITEM
stack.getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM).ifPresent {
if (!it.drain(Int.MAX_VALUE, IFluidHandler.FluidAction.SIMULATE).isEmpty) {
transfer.mode = FLUID
currentMode = FLUID
}
}
if (currentMode == FLUID) break
}
transfer.mode = currentMode

when (transfer.mode) {
FLUID -> {
val fluids = itemStacks
.mapNotNull { stack ->
val capability = stack.getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM).resolve().orElse(null) ?: return@mapNotNull null
return@mapNotNull capability.drain(Int.MAX_VALUE, IFluidHandler.FluidAction.SIMULATE)
val cap = stack.getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM).resolve().orElse(null)
cap?.drain(Int.MAX_VALUE, IFluidHandler.FluidAction.SIMULATE)
}
.mapNotNull { AEFluidKey.of(it) }
sortStacks.addAll(fluids)
fluids.forEach { aek ->
itemToSort[aek] = sortStacks
}
priorityGroup.addAll(fluids)
}
ITEM -> {
val items = itemStacks
.mapNotNull { AEItemKey.of(it) }
sortStacks.addAll(items)
items.forEach { aek ->
itemToSort[aek] = sortStacks
}
val items = itemStacks.mapNotNull { AEItemKey.of(it) }
priorityGroup.addAll(items)
}
}

if (priorityGroup.isNotEmpty()) {
priorityGroups.add(priorityGroup)
}
}
// 原子性替换
this.substitutionMap = AEKeySubstitutionMap(priorityGroups)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@
@MethodsReturnNonnullByDefault
public final class ClarifierPurificationUnitMachine extends WaterPurificationUnitMachine implements IFluidRendererMachine {

private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ClarifierPurificationUnitMachine.class);
private static final Fluid AIR = GTMaterials.Air.getFluid();

@Persisted
Expand Down
48 changes: 48 additions & 0 deletions src/main/java/com/gtocore/utils/AEKeySubstitutionMap.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.gtocore.utils

import appeng.api.stacks.AEKey
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap

import java.util.concurrent.ConcurrentHashMap

class AEKeySubstitutionMap(priorityGroups: List<List<AEKey>>) {

// 缓存:从任意Key映射到其首选Key,用于极速查询。
private val keyToPreferredCache = ConcurrentHashMap<AEKey, AEKey>()

// 核心规则:从任意Key映射到其所在的整个优先级列表。
private val keyToPriorityGroup: Map<AEKey, List<AEKey>>

init {
val mapBuilder = Object2ObjectOpenHashMap<AEKey, List<AEKey>>()
priorityGroups.forEach { group ->
if (group.isNotEmpty()) {
group.forEach { key ->
mapBuilder[key] = group
}
}
}
this.keyToPriorityGroup = mapBuilder
}

/**
* 获取给定AEKey的替换项(首选Key)。
*
* @param key 需要查询的原始AEKey。
* @return 替换后的首选AEKey。如果该Key没有定义替换规则,则返回其自身。
*/
fun getSubstitution(key: AEKey): AEKey {
keyToPreferredCache[key]?.let { return it }

val priorityGroup = keyToPriorityGroup[key]

val preferredKey = priorityGroup?.firstOrNull() ?: key

keyToPreferredCache[key] = preferredKey
return preferredKey
}

companion object {
val EMPTY = AEKeySubstitutionMap(emptyList())
}
}
72 changes: 72 additions & 0 deletions src/main/java/com/gtocore/utils/AEPatternRefresher.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package com.gtocore.utils

import com.gtocore.common.machine.multiblock.part.ae.MEPatternPartMachineKt

import appeng.api.networking.IGrid
import appeng.blockentity.crafting.PatternProviderBlockEntity
import com.gregtechceu.gtceu.api.machine.MetaMachine
import com.gtolib.api.ae2.IExpandedGrid
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn

data class RefreshProgress(val completed: Int, val total: Int)

/**
* 一个用于异步刷新AE2网络中所有样板的工具类。
* 这可以防止因一次性更新大量样板而导致的服务器卡顿。
*/
object AEPatternRefresher {

private const val TASK_CHUNK_SIZE = 10 // 每次处理的任务数量
private const val DELAY_BETWEEN_CHUNKS_MS = 100L // 每批任务之间的延迟(毫秒)

/**
* 异步刷新指定AE2网络中的所有样板提供者。
*
* @param grid 目标AE2网络。
* @return 一个Kotlin Flow,它会随刷新进度发出[RefreshProgress]状态。
* 调用者可以收集(collect)这个Flow来更新UI,例如进度条。
*/
fun refreshAllPatternsAsync(grid: IGrid): Flow<RefreshProgress> = flow {
val refreshTasks = mutableListOf<Runnable>()

grid.getActiveMachines(PatternProviderBlockEntity::class.java).forEach {
refreshTasks.add(Runnable { it.logic.updatePatterns() })
}

if (grid is IExpandedGrid) {
grid.machines.values()
.filter { it.isActive }
.mapNotNull { it.owner as? MEPatternPartMachineKt<*> }
.forEach { machine ->
refreshTasks.add(
Runnable {
(0 until machine.maxPatternCount).forEach { slotIndex ->
if (!machine.internalPatternInventory.getStackInSlot(slotIndex).isEmpty) {
machine.onPatternChange(slotIndex)
}
}
},
)
}
}

val totalTasks = refreshTasks.size
if (totalTasks == 0) {
emit(RefreshProgress(0, 0))
return@flow
}

emit(RefreshProgress(0, totalTasks))

refreshTasks.chunked(TASK_CHUNK_SIZE).forEachIndexed { index, chunk ->
chunk.forEach { it.run() }
val completed = ((index + 1) * TASK_CHUNK_SIZE).coerceAtMost(totalTasks)
emit(RefreshProgress(completed, totalTasks))
delay(DELAY_BETWEEN_CHUNKS_MS)
}
}.flowOn(Dispatchers.Default)
}