Mekanisme Pengelompokan Mega-Stack (MC 26.2)
Dokumen ini menyajikan rincian teknis dan matematis yang mendalam tentang agregasi item di tanah, perhitungan radius pencarian, pencegahan overflow 64-bit, dan pengoptimalan alokasi heap dalam Item Clumps untuk Minecraft 26.2.
📊 Kotak Info Fitur
| Properti | Spesifikasi |
|---|---|
| Nama Sistem | Agregator Mega-Stack Item Tanah |
| Kelas Target | net.minecraft.world.entity.item.ItemEntity |
| Batas Penggabungan Bawaan | 9.999 item (Dapat dikonfigurasi hingga $2.147.483.647$) |
| Radius Pencarian ($r$) | Horisontal $1$ hingga $10$ blok (Bawaan: $1$ blok) |
| Beban Tick | $0.00\text{ ms}$ pada item diam (hanya dijalankan saat setItem()) |
| Alokasi Memori | Nol alokasi objek saat pencarian & penyerapan (copyWithCount(int)) |
| GameRules Pengendali | item_clumps:enable_clumping, item_clumps:max_clump_size, item_clumps:merge_radius |
⚙️ Cara Kerja Agregasi Mega-Stack
Dalam vanilla Minecraft, item yang terjatuh memanggil mergeWithNeighbours() selama siklus tick-nya, mencari item serupa di sekitar. Namun, vanilla melarang keras penggabungan jika jumlah item entitas target melebihi itemStack.getMaxStackSize() (biasanya 64, atau 16 untuk mutiara ender / telur).
Item Clumps memotong pemeriksaan ini melalui ItemEntityMixin.java untuk menghapus batasan buatan ini sambil tetap menjaga keamanan komponen data secara ketat.
┌──────────────────────────────┐
│ ItemEntity A Ticks In │
└──────────────┬───────────────┘
│
▼
┌──────────────────────────────┐
│ Fast-Path Validation │
│ - Matching player target? │
│ - Identical DataComponents? │
└──────────────┬───────────────┘
│ (Pass)
▼
┌──────────────────────────────┐
│ In-Flight Magnet Check │
│ (Is either item magnetized?) │
└──────────────┬───────────────┘
│ (No)
▼
┌──────────────────────────────┐
│ 64-Bit Arithmetic Sum │
│ S = (long) A + (long) B │
└──────────────┬───────────────┘
│
┌───────────────┴───────────────┐
│ │
S <= maxClumpSize S > maxClumpSize
│ │
▼ ▼
┌───────────────────┐ ┌───────────────────┐
│ Complete Merge │ │ Partial Merge │
│ Larger absorbs │ │ Fill entity A to │
│ smaller entity; │ │ maxClumpSize; │
│ Younger age kept. │ │ B keeps remainder.│
└───────────────────┘ └───────────────────┘📐 Rumus Matematika & Perhitungan Presisi
1. Pelebaran Kotak Batas (Bounding Box) Horisontal
Vanilla mencari dalam kotak batas yang sangat sempit: $\text{AABB}_{\text{vanilla}} = \text{AABB}.\text{inflate}(0.5, 0.0, 0.5)$.
Item Clumps memperluas kotak batas horisontal secara dinamis menggunakan injeksi @Redirect tanpa alokasi memori tambahan:
$$\text{AABB}_{\text{clump}} = \text{boundingBox}.\text{inflate}(r, y, r)$$
Di mana:
- $r = \text{DynamicGameRuleManager}.\text{getInt}(\text{level}, \text{MERGE_RADIUS}) \in [1, 10]$ blok.
- $y = 0.0$ (ketinggian pencarian vertikal tetap dipertahankan secara ketat untuk mencegah item di lantai atau corong yang berbeda bertingkat saling bergabung).
2. Penjumlahan Integer 64-Bit & Keamanan Overflow Aritmatika
Ketika dua kelompok item raksasa bergabung di konfigurasi server besar (misalnya batas maksimum disetel ke 2 miliar), penjumlahan int 32-bit biasa dapat mengalami overflow menjadi bilangan negatif ($\text{Integer.MAX_VALUE} + 1 = -2,147,483,648$), merusak status entitas.
Item Clumps menghitung penjumlahan dalam presisi 64-bit:
$$S = (\text{long}),\text{thisCount} + (\text{long}),\text{otherCount}$$
- Penyerapan Penuh ($S \le \text{maxClumpSize}$): $$\text{count}_{\text{merged}} = (\text{int}),S$$
- Penyerapan Sebagian ($S > \text{maxClumpSize}$): $$\Delta = \text{maxClumpSize} - \text{thisCount}$$ $$\text{thisCount}' = \text{maxClumpSize}, \quad \text{otherCount}' = \text{otherCount} - \Delta$$
3. Ketelitian Komponen & Status
Item tidak akan pernah bergabung kecuali semua Komponen Data (sihir/enchantment, kerusakan durabilitas, nama kustom, trim zirah, efek ramuan) 100% identik bit-demi-bit:
$$\text{ItemStack}.\text{isSameItemSameComponents}(\text{stack}_A, \text{stack}_B) == \text{true}$$
💻 Implementasi Kode Sumber Java Asli
Dari ItemEntityMixin.java di Minecraft 26.2:
@Inject(method = "tryToMerge", at = @At("HEAD"), cancellable = true)
private void item_clumps$customMerge(ItemEntity other, CallbackInfo ci) {
ItemStack thisStack = this.getItem();
ItemStack otherStack = other.getItem();
// Fast-path exit before GameRule lookups
if (!Objects.equals(this.target, ((ItemEntityMixin)(Object)other).target) ||
!ItemStack.isSameItemSameComponents(thisStack, otherStack)) {
return;
}
if (!DynamicGameRuleManager.getBoolean(this.level(), ItemClumpsFabric.ENABLE_CLUMPING)) return;
// Magnet mod in-flight protection
if (net.fabricmc.loader.api.FabricLoader.getInstance().isModLoaded("magnet")) {
try {
java.lang.reflect.Method isMagnetizedMethod;
try {
isMagnetizedMethod = this.getClass().getMethod("ig_magnet$isMagnetized");
} catch (NoSuchMethodException e) {
isMagnetizedMethod = this.getClass().getMethod("ig$isMagnetized");
}
if ((boolean) isMagnetizedMethod.invoke(this) || (boolean) isMagnetizedMethod.invoke(other)) {
ci.cancel();
return;
}
} catch (Throwable ignored) {}
}
int thisCount = thisStack.getCount();
int otherCount = otherStack.getCount();
int maxClump = (ItemClumpsFabric.MAX_CLUMP_SIZE == null)
? thisStack.getMaxStackSize()
: DynamicGameRuleManager.getInt(this.level(), ItemClumpsFabric.MAX_CLUMP_SIZE);
long sum = (long) thisCount + (long) otherCount;
if (sum > (long) maxClump) {
int spaceLeft = maxClump - thisCount;
if (spaceLeft > 0) {
ItemStack thisCopy = thisStack.copyWithCount(maxClump);
this.setItem(thisCopy);
ItemStack otherCopy = otherStack.copyWithCount(otherCount - spaceLeft);
other.setItem(otherCopy);
this.pickupDelay = Math.max(this.pickupDelay, ((ItemEntityMixin)(Object)other).pickupDelay);
this.age = Math.min(this.age, ((ItemEntityMixin)(Object)other).age);
}
ci.cancel();
return;
}
// Full Merge: larger stack absorbs the smaller stack
if (otherCount < thisCount) {
ItemStack thisCopy = thisStack.copyWithCount((int) sum);
this.setItem(thisCopy);
this.pickupDelay = Math.max(this.pickupDelay, ((ItemEntityMixin)(Object)other).pickupDelay);
this.age = Math.min(this.age, ((ItemEntityMixin)(Object)other).age);
other.discard();
} else {
ItemStack otherCopy = otherStack.copyWithCount((int) sum);
other.setItem(otherCopy);
((ItemEntityMixin)(Object)other).pickupDelay = Math.max(((ItemEntityMixin)(Object)other).pickupDelay, this.pickupDelay);
((ItemEntityMixin)(Object)other).age = Math.min(((ItemEntityMixin)(Object)other).age, this.age);
this.discard();
}
ci.cancel();
}