Skip to content

Add Folia support - #330

Open
Raw2d wants to merge 3 commits into
nulli0n:masterfrom
Raw2d:folia-support
Open

Add Folia support#330
Raw2d wants to merge 3 commits into
nulli0n:masterfrom
Raw2d:folia-support

Conversation

@Raw2d

@Raw2d Raw2d commented Jul 4, 2026

Copy link
Copy Markdown

Summary

  • Declares folia-supported: true in plugin.yml and bumps the nightcore dependency to 2.16.3 (2.10.0, the version currently pinned, has no Folia scheduler bridge at all — NightPlugin.runTask() there just calls the plain BukkitScheduler directly).
  • Ports the three periodic tickers (CrateManager.playCrateEffects, HologramManager creation/rendering, OpeningManager.tickOpenings) to dispatch their actual world/entity-touching work through plugin.runTask(Location, …) / runTask(Entity, …) instead of running it on the global-region or async thread pool.
  • Fixes two real bugs surfaced by testing on an actual Folia server (not just compiling):
    1. DataManager/KeyManager/CrateManager used nightcore's runTask(task -> …) pattern. That single-arg lambda resolves to a legacy Consumer<BukkitTask> overload that bypasses nightcore's Folia-safe scheduler bridge and calls the raw BukkitScheduler directly — which throws UnsupportedOperationException on Folia, crashing the plugin on every enable. Switched all 10 call sites to zero-arg () -> … lambdas, which resolve to the Folia-safe Runnable overload.
    2. Crate.load() read block state (block.isEmpty()) synchronously while filtering out air-block positions, off whatever thread loaded/reloaded the crate config. Reproduced live: /crates reload threw IllegalStateException: Cannot read world asynchronously. Fixed by dispatching that read through plugin.runTask(location, …) per position; Crate.blockPositions and HologramManager's internal maps are now concurrent-safe since this can now be touched from multiple region threads.

Test plan

  • mvn clean package succeeds against the real published nightcore 2.16.3 artifact.
  • Ran on a real Folia 1.21.8 server (build 6): nightcore, packetevents, and ExcellentCrates all load and enable with no errors.
  • /crates reload (the exact path that previously crashed) completes cleanly with a real crate configured (block position + hologram + particle effect enabled).
  • Idled ~90s with the crate's periodic tickers actively dispatching per-location work — zero exceptions.
  • A real player joined, linked a block to a crate, saw the hologram and particle effect render correctly, and opened the crate in-game — all working.
  • Not tested: ProtocolLib as the hologram packet backend (only packetevents was exercised).

Raw2d added 3 commits July 4, 2026 22:16
- plugin.yml: declare folia-supported: true (hard requirement to load on Folia).
- CrateManager.playCrateEffects(): moved off the async task pool and now
  dispatches the actual particle spawn per crate-block location via
  plugin.runTask(Location, Runnable), instead of calling world/player
  particle APIs from a non-region thread.
- HologramManager: same pattern for hologram creation/rendering
  (createIfAbsent/render), split render() into a per-group renderGroup()
  dispatched per block location. displayMap and FakeDisplay's internal
  group map are now ConcurrentHashMap since group creation can now be
  dispatched concurrently across regions for the same crate.
- OpeningManager.tickOpenings(): dispatches each opening's tick onto its
  own player's thread instead of the global region thread, since ticking
  mutates that player's inventory/menu state.

Not yet done: ProtocolLib/packetevents Folia-safety audit, and a real
build/test pass (no Maven available in this environment to compile
against the private nightexpress/spigot-snapshot repos).
nightcore 2.10.0 (the version this project was pinned to) has no Folia
scheduler bridge at all -- NightPlugin.runTask() there just delegates to
the plain BukkitScheduler. The runTask(Location, Runnable) and
runTask(Entity, Runnable) overloads used by the ticker fixes only exist
from a later nightcore version onward, so Folia support requires this
bump regardless of the ticker changes.

