Skip to content
Closed
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 @@ -11,9 +11,24 @@ import net.minecraft.tags.TagKey
import net.minecraft.world.item.ItemStack
import net.minecraftforge.fluids.FluidStack
import net.minecraftforge.registries.ForgeRegistries
import java.util.concurrent.ConcurrentHashMap

@Suppress("unused")
object BuiltinNodeResolver {
private val itemNodesByMod = ConcurrentHashMap<String, List<NodeKey>>()
private val fluidNodesByMod = ConcurrentHashMap<String, List<NodeKey>>()
@Volatile
private var itemNodeCacheSize = -1

@Volatile
private var fluidNodeCacheSize = -1

@Volatile
private var allItemNodesCache: List<NodeKey> = emptyList()

@Volatile
private var allFluidNodesCache: List<NodeKey> = emptyList()

fun itemNode(stack: ItemStack): NodeKey? {
if (stack.isEmpty) {
return null
Expand Down Expand Up @@ -61,27 +76,64 @@ object BuiltinNodeResolver {
}
.orElse(emptyList())

fun allItemNodes(): List<NodeKey> =
BuiltInRegistries.ITEM
.map { item -> MatterNodes.item(BuiltInRegistries.ITEM.getKey(item).toLite()) }
fun allItemNodes(): List<NodeKey> {
refreshItemCacheIfNeeded()
return allItemNodesCache
}

fun allFluidNodes(): List<NodeKey> =
BuiltInRegistries.FLUID
.filter { fluid -> fluid != net.minecraft.world.level.material.Fluids.EMPTY }
.map { fluid -> MatterNodes.fluid(BuiltInRegistries.FLUID.getKey(fluid).toLite()) }
fun allFluidNodes(): List<NodeKey> {
refreshFluidCacheIfNeeded()
return allFluidNodesCache
}

fun itemNodesInMod(modId: String): List<NodeKey> =
allItemNodes().filter { it.id.namespace == modId }
fun itemNodesInMod(modId: String): List<NodeKey> {
refreshItemCacheIfNeeded()
return itemNodesByMod.computeIfAbsent(modId) { id ->
allItemNodesCache.filter { it.id.namespace == id }
}
}

fun fluidNodesInMod(modId: String): List<NodeKey> =
allFluidNodes().filter { it.id.namespace == modId }
fun fluidNodesInMod(modId: String): List<NodeKey> {
refreshFluidCacheIfNeeded()
return fluidNodesByMod.computeIfAbsent(modId) { id ->
allFluidNodesCache.filter { it.id.namespace == id }
}
}

fun itemAmount(stack: ItemStack): NodeAmount? =
itemNode(stack)?.let { NodeAmount(it, stack.count.coerceAtLeast(1).toLong()) }

fun fluidAmount(stack: FluidStack): NodeAmount? =
fluidNode(stack)?.takeIf { stack.amount > 0 }?.let { NodeAmount(it, stack.amount.toLong()) }

@Synchronized
private fun refreshItemCacheIfNeeded() {
val size = BuiltInRegistries.ITEM.size()
if (size != itemNodeCacheSize) {
itemNodeCacheSize = size
allItemNodesCache =
BuiltInRegistries.ITEM.map { item ->
MatterNodes.item(BuiltInRegistries.ITEM.getKey(item).toLite())
}
itemNodesByMod.clear()
}
}

@Synchronized
private fun refreshFluidCacheIfNeeded() {
val size = BuiltInRegistries.FLUID.size()
if (size != fluidNodeCacheSize) {
fluidNodeCacheSize = size
allFluidNodesCache =
BuiltInRegistries.FLUID
.filter { fluid -> fluid != net.minecraft.world.level.material.Fluids.EMPTY }
.map { fluid ->
MatterNodes.fluid(BuiltInRegistries.FLUID.getKey(fluid).toLite())
}
fluidNodesByMod.clear()
}
}

private fun ResourceLocation.toLite(): LiteResourceLocation =
LiteResourceLocation.of(namespace, path)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ class MatterIndexScreen(
private val focusedItemId: String? = null,
) : Screen(Component.literal("Replication Index")) {
private val entries: List<MatterIndexEntry> = loadEntries()
private val entryRecords: List<MatterIndexRecord> = entries.map { it.record }
private val entriesById: Map<String, MatterIndexEntry> = entries.associateBy { it.record.itemId }
private val matterTypes: List<String> =
entries
.flatMap { it.record.matterValues.keys }
Expand Down Expand Up @@ -487,18 +489,16 @@ class MatterIndexScreen(

private fun visibleEntries(): List<MatterIndexEntry> {
val query = MatterIndexFiltering.parseQuery(searchBox.value, replicatableOnly, disintegratableOnly)
val ordered = MatterIndexFiltering.filterAndSort(entries.map { it.record }, query, sort, ::displayAmount)
val byId = entries.associateBy { it.record.itemId }
return ordered.mapNotNull { byId[it.itemId] }
val ordered = MatterIndexFiltering.filterAndSort(entryRecords, query, sort, ::displayAmount)
return ordered.mapNotNull { entriesById[it.itemId] }
}

private fun selectedEntry(): MatterIndexEntry? =
entries.firstOrNull { it.record.itemId == selectedItemId }
selectedItemId?.let { entriesById[it] }

private fun equivalentEntries(): List<MatterIndexEntry> {
val records = MatterIndexFiltering.equivalentRecords(entries.map { it.record }, selectedEntry()?.record, ::displayAmount)
val byId = entries.associateBy { it.record.itemId }
return records.mapNotNull { byId[it.itemId] }
val records = MatterIndexFiltering.equivalentRecords(entryRecords, selectedEntry()?.record, ::displayAmount)
return records.mapNotNull { entriesById[it.itemId] }
}

private fun columns(layout: Layout): Columns {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,23 @@ import net.minecraft.resources.ResourceLocation
import net.minecraft.tags.TagKey
import net.minecraft.world.item.ItemStack
import net.neoforged.neoforge.fluids.FluidStack
import java.util.concurrent.ConcurrentHashMap

object BuiltinNodeResolver {
private val itemNodesByMod = ConcurrentHashMap<String, List<NodeKey>>()
private val fluidNodesByMod = ConcurrentHashMap<String, List<NodeKey>>()
@Volatile
private var itemNodeCacheSize = -1

@Volatile
private var fluidNodeCacheSize = -1

@Volatile
private var allItemNodesCache: List<NodeKey> = emptyList()

@Volatile
private var allFluidNodesCache: List<NodeKey> = emptyList()

fun itemNode(stack: ItemStack): NodeKey? {
if (stack.isEmpty) {
return null
Expand Down Expand Up @@ -59,27 +74,64 @@ object BuiltinNodeResolver {
}
.orElse(emptyList())

fun allItemNodes(): List<NodeKey> =
BuiltInRegistries.ITEM
.map { item -> MatterNodes.item(BuiltInRegistries.ITEM.getKey(item).toLite()) }
fun allItemNodes(): List<NodeKey> {
refreshItemCacheIfNeeded()
return allItemNodesCache
}

fun allFluidNodes(): List<NodeKey> =
BuiltInRegistries.FLUID
.filter { fluid -> fluid != net.minecraft.world.level.material.Fluids.EMPTY }
.map { fluid -> MatterNodes.fluid(BuiltInRegistries.FLUID.getKey(fluid).toLite()) }
fun allFluidNodes(): List<NodeKey> {
refreshFluidCacheIfNeeded()
return allFluidNodesCache
}

fun itemNodesInMod(modId: String): List<NodeKey> =
allItemNodes().filter { it.id.namespace == modId }
fun itemNodesInMod(modId: String): List<NodeKey> {
refreshItemCacheIfNeeded()
return itemNodesByMod.computeIfAbsent(modId) { id ->
allItemNodesCache.filter { it.id.namespace == id }
}
}

fun fluidNodesInMod(modId: String): List<NodeKey> =
allFluidNodes().filter { it.id.namespace == modId }
fun fluidNodesInMod(modId: String): List<NodeKey> {
refreshFluidCacheIfNeeded()
return fluidNodesByMod.computeIfAbsent(modId) { id ->
allFluidNodesCache.filter { it.id.namespace == id }
}
}

fun itemAmount(stack: ItemStack): NodeAmount? =
itemNode(stack)?.let { NodeAmount(it, stack.count.coerceAtLeast(1).toLong()) }

fun fluidAmount(stack: FluidStack): NodeAmount? =
fluidNode(stack)?.takeIf { stack.amount > 0 }?.let { NodeAmount(it, stack.amount.toLong()) }

@Synchronized
private fun refreshItemCacheIfNeeded() {
val size = BuiltInRegistries.ITEM.size()
if (size != itemNodeCacheSize) {
itemNodeCacheSize = size
allItemNodesCache =
BuiltInRegistries.ITEM.map { item ->
MatterNodes.item(BuiltInRegistries.ITEM.getKey(item).toLite())
}
itemNodesByMod.clear()
}
}

@Synchronized
private fun refreshFluidCacheIfNeeded() {
val size = BuiltInRegistries.FLUID.size()
if (size != fluidNodeCacheSize) {
fluidNodeCacheSize = size
allFluidNodesCache =
BuiltInRegistries.FLUID
.filter { fluid -> fluid != net.minecraft.world.level.material.Fluids.EMPTY }
.map { fluid ->
MatterNodes.fluid(BuiltInRegistries.FLUID.getKey(fluid).toLite())
}
fluidNodesByMod.clear()
}
}

private fun ResourceLocation.toLite(): LiteResourceLocation =
LiteResourceLocation.of(namespace, path)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ class MatterIndexScreen(
private val focusedItemId: String? = null,
) : Screen(Component.literal("Replication Index")) {
private val entries: List<MatterIndexEntry> = loadEntries()
private val entryRecords: List<MatterIndexRecord> = entries.map { it.record }
private val entriesById: Map<String, MatterIndexEntry> = entries.associateBy { it.record.itemId }
private val matterTypes: List<String> =
entries
.flatMap { it.record.matterValues.keys }
Expand Down Expand Up @@ -89,6 +91,10 @@ class MatterIndexScreen(
)
}

override fun tick() {
searchBox.tick()
}

override fun render(guiGraphics: GuiGraphics, mouseX: Int, mouseY: Int, partialTick: Float) {
hoveredStack = ItemStack.EMPTY
hoveredAmountTooltip = emptyList()
Expand Down Expand Up @@ -566,19 +572,16 @@ class MatterIndexScreen(

private fun visibleEntries(): List<MatterIndexEntry> {
val query = MatterIndexFiltering.parseQuery(searchBox.value, replicatableOnly, disintegratableOnly)
val ordered = MatterIndexFiltering.filterAndSort(entries.map { it.record }, query, sort, ::displayAmount)
val byId = entries.associateBy { it.record.itemId }
return ordered.mapNotNull { byId[it.itemId] }
val ordered = MatterIndexFiltering.filterAndSort(entryRecords, query, sort, ::displayAmount)
return ordered.mapNotNull { entriesById[it.itemId] }
}

private fun selectedEntry(): MatterIndexEntry? =
entries.firstOrNull { it.record.itemId == selectedItemId }
selectedItemId?.let { entriesById[it] }

private fun equivalentEntries(): List<MatterIndexEntry> {
val records =
MatterIndexFiltering.equivalentRecords(entries.map { it.record }, selectedEntry()?.record, ::displayAmount)
val byId = entries.associateBy { it.record.itemId }
return records.mapNotNull { byId[it.itemId] }
val records = MatterIndexFiltering.equivalentRecords(entryRecords, selectedEntry()?.record, ::displayAmount)
return records.mapNotNull { entriesById[it.itemId] }
}

private fun columns(layout: Layout): Columns {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,12 @@ object MatterSelectorMaterializer {
): Map<NodeKey, ExplicitMatterValue> {
val values = linkedMapOf<NodeKey, ExplicitMatterValue>()
val expansion = MatterRuleExpansion(expandTag, expandMod, allNodes, rawValue)
for (rule in rules.sortedWith(compareBy<MatterRule> { it.source.priority })) {
val orderedRules =
rules.withIndex().sortedWith(
compareBy<IndexedValue<MatterRule>> { it.value.source.priority }
.thenBy { it.index },
)
for ((_, rule) in orderedRules) {
val targets = rule.selector.expand(expansion)
for (target in targets) {
values[target] = rule.value
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,9 @@ object MatterSelectorStorageCodec {
val root = JsonObject()
val entries = JsonArray()
for (rule in rules) {
val selector = rule.selector.staticKey ?: continue
val selector =
rule.selector.staticKey
?: throw IllegalArgumentException("Cannot save non-static selector: ${rule.selector}")
val value = rule.value
val json = JsonObject()
json.addProperty("selector", selector.kind.name.lowercase())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,12 @@ class MutableMatterSelectors {

fun snapshot(): Map<MatterSelectorKey, ExplicitMatterValue> {
val values = linkedMapOf<MatterSelectorKey, ExplicitMatterValue>()
for (rule in rules.sortedWith(compareBy<MatterRule> { it.source.priority })) {
val orderedRules =
rules.withIndex().sortedWith(
compareBy<IndexedValue<MatterRule>> { it.value.source.priority }
.thenBy { it.index },
)
for ((_, rule) in orderedRules) {
val key = rule.selector.staticKey ?: continue
values[key] = rule.value
}
Expand Down
81 changes: 81 additions & 0 deletions common/src/test/kotlin/MatterSelectorOrderingTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package com.p_nsk.replicated_integration.api

import com.p_nsk.replicated_integration.api.model.ExplicitMatterSource
import com.p_nsk.replicated_integration.api.model.ExplicitMatterValue
import com.p_nsk.replicated_integration.api.model.LiteMatterCompound
import com.p_nsk.replicated_integration.api.model.LiteResourceLocation
import com.p_nsk.replicated_integration.api.node.NodeKey
import com.p_nsk.replicated_integration.api.selector.AnyMatterRuleSelector
import com.p_nsk.replicated_integration.api.selector.MatterRule
import com.p_nsk.replicated_integration.api.selector.MatterSelectorKey
import com.p_nsk.replicated_integration.api.selector.MatterSelectorKind
import com.p_nsk.replicated_integration.api.selector.MatterSelectorMaterializer
import com.p_nsk.replicated_integration.api.selector.MatterSelectorStorageCodec
import com.p_nsk.replicated_integration.api.selector.MutableMatterSelectors
import com.p_nsk.replicated_integration.api.selector.asRuleSelector
import java.nio.file.Files
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith

class MatterSelectorOrderingTest {
@Test
fun materializerKeepsInsertionOrderForSamePriority() {
val type = LiteResourceLocation.of("replicated_integration", "item")
val id = LiteResourceLocation.of("minecraft", "stone")
val selector = MatterSelectorKey(MatterSelectorKind.NODE, type, id).asRuleSelector()
val firstValue =
ExplicitMatterValue.Set(
LiteMatterCompound(mapOf(LiteResourceLocation.of("replication", "earth") to 1.0)),
ExplicitMatterSource.CONFIG,
)
val secondValue =
ExplicitMatterValue.Set(
LiteMatterCompound(mapOf(LiteResourceLocation.of("replication", "earth") to 2.0)),
ExplicitMatterSource.CONFIG,
)
val rules = listOf(MatterRule(selector, firstValue), MatterRule(selector, secondValue))

val materialized =
MatterSelectorMaterializer.materialize(
rules,
expandTag = { _, _ -> emptyList() },
)

assertEquals(secondValue, materialized[NodeKey(type, id)])
}

@Test
fun snapshotKeepsInsertionOrderForSamePriority() {
val type = LiteResourceLocation.of("replicated_integration", "item")
val id = LiteResourceLocation.of("minecraft", "stone")
val key = MatterSelectorKey(MatterSelectorKind.NODE, type, id)
val selectors = MutableMatterSelectors()
val firstCompound = LiteMatterCompound(mapOf(LiteResourceLocation.of("replication", "earth") to 1.0))
val secondCompound = LiteMatterCompound(mapOf(LiteResourceLocation.of("replication", "earth") to 2.0))

selectors.put(key, firstCompound, ExplicitMatterSource.CONFIG)
selectors.put(key, secondCompound, ExplicitMatterSource.CONFIG)

val snapshot = selectors.snapshot()

assertEquals(ExplicitMatterValue.Set(secondCompound, ExplicitMatterSource.CONFIG), snapshot[key])
}

@Test
fun saveRulesRejectsNonStaticSelectors() {
val type = LiteResourceLocation.of("replicated_integration", "item")
val id = LiteResourceLocation.of("minecraft", "stone")
val staticSelector = MatterSelectorKey(MatterSelectorKind.NODE, type, id).asRuleSelector()
val rule =
MatterRule(
AnyMatterRuleSelector(listOf(staticSelector)),
ExplicitMatterValue.Deny(ExplicitMatterSource.CONFIG),
)
val path = Files.createTempDirectory("repint").resolve("rules.json")

assertFailsWith<IllegalArgumentException> {
MatterSelectorStorageCodec.saveRules(path, listOf(rule))
}
}
}
Loading
Loading