Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
d6b2c05
Optomize memory usage and cache usage
Trainboy15 May 18, 2026
6952825
Update version from 1.0.1-beta to 1.0.0-patch
Trainboy15 May 18, 2026
743151f
Remove gradle temp
Trainboy15 May 18, 2026
0a82893
Refactor Gradle CI workflow for improved structure
Trainboy15 May 18, 2026
f499758
Update build artifact
invalid-email-address May 18, 2026
ecedd55
Remove deployment step from gradle.yml
Trainboy15 May 18, 2026
35d2da4
Update build artifact
invalid-email-address May 18, 2026
52ea90c
Fix config
Trainboy15 May 18, 2026
bcfb2b9
Add NPE catches
Trainboy15 May 18, 2026
a632e1f
Merge pull request #1 from Trainboy15/testing
Trainboy15 May 18, 2026
e6d7ee9
Update build artifact
invalid-email-address May 18, 2026
cd5988f
Tab indentation
Trainboy15 May 18, 2026
5b1cae7
Update build artifact
invalid-email-address May 18, 2026
abb5169
Clean up whitespace
UplandJacob May 18, 2026
ab3107e
Update build artifact
invalid-email-address May 18, 2026
b64bd0d
Fix IllegalArgumentException
Trainboy15 May 19, 2026
d5d0b4c
Update build artifact
invalid-email-address May 19, 2026
d7da218
Add update check
Trainboy15 May 19, 2026
c327678
Fixed update checker
Trainboy15 May 19, 2026
854f750
Update build artifact
invalid-email-address May 19, 2026
951fab7
Add testing... IDK if it will work
Trainboy15 May 19, 2026
ddd22ad
Change to java 17
Trainboy15 May 19, 2026
c97efac
Fix dependencies?
Trainboy15 May 19, 2026
8005e8f
FR this time trust
Trainboy15 May 19, 2026
509cf1b
Update build artifact
invalid-email-address May 19, 2026
2967f45
Refactor GitHub Actions workflow for tests
Trainboy15 May 19, 2026
21ac55b
Update build artifact
invalid-email-address May 19, 2026
acf5f53
maybe fix memory issuses
Trainboy15 May 20, 2026
b2a43a6
Update build artifact
invalid-email-address May 20, 2026
707b3cf
Impement @UplandJacob's changes
Trainboy15 May 21, 2026
844dbd9
Update build artifact
invalid-email-address May 21, 2026
9cc2862
undo some things for now
UplandJacob May 23, 2026
c23163c
Update build artifact
invalid-email-address May 23, 2026
e5c2f5f
oop more
UplandJacob May 23, 2026
fe9c89c
Update build artifact
invalid-email-address May 23, 2026
6427b1e
Remove unnecessary line from gradle.yml
UplandJacob May 23, 2026
fb46122
Update build artifact
invalid-email-address May 23, 2026
6a48b75
Implement some of @UplandJacob's changes
Trainboy15 May 26, 2026
0c404b3
Update build artifact
invalid-email-address May 26, 2026
1ca2cda
Sync with upstream
Trainboy15 May 29, 2026
6fb653a
Revert "Sync with upstream"
UplandJacob Jun 2, 2026
517518d
Merge remote-tracking branch 'origin/main' into Trainboy15/main
UplandJacob Jun 2, 2026
be6551f
Update build artifact
invalid-email-address Jun 2, 2026
1363885
Remove unnecessary temp vars (used only once each)
UplandJacob Jun 2, 2026
68a1656
Merge commit 'refs/pull/18/head' of https://github.com/TuffNetwork/Tu…
UplandJacob Jun 2, 2026
f23294f
Update build artifact
invalid-email-address Jun 2, 2026
b8801b2
Remove unused version checking var
UplandJacob Jun 2, 2026
126fd78
Update build artifact
invalid-email-address Jun 2, 2026
7a8a860
Remove old tests
Trainboy15 Jun 3, 2026
2f3586c
Fix test logging configuration in build.gradle
Trainboy15 Jun 3, 2026
c5c1873
Update actions/checkout to v6
UplandJacob Jun 4, 2026
25ce262
Fix buffer index order with optimized loop order
UplandJacob Jun 4, 2026
5802769
Update build artifact
invalid-email-address Jun 4, 2026
b74a4b1
Move test to correct dir
UplandJacob Jun 13, 2026
4c7e53f
Merge branch 'main'
UplandJacob Jun 13, 2026
6e6af3f
Merge commit 'refs/pull/18/head'
UplandJacob Jun 13, 2026
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
36 changes: 36 additions & 0 deletions .github/workflows/test.yml
Comment thread
UplandJacob marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: Build & Test

on:
push:
branches: [ "**" ]
pull_request:
branches: [ "**" ]

jobs:
test:
name: Unit Tests
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v6

