Overview
Performance optimization strategies:- Efficient tracker management
- Animation optimization
- Network packet optimization
- Memory management
- Async operations
Tracker Management
Efficient Tracker Creation
import kr.toxicity.model.api.BetterModel;
import kr.toxicity.model.api.entity.BaseEntity;
import kr.toxicity.model.api.tracker.EntityTracker;
import kr.toxicity.model.api.tracker.TrackerModifier;
import org.bukkit.entity.Entity;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
public class OptimizedTrackerManager {
private final Map<UUID, EntityTracker> trackerCache = new ConcurrentHashMap<>();
// GOOD: Use getOrCreate to prevent duplicates
public EntityTracker getTracker(Entity entity, String modelName) {
return trackerCache.computeIfAbsent(entity.getUniqueId(), uuid ->
BetterModel.model(modelName)
.map(renderer -> renderer.getOrCreate(BaseEntity.of(entity)))
.orElse(null)
);
}
// GOOD: Batch cleanup
public void cleanupInactiveTrackers() {
trackerCache.entrySet().removeIf(entry -> {
EntityTracker tracker = entry.getValue();
if (tracker.isClosed() || tracker.playerCount() == 0) {
tracker.close();
return true;
}
return false;
});
}
// BAD: Creating multiple trackers
public void badExample(Entity entity) {
// Don't do this - creates duplicate trackers
BetterModel.model("model").ifPresent(r -> r.create(BaseEntity.of(entity)));
BetterModel.model("model").ifPresent(r -> r.create(BaseEntity.of(entity)));
}
}
Animation Optimization
Efficient Animation Updates
import kr.toxicity.model.api.animation.AnimationModifier;
import kr.toxicity.model.api.tracker.Tracker;
import org.bukkit.entity.LivingEntity;
public class AnimationOptimization {
// GOOD: Cache animation state
private String currentAnimation = "idle";
public void updateAnimation(Tracker tracker, LivingEntity entity) {
String newAnimation = determineAnimation(entity);
// Only update if animation changed
if (!newAnimation.equals(currentAnimation)) {
tracker.stopAnimation(currentAnimation);
tracker.animate(newAnimation);
currentAnimation = newAnimation;
}
}
// GOOD: Use tick intervals instead of every frame
public void setupOptimizedUpdates(Tracker tracker) {
// Update every 5 ticks (250ms) instead of every frame (25ms)
tracker.tick(5, (t, bundler) -> {
// Update logic
});
}
// GOOD: Use predicates for conditional animations
public void setupConditionalAnimation(Tracker tracker, LivingEntity entity) {
tracker.animate("special", AnimationModifier.builder()
.predicate(() -> entity.getHealth() < entity.getMaxHealth() * 0.5)
.build()
);
}
// BAD: Updating animation every frame without checking
public void badExample(Tracker tracker) {
tracker.tick((t, bundler) -> {
// Don't do this - wastes resources
t.stopAnimation("idle");
t.animate("idle");
});
}
private String determineAnimation(LivingEntity entity) {
if (entity.getVelocity().lengthSquared() > 0.01) return "walk";
return "idle";
}
}
Sight Trace Optimization
Visibility Management
import kr.toxicity.model.api.tracker.TrackerModifier;
import kr.toxicity.model.api.util.EntityUtil;
import org.bukkit.entity.Player;
public class VisibilityOptimization {
// GOOD: Disable sight trace for static models
public TrackerModifier createStaticModifier() {
return TrackerModifier.builder()
.sightTrace(false) // No line-of-sight check needed
.damageAnimation(false)
.damageTint(false)
.build();
}
// GOOD: Custom visibility check with caching
public void setupCustomVisibility(Tracker tracker) {
// Check every 10 ticks instead of every frame
tracker.tick(10, (t, bundler) -> {
t.getPipeline().viewFilter(player ->
canSeeModel(player, t.location())
);
});
}
private boolean canSeeModel(Player player, PlatformLocation location) {
// Custom visibility logic with distance check first
double distanceSq = player.getLocation().distanceSquared(location.toBukkit());
if (distanceSq > 2500) return false; // Beyond 50 blocks
// Only do expensive line-of-sight check if within range
return EntityUtil.canSee(player.getEyeLocation(), location);
}
}
Update Action Optimization
Batching Updates
import kr.toxicity.model.api.tracker.TrackerUpdateAction;
import kr.toxicity.model.api.util.function.BonePredicate;
public class UpdateOptimization {
// GOOD: Composite updates
public void applyMultipleUpdates(Tracker tracker) {
tracker.update(TrackerUpdateAction.composite(
TrackerUpdateAction.glow(true),
TrackerUpdateAction.glowColor(0xFF0000),
TrackerUpdateAction.brightness(15, 15),
TrackerUpdateAction.enchant(true)
));
}
// GOOD: Use bone predicates efficiently
public void targetSpecificBones(Tracker tracker) {
// Cache predicate
BonePredicate armPredicate = BonePredicate.name("left_arm")
.or(BonePredicate.name("right_arm"));
tracker.update(
TrackerUpdateAction.glow(true),
armPredicate
);
}
// BAD: Multiple individual updates
public void badExample(Tracker tracker) {
// Don't do this - sends multiple packet bundles
tracker.update(TrackerUpdateAction.glow(true));
tracker.update(TrackerUpdateAction.glowColor(0xFF0000));
tracker.update(TrackerUpdateAction.brightness(15, 15));
tracker.update(TrackerUpdateAction.enchant(true));
}
}
Memory Management
Preventing Memory Leaks
import java.util.Map;
import java.util.WeakHashMap;
import java.util.UUID;
public class MemoryManagement {
// GOOD: Use WeakHashMap for temporary references
private final Map<UUID, EntityTracker> temporaryTrackers = new WeakHashMap<>();
// GOOD: Clean up on plugin disable
public void onDisable() {
temporaryTrackers.values().forEach(tracker -> {
if (!tracker.isClosed()) {
tracker.close();
}
});
temporaryTrackers.clear();
}
// GOOD: Remove listeners when done
public void cleanupTracker(EntityTracker tracker) {
// Close tracker properly
tracker.close();
// Remove from caches
temporaryTrackers.remove(tracker.sourceEntity().uuid());
}
// GOOD: Implement cleanup task
public void scheduleCleanup(JavaPlugin plugin) {
plugin.getServer().getScheduler().runTaskTimer(plugin, () -> {
temporaryTrackers.entrySet().removeIf(entry -> {
EntityTracker tracker = entry.getValue();
return tracker.isClosed() || tracker.sourceEntity().dead();
});
}, 1200L, 1200L); // Every 60 seconds
}
}
Async Operations
Offloading Heavy Tasks
import kr.toxicity.model.api.BetterModel;
import org.bukkit.scheduler.BukkitRunnable;
public class AsyncOptimization {
// GOOD: Load models asynchronously
public void loadModelAsync(JavaPlugin plugin, String modelName, Consumer<ModelRenderer> callback) {
new BukkitRunnable() {
@Override
public void run() {
BetterModel.model(modelName).ifPresent(renderer -> {
// Switch back to main thread for tracker creation
new BukkitRunnable() {
@Override
public void run() {
callback.accept(renderer);
}
}.runTask(plugin);
});
}
}.runTaskAsynchronously(plugin);
}
// GOOD: Use BetterModel's async scheduler
public void asyncUpdate(Tracker tracker, Runnable heavyTask) {
BetterModel.platform().scheduler().asyncTask(() -> {
// Heavy computation
heavyTask.run();
// Update on main thread
tracker.task(() -> {
// Apply results
});
});
}
}
Best Practices Summary
Performance Checklist
Tracker Management
- Use
getOrCreate()to prevent duplicates - Cache tracker references
- Implement cleanup tasks
- Use WeakHashMap for temporary references
Animation Optimization
- Cache animation states
- Use tick intervals instead of frames
- Only update when state changes
- Use predicates for conditional animations
Network Optimization
- Batch updates with
composite() - Disable unnecessary features (sight trace, damage tint)
- Use view distance limits
- Implement per-player animations sparingly
- Profile your plugin with tools like Spark
- Monitor tracker count with
/bettermodel debug - Use sight trace only when necessary
- Batch updates whenever possible
- Implement aggressive cleanup for temporary models
- Don’t create trackers every tick
- Avoid updating animations without state checks
- Don’t forget to close trackers
- Monitor network usage with many models
- Test with realistic player counts
Monitoring Performance
Debug and Profiling
import kr.toxicity.model.api.BetterModel;
import kr.toxicity.model.api.config.DebugConfig;
public class PerformanceMonitoring {
public void enableDebugLogging() {
// Enable debug logging for specific components
BetterModel.config().debug().enable(DebugConfig.DebugOption.TRACKER);
}
public void getModelStats() {
// Get loaded model count
int modelCount = BetterModel.models().size();
int limbCount = BetterModel.limbs().size();
System.out.println("Loaded models: " + modelCount);
System.out.println("Loaded limbs: " + limbCount);
}
public void logTrackerInfo(EntityTracker tracker) {
System.out.println("Tracker: " + tracker.name());
System.out.println("Players: " + tracker.playerCount());
System.out.println("Closed: " + tracker.isClosed());
System.out.println("Scheduled: " + tracker.isScheduled());
}
}
Next Steps
Basic Entity Model
Start with optimized implementations
Installation
Configure BetterModel settings
API Reference
Explore the complete API
Bukkit/Spigot/Paper
Platform-specific guides
