Un-tangle matter system
This commit is contained in:
parent
37e4f33af6
commit
9592860349
@ -42,7 +42,6 @@ import ru.dbotthepony.mc.otm.item.QuantumBatteryItem;
|
||||
import ru.dbotthepony.mc.otm.item.weapon.AbstractWeaponItem;
|
||||
import ru.dbotthepony.mc.otm.item.PortableCondensationDriveItem;
|
||||
import ru.dbotthepony.mc.otm.matter.MatterManager;
|
||||
import ru.dbotthepony.mc.otm.matter.RecipeResolverManager;
|
||||
import ru.dbotthepony.mc.otm.network.*;
|
||||
import ru.dbotthepony.mc.otm.registry.*;
|
||||
import ru.dbotthepony.mc.otm.storage.*;
|
||||
@ -121,7 +120,7 @@ public final class OverdriveThatMatters {
|
||||
var modBus = FMLJavaModLoadingContext.get().getModEventBus();
|
||||
|
||||
MRegistry.INSTANCE.initialize(modBus);
|
||||
RecipeResolverManager.INSTANCE.initialize(modBus);
|
||||
MatterManager.INSTANCE.initialize(modBus);
|
||||
|
||||
modBus.addListener(EventPriority.HIGHEST, this::setup);
|
||||
modBus.addListener(EventPriority.NORMAL, this::setupClient);
|
||||
@ -171,8 +170,7 @@ public final class OverdriveThatMatters {
|
||||
EVENT_BUS.addListener(EventPriority.NORMAL, AndroidResearchManager.INSTANCE::syncEvent);
|
||||
|
||||
EVENT_BUS.addListener(EventPriority.NORMAL, MatterManager.INSTANCE::reloadEvent);
|
||||
EVENT_BUS.addListener(EventPriority.NORMAL, RecipeResolverManager.INSTANCE::reloadEvent);
|
||||
EVENT_BUS.addListener(EventPriority.NORMAL, RecipeResolverManager.INSTANCE::onServerStarted);
|
||||
EVENT_BUS.addListener(EventPriority.NORMAL, MatterManager.INSTANCE::onServerStarted);
|
||||
|
||||
EVENT_BUS.addListener(EventPriority.NORMAL, SynchronizedBlockEntity.Companion::onServerStopping);
|
||||
EVENT_BUS.addListener(EventPriority.NORMAL, SynchronizedBlockEntity.Companion::onLevelUnload);
|
||||
|
@ -251,7 +251,7 @@ class BlackHoleBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : Synchro
|
||||
if (item.item.item === MItems.GRAVITATIONAL_DISRUPTOR) {
|
||||
collapse()
|
||||
} else {
|
||||
val mass = MatterManager.getValue(item.item)
|
||||
val mass = MatterManager.get(item.item)
|
||||
|
||||
if (mass.hasMatterValue)
|
||||
this.mass += mass.matter
|
||||
|
@ -220,7 +220,7 @@ class MatterDecomposerBlockEntity(pos: BlockPos, state: BlockState)
|
||||
copy.count = 1
|
||||
|
||||
if (MatterManager.canDecompose(copy)) {
|
||||
val matter = MatterManager.getValue(copy)
|
||||
val matter = MatterManager.get(copy)
|
||||
stack.count--
|
||||
|
||||
return DecomposerJob((level?.random?.nextDouble() ?: 1.0) <= 0.2, matter.matter, matter.complexity) to null
|
||||
|
@ -194,7 +194,7 @@ class MatterReplicatorBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) :
|
||||
val graph = matterNode.graph as MatterNetworkGraph? ?: return null to null
|
||||
val allocation = graph.allocateTask(simulate = false) ?: return null to IdleReason.OBSERVING
|
||||
val stack = allocation.task.stack(1)
|
||||
val matter = MatterManager.getValue(stack)
|
||||
val matter = MatterManager.get(stack)
|
||||
|
||||
// ????????
|
||||
if (!matter.hasMatterValue) return null to null
|
||||
|
@ -198,7 +198,7 @@ class MatterScannerBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) :
|
||||
val copy = stack.copy().also { it.count = 1 }
|
||||
stack.shrink(1)
|
||||
container.setChanged()
|
||||
val complexity = MatterManager.getValue(copy).complexity
|
||||
val complexity = MatterManager.get(copy).complexity
|
||||
return ItemJob(copy, (if (complexity > 1.0) complexity.pow(2.0) else complexity.pow(0.5)), BASE_CONSUMPTION) to null
|
||||
}
|
||||
|
||||
|
@ -420,3 +420,11 @@ fun <T> Collection<T>.probablyParallelStream(): Stream<out T> {
|
||||
}
|
||||
|
||||
fun <T> Array<T>.stream(): Stream<T> = Arrays.stream(this)
|
||||
|
||||
fun <T> Stream<T?>.filterNotNull(): Stream<T> {
|
||||
return filter { it != null } as Stream<T>
|
||||
}
|
||||
|
||||
inline fun <reified T> Stream<*>.filterIsInstance(): Stream<T> {
|
||||
return filter { it is T } as Stream<T>
|
||||
}
|
||||
|
@ -31,18 +31,16 @@ class CreativePatternItem : Item(Properties().rarity(Rarity.EPIC).tab(OverdriveT
|
||||
private val resolver = LazyOptional.of<IPatternStorage> { this }
|
||||
|
||||
override val patterns: Stream<out IPatternState>
|
||||
get() = MatterManager.resolveAndStream().map { PatternState(UUID(34783464838L, 4463458382L + ForgeRegistries.ITEMS.getID(it.key)), it.key, 1.0) }
|
||||
get() = MatterManager.valuesList.stream().map { PatternState(UUID(34783464838L, 4463458382L + ForgeRegistries.ITEMS.getID(it.first)), it.first, 1.0) }
|
||||
|
||||
override val patternCapacity: Int
|
||||
get() {
|
||||
MatterManager.resolveEverything()
|
||||
return MatterManager.directlyComputedEntriesSize
|
||||
return MatterManager.valuesList.size
|
||||
}
|
||||
|
||||
override val storedPatterns: Int
|
||||
get() {
|
||||
MatterManager.resolveEverything()
|
||||
return MatterManager.directlyComputedEntriesSize
|
||||
return MatterManager.valuesList.size
|
||||
}
|
||||
|
||||
override fun insertPattern(
|
||||
|
@ -11,7 +11,7 @@ import net.minecraft.world.item.Item
|
||||
import ru.dbotthepony.mc.otm.core.ImpreciseFraction
|
||||
import ru.dbotthepony.mc.otm.core.set
|
||||
|
||||
sealed class AbstractRegistryAction {
|
||||
sealed class AbstractRegistryAction : Comparable<AbstractRegistryAction> {
|
||||
sealed class Condition {
|
||||
abstract fun test(): Boolean
|
||||
}
|
||||
@ -120,7 +120,7 @@ sealed class AbstractRegistryAction {
|
||||
return true
|
||||
}
|
||||
|
||||
abstract fun update(registry: MutableCollection<MatterManager.MutableEntry>, modifier: ResourceLocation)
|
||||
internal abstract fun update(registry: MutableCollection<MutableEntry>, modifier: ResourceLocation)
|
||||
|
||||
protected inline fun fail(reason: () -> String) {
|
||||
if (errorOnFailure) {
|
||||
@ -138,6 +138,22 @@ sealed class AbstractRegistryAction {
|
||||
return other.priority == null
|
||||
}
|
||||
|
||||
override fun compareTo(other: AbstractRegistryAction): Int {
|
||||
if (other.priority == null) {
|
||||
if (priority == null) {
|
||||
return 0
|
||||
}
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
if (priority == null) {
|
||||
return -1
|
||||
}
|
||||
|
||||
return priority.compareTo(other.priority)
|
||||
}
|
||||
|
||||
open fun toJson(): JsonObject {
|
||||
return JsonObject().also {
|
||||
if (key != null)
|
||||
|
@ -54,8 +54,7 @@ class ComputeAction : AbstractRegistryAction {
|
||||
}
|
||||
}
|
||||
|
||||
abstract val matter: ImpreciseFraction?
|
||||
abstract val complexity: Double?
|
||||
internal abstract fun provideValues(): MatterManager.Result
|
||||
|
||||
open fun toJson(): JsonObject {
|
||||
return JsonObject().also {
|
||||
@ -64,20 +63,23 @@ class ComputeAction : AbstractRegistryAction {
|
||||
}
|
||||
}
|
||||
|
||||
fun update(self: IMatterValue): IMatterValue? {
|
||||
val matter = matter ?: return null
|
||||
val complexity = complexity ?: return null
|
||||
internal fun update(self: IMatterValue): MatterManager.Result {
|
||||
val grab = provideValues()
|
||||
|
||||
return MatterValue(
|
||||
matterFunction.updateValue(self.matter, matter),
|
||||
complexityFunction.updateValue(self.complexity, complexity)
|
||||
)
|
||||
if (grab.value == null) {
|
||||
return grab
|
||||
}
|
||||
|
||||
return MatterManager.Result(MatterValue(
|
||||
matterFunction.updateValue(self.matter, grab.value.matter),
|
||||
complexityFunction.updateValue(self.complexity, grab.value.complexity)
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
class Constant : Source {
|
||||
override val matter: ImpreciseFraction
|
||||
override val complexity: Double
|
||||
val matter: ImpreciseFraction
|
||||
val complexity: Double
|
||||
|
||||
constructor(
|
||||
matter: ImpreciseFraction,
|
||||
@ -103,6 +105,10 @@ class ComputeAction : AbstractRegistryAction {
|
||||
}
|
||||
}
|
||||
|
||||
override fun provideValues(): MatterManager.Result {
|
||||
return MatterManager.Result(MatterValue(matter, complexity))
|
||||
}
|
||||
|
||||
override fun toJson(): JsonObject {
|
||||
return super.toJson().also {
|
||||
it["type"] = "constant"
|
||||
@ -135,18 +141,10 @@ class ComputeAction : AbstractRegistryAction {
|
||||
key = ResourceLocation.tryParse(json["key"]?.asString ?: throw JsonSyntaxException("Missing key")) ?: throw JsonSyntaxException("Invalid key value: ${json["key"]}")
|
||||
}
|
||||
|
||||
// sometimes kotlin compiler is dumb
|
||||
private fun key() = key
|
||||
|
||||
private val matterTuple by lazy {
|
||||
MatterManager.getValue(ForgeRegistries.ITEMS.getValue(key()) ?: throw NullPointerException("${key()} does not point to anything"))
|
||||
override fun provideValues(): MatterManager.Result {
|
||||
return MatterManager.compute(ForgeRegistries.ITEMS.getValue(key) ?: throw NullPointerException("$key does not point to anything"))
|
||||
}
|
||||
|
||||
override val matter: ImpreciseFraction
|
||||
get() = matterTuple.matter
|
||||
override val complexity: Double
|
||||
get() = matterTuple.complexity
|
||||
|
||||
override fun toJson(): JsonObject {
|
||||
return super.toJson().also {
|
||||
it["type"] = "key"
|
||||
@ -170,18 +168,10 @@ class ComputeAction : AbstractRegistryAction {
|
||||
tag = ItemTags.create(ResourceLocation.tryParse(json["tag"]?.asString ?: throw JsonSyntaxException("Missing tag")) ?: throw JsonSyntaxException("Invalid tag value: ${json["tag"]}"))
|
||||
}
|
||||
|
||||
// sometimes kotlin compiler is dumb
|
||||
private fun tag() = tag
|
||||
|
||||
private val matterTuple by lazy {
|
||||
MatterManager.searchTagEntry(tag())
|
||||
override fun provideValues(): MatterManager.Result {
|
||||
return MatterManager.compute(tag)
|
||||
}
|
||||
|
||||
override val matter: ImpreciseFraction?
|
||||
get() = matterTuple?.matter
|
||||
override val complexity: Double?
|
||||
get() = matterTuple?.complexity
|
||||
|
||||
override fun toJson(): JsonObject {
|
||||
return super.toJson().also {
|
||||
it["type"] = "tag"
|
||||
@ -238,16 +228,20 @@ class ComputeAction : AbstractRegistryAction {
|
||||
values = ImmutableList.copyOf(value)
|
||||
}
|
||||
|
||||
private fun values() = values
|
||||
|
||||
val computed: IMatterValue? by lazy {
|
||||
internal fun tryCompute(): MatterManager.Result {
|
||||
var compute: IMatterValue = IMatterValue.Companion
|
||||
|
||||
for (fn in values()) {
|
||||
compute = fn.update(compute) ?: return@lazy null
|
||||
for (fn in values) {
|
||||
val result = fn.update(compute)
|
||||
|
||||
if (result.value == null) {
|
||||
return result
|
||||
} else {
|
||||
compute = result.value
|
||||
}
|
||||
}
|
||||
|
||||
compute
|
||||
return MatterManager.Result(compute)
|
||||
}
|
||||
|
||||
constructor(json: JsonObject) : super(json) {
|
||||
@ -280,7 +274,7 @@ class ComputeAction : AbstractRegistryAction {
|
||||
}
|
||||
}
|
||||
|
||||
override fun update(registry: MutableCollection<MatterManager.MutableEntry>, modifier: ResourceLocation) {
|
||||
override fun update(registry: MutableCollection<MutableEntry>, modifier: ResourceLocation) {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ class DeleteAction : AbstractRegistryAction {
|
||||
}
|
||||
}
|
||||
|
||||
override fun update(registry: MutableCollection<MatterManager.MutableEntry>, modifier: ResourceLocation) {
|
||||
override fun update(registry: MutableCollection<MutableEntry>, modifier: ResourceLocation) {
|
||||
if (!checkConditions()) {
|
||||
return
|
||||
}
|
||||
@ -37,7 +37,7 @@ class DeleteAction : AbstractRegistryAction {
|
||||
val iterator = registry.iterator()
|
||||
|
||||
for (value in iterator) {
|
||||
if (value is MatterManager.MutableKeyEntry && value.key == key) {
|
||||
if (value is MutableKeyEntry && value.key == key) {
|
||||
iterator.remove()
|
||||
return
|
||||
}
|
||||
@ -48,7 +48,7 @@ class DeleteAction : AbstractRegistryAction {
|
||||
val iterator = registry.iterator()
|
||||
|
||||
for (value in iterator) {
|
||||
if (value is MatterManager.MutableTagEntry && value.tag == tag) {
|
||||
if (value is MutableTagEntry && value.tag == tag) {
|
||||
iterator.remove()
|
||||
return
|
||||
}
|
||||
|
@ -54,7 +54,7 @@ class InsertAction : AbstractRegistryAction {
|
||||
}
|
||||
}
|
||||
|
||||
override fun update(registry: MutableCollection<MatterManager.MutableEntry>, modifier: ResourceLocation) {
|
||||
override fun update(registry: MutableCollection<MutableEntry>, modifier: ResourceLocation) {
|
||||
checkNotNull(matter) { "Missing matter value, which is required for insert action (for $modifier)" }
|
||||
checkNotNull(complexity) { "Missing complexity value, which is required for insert action (for $modifier)" }
|
||||
|
||||
@ -68,7 +68,7 @@ class InsertAction : AbstractRegistryAction {
|
||||
if (replaceIfExists) {
|
||||
// search & replace in parallel, if possible
|
||||
val replaced = registry.probablyParallelStream().anyMatch {
|
||||
if (it is MatterManager.MutableKeyEntry && it.key == key) {
|
||||
if (it is MutableKeyEntry && it.key == key) {
|
||||
if (!comparePriority || it.priority < priority!!) {
|
||||
it.matter = matter
|
||||
it.complexity = complexity
|
||||
@ -85,7 +85,7 @@ class InsertAction : AbstractRegistryAction {
|
||||
|
||||
if (!replaced) {
|
||||
registry.add(
|
||||
MatterManager.MutableKeyEntry(
|
||||
MutableKeyEntry(
|
||||
key,
|
||||
mutableListOf(modifier),
|
||||
matter,
|
||||
@ -95,13 +95,13 @@ class InsertAction : AbstractRegistryAction {
|
||||
)
|
||||
}
|
||||
} else {
|
||||
if (registry.probablyParallelStream().anyMatch { it is MatterManager.MutableKeyEntry && it.key == key }) {
|
||||
if (registry.probablyParallelStream().anyMatch { it is MutableKeyEntry && it.key == key }) {
|
||||
fail { "Value with key $key already exists" }
|
||||
return
|
||||
}
|
||||
|
||||
registry.add(
|
||||
MatterManager.MutableKeyEntry(
|
||||
MutableKeyEntry(
|
||||
key,
|
||||
mutableListOf(modifier),
|
||||
matter,
|
||||
@ -114,7 +114,7 @@ class InsertAction : AbstractRegistryAction {
|
||||
if (replaceIfExists) {
|
||||
// search & replace in parallel, if possible
|
||||
val replaced = registry.probablyParallelStream().anyMatch {
|
||||
if (it is MatterManager.MutableTagEntry && it.tag == tag) {
|
||||
if (it is MutableTagEntry && it.tag == tag) {
|
||||
if (!comparePriority || it.priority < priority!!) {
|
||||
it.matter = matter
|
||||
it.complexity = complexity
|
||||
@ -131,7 +131,7 @@ class InsertAction : AbstractRegistryAction {
|
||||
|
||||
if (!replaced) {
|
||||
registry.add(
|
||||
MatterManager.MutableTagEntry(
|
||||
MutableTagEntry(
|
||||
tag!!,
|
||||
mutableListOf(modifier),
|
||||
matter,
|
||||
@ -141,13 +141,13 @@ class InsertAction : AbstractRegistryAction {
|
||||
)
|
||||
}
|
||||
} else {
|
||||
if (registry.probablyParallelStream().anyMatch { it is MatterManager.MutableTagEntry && it.tag == tag }) {
|
||||
if (registry.probablyParallelStream().anyMatch { it is MutableTagEntry && it.tag == tag }) {
|
||||
fail { "Value with tag $tag already exists" }
|
||||
return
|
||||
}
|
||||
|
||||
registry.add(
|
||||
MatterManager.MutableTagEntry(
|
||||
MutableTagEntry(
|
||||
tag!!,
|
||||
mutableListOf(modifier),
|
||||
matter,
|
||||
|
@ -17,7 +17,7 @@ import java.util.function.Consumer
|
||||
open class MatterDataProvider(protected val dataGenerator: DataGenerator, val namespace: String?) : DataProvider {
|
||||
constructor(event: GatherDataEvent) : this(event.generator, event.modContainer.namespace)
|
||||
|
||||
protected val pathProvider: DataGenerator.PathProvider = dataGenerator.createPathProvider(DataGenerator.Target.DATA_PACK, MatterManager.DIRECTORY)
|
||||
protected val pathProvider: DataGenerator.PathProvider = dataGenerator.createPathProvider(DataGenerator.Target.DATA_PACK, MatterManager.MATTER_DIRECTORY)
|
||||
protected val actions = LinkedHashMap<ResourceLocation, AbstractRegistryAction>()
|
||||
|
||||
sealed class Configuration(val name: ResourceLocation) {
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -39,609 +39,3 @@ import ru.dbotthepony.mc.otm.core.registryName
|
||||
import ru.dbotthepony.mc.otm.core.stream
|
||||
import ru.dbotthepony.mc.otm.registry.RegistryDelegate
|
||||
import java.util.stream.Stream
|
||||
|
||||
/**
|
||||
* This manager holds json defined recipe resolvers (they process their own)
|
||||
*/
|
||||
object RecipeResolverManager : SimpleJsonResourceReloadListener(GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create(), "otm_recipe_resolver") {
|
||||
/**
|
||||
* because it is immutable
|
||||
*/
|
||||
data class ImmutableStack(
|
||||
val item: Item,
|
||||
val multiplier: Double = 1.0
|
||||
) {
|
||||
constructor(item: Item, count: Int) : this(item, 1.0 / count.toDouble())
|
||||
constructor(item: ItemStack) : this(item.item, item.count)
|
||||
}
|
||||
|
||||
class ResolvedRecipe(
|
||||
inputs: Stream<Stream<ImmutableStack>>,
|
||||
val output: ImmutableStack,
|
||||
val transformMatterValue: (ImpreciseFraction) -> ImpreciseFraction = { it },
|
||||
val transformComplexity: (Double) -> Double = { it },
|
||||
|
||||
/**
|
||||
* if we can't resolve one of ingredient's matter value - consider output also having no matter value
|
||||
*/
|
||||
val isCritical: Boolean = true,
|
||||
val name: ResourceLocation? = null,
|
||||
|
||||
/**
|
||||
* Whenever to allow "backtracking" recipe ingredients to recipe outputs.
|
||||
*
|
||||
* When an item has no recipes, but takes part in recipes with output of already determined matter value
|
||||
* (e.g. 4 rabbit hides -> 1 leather), we can assume that we can determine matter value of ingredients based on matter value of output
|
||||
*
|
||||
* "Backtrack" can happen if everything of the next holds true:
|
||||
* * All recipes with item in question contains only that item; and
|
||||
* * All recipes allow backtracking
|
||||
*/
|
||||
val allowBacktrack: Boolean = true,
|
||||
) {
|
||||
val inputs: List<List<ImmutableStack>> = inputs
|
||||
.map { it.filter { it.multiplier > 0.0 }.collect(ImmutableList.toImmutableList()) }
|
||||
.filter { it.isNotEmpty() }
|
||||
.collect(ImmutableList.toImmutableList())
|
||||
|
||||
constructor(
|
||||
inputs: Collection<Collection<ImmutableStack>>,
|
||||
output: ImmutableStack,
|
||||
transformMatterValue: (ImpreciseFraction) -> ImpreciseFraction = { it },
|
||||
transformComplexity: (Double) -> Double = { it },
|
||||
) : this(inputs.stream().map { it.stream() }, output, transformMatterValue, transformComplexity)
|
||||
|
||||
val formattedName: String
|
||||
get() = if (name == null) "One of recipes" else "Recipe '$name'"
|
||||
}
|
||||
|
||||
fun interface Finder {
|
||||
/**
|
||||
* Returned stream can be either parallel or sequential
|
||||
*
|
||||
* Parallel streams will _greatly_ improve performance on mid-end range computers,
|
||||
* but you need to make sure your stream has no side effects (such as binding late-binding tags)
|
||||
*
|
||||
* Internally, OTM separate parallel streams from sequential so sequential streams happen strictly on server thread
|
||||
* (if you can't avoid side effects, or don't bother with synchronization).
|
||||
*
|
||||
* You can safely call [MatterManager.getDirect] inside returned stream, both parallel and sequential.
|
||||
*/
|
||||
fun find(server: MinecraftServer, json: JsonObject): Stream<ResolvedRecipe>
|
||||
}
|
||||
|
||||
private val delegate = RegistryDelegate<Finder>("recipe_resolver") {
|
||||
disableSync()
|
||||
disableSaving()
|
||||
}
|
||||
|
||||
private val registrar = DeferredRegister.create(delegate.key, OverdriveThatMatters.MOD_ID)
|
||||
private val LOGGER = LogManager.getLogger()
|
||||
|
||||
init {
|
||||
registrar.register("simple") {
|
||||
Finder { server, data ->
|
||||
val location = (data["recipe_type"] ?: throw JsonSyntaxException("Missing recipe type")).let { ResourceLocation.tryParse(it.asString) } ?: throw JsonSyntaxException("Invalid recipe type: ${data["recipe_type"]}")
|
||||
|
||||
if (!ForgeRegistries.RECIPE_TYPES.containsKey(location)) {
|
||||
LOGGER.error("Invalid or missing recipe category: $location!")
|
||||
return@Finder Stream.empty()
|
||||
}
|
||||
|
||||
val findRecipeType = ForgeRegistries.RECIPE_TYPES.getValue(location) as RecipeType<Recipe<Container>>? ?: throw ConcurrentModificationException()
|
||||
|
||||
val isCritical = data["is_critical"]?.asBoolean ?: true
|
||||
val ignoreDamageables = data["ignore_damageables"]?.asBoolean ?: false
|
||||
val allowBacktrack = data["allow_backtrack"]?.asBoolean ?: true
|
||||
|
||||
var stream = server.recipeManager.byType(findRecipeType).values.parallelStream().filter { !it.isIncomplete }
|
||||
|
||||
if (ignoreDamageables) {
|
||||
stream = stream.filter { it.ingredients.stream().flatMap { it.items.stream() }.noneMatch { it.isDamageableItem } }
|
||||
}
|
||||
|
||||
stream.map {
|
||||
ResolvedRecipe(
|
||||
it.ingredients.stream()
|
||||
.filter { !it.isActuallyEmpty }
|
||||
.map { it.items.stream().map(::ImmutableStack) },
|
||||
ImmutableStack(it.resultItem),
|
||||
isCritical = isCritical,
|
||||
name = it.id,
|
||||
allowBacktrack = allowBacktrack
|
||||
)
|
||||
}
|
||||
.filter {
|
||||
if (it.inputs.isEmpty()) {
|
||||
LOGGER.warn("${it.formattedName} with output '${it.output.item.registryName}' ended up with no inputs!")
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun initialize(bus: IEventBus) {
|
||||
bus.addListener(delegate::build)
|
||||
registrar.register(bus)
|
||||
}
|
||||
|
||||
fun reloadEvent(event: AddReloadListenerEvent) {
|
||||
event.addListener(this)
|
||||
}
|
||||
|
||||
fun onServerStarted(event: ServerStartedEvent) {
|
||||
resolve(event.server)
|
||||
}
|
||||
|
||||
@JvmStatic val RESOLVERS get() = delegate.get()
|
||||
@JvmStatic val RESOLVERS_KEY get() = delegate.key
|
||||
|
||||
/**
|
||||
* it exists solely for fail-fast to find bugs, do not rely on this property
|
||||
*/
|
||||
var ready = false
|
||||
private set
|
||||
|
||||
var resolved = false
|
||||
private set
|
||||
|
||||
private var foundResolvers: Map<ResourceLocation, Pair<Finder, JsonObject>> = ImmutableMap.of()
|
||||
|
||||
override fun prepare(
|
||||
p_10771_: ResourceManager,
|
||||
p_10772_: ProfilerFiller
|
||||
): MutableMap<ResourceLocation, JsonElement> {
|
||||
ready = false
|
||||
resolved = false
|
||||
// determinedValues.clear() // can't clear here, it is executed on separate thread
|
||||
return super.prepare(p_10771_, p_10772_)
|
||||
}
|
||||
|
||||
override fun apply(
|
||||
map: Map<ResourceLocation, JsonElement>,
|
||||
resourceManager: ResourceManager,
|
||||
profilerFiller: ProfilerFiller
|
||||
) {
|
||||
ready = false
|
||||
resolved = false
|
||||
determinedValues.clear()
|
||||
commentary.clear()
|
||||
|
||||
val builder = ImmutableMap.Builder<ResourceLocation, Pair<Finder, JsonObject>>()
|
||||
|
||||
for ((key, json) in map) {
|
||||
if (json !is JsonObject) {
|
||||
throw JsonParseException("$key is not a json object")
|
||||
}
|
||||
|
||||
val location = (json["type"] ?: throw JsonSyntaxException("Missing resolver type")).let { ResourceLocation.tryParse(it.asString) } ?: throw JsonSyntaxException("Invalid resolver type: ${json["type"]}")
|
||||
|
||||
if (!RESOLVERS.containsKey(location)) {
|
||||
throw JsonParseException("Resolver type $location does not exist (in $key)")
|
||||
}
|
||||
|
||||
val resolver = RESOLVERS.getValue(location) ?: throw ConcurrentModificationException()
|
||||
builder.put(key, resolver to json)
|
||||
}
|
||||
|
||||
foundResolvers = builder.build()
|
||||
|
||||
ready = true
|
||||
}
|
||||
|
||||
private data class Accumulator(
|
||||
val input2Recipes: Reference2ObjectOpenHashMap<Item, ReferenceOpenHashSet<ResolvedRecipe>> = Reference2ObjectOpenHashMap(),
|
||||
val output2Recipes: Reference2ObjectOpenHashMap<Item, ReferenceOpenHashSet<ResolvedRecipe>> = Reference2ObjectOpenHashMap(),
|
||||
) {
|
||||
val heuristicsSize: Long
|
||||
get() {
|
||||
return input2Recipes.size.toLong() + output2Recipes.size.toLong()
|
||||
}
|
||||
|
||||
fun combine(other: Accumulator) {
|
||||
for ((k, v) in other.input2Recipes) {
|
||||
val existing = input2Recipes[k]
|
||||
|
||||
if (existing == null) {
|
||||
input2Recipes[k] = v
|
||||
} else {
|
||||
existing.addAll(v)
|
||||
}
|
||||
}
|
||||
|
||||
for ((k, v) in other.output2Recipes) {
|
||||
val existing = output2Recipes[k]
|
||||
|
||||
if (existing == null) {
|
||||
output2Recipes[k] = v
|
||||
} else {
|
||||
existing.addAll(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun add(recipe: ResolvedRecipe) {
|
||||
output2Recipes.computeIfAbsent(recipe.output.item, Reference2ObjectFunction { ReferenceOpenHashSet() }).add(recipe)
|
||||
|
||||
for (input1 in recipe.inputs) {
|
||||
for (input in input1) {
|
||||
input2Recipes.computeIfAbsent(input.item, Reference2ObjectFunction { ReferenceOpenHashSet() }).add(recipe)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getDetermined(index: Item): IMatterValue {
|
||||
return determinedValues[index] ?: IMatterValue.Companion
|
||||
}
|
||||
|
||||
private var input2Recipes: Map<Item, Collection<ResolvedRecipe>> = mapOf()
|
||||
private var output2Recipes: Map<Item, Collection<ResolvedRecipe>> = mapOf()
|
||||
|
||||
private val determinedValues = Reference2ObjectOpenHashMap<Item, IMatterValue>()
|
||||
private val commentary = Reference2ObjectOpenHashMap<Item, Component>()
|
||||
private val seenItems = ArrayDeque<Item>()
|
||||
|
||||
fun getCommentary(item: Item): MutableComponent? {
|
||||
return commentary[item]?.copy()
|
||||
}
|
||||
|
||||
private data class Result(val type: Type, val value: IMatterValue? = null) {
|
||||
constructor(value: IMatterValue) : this(Type.RESOLVED, value)
|
||||
|
||||
val isSkipped get() = type === Type.SKIPPED
|
||||
val isMissing get() = type === Type.MISSING
|
||||
|
||||
enum class Type {
|
||||
RESOLVED, SKIPPED, MISSING
|
||||
}
|
||||
|
||||
companion object {
|
||||
// recursive recipes should be ignored until we resolve everything else
|
||||
val SKIPPED = Result(Type.SKIPPED)
|
||||
|
||||
// missing matter values are fatal, whole recipe chain is then considered defunct
|
||||
val MISSING = Result(Type.MISSING)
|
||||
}
|
||||
}
|
||||
|
||||
private var changes = false
|
||||
private var cachedIterationResults = Reference2ObjectOpenHashMap<Item, Result>()
|
||||
|
||||
private fun doTryToBacktrack(item: Item, makeCommentary: Boolean): Result {
|
||||
val recipes = input2Recipes[item]
|
||||
|
||||
if (recipes == null || recipes.isEmpty() || !recipes.all { it.allowBacktrack } || !recipes.all { it.inputs.all { it.all { it.item == item } } }) {
|
||||
if (makeCommentary)
|
||||
commentary[item] = TextComponent("Item '${item.registryName}' has no recipes")
|
||||
|
||||
return Result.MISSING
|
||||
}
|
||||
|
||||
var minimal: IMatterValue? = null
|
||||
var minimalMultiplier = 0.0
|
||||
var foundRecipe: ResolvedRecipe? = null
|
||||
|
||||
for (recipe in recipes) {
|
||||
val value = doDetermineValue(recipe.output.item)
|
||||
|
||||
if (value.value != null) {
|
||||
if (minimal == null || minimal < value.value) {
|
||||
minimal = value.value
|
||||
minimalMultiplier = recipe.output.multiplier / recipe.inputs.size
|
||||
foundRecipe = recipe
|
||||
}
|
||||
} else if (!value.isSkipped) {
|
||||
LOGGER.error("${item.registryName} cant resolve ${recipe.output.item}")
|
||||
minimal = null
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (minimal == null) {
|
||||
if (makeCommentary)
|
||||
commentary[item] = TextComponent("Item '${item.registryName}' has no valid backtracking recipes, and no output recipes")
|
||||
|
||||
return Result.MISSING
|
||||
}
|
||||
|
||||
val result = MatterValue(minimal.matter * minimalMultiplier, minimal.complexity * minimalMultiplier)
|
||||
|
||||
changes = true
|
||||
determinedValues[item] = result
|
||||
commentary[item] = TextComponent("Matter value backtracked from ${foundRecipe!!.formattedName}")
|
||||
return Result(result)
|
||||
}
|
||||
|
||||
private fun tryToBacktrack(item: Item, makeCommentary: Boolean): Result {
|
||||
val getResult = cachedIterationResults[item]
|
||||
|
||||
if (getResult != null) {
|
||||
return getResult
|
||||
}
|
||||
|
||||
var value: IMatterValue? = MatterManager.getDirect(item)
|
||||
|
||||
if (value?.hasMatterValue == true) {
|
||||
return Result(value)
|
||||
}
|
||||
|
||||
value = determinedValues[item]
|
||||
|
||||
if (value?.hasMatterValue == true) {
|
||||
return Result(value)
|
||||
}
|
||||
|
||||
if (item in seenItems && item != seenItems.last()) {
|
||||
return Result.SKIPPED
|
||||
}
|
||||
|
||||
seenItems.addLast(item)
|
||||
|
||||
try {
|
||||
val result = doTryToBacktrack(item, makeCommentary)
|
||||
cachedIterationResults[item] = result
|
||||
return result
|
||||
} finally {
|
||||
seenItems.removeLast()
|
||||
}
|
||||
}
|
||||
|
||||
private fun doDetermineValue(item: Item): Result {
|
||||
var minimalMatter: ImpreciseFraction? = null
|
||||
var minimalComplexity: Double? = null
|
||||
|
||||
val recipes = output2Recipes[item]
|
||||
|
||||
if (recipes == null || recipes.isEmpty()) {
|
||||
return tryToBacktrack(item, true)
|
||||
}
|
||||
|
||||
var hadSkips = false
|
||||
var minimalRecipe: ResolvedRecipe? = null
|
||||
|
||||
recipesLoop@ for (recipe in recipes) {
|
||||
if (recipe.inputs.isEmpty()) {
|
||||
// TODO: should we ignore empty recipes?
|
||||
commentary[item] = TextComponent("${recipe.formattedName} is empty")
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
var accumulatedMatter: ImpreciseFraction? = null
|
||||
var accumulatedComplexity: Double? = null
|
||||
|
||||
inputsLoop@ for ((i, inputs) in recipe.inputs.withIndex()) {
|
||||
var minimal: IMatterValue? = null
|
||||
var minimalMultiplier = 0.0
|
||||
|
||||
for (input in inputs) {
|
||||
val ivalue = determineValue(input.item)
|
||||
|
||||
if (ivalue.isMissing) {
|
||||
commentary[item] = TextComponent("Input '${input.item.registryName}' at input slot $i in ${recipe.formattedName} has no matter value")
|
||||
|
||||
if (recipe.isCritical) {
|
||||
return Result.MISSING
|
||||
} else {
|
||||
continue@recipesLoop
|
||||
}
|
||||
} else if (ivalue.isSkipped) {
|
||||
commentary[item] = TextComponent("Input '${input.item.registryName}' at input slot $i in ${recipe.formattedName} is recursive")
|
||||
hadSkips = true
|
||||
continue@recipesLoop
|
||||
}
|
||||
|
||||
if (minimal == null || minimal > ivalue.value!!) {
|
||||
minimal = ivalue.value
|
||||
minimalMultiplier = input.multiplier
|
||||
}
|
||||
}
|
||||
|
||||
if (minimal == null || !minimal.hasMatterValue) {
|
||||
commentary[item] = TextComponent("'${recipe.formattedName}' has invalid input at slot $i (possible inputs: ${inputs.joinToString(", ", transform = { it.item.registryName.toString() }) }) (???)")
|
||||
return Result.MISSING
|
||||
}
|
||||
|
||||
if (accumulatedMatter == null || accumulatedComplexity == null) {
|
||||
accumulatedMatter = minimal.matter * minimalMultiplier
|
||||
accumulatedComplexity = minimal.complexity * minimalMultiplier
|
||||
} else {
|
||||
accumulatedMatter += minimal.matter * minimalMultiplier
|
||||
accumulatedComplexity += minimal.complexity * minimalMultiplier
|
||||
}
|
||||
}
|
||||
|
||||
if (accumulatedMatter == null || accumulatedComplexity == null) {
|
||||
throw RuntimeException("This piece should be unreachable")
|
||||
}
|
||||
|
||||
if (!accumulatedMatter.isPositive || accumulatedComplexity <= 0.0) {
|
||||
commentary[item] = TextComponent("${recipe.formattedName} ended up with negative matter value and/or complexity (???)")
|
||||
return Result.MISSING
|
||||
}
|
||||
|
||||
if (minimalMatter == null || minimalMatter > accumulatedMatter) {
|
||||
minimalMatter = recipe.transformMatterValue(accumulatedMatter * recipe.output.multiplier)
|
||||
minimalComplexity = recipe.transformComplexity(accumulatedComplexity * recipe.output.multiplier)
|
||||
minimalRecipe = recipe
|
||||
}
|
||||
}
|
||||
|
||||
if (minimalMatter == null || minimalComplexity == null) {
|
||||
if (hadSkips) {
|
||||
val backtrack = tryToBacktrack(item, false)
|
||||
|
||||
if (backtrack.value == null) {
|
||||
return Result.SKIPPED
|
||||
} else {
|
||||
return backtrack
|
||||
}
|
||||
}
|
||||
|
||||
if (item !in commentary)
|
||||
commentary[item] = TextComponent("'${item.registryName}' ended up with no valid recipes (???)")
|
||||
|
||||
return Result.MISSING
|
||||
}
|
||||
|
||||
val result = MatterValue(minimalMatter, minimalComplexity)
|
||||
|
||||
changes = true
|
||||
determinedValues[item] = result
|
||||
commentary[item] = TextComponent("Matter value derived from ${minimalRecipe!!.formattedName}")
|
||||
|
||||
return Result(result)
|
||||
}
|
||||
|
||||
private fun determineValue(item: Item): Result {
|
||||
val getResult = cachedIterationResults[item]
|
||||
|
||||
if (getResult != null) {
|
||||
return getResult
|
||||
}
|
||||
|
||||
var value: IMatterValue? = MatterManager.getDirect(item)
|
||||
|
||||
if (value?.hasMatterValue == true) {
|
||||
return Result(value)
|
||||
}
|
||||
|
||||
value = determinedValues[item]
|
||||
|
||||
if (value?.hasMatterValue == true) {
|
||||
return Result(value)
|
||||
}
|
||||
|
||||
if (item in seenItems) {
|
||||
return Result.SKIPPED
|
||||
}
|
||||
|
||||
seenItems.addLast(item)
|
||||
|
||||
try {
|
||||
val result = doDetermineValue(item)
|
||||
cachedIterationResults[item] = result
|
||||
return result
|
||||
} finally {
|
||||
seenItems.removeLast()
|
||||
}
|
||||
}
|
||||
|
||||
private fun resolve(server: MinecraftServer) {
|
||||
if (resolved) {
|
||||
return
|
||||
}
|
||||
|
||||
check(ready) { "RecipeResolverManager is not ready to resolve matter values" }
|
||||
check(MatterManager.canCompute) { "MatteryManager is not ready to compute matter values" }
|
||||
|
||||
var time = SystemTime()
|
||||
LOGGER.info("Finding recipes...")
|
||||
|
||||
determinedValues.clear()
|
||||
seenItems.clear()
|
||||
commentary.clear()
|
||||
|
||||
val foundStreams = foundResolvers.map {
|
||||
try {
|
||||
it.value.first.find(server, it.value.second)
|
||||
} catch(err: Throwable) {
|
||||
LOGGER.fatal("Recipe resolver ${it.key} experienced internal error", err)
|
||||
throw RuntimeException("Recipe resolver ${it.key} experienced internal error", err)
|
||||
}
|
||||
}
|
||||
|
||||
val sequentialStreams = foundStreams.filter { !it.isParallel }
|
||||
val parallelStreams = foundStreams.filter { it.isParallel }
|
||||
|
||||
val streamA = Streams.concat(*sequentialStreams.toTypedArray())
|
||||
val streamB = Streams.concat(*parallelStreams.toTypedArray())
|
||||
|
||||
val resultA = streamA.collect(::Accumulator, Accumulator::add, Accumulator::combine)
|
||||
val resultB = streamB.collect(::Accumulator, Accumulator::add, Accumulator::combine)
|
||||
|
||||
val result: Accumulator
|
||||
|
||||
if (resultA.heuristicsSize > resultB.heuristicsSize) {
|
||||
result = resultA
|
||||
resultA.combine(resultB)
|
||||
} else {
|
||||
result = resultB
|
||||
resultB.combine(resultA)
|
||||
}
|
||||
|
||||
val (input2Recipes, output2Recipes) = result
|
||||
|
||||
LOGGER.info("Finding recipes took ${time.millis}ms, involving ${input2Recipes.keys.size} unique inputs and ${output2Recipes.keys.size} unique outputs")
|
||||
LOGGER.info("Resolving recipes...")
|
||||
|
||||
this.input2Recipes = input2Recipes
|
||||
this.output2Recipes = output2Recipes
|
||||
|
||||
changes = true
|
||||
var i = 0
|
||||
var ops = 0
|
||||
|
||||
val toDetermine = ReferenceLinkedOpenHashSet<Item>()
|
||||
toDetermine.addAll(input2Recipes.keys)
|
||||
toDetermine.addAll(output2Recipes.keys)
|
||||
|
||||
if (LOGGER.isDebugEnabled) {
|
||||
LOGGER.debug("Gonna try to search for matter values for next items:")
|
||||
|
||||
val names = ArrayList<String>()
|
||||
var length = 0
|
||||
|
||||
for (value in toDetermine) {
|
||||
val name = value.registryName!!.toString()
|
||||
|
||||
if (length != 0 && length + name.length < 400) {
|
||||
names.add(name)
|
||||
length += name.length
|
||||
} else if (length == 0) {
|
||||
names.add(name)
|
||||
length = name.length
|
||||
} else {
|
||||
LOGGER.debug(names.joinToString(", "))
|
||||
names.clear()
|
||||
length = 0
|
||||
}
|
||||
}
|
||||
|
||||
if (names.isNotEmpty()) {
|
||||
LOGGER.debug(names.joinToString(", "))
|
||||
}
|
||||
}
|
||||
|
||||
time = SystemTime()
|
||||
|
||||
while (changes) {
|
||||
ops += cachedIterationResults.size
|
||||
cachedIterationResults = Reference2ObjectOpenHashMap()
|
||||
changes = false
|
||||
i++
|
||||
|
||||
val iterator = toDetermine.iterator()
|
||||
|
||||
for (value in iterator) {
|
||||
if (determineValue(value).value?.hasMatterValue == true) {
|
||||
iterator.remove()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (item in toDetermine) {
|
||||
if (item !in commentary) {
|
||||
commentary[item] = TextComponent("Item ${item.registryName} was never visited")
|
||||
}
|
||||
}
|
||||
|
||||
resolved = true
|
||||
|
||||
LOGGER.info("Resolving recipes took ${time.millis}ms in $i iterations with ~$ops operations, determined ${determinedValues.size} matter values")
|
||||
}
|
||||
}
|
||||
|
@ -123,7 +123,7 @@ class UpdateAction : AbstractRegistryAction {
|
||||
}
|
||||
}
|
||||
|
||||
fun apply(value: MatterManager.MutableEntry, modifier: ResourceLocation) {
|
||||
private fun apply(value: MutableEntry, modifier: ResourceLocation) {
|
||||
if (matterFunctions.isNotEmpty())
|
||||
value.matter = matterFunctions.apply(value.matter)
|
||||
else if (matter != null)
|
||||
@ -142,7 +142,7 @@ class UpdateAction : AbstractRegistryAction {
|
||||
value.modificationChain.add(modifier)
|
||||
}
|
||||
|
||||
override fun update(registry: MutableCollection<MatterManager.MutableEntry>, modifier: ResourceLocation) {
|
||||
override fun update(registry: MutableCollection<MutableEntry>, modifier: ResourceLocation) {
|
||||
if(!(
|
||||
matter != null ||
|
||||
complexity != null ||
|
||||
@ -174,7 +174,7 @@ class UpdateAction : AbstractRegistryAction {
|
||||
val iterator = registry.iterator()
|
||||
|
||||
for (value in iterator) {
|
||||
if (value is MatterManager.MutableKeyEntry && value.key == key) {
|
||||
if (value is MutableKeyEntry && value.key == key) {
|
||||
apply(value, modifier)
|
||||
return
|
||||
}
|
||||
@ -185,7 +185,7 @@ class UpdateAction : AbstractRegistryAction {
|
||||
val iterator = registry.iterator()
|
||||
|
||||
for (value in iterator) {
|
||||
if (value is MatterManager.MutableTagEntry && value.tag == tag) {
|
||||
if (value is MutableTagEntry && value.tag == tag) {
|
||||
apply(value, modifier)
|
||||
return
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user