Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,7 @@ hs_err_pid*
/dependency-reduced-pom.xml
/.classpath
/.DS_Store

# IntelliJ IDEA
/.idea/
*.iml
8 changes: 4 additions & 4 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Maven project, Java 21, Paper 1.21.11 API, BentoBox 3.14.0, AOneBlock 1.18.0, Ch
- Run a single test method: `mvn test -Dtest=TopBlockManagerTest#testFormatLevelShorthandKilo`
- The Surefire config sets a long list of `--add-opens` JVM flags — required for Mockito + MockBukkit reflection on Java 21; do not remove them when tweaking the build.

Version handling is driven by Maven properties: `build.version` is the human version (currently 2.1.0), `revision` resolves to `${build.version}-SNAPSHOT` locally and to `${build.version}` under the `master` profile (activated by `GIT_BRANCH=origin/master` on Jenkins). `build.number` is `-LOCAL` locally, `-b<num>` on CI, empty on master. Don't hand-edit `<version>` — bump `build.version`.
Version handling is driven by Maven properties: `build.version` is the human version (currently 2.1.1), `revision` resolves to `${build.version}-SNAPSHOT` locally and to `${build.version}` under the `master` profile (activated by `GIT_BRANCH=origin/master` on Jenkins). `build.number` is `-LOCAL` locally, `-b<num>` on CI, empty on master. Don't hand-edit `<version>` — bump `build.version`.

## Runtime entry points (Pladdon pattern)

Expand All @@ -40,9 +40,9 @@ AOneBlock and ChunkBlock have twin APIs (`getBlockListener().getAllIslands()`, `

`TopBlockManager` is a `Listener` that reacts to `BentoBoxReadyEvent` (handler is `public void onBentoBoxReady` — Bukkit silently skips private @EventHandler methods, which is what broke the addon historically) to start a repeating Bukkit task. The task period is `settings.getRefreshTime() * 20L * 60` ticks (minutes → ticks). Each tick of the task:

1. Calls `refreshAll()` — for every hook, reads every island of that game mode via `hook.getAllIslandData()`, so the refresh interval is intentionally coarse (default 5 min, min 1 min).
2. Builds a fresh `List<TopTenData>` (record of island + blockNumber + lifetime + phaseName) per hook, kept in a `Map<TopBlockHook, List<TopTenData>>` — sorted at read time via `Comparator` on `lifetime` then `blockNumber`.
3. Updates `PlaceholderManager`'s cached per-hook snapshots.
1. Calls `refreshAll()` — for every hook, reads every island of that game mode via `hook.getAllIslandData()`, so the refresh interval is intentionally coarse (default 5 min, min 1 min). `getAllIslandData()` is a full synchronous database read (`loadObjects()` in the game mode), so `refresh(hook)` runs it on an async task and only hops back to the main thread (`processIslandData`) for the island registry / player / permission lookups — keep the async/sync split when changing this code.
2. `processIslandData` builds a fresh `List<TopTenData>` (record of island + blockNumber + lifetime + phaseName) per hook, kept in a `Map<TopBlockHook, List<TopTenData>>` — sorted at read time via `Comparator` on `lifetime` then `blockNumber`.
3. It then updates `PlaceholderManager`'s cached per-hook snapshots (per hook, as each async load completes).

Placeholders are registered once per hook via a `runTaskLater` 10-tick delay after the first ready event (so PAPI / BentoBox's `PlaceholdersManager` is up). Names follow `island_<field>_top_<1..10>` and are scoped to each hook's `GameModeAddon`, so the PAPI prefix keeps game modes apart (`%aoneblock_...%` vs `%chunkblock_...%`). The `TopBlock.TEN` constant is the source of truth for the list size.

Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@
<!-- Do not change unless you want different name for local builds. -->
<build.number>-LOCAL</build.number>
<!-- This allows to change between versions. -->
<build.version>2.1.0</build.version>
<build.version>2.1.1</build.version>
<sonar.projectKey>BentoBoxWorld_TopBlock</sonar.projectKey>
<sonar.organization>bentobox-world</sonar.organization>
<sonar.host.url>https://sonarcloud.io</sonar.host.url>
Expand Down
22 changes: 15 additions & 7 deletions src/main/java/world/bentobox/topblock/TopBlockManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

import world.bentobox.bentobox.api.events.BentoBoxReadyEvent;
import world.bentobox.bentobox.database.objects.Island;
import world.bentobox.topblock.hooks.IslandBlockData;
import world.bentobox.topblock.hooks.TopBlockHook;


Expand Down Expand Up @@ -73,12 +74,8 @@ public TopBlockManager(TopBlock addon) {
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
public void onBentoBoxReady(BentoBoxReadyEvent e) {
// Load the top ten from each hooked game mode every so often
Bukkit.getScheduler().runTaskTimer(addon.getPlugin(), () -> {
// Update TopTens
refreshAll();
// Update placeholders
phm.updateTopTen();
}, 0, addon.getSettings().getRefreshTime() * 20L * 60);
Bukkit.getScheduler().runTaskTimer(addon.getPlugin(), this::refreshAll,
0, addon.getSettings().getRefreshTime() * 20L * 60);
// Register placeholders after everything is loaded
Bukkit.getScheduler().runTaskLater(addon.getPlugin(),
() -> addon.getHooks().forEach(phm::registerPlaceholders), 10L);
Expand All @@ -89,14 +86,25 @@ void refreshAll() {
}

void refresh(TopBlockHook hook) {
// getAllIslandData() reads the game mode's whole island database, so keep it off the main thread
Bukkit.getScheduler().runTaskAsynchronously(addon.getPlugin(), () -> {
List<IslandBlockData> islandData = hook.getAllIslandData();
// Island registry, players and permissions are main-thread only
Bukkit.getScheduler().runTask(addon.getPlugin(), () -> processIslandData(hook, islandData));
});
}

void processIslandData(TopBlockHook hook, List<IslandBlockData> islandData) {
List<TopTenData> data = new ArrayList<>();
hook.getAllIslandData().stream().filter(i -> i.lifetime() > 0).forEach(i ->
islandData.stream().filter(i -> i.lifetime() > 0).forEach(i ->
addon.getIslands().getIslandById(i.uniqueId())
.filter(island -> hook.getGameMode().inWorld(island.getWorld()))
.filter(this::ownerInTopTen)
.ifPresent(island ->
data.add(new TopTenData(island, i.blockNumber(), i.lifetime(), i.phaseName()))));
topTens.put(hook, data);
// Update placeholders
phm.updateTopTen();
}

/**
Expand Down
9 changes: 9 additions & 0 deletions src/test/java/world/bentobox/topblock/CommonTestSetup.java
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,15 @@ public void setUp() throws Exception {
mockedBukkit.when(Bukkit::getItemFactory).thenReturn(itemFactory);
mockedBukkit.when(Bukkit::getServer).thenReturn(server);
mockedBukkit.when(Bukkit::getScheduler).thenReturn(sch);
// TopBlockManager.refresh() hops async -> sync via the scheduler; run both legs inline in tests
when(sch.runTaskAsynchronously(any(), any(Runnable.class))).thenAnswer(i -> {
i.getArgument(1, Runnable.class).run();
return null;
});
when(sch.runTask(any(), any(Runnable.class))).thenAnswer(i -> {
i.getArgument(1, Runnable.class).run();
return null;
});
// By default treat island owners as offline so the intopten filter
// does not exclude them. Tests that need an online owner can override.
mockedBukkit.when(() -> Bukkit.getPlayer(any(UUID.class))).thenReturn(null);
Expand Down
Loading