Skip to content

Curios API

BonsUnleashed edited this page Oct 7, 2026 · 4 revisions

Curios API

Minecraft 1.20.1 / Forge: this page documents that build and its measurements. For the separate 170-control Minecraft 1.21.1 port, see Minecraft 1.21.1 NeoForge.

Six controls for Curios API 5.14.1+1.20.1 (mod id curios): slot lookups with one map probe, a modifier map that is only allocated when used, a quick answer for other mods' capability queries, a tick skip for entities without slots, tag keys that are kept, and one fix that stops two threads from corrupting the shared slot caches. Curios is a shared library, so a saving per call is multiplied by every mod that uses it. Every switch checks the code it would change against the tested build; another build is left untouched with one WARN line (see How the patches are applied).

Key Target Side Since Kind Result
curios_foreign_capability_fast_path Curios API 5.14.1+1.20.1 BOTH 1.0.21 opt other capabilities skip the slot lookup
curios_lazy_modifier_map Curios API 5.14.1+1.20.1 BOTH 1.0.8 opt 392 → 0 B when idle
curios_slot_map_lookup Curios API 5.14.1+1.20.1 BOTH 1.0.6 opt 12.76 → 10.16 ns
curios_slotless_tick_skip Curios API 5.14.1+1.20.1 BOTH 1.0.28 opt 409 → 9 ns per tick handler call with 25 capability providers
curios_tag_predicate_keys Curios API 5.14.1+1.20.1 BOTH 1.0.30 opt 161 → 41 ns and 96 → 0 bytes per slot tag test
curios_thread_safe_caches Curios API 5.14.1+1.20.1 BOTH 1.0.30 fix fix: 11,439 errors with 8 threads before, none after

curios_slot_map_lookup

Since: 1.0.6 · Script (1.0.19 and earlier): furious6-CuriosEntityManager.js Patched: top.theillusivec4.curios.common.data.CuriosEntityManager.getEntitySlots Mixin (since 1.0.20): CuriosEntityManagerMixin

What upstream did. Probed the immutable entity-slot map twice (containsKey, then get).

What the patch does. A single get for ImmutableMap instances, returning the same map or the same empty map; any other map implementation keeps the original two-probe path.

What stays the same. Exact returned map identity, missing and null keys, API routes, reload and synchronisation.

Measured. Successful lookup with 96 entries: 12.76 → 10.16 ns on the client; the server case was neutral because the JIT already removed the second probe; zero allocation both ways.


curios_lazy_modifier_map

Since: 1.0.8 · Script (1.0.19 and earlier): furious8-curios.js Patched: top.theillusivec4.curios.common.capability.CurioInventoryCapability$CurioInventoryWrapper.clearCachedSlotModifiers Mixin (since 1.0.20): CurioInventoryWrapperMixin

What upstream did. Constructed a Guava HashMultimap with its bookkeeping and view wrappers even when no slot attribute would be added, on every call.

What the patch does. Collects slot modifiers lazily in the same underlying Guava-created HashMap/HashSets (expected sizes 12/2) and skips the final traversal when nothing was accumulated.

What stays the same. Every inventory traversal, attribute event, removal callback and ordering; nothing is cached across calls. 280,852 additional comparisons against Guava covered collisions, duplicates, nulls and iteration order.

Measured. No cached modifiers 64.88 → 20.52 ns and 392 → 0 B; cached modifiers with empty items 219.05 → 178.37 ns and 392 → 0 B; actual slot-modifier removal 7,820.68 → 7,288.03 ns and 24,904 → 24,040 B.


curios_foreign_capability_fast_path

Since: 1.0.21 · Side: BOTH · Mixin: ForeignCapabilityMixin Patched: top.theillusivec4.curios.common.capability.CurioInventoryCapability$Provider.getCapability

What upstream did. Curios attaches its capability provider to living entities, and Forge asks that provider about every capability anyone queries on such an entity. The provider looked up the entity's slot map first and only then checked whether the query was for the Curios inventory at all, so every other mod's capability query paid a slot-map lookup.

