P3-04 : add TiedUpAnimationRegistry.resolveWithFallback + EMPTY_ANIMATION stub
Nouveau helper statique consomme par ClientRigEquipmentHandler.rebuildBondageAnimations (P3-05) et PacketPlayRigAnim.handleOnClient (P3-12) pour resoudre un anim ID avec fallback safe si miss. Design : - Lookup delegue a AnimationManager.byKey(ResourceLocation) — l'API existante pour la resolution par registry name. - Fallback = TiedUpRigRegistry.EMPTY_ANIMATION (singleton canonique) plutot qu'un stub empty_fallback separe. Les sites runtime (Layer#off, AnimationPlayer#isEmpty, LayerOffAnimation#getNextAnimation) comparent via == EMPTY_ANIMATION — retourner une autre instance provoquerait des false-negatives sur ces checks d'identite. - Dedup WARN via ConcurrentHashMap.newKeySet() : un ID donne ne log qu'une fois par session, evite le spam si le miss vient d'un item data-driven appele tick apres tick. Pattern inspire de RigAnimationTickHandler.LOGGED_ERRORS. - resetWarnedMissing() expose pour tests + runtime reload (F3+T datapack). - Branche defensive : id=null swallow + log (cas pathologique caller). 4 tests unitaires : - happy path (ID enregistre via AnimationManager.AnimationBuilder → assertSame) - fallback safe (ID inconnu → EMPTY_ANIMATION, non-null) - no-throw (ID inconnu + null swallow sans exception) - dedup observable (reset puis re-call sur meme ID re-warn, sanity check fake accessor distinct de EMPTY_ANIMATION) 65 tests rig GREEN (57 baseline + 4 nouveaux + autres).
This commit is contained in:
@@ -4,10 +4,15 @@
|
||||
|
||||
package com.tiedup.remake.rig;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
|
||||
import com.tiedup.remake.rig.anim.AnimationManager;
|
||||
import com.tiedup.remake.rig.anim.AnimationManager.AnimationAccessor;
|
||||
import com.tiedup.remake.rig.anim.client.Layer;
|
||||
import com.tiedup.remake.rig.anim.client.property.ClientAnimationProperties;
|
||||
import com.tiedup.remake.rig.anim.types.DirectStaticAnimation;
|
||||
@@ -153,4 +158,108 @@ public final class TiedUpAnimationRegistry {
|
||||
public static boolean isReady() {
|
||||
return CONTEXT_STAND_IDLE != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set (thread-safe) des IDs pour lesquels un WARN de fallback a déjà été
|
||||
* émis. Évite le spam log si un consumer appelle
|
||||
* {@link #resolveWithFallback(ResourceLocation)} tick après tick avec un ID
|
||||
* invalide (ex. un item bondage data-driven qui référence un anim ID cassé,
|
||||
* appelé dans la boucle de rendu). Un seul WARN par ID unique sur toute la
|
||||
* durée de vie du process (jusqu'à {@link #resetWarnedMissing()}).
|
||||
*
|
||||
* <p>Pattern inspiré de
|
||||
* {@code RigAnimationTickHandler.LOGGED_ERRORS} — {@code ConcurrentHashMap.newKeySet()}
|
||||
* pour être safe en cas d'appels concurrents (client tick thread + network
|
||||
* handler thread pour {@code PacketPlayRigAnim.handleOnClient}).</p>
|
||||
*/
|
||||
private static final Set<ResourceLocation> WARNED_MISSING_ANIMS = ConcurrentHashMap.newKeySet();
|
||||
|
||||
/**
|
||||
* Résout une animation par {@link ResourceLocation} avec fallback safe
|
||||
* si le registry ne la connaît pas.
|
||||
*
|
||||
* <p>Utilisé par le pipeline d'équipement
|
||||
* ({@code ClientRigEquipmentHandler.rebuildBondageAnimations}, P3-05) et
|
||||
* le packet cinematic ({@code PacketPlayRigAnim.handleOnClient}, P3-12).
|
||||
* Un miss dans le registry peut survenir dans plusieurs scénarios :</p>
|
||||
* <ul>
|
||||
* <li>Typo modder dans un JSON data-driven bondage item</li>
|
||||
* <li>Datapack pas encore rechargé ({@code /reload} pending)</li>
|
||||
* <li>Animation supprimée entre deux versions du mod</li>
|
||||
* <li>Race entre packet réception et
|
||||
* {@code AnimationManager.apply()} en début de session</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Dans tous ces cas, on retourne {@link TiedUpRigRegistry#EMPTY_ANIMATION}
|
||||
* — un singleton qui ne joue rien visuellement (pas de keyframe, pose
|
||||
* identity). Mieux qu'un NPE pour la robustesse du pipeline : l'anim
|
||||
* équipement continue de tourner avec les anim connues, et l'anim inconnue
|
||||
* est juste silencieusement un no-op.</p>
|
||||
*
|
||||
* <p><b>Dedup WARN</b> : un miss donné ne log qu'une fois par session
|
||||
* ({@link #WARNED_MISSING_ANIMS}). Ça évite le spam dans la console quand
|
||||
* l'ID invalide est consommé tick après tick (ex. équipement resté en place).
|
||||
* Le set peut être reset via {@link #resetWarnedMissing()} (hot-reload
|
||||
* F3+T, runtime datapack reload).</p>
|
||||
*
|
||||
* <p><b>Pourquoi réutiliser {@link TiedUpRigRegistry#EMPTY_ANIMATION}</b> vs
|
||||
* créer un {@code empty_fallback} séparé : plusieurs sites du runtime
|
||||
* ({@code Layer#off}, {@code AnimationPlayer#isEmpty}, {@code LayerOffAnimation#getNextAnimation})
|
||||
* testent l'identité via {@code == EMPTY_ANIMATION}. Retourner une autre
|
||||
* instance d'empty provoquerait des false-negatives sur ces checks — le
|
||||
* runtime penserait qu'une anim réelle joue alors qu'en fait c'est un
|
||||
* empty différent. Le singleton canonique évite ce piège.</p>
|
||||
*
|
||||
* @param id l'ID registry à résoudre (ex. {@code tiedup:idle_context_bound})
|
||||
* @return l'{@link AnimationAccessor} enregistré, ou
|
||||
* {@link TiedUpRigRegistry#EMPTY_ANIMATION} si l'ID est inconnu.
|
||||
* Jamais null.
|
||||
*/
|
||||
public static AnimationAccessor<? extends StaticAnimation> resolveWithFallback(
|
||||
ResourceLocation id
|
||||
) {
|
||||
if (id == null) {
|
||||
// null ID : log + fallback. Pas de dedup (cas pathologique — le
|
||||
// caller a un bug, pas un miss de datapack).
|
||||
TiedUpRigConstants.LOGGER.warn(
|
||||
"[TiedUpAnimationRegistry] resolveWithFallback appelé avec id=null, "
|
||||
+ "using EMPTY_ANIMATION fallback."
|
||||
);
|
||||
return TiedUpRigRegistry.EMPTY_ANIMATION;
|
||||
}
|
||||
|
||||
AnimationAccessor<? extends StaticAnimation> anim = AnimationManager.byKey(id);
|
||||
if (anim != null) {
|
||||
return anim;
|
||||
}
|
||||
|
||||
// Miss — fallback + dedup warn. Set.add retourne true si l'ID n'était
|
||||
// pas déjà dans le set → premier miss, on log. Sinon, silent no-op.
|
||||
if (WARNED_MISSING_ANIMS.add(id)) {
|
||||
TiedUpRigConstants.LOGGER.warn(
|
||||
"[TiedUpAnimationRegistry] Animation not found: '{}', using EMPTY_ANIMATION fallback. "
|
||||
+ "Check datapack JSON or run /reload.",
|
||||
id
|
||||
);
|
||||
}
|
||||
return TiedUpRigRegistry.EMPTY_ANIMATION;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset le set dedup des WARN missing. Utilisé dans deux contextes :
|
||||
* <ul>
|
||||
* <li>Tests unitaires — pour réinitialiser l'état statique entre test
|
||||
* cases (sinon un test qui warn sur un ID pollue les suivants).</li>
|
||||
* <li>Runtime reload (F3+T / datapack reload) — après un reload, des
|
||||
* anims précédemment missing peuvent être dispo maintenant ; on
|
||||
* veut pouvoir re-warn si elles retombent en miss après un autre
|
||||
* reload.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Thread-safe via {@link ConcurrentHashMap#newKeySet()} — pas de
|
||||
* synchronisation externe nécessaire.</p>
|
||||
*/
|
||||
public static void resetWarnedMissing() {
|
||||
WARNED_MISSING_ANIMS.clear();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user