Files
ForgeMultipart/docs/api/PART_REGISTRATION.md
Algent ed861b6ca5 docs: trim audit noise from API guides
- drop per-guide Validation sections; manual gates stay in MANUAL_CHECKS
- drop audit/provenance lines (reference checkouts, dated refreshes, commit pins)
- state version status and Scala-free compile check once in API.md
- replace API.md status prose with link to migration status
- fix RELEASE_NOTES dangling sentence
- rewrite README for GTNH fork and Java port
2026-10-15 16:39:36 +02:00

5.7 KiB

Registering part factories from Java

API index

Use MultiPartRegistry.registerPartFactory(IPartFactory2, String...). It delegates to the existing registry and avoids the Scala types in the registerParts overload family.

To inspect a registered factory without constructing a part, see factory lookup, including Schematica's replacement for private registry-map reflection.

Example and timing

The complete compiling example implements both required IPartFactory2.createPart methods, opts into saved-NBT client previews, and registers one persistent type:

MultiPartRegistry.registerPartFactory(new PartRegistrationExample(), PartRegistrationExample.PART_TYPE);

Call from your mod's common preInit or init handler on both sides, before FMP closes registration in postInit. FML must have an active mod container; that container becomes the owner returned by getModContainer(type). Registration stores the factory without constructing, loading, binding or placing a part.

The example creates fresh, unbound instances whose getType() returns yourmod:part. Replace that sample ID and part implementation with your own. Geometry, rendering and gameplay callbacks belong to the real part. Preserve already published IDs exactly when migrating; adding a namespace to an existing ID changes its save/network identity. Both single IDs and arrays are accepted. Multiple names can share a factory that selects the part class by name.

Factory and payload ownership

IPartFactory2 has two abstract methods, so it is not a single-method lambda interface. Its third method is an optional default:

Construction path Factory input What happens next
Server/NBT createPart(String, NBTTagCompound) receives the original tag object Normal reconstruction calls part.load with that same tag
Client/description createPart(String, MCDataInput) receives the original input, after the registry ID Normal reconstruction calls part.readDesc with that same input at its current cursor
Client preview/NBT createPartForClientPreview(String, NBTTagCompound) receives the original tag object Preview reconstruction calls part.load with that same tag; the default returns null

A factory may inspect NBT or consume a packet discriminator to choose a class. There is no packet copy or rewind; coordinate any consumed prefix with the part's writeDesc and readDesc protocol. FMP's microblock factory consumes the material ID this way. Do not call load or readDesc in the factory merely to initialize the remaining payload, because normal reconstruction calls them afterward. Return a fresh, unbound part for every supported name.

Direct MultiPartRegistry.loadPart and readPart calls only construct the part; their caller remains responsible for the following payload/lifecycle work. The deprecated createPart(name, boolean) passes a null NBT or packet argument; factories reached through that old path must tolerate it. Neither registration entry adds eager factory validation.

Ordering and failures

IDs are processed in array order. The array itself is not retained, so changing it afterward does not rename registered parts. The factory object is retained. A duplicate ID throws immediately, leaving any earlier IDs from that call registered and skipping the suffix. Registration is not transactional; fix duplicate IDs rather than retrying the whole batch. Later server ID-map setup sorts registered names; registration order is not a stable network numeric ID.

The new entry preserves the old Java array entry's failure order: a closed registry fails first, then the registry enters its registering state and checks FML's active container, then it reads the array. Empty arrays still perform the state/container checks; a null array fails after them. Supply a non-null factory and valid, unique non-null names; the legacy implementation does not eagerly validate those values. Iterator conversion in the retained Scala sequence entry still occurs before these checks, so malformed Scala inputs can fail earlier.

Factory exceptions propagate when constructing parts, not during registration. A registration call does not probe factory results or promise that a later NBT/packet will be accepted.

Migrating old callers

  • IPartFactory2 callers: retain the factory and IDs, then call registerPartFactory. ProjectRed can keep its existing server/NBT and client/packet methods. Override createPartForClientPreview only when saved NBT can select a fresh client variant. Scala code can pass a String[]/Array[String] as Java varargs using : _*.
  • IPartFactory or Scala (String, Boolean) => TMultiPart callers: implement IPartFactory2; route the NBT method to the old constructor with false and the packet method with true. Keep any side-specific class selection. A factory that ignores the old Boolean, such as ForgeRelocationFMP's frame factory, can construct the same part class in both.
  • Keep converter registration and trait registration separate. This change neither replaces registerConverter nor completes ProjectRed's external microblock trait migration or Schematica's private registry-map migration.

All eight old static/companion registration descriptors remain. Both IPartFactory2 sequence entries are now deprecated; the older Boolean/function entries were already deprecated and now point to the supported replacement. The old Java array entries remain callable too. Prefer the new name for consumer Java source: the old overload family requires scala.collection.Seq and scala.Function2 on the compile classpath even for Java factory/string inputs.