What the patch does. Returns LazyOptional.empty() at once for any capability other than the Curios inventory. That is the same empty singleton the original returns for such a query on both of its paths. Curios inventory queries run unchanged; the method body is otherwise Curios' own (LGPL-3.0).

What stays the same. The object returned for every capability: in the 1.0.21 client run the providers of the client player and of the server player returned the same object as the original logic for all 159 registered capabilities and for a null capability.

Measured. The slot-map lookup no longer runs for foreign queries; it was 0.25 % of the server thread and 0.09 % of the client render thread in profiles.

Since 1.0.34. Only for an entity that has a world. On a living entity built without one, Curios' slot lookup fails for every capability, and that failure is kept as it was.


curios_slotless_tick_skip

Since: 1.0.28 · Target: Curios API 5.14.1+1.20.1 · Side: BOTH · Kind: opt

Mixins: CuriosEventHandlerSlotlessMixin, CapabilityProviderCuriosAccessor, LivingEntityCuriosStateMixin

Curios tick skips the lookup on entities without slots. Curios' tick handler runs for every living entity every tick and starts by looking up the Curios inventory: a walk over all of the entity's capability providers. For an entity type no datapack gives slots to (animals, fish, villagers, most modded mobs) Curios' own provider answers empty and the walk finds nothing. Once that walk has come back empty on an entity, the answer is reused while the type still has no slots and the entity keeps the same, valid capabilities. An entity on which another mod answers the inventory is never skipped, and a datapack reload that adds slots is seen at once.

Measured: 409 -> 9 ns per tick handler call with 25 capability providers, 16 -> 9 ns with Curios' provider alone, on real classes; 317,976 checks identical.


curios_tag_predicate_keys

Since: 1.0.30 · Target: Curios API 5.14.1+1.20.1 · Side: BOTH · Kind: opt

Mixin: TagPredicateKeysMixin

Curios slot tag keys kept. Curios checks whether an item fits a slot with its built-in "curios:tag" test, which builds two resource locations and looks up two tag keys every time it runs. Every item tooltip runs it once per slot type of the player (14 here), and JEI's search index asks for the tooltip of every item when you join a world. Now the two tag keys are made once per slot id and kept; they are the very keys the test would build, and the item checks run as before, so every answer is the same. Slot ids that are not plain lowercase paths still build their keys every time, exactly as before.

Idea: found in our own join profile (Curios' tooltip handler was 12% of JEI's index build); no outside mod

Measured: offline on Curios' own classes, 188,081 checks identical (82,040 answers, 23,445 exceptions, 8 threads, tag reloads); the test took 161 -> 41 ns and 96 -> 0 bytes per call, one item tooltip's slot checks 2.7 -> 1.1 us.


curios_thread_safe_caches

Since: 1.0.30 · Target: Curios API 5.14.1+1.20.1 · Side: BOTH · Kind: fix

Mixins: SlotAttributeMapMixin, SlotUuidMapMixin

Curios: the shared slot-attribute and slot-UUID caches can no longer be corrupted by two threads. Curios keeps two plain hash maps, one object per slot type (the slot's attribute) and one UUID per slot, filled by whichever thread asks first. In single player the client asks while drawing item tooltips and the built-in server asks while equipment changes, so both threads can fill the same map at once. That can throw an error out of the middle of a tooltip or an equipment update, or lose an entry so that one slot type ends up with two attribute objects (bonuses added under one and removed under the other). Each map has a single method that reads and fills it; that method now runs under its own lock. The maps, the order of their calls and every answer stay exactly the same for a single thread, including unusual slot names, so nothing changes except that two threads take turns.

Measured: offline on Curios' own classes, 8 threads filling the caches together (300 rounds) gave 11,439 errors and 183 slot types with two attribute objects before the fix and none after; 17,179 single-thread checks identical (classes, names, UUIDs, which calls create a new object, map order).

Bons and Furious

Minecraft 1.20.1 / Forge 1.0.34

Compatibility

Controls by mod

Links

Clone this wiki locally