- name: Set up Java 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
cache: gradle

- name: Grant execute permission for gradlew
run: chmod +x gradlew

- name: Run tests
run: ./gradlew test --no-daemon

- name: Upload test reports
if: always()
uses: actions/upload-artifact@v4
with:
name: test-reports
path: build/reports/tests/test/
26 changes: 19 additions & 7 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -24,26 +24,30 @@ repositories {
maven { url = 'https://repo.codemc.io/repository/maven-snapshots/' }
maven { url = 'https://jitpack.io' }
maven { url = 'https://repo.dmulloy2.net/repository/public/' }
maven { url = 'https://repo.seeseemelk.be/repository/maven-public/' } // MockBukkit
}

dependencies {
compileOnly 'org.spigotmc:spigot-api:1.18.2-R0.1-SNAPSHOT'

implementation 'com.github.retrooper:packetevents-spigot:2.12.1'

compileOnly 'com.viaversion:viabackwards:5.3.2'
compileOnly 'com.viaversion:viaversion:5.9.1'

compileOnly 'it.unimi.dsi:fastutil:8.5.16'

implementation 'com.fasterxml.jackson.core:jackson-databind:2.15.2'

compileOnly 'io.netty:netty-all:4.1.97.Final'
compileOnly 'io.netty:netty-all:4.1.97.Final'

implementation 'org.java-websocket:Java-WebSocket:1.6.0'

compileOnly 'org.projectlombok:lombok:1.18.30'
annotationProcessor 'org.projectlombok:lombok:1.18.30'

testImplementation 'org.junit.jupiter:junit-jupiter:5.10.2'
testImplementation 'com.github.seeseemelk:MockBukkit-v1.18:3.86.0'
}