Also add the missing java.util.concurrent.ConcurrentHashMap import in
HologramManager (java.util.* doesn't cover java.util.concurrent).

Verified: set up Maven 3.9.16 locally and ran a full `mvn clean package`
against the real, published nightcore 2.16.3 artifact -- builds clean,
only pre-existing deprecation warnings unrelated to this change.
Set up a local Folia 1.21.8 test server (build 6), built nightcore 2.16.3
and packetevents from their real shaded/release artifacts, and ran
ExcellentCrates against it. Two genuine bugs surfaced that a compile-only
check couldn't catch:

1. DataManager/CrateManager/KeyManager used nightcore's legacy
   runTask(Consumer<BukkitTask>) / runTaskAsync(Consumer<BukkitTask>)
   overloads via lambdas like `task -> ...`. That overload bypasses
   nightcore's Folia-safe AdaptedScheduler entirely and calls the raw
   BukkitScheduler directly, which throws UnsupportedOperationException
   on Folia -- this crashed ExcellentCrates on every enable. Switched all
   10 call sites to the zero-arg `() -> ...` form, which resolves to the
   Runnable overload and correctly routes through the scheduler bridge.

2. Crate.load() read block state (block.isEmpty()) synchronously while
   filtering out air-block positions, straight off whatever thread
   loaded/reloaded the crate config. Reproduced live: `/crates reload`
   threw "Cannot read world asynchronously" because the command thread
   wasn't the block's owning region thread. Fixed by dispatching the
   block read through plugin.runTask(location, ...) per position, and
   made Crate.blockPositions a concurrent set since removal can now
   happen from multiple region threads.

Verified: full mvn build succeeds, plugin loads/enables/reloads cleanly
on a real Folia server with an actual configured crate (block position,
hologram, and particle effect all enabled), and idles for 90s with zero
exceptions from the periodic tickers.
@creatorfromhell

creatorfromhell commented Jul 5, 2026

Copy link
Copy Markdown

What's the difference between this and #324 beyond the seemingly AI-generated PR message?

@Raw2d

Raw2d commented Jul 6, 2026

Copy link
Copy Markdown
Author

Fair question, and happy to be concrete about it rather than just asserting it's different.

Functionally, #324 and this PR both fix the same runTask(task -> ...) footgun (the single-arg lambda silently falling back to the legacy BukkitScheduler-backed overload) across most of the same call sites, and both wrap Crate.load()'s block-state read so it doesn't run off-thread. Where they diverge:

  • CrateManager.playCrateEffects() is untouched in Folia Support #324. It's still driven by addAsyncTask and reads worldPos.isChunkLoaded() / builds a Location directly on that async thread. On Folia that throws (Cannot read world asynchronously or equivalent) the first time a crate with effects enabled ticks near a player — same failure class as the two bugs Folia Support #324 already fixes elsewhere, just not caught in that method. This PR routes that per-position work through plugin.runTask(Location, ...) like the other tickers.
  • This PR was verified against a real Folia 1.21.8 server (not just mvn package) — enable, /crates reload, ~90s of idle ticking with effects/holograms live, and an actual player linking a block and opening a crate, per the Test Plan checklist above. I don't see equivalent live-server verification described on Folia Support #324.

On the PR description: yes, I used AI assistance to help draft the summary/test-plan text, but the changes themselves were driven by reproducing actual crashes on a live Folia server, not generated blind. Happy to expand on any part of the diff if useful.

@R00tB33rMan

Copy link
Copy Markdown

The violations you're describing derive from Canvas (not Folia) explicitly. You're right that these should be considered; however, it's not worth saying much more, given that your response (on top of the PR) is fully AI-generated. It'd be better to PR changes against my existing PR (since it's fully up-to-date), rather than to reinvent the wheel

@Raw2d

Raw2d commented Aug 1, 2026

Copy link
Copy Markdown
Author

Sorry, I should have been clearer from the start. English is not my first language, so I use AI to help me write the PR description and the comments, and I use it while coding too. I am not going to pretend otherwise. But I have a degree in software development, so I am not leaning on it 100%. I read what goes in and I understand the scheduler changes I made here.

The two bugs in this PR did not come from a model guessing at what might be wrong. I hit them by running the plugin on a real server and reading the stack traces.

About Canvas, I never installed it. My testing was on a plain Folia 1.21.8 server. The Cannot read world asynchronously came from /crates reload there, and the UnsupportedOperationException on enable came from the same setup. If that one is actually specific to a fork and not Folia itself, I am happy to be corrected. I can set the server up again and post the full log so you can see exactly what threw and where.

On reinventing the wheel, that is fair and I do not mind. If you and nulli0n prefer one PR, I can open a PR against your branch with the playCrateEffects change and close this one. Just tell me which you want.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants