Skip to content

Latest commit

 

History

History
211 lines (140 loc) · 8.46 KB

File metadata and controls

211 lines (140 loc) · 8.46 KB

Machine runtime & recipe execution architecture

This page describes the runtime skeleton of a machine and the recipe execution architecture in PrototypeMachinery.

Docs follow the current implementation. If you find a mismatch, treat the code as the source of truth.


Chinese original:

Code locations

  • Types and runtime instances:

    • src/main/kotlin/api/machine/*
    • src/main/kotlin/impl/machine/*
    • Block entity: src/main/kotlin/common/block/entity/MachineBlockEntity.kt
  • Component system (ECS variant):

    • src/main/kotlin/api/machine/component/*
    • src/main/kotlin/impl/machine/component/*
  • Recipes and processes:

    • src/main/kotlin/api/recipe/*
    • src/main/kotlin/impl/recipe/*

From MachineType to MachineInstance

  • MachineType: the definition of "what this machine is" (structure, component types, metadata, ...)
  • MachineBlockEntity.initialize(machineType): creates and binds a MachineInstanceImpl
  • Formed state: when a multiblock match succeeds, the instance updates its formed status, used by rendering and runtime permission checks.

Component system (MachineComponent / MachineSystem)

  • Components are declared and constructed via MachineComponentType.
  • MachineSystem provides tick-driving and system ordering (dependency relations -> topological sorting).
  • Some components can have system == null, meaning they are data-only and not ticked.

Recipe model (MachineRecipe / RecipeProcess)

  • MachineRecipe: the recipe definition, primarily a list of requirements grouped by requirement type.
  • RecipeProcess: a runtime process instance.
    • Holds a seed to allow reproducible randomness.
    • Has a dedicated attribute overlay (attributeMap overlay per process).

Requirement layer (transactional requirement systems)

RecipeRequirementType is bound to a RecipeRequirementSystem.

Core API:

  • src/main/kotlin/api/recipe/requirement/component/system/RecipeRequirementSystem.kt
    • start(process, component): RequirementTransaction
    • acquireTickTransaction(process, component): RequirementTransaction (optional; for tickable requirements)
    • onEnd(process, component): RequirementTransaction

Transaction semantics (important)

  • Obtaining a transaction (start/tick/end) may produce immediate side effects, e.g.

    • reserving items
    • draining energy
    • writing temporary state
  • The caller must decide based on RequirementTransaction.result:

    • Success: must call commit()
    • Blocked: must call commit() as well
      • and the system must guarantee "no side effects" or "recoverable side effects" semantics to avoid deadlocks
    • Failure: must call rollback()
  • The executor must treat a set of requirements within the same stage as one atomic stage. If any requirement returns Blocked / Failure, it must rollback all transactions already acquired in this stage.

Default executor implementation:

  • src/main/kotlin/impl/machine/component/system/FactoryRecipeProcessorSystem.kt
    • stages: START -> (TICK...)* -> END
    • collects transactions per stage; commits all on success; rolls back (reverse order) on failure/blocked
    • stable ordering (by type id) to keep behavior reproducible and tests deterministic

This layer is the main extension point for complex behaviors (input/output, chance, multipliers, candidates, ...).

Requirement overlay (process-level overrides)

Each RecipeProcess can carry an overlay; requirement execution resolves the "effective component" before running.

  • Entry: src/main/kotlin/impl/recipe/requirement/overlay/RecipeRequirementOverlay.kt
  • Overlay components: impl/recipe/process/component/RecipeOverlayProcessComponent*

Typical uses:

  • Apply different consumption multipliers / filters per process instance
  • Provide independent parameter views for parallel processes

Process components / systems

RecipeProcess also supports a component system. Each RecipeProcessComponentType may bind a process system, executed each machine tick (pre/tick/post).

  • FactoryRecipeProcessorSystem calls tickProcessComponents(process, Phase.*) per tick
  • Lifecycle helper component:
    • impl/recipe/process/component/RecipeLifecycleStateProcessComponent* (e.g. flags like started)

Recipe indexing

To avoid scanning all recipes frequently, the project uses indexing:

  • IRecipeIndexRegistry / RecipeIndexRegistry
  • RequirementIndexFactory builds indexes for each requirement type

Note: this “recipe indexing” refers to runtime scanning acceleration (shrinking the candidate set), not JEI IIngredients indexing (search / lookup).

Goals & constraints

  • Goal: reduce the number of recipes that need expensive simulate-based checks in FactoryRecipeScanningSystem.
  • Constraint: indexes must be conservative (prefer false positives; avoid false negatives).
  • Only use cheap, observable machine state:
    • Item / Fluid: prefer storage-backed key-level containers (can enumerate and count keys).
    • Capability-backed containers without enumeration should yield “no opinion” (lookup() == null) or disable indexing.

Return semantics (recommended)

  • RequirementIndex.lookup(machine):
    • null: no opinion (e.g. machine has no observable containers of that type).
    • emptySet(): strong filter meaning “no recipes match current state”.
  • RecipeIndex.lookup(machine) intersects non-null results.
    • If all indices return null, the caller should treat it as “index unavailable” and fall back to normal scanning.

Planned indices by requirement type

ITEM (ItemRequirementComponent)

  • Key: PMKey<ItemStack> prototype equality (count ignored).
  • Build:
    • recipesByInputKey: Map<PMKey<ItemStack>, Set<MachineRecipe>>
    • (optional) requiredByRecipe: Map<MachineRecipe, Map<PMKey<ItemStack>, Long>> for amount-level filtering
  • Lookup:
    • Read only PortMode.OUTPUT item sources.
    • Use only storage-backed containers (enumerate keys + amounts).
    • Union by available keys, then (optionally) check totals against requiredByRecipe.

FLUID (FluidRequirementComponent)

  • Key: PMKey<FluidStack> prototype equality.
  • Build:
    • Use inputs + inputsPerTick for coarse “presence” filtering.
    • Prefer amount-level filtering only for inputs initially, to avoid false negatives.
  • Lookup:
    • Read only PortMode.OUTPUT fluid sources.
    • Use only storage-backed containers.

ENERGY (EnergyRequirementComponent)

  • Build a simple threshold table per recipe (e.g. input, optionally input + inputPerTick).
  • Lookup sums PortMode.OUTPUT StructureEnergyContainer.stored and filters by threshold.

Flattening wrapper requirements

  • CheckpointRequirementComponent(requirement=...): unwrap and index the inner requirement.
  • SelectiveRequirementComponent(candidates=[...]): union candidates into indices (conservative).

Item / Fluid chance + fuzzy inputs + random outputs (proposal)

This is a design proposal to be implemented later.

Required rules

  1. During checks (simulate): treat chance as 100%.
  2. During actual IO (execute): apply the effective chance.
  3. If chance and fuzzy IO coexist:
    • checks still run fuzzy checks with chance=100%
    • actual IO first decides chance; if it fails, skip the fuzzy operation.

Deterministic randomness

Use RecipeProcess.seed via RecipeProcess.getRandom(salt). For per-tick randomness, introduce a persisted tick counter process component and include it in the salt.

Chance above 200% and parallelism

Let parallelism be $k$ and effective chance percent be $C$ ($c=C/100$, can be > 2).

Split:

$$c = g + p,\quad g=\lfloor c \rfloor,; p\in[0,1)$$

Define “number of successful executions” as:

$$S = g\cdot k + \mathrm{Binomial}(k, p)$$

This avoids the “all or nothing” extreme while staying stochastic and reproducible.

Fuzzy input locking

  • During checks: pick the first satisfiable candidate and lock it.
  • Locks must be rollbackable and thus stored in a process component (e.g. locks[(requirementId, groupIndex, stage)]).

Random output (weighted without replacement)

  • During checks: perform a strict capacity check (chance=100%).
    • Conservative approach: validate the worst-case selection (top-N by required count).
  • During execution: pick N distinct candidates via weighted sampling without replacement using the process RNG.

See also