processResources {
Expand All @@ -58,12 +62,12 @@ processResources {
shadowJar {
archiveClassifier.set('')
archiveFileName.set("${project.name}-${project.version}.jar")

relocate 'com.github.retrooper.packetevents', 'tf.tuff.packetevents'
relocate 'io.github.retrooper.packetevents', 'tf.tuff.packetevents'
relocate 'com.fasterxml.jackson', 'tf.tuff.jackson'
relocate 'org.java_websocket', 'tf.tuff.websocket'

exclude 'META-INF/*.SF'
exclude 'META-INF/*.DSA'
exclude 'META-INF/*.RSA'
Expand All @@ -80,3 +84,11 @@ tasks.named('jar') {
tasks.named('build') {
dependsOn shadowJar
}

test {
useJUnitPlatform()
testLogging {
events "passed", "skipped", "failed"
exceptionFormat = "full"
}
}
Binary file modified builds/TuffXPlus-1.0.1-beta.jar
Comment thread
UplandJacob marked this conversation as resolved.
Binary file not shown.
2 changes: 2 additions & 0 deletions src/main/java/tf/tuff/TuffX.java
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ public void onDisable() {
}

PacketEvents.getAPI().terminate();

getServer().getMessenger().unregisterIncomingPluginChannel(this);
Comment on lines 103 to +105

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Plugin message registrations:"
rg -n --type=java 'register(Incoming|Outgoing)PluginChannel|unregister(Incoming|Outgoing)PluginChannel' src

echo
echo "Expected: every registerOutgoingPluginChannel(...) has a matching unregisterOutgoingPluginChannel(...) on the disable path."

Repository: TuffNetwork/TuffXPlus

Length of output: 2239


Fix plugin-channel teardown: TuffX only unregisters incoming; missing outgoing/incoming unregisters are in Y0Plugin and TuffActions.

  • src/main/java/tf/tuff/TuffX.java:105 calls only getServer().getMessenger().unregisterIncomingPluginChannel(this) and TuffX has no registerOutgoingPluginChannel(...) usage, so “mirror outgoing cleanup here” doesn’t apply.
  • Ensure matching unregisterOutgoingPluginChannel(...) / unregisterIncomingPluginChannel(...) for the channels registered in:
    • src/main/java/tf/tuff/y0/Y0Plugin.java:232-233
    • src/main/java/tf/tuff/tuffactions/TuffActions.java:68-69 (missing any corresponding unregisters can trigger “registration already exists” on reload).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/tf/tuff/TuffX.java` around lines 103 - 105, TuffX currently
only calls PacketEvents.getAPI().terminate() and unregisters incoming channels
via getServer().getMessenger().unregisterIncomingPluginChannel(this); update
TuffX.shutdown/disable logic to explicitly unregister any plugin channels that
other classes register by calling
getServer().getMessenger().unregisterOutgoingPluginChannel(...) and
getServer().getMessenger().unregisterIncomingPluginChannel(...) for the exact
channel names used in Y0Plugin (the channels registered in Y0Plugin.register...
at the Y0Plugin class) and in TuffActions (the channels registered in
TuffActions at its register calls), ensuring every
registerOutgoing/registerIncoming has a matching unregister to prevent
“registration already exists” on reload.

}

public void reloadTuffX(){
Expand Down
46 changes: 30 additions & 16 deletions src/main/java/tf/tuff/viablocks/CustomBlockListener.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@
import tf.tuff.netty.ChunkInjector;
import tf.tuff.viablocks.version.VersionAdapter;

import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.longs.LongArrayList;
import it.unimi.dsi.fastutil.longs.LongList;

public class CustomBlockListener {

public final ViaBlocksPlugin plugin;
Expand Down Expand Up @@ -81,6 +86,7 @@ public byte[] getCachedChunkData(String worldName, int x, int z) {
}

public void setChunkInjector(ChunkInjector injector) {
if (injector == null) return;
this.chunkInjector = injector;
}

Expand Down Expand Up @@ -256,21 +262,28 @@ public void cacheChunkWithCallback(World world, int x, int z, Consumer<byte[]> c
}

private Map<Integer, List<Long>> findModernBlocksInChunk(ChunkSnapshot chunkSnapshot, int minHeight, int maxHeight) {
Map<Integer, List<Long>> foundBlocks = new HashMap<>();
Int2ObjectMap<LongList> foundBlocks = new Int2ObjectOpenHashMap<>();

int chunkX = chunkSnapshot.getX() << 4;
int chunkZ = chunkSnapshot.getZ() << 4;

for (int y = minHeight; y < maxHeight; y++) {
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {

for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
for (int y = minHeight; y < maxHeight; y++) {

// Check material FIRST — getBlockType() returns an enum, no allocation
Material blockType = chunkSnapshot.getBlockType(x, y, z);
if (blockType == Material.AIR || !this.modernMaterials.contains(blockType)) {

if (blockType == Material.AIR
|| blockType == Material.CAVE_AIR
|| blockType == Material.VOID_AIR
|| !this.modernMaterials.contains(blockType)) {
continue;
}
@SuppressWarnings("null")
@Nonnull BlockData data = chunkSnapshot.getBlockData(x, y, z);

// Only allocate BlockData for confirmed modern blocks
BlockData data = chunkSnapshot.getBlockData(x, y, z);

Integer cachedId = blockDataIdCache.getIfPresent(data);
int materialId;
if (cachedId != null) {
Expand All @@ -279,22 +292,23 @@ private Map<Integer, List<Long>> findModernBlocksInChunk(ChunkSnapshot chunkSnap
materialId = this.paletteManager.getOrCreateId(data.getAsString());
blockDataIdCache.put(data, materialId);
}

if (materialId != -1) {
long packedLocation = packLocation(chunkX + x, y, chunkZ + z);
List<Long> locs = foundBlocks.get(materialId);
LongList locs = foundBlocks.get(materialId);
if (locs == null) {
locs = new ArrayList<>();
locs = new LongArrayList();
foundBlocks.put(materialId, locs);
}
locs.add(packedLocation);
}
}
}
}
return foundBlocks;
}


return (Map<Integer, List<Long>>) (Map<?, ?>) foundBlocks;
}

public byte[] getExtraDataForMultiBlock(World world, List<Long> locations) {
Map<Integer, List<Long>> foundBlocks = new HashMap<>();

Expand Down
41 changes: 27 additions & 14 deletions src/main/java/tf/tuff/y0/Y0Plugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
Expand Down Expand Up @@ -251,6 +252,7 @@ public boolean isPlayerReady(Player player) {
}

public void setChunkInjector(ChunkInjector injector) {
if (injector == null) return;
this.chunkInjector = injector;
}

Expand Down Expand Up @@ -673,49 +675,60 @@ public void handleChunkLoad(ChunkLoadEvent event) {
}

private byte[] createSectionPayload(ChunkSnapshot s, int x, int z, int sy, Object2ObjectOpenHashMap<BlockData, int[]> c) throws IOException {
// Ensure thread-local buffer is exactly 12,288 bytes to prevent overflow
byte[] bd = threadData.get();
Arrays.fill(bd, (byte) 0);
int idx = 0;
boolean h = false;
boolean hasContent = false;
int by = sy << 4;

for (int y = 0; y < 16; y++) {
int wy = by + y;
// Optimized Loop Order: Matches standard Minecraft internal memory layouts
for (int xx = 0; xx < 16; xx++) {
for (int zz = 0; zz < 16; zz++) {
for (int xx = 0; xx < 16; xx++) {
for (int y = 0; y < 16; y++) {
int wy = by + y;

BlockData bdata = s.getBlockData(xx, wy, zz);
int[] ld = c.getOrDefault(bdata, EMPTY_LEGACY);
if (ld == EMPTY_LEGACY && v != null) {
ld = v.toLegacy(bdata);
int[] ld = c.get(bdata); // Fast map lookup

if (ld == null) { // Avoid getOrDefault overhead
ld = (v != null) ? v.toLegacy(bdata) : EMPTY_LEGACY;
c.put(bdata, ld);
}

// Bitwise packing
short lb = (short) ((ld[1] << 12) | (ld[0] & 0xFFF));
byte pl = (byte) ((s.getBlockSkyLight(xx, wy, zz) << 4) | s.getBlockEmittedLight(xx, wy, zz));

bd[idx++] = (byte) (lb >> 8);
bd[idx++] = (byte) lb;
bd[idx++] = pl;
// Write sequence
int linear = ((y << 8) | (zz << 4) | xx) * 3;
bd[linear] = (byte) (lb >> 8);
bd[linear + 1] = (byte) lb;
bd[linear + 2] = pl;
if (linear + 3 > idx) idx = linear + 3;

if (lb != 0 || pl != 0) {
h = true;
hasContent = true;
}
}
}
}

if (!h) return null;
if (!hasContent) return null;

ByteArrayOutputStream bout = threadOut.get();
bout.reset();

// DataOutputStream wrapper safely writes schema
try (DataOutputStream out = new DataOutputStream(bout)) {
out.writeUTF("chunk_data");
out.writeInt(x);
out.writeInt(z);
out.writeInt(sy);
out.write(bd, 0, idx);
return bout.toByteArray();
}

return bout.toByteArray();
}

public void handleBlockBreak(BlockBreakEvent event) {
Expand Down Expand Up @@ -887,4 +900,4 @@ private byte[] createLightPayload(ChunkSnapshot s, Coords sc) throws IOException
}
}

}
}
37 changes: 37 additions & 0 deletions src/test/java/tf/tuff/TuffXTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package tf.tuff;

import be.seeseemelk.mockbukkit.MockBukkit;
import be.seeseemelk.mockbukkit.ServerMock;
import be.seeseemelk.mockbukkit.entity.PlayerMock;
import org.junit.jupiter.api.*;

import static org.junit.jupiter.api.Assertions.*;

class TuffXTest {
Comment on lines +1 to +10

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Test file location:"
fd -p 'TuffXTest.java'

echo
echo "Gradle source-set configuration:"
rg -n 'sourceSets|useJUnitPlatform|test \{' build.gradle settings.gradle gradle.properties 2>/dev/null || true

echo
echo "Expected: either the test file lives under src/test/java/... or build.gradle defines a custom test source set that includes src/test."

Repository: TuffNetwork/TuffXPlus

Length of output: 340


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== build.gradle lines 70-120 =="
sed -n '70,120p' build.gradle

echo
echo "== Any sourceSets configuration in repo (limited to build.gradle/gradle/*.gradle) =="
rg -n "sourceSets" -S . || true

echo
echo "== List of Java test files under src/test =="
fd -e java . src/test 2>/dev/null | head -n 200

Repository: TuffNetwork/TuffXPlus

Length of output: 696


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Test file location:"
fd -p 'TuffXTest.java'

echo
echo "Gradle source-set configuration:"
rg -n 'sourceSets|sourceDirs|test \{' build.gradle settings.gradle gradle.properties 2>/dev/null || true

echo
echo "Expected: either the test file lives under src/test/java/... or build.gradle defines a custom test source set/sourceDirs including src/test."

Repository: TuffNetwork/TuffXPlus

Length of output: 308


Move TuffXTest into Gradle’s active test source set (src/test/java).

src/test/TuffXTest.java is not under src/test/java, and build.gradle only configures test { useJUnitPlatform() } with no sourceSets override—so this test can be skipped entirely in CI. Also, since it declares package tf.tuff;, it should be located at src/test/java/tf/tuff/TuffXTest.java.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/test/TuffXTest.java` around lines 1 - 10, The test class TuffXTest is
outside Gradle’s default test source set; move the file TuffXTest (which
declares package tf.tuff) into the standard test directory so its path matches
the package (src/test/java/tf/tuff/TuffXTest.java) or alternatively update
Gradle’s sourceSets configuration to include its current location; ensure the
package declaration remains tf.tuff and that the class name TuffXTest and
imports are unchanged so JUnit (test { useJUnitPlatform() }) will run it in CI.


private static ServerMock server;
private static TuffX plugin;

@BeforeEach
void setUp() {
server = MockBukkit.mock();
plugin = MockBukkit.load(TuffX.class);
}

@AfterEach
void tearDown() {
MockBukkit.unmock();
}

@Test
void pluginEnablesSuccessfully() {
assertTrue(plugin.isEnabled(), "Plugin should be enabled after load");
}


@Test
void reloadDoesNotThrow() {
assertDoesNotThrow(() -> plugin.reloadTuffX(),
"reloadTuffX() should not throw");
}
}