fix: Folia thread-safety for rotation reads and death model cleanup#286
fix: Folia thread-safety for rotation reads and death model cleanup#286arkarang wants to merge 3 commits intotoxicity188:v2-devfrom
Conversation
05ae69f to
e2d1df2
Compare
There was a problem hiding this comment.
이미 최종값인 head랑 body 부터 throttle이 걸려있는데 왜 넣은 거임?
There was a problem hiding this comment.
AI 피셜:
headSupplier/bodySupplier는 최종 합성값(Quaternionf/ModelRotation)에 throttle이 걸려있는데, throttle이 만료되서 람다가
실행될 때 entity.pitch(), entity.bodyYaw() 등이 EXECUTOR 스레드에서 NMS 핸들(handle().xRot 등)을 직접 읽어요.
EntityTracker에서 damageTick, walkSpeed에 이미 같은 패턴이 적용되어있는 것과 동일한 이유입니다.
그렇데
There was a problem hiding this comment.
그거 엔티티 사망 때 애니메이션이 있으면 모델 삭제를 미루기 위한 flag에 가까운데 테스트 해본 거 맞음?
death가 없더라도 엔티티가 remove 되기 전까진 사망했다고 안 지워질 텐데
There was a problem hiding this comment.
맞아요 그래서 수정했습니다. 20틱 fallback은 제거하고, forRemoval(true)를 항상 설정 + despawn()에서 forRemoval 트래커를
스킵하지 않고 close() 하도록 변경했어요. death 애니메이션 있으면 콜백에서 close, 없으면 EntityRemoveFromWorldEvent →
despawn() → close() 흐름으로 정리됩니다.
그렇데
There was a problem hiding this comment.
근데 MobModel 관련해서 MythicMobs 쪽 호환성 이슈도 있어보이긴 해서 저도 테스트 좀 더 해봐야할듯.
주말에 테스트하고 결과알랴줌
e2d1df2 to
c3c06c6
Compare
fdd3b48 to
3f7cdae
Compare
Change updateBaseEntity() from asyncTaskLater to taskLater(entity) so syncEntity/syncPosition reads run on the owning region thread.
37a80a4 to
4374e49
Compare
EntityBodyRotator: wrap pitch/bodyYaw/headYaw/yaw/onWalk in throttleTickFloat/Boolean so EXECUTOR worker threads read cached values instead of hitting NMS handles off the owning region thread. EntityManager (Bukkit + Fabric): always set forRemoval(true) on death, and schedule a 20-tick fallback close() when no death animation exists — prevents display entities from lingering. EntityTrackerRegistry#despawn(): close forRemoval trackers on world removal instead of skipping them. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The despawn() safety net in EntityTrackerRegistry already closes forRemoval trackers when EntityRemoveFromWorldEvent fires, making the delayed taskLater redundant. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Summary
updateBaseEntity()에서asyncTaskLater→taskLater(entity)로 변경 — entity state 읽기를 owning region thread에서 실행pitch(),bodyYaw(),headYaw(),yaw(),onWalk()직접 호출을throttleTickFloat/throttleTickBoolean으로 래핑 — EXECUTOR 워커 스레드에서 NMS 핸들 직접 읽기 방지 (Folia 스레드 소유권 위반 해결)forRemoval(true)를 항상 설정 — death 애니메이션 유무와 무관하게 트래커가 사망 상태로 표시됨forRemoval트래커를 스킵하지 않고close()호출 — 엔티티가 월드에서 제거될 때 확실히 정리Context
회전값 throttle (EntityBodyRotator)
bodyRotation0(),stableBodyYaw(), head/body supplier 람다에서entity.pitch(),entity.bodyYaw()등이 EXECUTOR 10ms tick에서 직접 호출됨. Folia에서는 이 값들이 Region Thread에서만 원자적으로 업데이트되므로, 다른 스레드에서 읽으면 stale read 또는 스레드 소유권 검증 실패 발생.기존
FunctionUtil.throttleTickFloat패턴(AtomicLong.compareAndSet+volatile cache)을 활용하여 50ms 간격 thread-safe 캐싱 적용.EntityTracker에서damageTick,walkSpeed등은 이미 이 패턴 사용 중이었으나EntityBodyRotator는 미적용 상태였음.사망 모델 잔류 (EntityManager + EntityTrackerRegistry)
death 애니메이션이 없는 몹 사망 시:
animate("death")→false반환forRemoval(true)미호출 → 트래커 활성 상태 유지EntityRemoveFromWorldEvent→despawn()→forRemoval안 되어있으니despawn()호출 →entity.dead()시점에 따라close()안 될 수 있음수정 후:
forRemoval(true)항상 설정despawn()에서forRemoval트래커를close()→EntityRemoveFromWorldEvent시점에 확실히 정리Test plan
Thread failed main thread check없는지 확인🤖 Generated with Claude Code