Skip to main content
Learn how to optimize BetterModel for large servers with many players and models.

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

1

Tracker Management

  • Use getOrCreate() to prevent duplicates
  • Cache tracker references
  • Implement cleanup tasks
  • Use WeakHashMap for temporary references
2

Animation Optimization

  • Cache animation states
  • Use tick intervals instead of frames
  • Only update when state changes
  • Use predicates for conditional animations
3

Network Optimization

  • Batch updates with composite()
  • Disable unnecessary features (sight trace, damage tint)
  • Use view distance limits
  • Implement per-player animations sparingly
4

Memory Management

  • Always close trackers when done
  • Remove event listeners
  • Clear caches periodically
  • Monitor memory usage
  • 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

Build docs developers (and LLMs) love