Repository navigation
Threads and Folia
ChatGames runs on Spigot, Paper and Folia, and handles chat on the chat thread, so some of its work happens off the main thread. This page says which API calls are safe where, and how to keep your code safe on Folia.
| Call | Where |
|---|---|
getCurrentGame |
any thread |
addRaceProgress, addChallengeProgress
|
any thread |
getStats, addPoints
|
any thread |
getTopPoints, getTopTimes
|
any thread |
registerGame, registerRace, registerRewardAction, registerChallengeType, unregister
|
main thread (normally in onEnable) |
startGame, stopGame, getGame, getGames
|
main thread |
"Main thread" means the server thread on Spigot and Paper, and the global region on Folia.
The "any thread" calls are the ones you would call from listeners: async events (chat, Votifier, your database callbacks) and, on Folia, region threads (every player and block event). Call them directly; don't schedule them first.
| Your code | Runs on |
|---|---|
a chat game's QuestionGenerator
|
main thread / global region |
a race's targetName and targetFilter
|
main thread / global region |
a RewardAction
|
main thread / global region |
| event listeners | see the Events table; some are async |
On Folia there is no single main thread. Each area of the world has its own thread, and the global region handles things that don't belong to an area. The rule: touch a player, an entity or a block only on the thread that owns it. Sending a player a message is fine from anywhere; changing their inventory, health or location, or spawning an entity, is not.
Reward actions run on the global region, which owns no player. So a reward action that changes the player must hand that work to the player's own thread.
If your plugin supports Folia, you build against the Paper API (1.20 or newer), which has per-entity schedulers:
private static final boolean FOLIA = classExists("io.papermc.paper.threadedregions.RegionizedServer");
private static boolean classExists(String name) {
try {
Class.forName(name);
return true;
} catch (ClassNotFoundException e) {
return false;
}
}
/** Runs the task where the player's data may be changed: their region on Folia, the main thread elsewhere. */
void runForPlayer(Player player, Runnable task) {
if (FOLIA) {
player.getScheduler().run(this, scheduled -> task.run(), null);
} else if (Bukkit.isPrimaryThread()) {
task.run();
} else {
Bukkit.getScheduler().runTask(this, task);
}
}Then, in a reward action:
api.registerRewardAction(this, "token", (player, argument) ->
runForPlayer(player, () -> player.getInventory().addItem(myTokenItem(1))));A library like FoliaLib gives you the same thing if your plugin must also build against old Spigot versions.
ChatGamesAnswerEvent and chat-game ChatGamesWinEvents usually come from the chat thread, and
ChatGamesPointsChangeEvent, ChatGamesChallengeCompleteEvent and ChatGamesStreakEvent can come
from any thread. In those listeners:
- reading the event, changing it (
setPoints,setCancelled) and sending messages is fine; - for anything that touches the world or the player's state, use
runForPlayerabove (orBukkit.getScheduler().runTaskon Spigot/Paper); - keep your own data thread-safe (
ConcurrentHashMap, atomic counters).
If your plugin works on Folia, add folia-supported: true to plugin.yml. Without it, Folia won't load
your plugin, but ChatGames still works without your content.
Basics
Adding content
Reacting and reading
Reference