This project's storage implementations target the "industrial mod large-capacity container" use-case:
- type-counted (by resource type) instead of "slots + ItemStack stacks"
- amounts are
Longto avoidIntoverflow - incremental GUI sync: server changes -> client GUI only receives deltas
API:
src/main/kotlin/api/storage/ResourceStorage.ktsrc/main/kotlin/api/storage/ObservableResourceStorage.ktsrc/main/kotlin/api/storage/ResourceStorageListener.kt
Implementations:
src/main/kotlin/impl/storage/ResourceStorageImpl.ktsrc/main/kotlin/impl/storage/ItemResourceStorage.ktsrc/main/kotlin/impl/storage/FluidResourceStorage.ktsrc/main/kotlin/impl/storage/EnergyStorageImpl.kt
GUI sync (ModularUI):
src/main/kotlin/client/gui/sync/ResourceStorageSyncHandler.ktsrc/main/kotlin/client/gui/sync/ResourceSlotSyncHandler.ktsrc/main/kotlin/client/gui/sync/EnergySyncHandler.kt
Typical constraints:
maxTypes: max number of distinct resource types (distinct PMKeys)maxCountPerType: max amount per type
So it behaves more like a dictionary than a traditional inventory.
Key points:
- PMKey as index: same resource type is merged into one entry.
- listeners: notify UI sync layer on changes.
- NBT persistence: store a list/map of key + amount.
- pendingChanges: indicates whether incremental changes exist.
The client should not treat its local snapshot as authoritative; the server is the source of truth.
To bridge large-capacity type-counted storage with traditional slot interaction, the project provides SlottedResourceStorage:
- API:
src/main/kotlin/api/storage/SlottedResourceStorage.kt - capabilities:
slotCount: fixed number of slots (stable indices, good for grids)getSlot(index): read the key shown in that slot (may be empty)extractFromSlot(index, amount, simulate): extract via slot interactiondrainPendingSlotChanges(): returns-and-clears dirty slot indices since last drain
Important: the slotted view does not require a strict "one resource type per slot" mapping. The same type may appear in multiple slots (segmentation, parallel interaction).
Current item storage (src/main/kotlin/impl/storage/ItemResourceStorage.kt) combines:
- core totals:
totals: Map<UniquePMItemKey, Long> - slotted view: allocate multiple slots per key, segmenting by
slotCap- total
$N$ , slot cap$C$ => slots needed is about$\lceil N / C \rceil$ - each slot shows
min(C, remaining)
- total
To remain compatible with vanilla ItemStack.count (Int) and common assumptions:
PMItemKeyImpl.get()clamps counts to aboutInt.MAX_VALUE / 2ItemResourceStoragealso clampsslotCapsimilarly
ResourceSlotSyncHandler (src/main/kotlin/client/gui/sync/ResourceSlotSyncHandler.kt) prefers dirty-slot deltas:
- server storage records which slot indices changed
- sync layer calls
drainPendingSlotChanges()and only refreshes those slots - fallback: if dirty slots are not recorded correctly, sync layer can fall back to full sync to preserve correctness
This significantly reduces CPU/network overhead in high-slot-count, high-frequency tick scenarios.
Energy storage uses a separate implementation (compatible with IEnergyStorage):
- capacity / maxReceive / maxExtract can be updated dynamically
- when capacity changes, stored energy is clamped to avoid exceeding new capacity
Hatches hold the corresponding storage instances internally:
- Item hatch ->
ItemResourceStorage - Fluid hatch ->
FluidResourceStorage - Energy hatch ->
EnergyStorageImpl
and expose Forge capabilities:
IItemHandler/IFluidHandler/IEnergyStorage
See:
Chinese original: