Folia is Paper’s experimental multi-threaded server implementation that splits the world into independent regions, each running on separate threads. BetterModel fully supports Folia with region-aware scheduling and thread-safe operations.
Folia is experimental and still in active development. While BetterModel is tested on Folia, expect potential issues as Folia evolves.
Region-Based Threading
Each region runs on its own thread with independent tick loop
Used for tasks that must run in a specific region (entity operations):
Region-Specific Task
BetterModelBukkit platform = BetterModelBukkit.platform();// Schedule task in entity's regionplatform.scheduler().task(entity.getLocation(), () -> { // This runs in the region containing the entity // Safe to modify entity, spawn displays, etc.});
Location-based tasks automatically execute in the correct region thread.
Async Scheduler
Used for tasks that don’t touch world state:
Async Task
platform.scheduler().asyncTask(() -> { // This runs in async thread pool // Cannot modify world/entities directly // Use for I/O, calculations, etc.});
Global Region Scheduler
Folia also has a global region scheduler for tasks that span regions:
Global Task
Bukkit.getGlobalRegionScheduler().run(plugin, (task) -> { // Runs once across all regions});
BetterModel uses this internally for plugin-wide operations.
public void modifyModel(Entity entity) { // WRONG: May be called from different region thread EntityTracker tracker = getTracker(entity); tracker.animate("attack"); // Not safe! // CORRECT: Schedule in entity's region BetterModelBukkit.platform().scheduler().task( entity.getLocation(), () -> { EntityTracker tracker = getTracker(entity); tracker.animate("attack"); // Safe! } );}
Critical Rule: Always schedule entity operations in the entity’s region using scheduler().task(location, runnable).
public class ModelUpdater { public void updateAllModels(Collection<Entity> entities) { // Group entities by region Map<Location, List<Entity>> byRegion = entities.stream() .collect(Collectors.groupingBy(Entity::getLocation)); // Schedule update in each region byRegion.forEach((location, regionEntities) -> { platform.scheduler().task(location, () -> { regionEntities.forEach(entity -> { getTracker(entity).animate("update"); }); }); }); }}
public void debugThread() { Thread thread = Thread.currentThread(); logger.info("Thread: " + thread.getName()); // Folia region threads are named like "RegionScheduler - x" if (thread.getName().startsWith("RegionScheduler")) { logger.info("Running in region thread"); }}
You cannot safely iterate all entities across regions:
// UNSAFE on Foliafor (Entity entity : world.getEntities()) { // May be in different region thread!}
Instead, track entities in your own region-aware data structures.
Event Handler Limitations
Event handlers run in the region where the event occurred:
@EventHandlerpublic void onDamage(EntityDamageEvent event) { // This runs in the entity's region Entity entity = event.getEntity(); // Safe to modify entity directly here}
But you can’t safely access entities in other regions from the event.
Plugin Messaging
Plugin channels work differently on Folia. BetterModel handles this internally, but be aware if you’re implementing custom cross-region communication.
Good news: If you’re using BetterModel’s API correctly, your code should work on Folia without changes!
The scheduler abstraction handles platform differences:
Platform-Agnostic Code
// This works on both Paper and FoliaBetterModelBukkit platform = BetterModelBukkit.platform();platform.scheduler().task(location, () -> { // Your code});platform.scheduler().asyncTask(() -> { // Async code});
Behind the scenes:
Paper: Uses Bukkit.getScheduler()
Folia: Uses Bukkit.getRegionScheduler() and Bukkit.getAsyncScheduler()