Migrating from GeckoLib
Strut began as a fork of GeckoLib. This page is the checklist for moving a mod over: names, boilerplate you can now delete, and the behaviours that differ.
There are no compatibility aliases. The old Geo* names are gone, not deprecated. Changing your imports is a one-time job and the compiler finds every place that needs it.
Steps
- Swap the dependency.
- Rename the classes.
- Convert models and animations to the binary formats.
- Delete the boilerplate you no longer need.
- Move animation decisions to the server.
- Replace string-parsed keyframe handlers with typed events.
1. Dependency
Replace the GeckoLib dependency with Strut (see Getting started) and change the depends entry in fabric.mod.json from geckolib to strut. Packages move from software.bernie.geckolib to com.sandymandy.strut. Most subpackages keep their names (animatable, animation, model, renderer, cache.object), but a few classes moved, and GeckoLibUtil is StrutUtil. Fix the package prefix first, then let the compiler point at what remains.
2. Names
Every identifier containing Geo followed by a capital letter now says Strut instead; a lower-case geo followed by a capital (geoModel) became strut. The main ones:
| GeckoLib | Strut |
|---|---|
GeoAnimatable | StrutAnimatable |
GeoEntity | StrutEntity |
GeoItem | StrutItem |
GeoBlockEntity | StrutBlockEntity |
GeoReplacedEntity | StrutReplacedEntity |
GeoModel, DefaultedEntityGeoModel, DefaultedItemGeoModel, DefaultedBlockGeoModel | StrutModel, DefaultedEntityStrutModel, DefaultedItemStrutModel, DefaultedBlockStrutModel |
GeoEntityRenderer, GeoItemRenderer, GeoBlockRenderer, GeoArmorRenderer | StrutEntityRenderer, StrutItemRenderer, StrutBlockRenderer, StrutArmorRenderer |
GeoRenderProvider | StrutRenderProvider |
BakedGeoModel | BakedStrutModel |
GeckoLibUtil | StrutUtil |
The complete list is at the end of this page. On a Unix shell, a first pass over your sources can be:
find src -name "*.java" | xargs sed -i -E 's/Geo([A-Z])/Strut\1/g; s/\bgeo([A-Z])/strut\1/g'
then fix the package prefix and GeckoLibUtil. Names that did not change: AnimationController, AnimatableManager, RawAnimation, PlayState, DataTickets, GeometryTree. The geo/ and animations/ asset folders are no longer read: models go under assets/<ns>/strut/models/ and animations under assets/<ns>/strut/animations/ (move the files, nothing inside them changes), and an armour model ns:armour/x is strut/models/armour/x.strutmodel.
3. Models and animations
Strut's conventions, its server-side event timelines and its Blockbench plugin work on .strutmodel and .strutanim. Open your existing models in Blockbench with the Strut plugin and save them in the binary formats. The conventional model classes (StrutModel.entity(id) and friends, formerly the Defaulted*GeoModel classes) now resolve .strutmodel and .strutanim paths instead of .geo.json and .animation.json. Legacy JSON files can still be loaded by naming them explicitly, but events read binary clips only.
4. Boilerplate you can delete
A typical GeckoLib entity:
public class HeroEntity extends PathAwareEntity implements GeoEntity {
private final AnimatableInstanceCache cache = GeckoLibUtil.createInstanceCache(this);
public HeroEntity(EntityType<? extends HeroEntity> type, World world) {
super(type, world);
}
@Override
public void registerControllers(AnimatableManager.ControllerRegistrar controllers) {
controllers.add(new AnimationController<>(this, "base", 0, state -> state.setAndContinue(...)));
}
@Override
public AnimatableInstanceCache getAnimatableInstanceCache() {
return this.cache;
}
@Override
public double getTick(Object entity) {
return ((Entity)entity).age;
}
}
// plus HeroModel extends GeoModel, HeroRenderer extends GeoEntityRenderer,
// and EntityRendererRegistry.register(AVA, HeroRenderer::new)
In Strut:
public class HeroStrutEntity extends PathAwareEntity implements StrutEntity {
public HeroStrutEntity(EntityType<? extends HeroStrutEntity> type, World world) {
super(type, world);
}
// No cache field, no getAnimatableInstanceCache(), no getTick(), and no registerControllers()
// unless you animate on the client. The server plays animations; see "Playing animations".
}
and one line in the client initialiser:
StrutClient.entity(AVA);
| Delete | Because |
|---|---|
The cache field and getAnimatableInstanceCache | Held by a slot Strut adds to entities, block entities and items. |
getTick on an entity | Defaults to the entity's age. |
registerControllers | Optional. For server-driven entities the controllers are created when the server plays on them. |
| Model subclasses that only return three constant paths | The convention finds them, or use StrutModel.builder(). |
| Renderer subclasses that only have a constructor | StrutClient.entity(type). |
createGeoRenderer with an anonymous render provider, on items | StrutClient.item(item). (The old override still works.) |
registerSyncedAnimatable in an item's constructor | Items register themselves. |
See Entities and Items, armor and blocks.
5. Animation authority
This is the one real behavioural change. In GeckoLib a controller's handler runs on each client and decides what plays. In Strut a StrutEntity's controllers are server-authoritative by default: the server plays, seeks and changes the rate, and the handler is not consulted. Move the decision to server code:
ServerAnimationStates.forEntity(entity, false)
.play("base", "walk", entity.getWorld().getTime(), 0, 1f, LoopMode.LOOP);
A controller can still be client-decided, either by opting out with authorityMode(AuthorityMode.CLIENT_COSMETIC) or because it belongs to an item or block entity, which are client-cosmetic by default. A GeckoLib handler that read entity state to choose an animation is the code to look at first. See Playing animations.
6. Keyframe events
GeckoLib's sound, particle and custom-instruction keyframe handlers ran on the client from the animation frame, so a lagging or culled client could miss or repeat them. In Strut, gameplay events are delivered on the server exactly once, and cosmetic events on clients at most once. A custom instruction that was a string you parsed yourself becomes a typed event you register once; a marker becomes StrutEvents.onServerMarker. See Events.
Other differences
| Area | Difference |
|---|---|
| Formats | Binary .strutmodel and .strutanim are the supported path. The Blockbench plugin reads and writes them. |
| Loaders | Fabric only, Minecraft 1.21.1. |
| Meshes | Free-form meshes and armature skinning, in addition to cubes. |
| Bones | Server-synced bone overrides and attachments, see Bone overrides and attachments. |
| Content delivery | Asset sync delivers server-owned models, textures and sounds. |
| Rendering | An instanced GPU path with shader-pack support and shadows, see Rendering and performance. It is the only path: there is no CPU walk of the bones, so what hooked it is gone. renderRecursively, renderCubesOfBone, renderMeshesOfBone, renderMesh, createVerticesOfQuad and the rest of the per-bone drawing methods, reRender, checkAndRefreshBuffer and the Dynamic*Renderer classes no longer exist. What they were used for: a bone's texture or colour is a bone override (colour and texture, per bone, frame-scoped if you like); a renderer that tints bones itself overrides customBoneVisuals (see DyeableStrutArmorRenderer); something drawn on a bone (an item in a block's slot) is drawn in renderAttachments, with boneMatrixAtPivot(animatable, model, bone) multiplied onto the stack. Glowing-entity outlines of Strut models are not specially supported. |
Complete rename map
Every renamed identifier, generated from the change itself. Names in test code are omitted.
| GeckoLib | Strut |
|---|---|
AutoGlowingGeoLayer | AutoGlowingStrutLayer |
BakedGeoModel | BakedStrutModel |
BlockAndItemGeoLayer | removed: use entity.attachments() |
BoneFilterGeoLayer | BoneFilterStrutLayer |
DefaultedBlockGeoModel | DefaultedBlockStrutModel |
DefaultedEntityGeoModel | DefaultedEntityStrutModel |
DefaultedGeoModel | DefaultedStrutModel |
DefaultedItemGeoModel | DefaultedItemStrutModel |
DeferredGeoRenderProvider | DeferredStrutRenderProvider |
DyeableGeoArmorRenderer | DyeableStrutArmorRenderer |
DynamicGeoBlockRenderer, DynamicGeoEntityRenderer, DynamicGeoItemRenderer | removed: they were hooks of the CPU render path (see below) |
FakeGeoAnimatable | FakeStrutAnimatable |
FakeGeoEntity | FakeStrutEntity |
FastBoneFilterGeoLayer | FastBoneFilterStrutLayer |
GeoAbstractTexture | StrutAbstractTexture |
GeoAnimatable | StrutAnimatable |
GeoAnimatables | StrutAnimatables |
GeoArmature | StrutArmature |
GeoArmatureBones | StrutArmatureBones |
GeoArmor | StrutArmor |
GeoArmorRenderer | StrutArmorRenderer |
GeoBlock | StrutBlock |
GeoBlockEntity | StrutBlockEntity |
GeoBlockRenderer | StrutBlockRenderer |
GeoBone | GroupBoneDefinition (shared, immutable) and GroupBoneHandle (per instance, mutable) |
GeoCube | StrutCube |
GeoCubes | StrutCubes |
GeoEntity | StrutEntity |
GeoEntityRenderer | StrutEntityRenderer |
GeoGlowingTextureMeta | StrutGlowingTextureMeta |
GeoItem | StrutItem |
GeoItemRenderer | StrutItemRenderer |
GeoItems | StrutItems |
GeoMesh | StrutMesh |
GeoModel | StrutModel |
GeoObjectRenderer | StrutObjectRenderer |
GeoPoly | StrutPoly |
GeoQuad | StrutQuad |
GeoRenderEvent | StrutRenderEvent |
GeoRenderLayer | removed: use entity.overrides() and entity.attachments() |
GeoRenderProvider | StrutRenderProvider |
GeoRenderer | StrutRenderer |
GeoReplacedEntity | StrutReplacedEntity |
GeoReplacedEntityRenderer | StrutReplacedEntityRenderer |
GeoSkin | StrutSkin |
GeoTexture | StrutTexture |
GeoVertex | StrutVertex |
SingletonGeoAnimatable | SingletonStrutAnimatable |
StatelessGeoBlockEntity | StatelessStrutBlockEntity |
StatelessGeoEntity | StatelessStrutEntity |
StatelessGeoObject | StatelessStrutObject |
StatelessGeoReplacedEntity | StatelessStrutReplacedEntity |
StatelessGeoSingletonAnimatable | StatelessStrutSingletonAnimatable |
StatelessSingletonGeoAnimatable | StatelessSingletonStrutAnimatable |
constructGeoModel | constructStrutModel |
createGeoRenderer | createStrutRenderer |
geoArmorRenderer | strutArmorRenderer |
geoEntity | strutEntity |
geoEntityDefaultsToServer | strutEntityDefaultsToServer |
geoItem | strutItem |
geoItemRenderer | strutItemRenderer |
geoModel | strutModel |
geoRenderer | strutRenderer |
getGeoArmorRenderer | getStrutArmorRenderer |
getGeoItemRenderer | getStrutItemRenderer |
getGeoModel | getStrutModel |
getGeoModelForArmor | getStrutModelForArmor |
getGeoModelForBlock | getStrutModelForBlock |
getGeoModelForEntity | getStrutModelForEntity |
getGeoModelForEntityType | getStrutModelForEntityType |
getGeoModelForItem | getStrutModelForItem |
nonGeoEntityDefaultsToClientCosmetic | nonStrutEntityDefaultsToClientCosmetic |
registerGeoBone | registerStrutBone |
tryRenderGeoArmorPiece | tryRenderStrutArmorPiece |