Skip to content

Commit 70f93cd

Browse files
committed
More performance tweaks, add missing Override annotations
1 parent 3a95556 commit 70f93cd

File tree

4 files changed

+107
-61
lines changed

4 files changed

+107
-61
lines changed

CLAUDE.md

Lines changed: 51 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@ Dynmap is a dynamic web mapping plugin/mod for Minecraft servers. It's a multi-p
1515
# Build outputs go to /target directory
1616

1717
# Build specific module (for faster iteration, but NOT for PR submissions)
18-
./gradlew :fabric-1.18:build
18+
./gradlew :DynmapCore:build
19+
20+
# Run unit tests (DynmapCore only — JUnit 4)
21+
./gradlew :DynmapCore:test
1922

2023
# Forge 1.12.2 (requires JDK 8 - set JAVA_HOME accordingly)
2124
cd oldgradle
@@ -27,20 +30,24 @@ cd oldgradle
2730
- Forge 1.12.2 (oldgradle): JDK 8 strictly required
2831
- Runtime targets: JDK 8 (1.16-), JDK 16 (1.17.x), JDK 17 (1.18-1.20.4), JDK 21 (1.20.5+)
2932

33+
**Build notes:**
34+
- `gradle.properties` sets `org.gradle.parallel=false` and `org.gradle.daemon=false` — do not change these
35+
- `snakeyaml` is pinned at 1.23 intentionally — newer versions break on Windows-encoded config files
36+
3037
## Architecture
3138

32-
### Module Structure (71 modules total)
39+
### Module Structure
3340

3441
**Core Shared Modules:**
35-
- `DynmapCoreAPI/` - Stable public API for external plugins/mods (markers, mod support, rendering)
42+
- `DynmapCoreAPI/` - Stable public API for external plugins/mods (markers, mod support, rendering). Published to `repo.mikeprimm.com`. The `org.dynmap.renderer` package here defines `DynmapBlockState` — the central block state abstraction used everywhere.
3643
- `DynmapCore/` - Internal shared implementation (NOT stable - subject to breaking changes)
3744
- `dynmap-api/` - Bukkit-specific public API
3845

3946
**Platform Implementations:**
40-
- `spigot/` - Bukkit/PaperMC implementation
41-
- `bukkit-helper-*` - Version-specific NMS code (25 versions: 1.13-1.21)
42-
- `fabric-*` - Fabric mod implementations (14 versions: 1.14.4-1.21.11)
43-
- `forge-*` - Forge mod implementations (14 versions: 1.14.4-1.21.11)
47+
- `spigot/` - Bukkit/PaperMC implementation (`DynmapPlugin.java`)
48+
- `bukkit-helper-*` - Version-specific NMS code (one per MC version: 1.13-1.21)
49+
- `fabric-*` - Fabric mod implementations (1.14.4-1.21.x)
50+
- `forge-*` - Forge mod implementations (1.14.4-1.21.x); `forge-1.12.2` lives in `oldgradle/`
4451

4552
### Dependency Flow
4653
```
@@ -54,31 +61,47 @@ Platform-specific modules (Spigot, Fabric, Forge)
5461
```
5562

5663
### Key Components in DynmapCore
57-
- `MapManager` - Tile/map rendering orchestration
58-
- `DynmapCore.java` - Main coordination hub (~3,100 lines)
59-
- `storage/` - Storage backends (FileTree, MySQL, PostgreSQL, SQLite, S3)
60-
- `hdmap/` - HD map rendering (block models, shaders, textures)
61-
- `web/` - Embedded Jetty server with servlets
62-
- `markers/` - Marker system implementation
63-
64-
## Critical Contribution Rules
65-
66-
**PRs must build and test on ALL platforms including oldgradle. Changes to DynmapCore/DynmapCoreAPI require testing on all platforms.**
6764

68-
- **Java 8 compatibility required** - Code must compile and run on Java 8
69-
- **Java only** - No Kotlin, Scala, or other JVM languages
70-
- **No dependency updates** - Library versions are tied to platform compatibility
71-
- **No platform-specific code** - Must work on Windows, Linux (x86/ARM), macOS, Docker
72-
- **Small PRs only** - One feature per PR, no style/formatting changes
73-
- **No mod-specific code** - Use Dynmap APIs instead; external mods should depend on DynmapCoreAPI
74-
- **Apache License v2** - All code must be compatible
65+
- `DynmapCore.java` — Main coordination hub (~3,100 lines); bootstrapped by each platform
66+
- `MapManager.java` — Tile rendering orchestration; owns the render thread pool and `FullWorldRenderState` queue
67+
- `hdmap/` — HD map rendering pipeline:
68+
- `IsoHDPerspective` — Isometric raytrace engine (the hot rendering path)
69+
- `HDBlockModels` / `HDScaledBlockModels` — Block geometry (patch/volumetric/scaled models)
70+
- `TexturePack` / `TexturePackLoader` — Texture resolution from resource packs
71+
- `hdmap/renderer/` — Custom block renderers (stairs, fences, doors, etc.) implementing `CustomRenderer`
72+
- Shaders (`DefaultHDShader`, `CaveHDShader`, `TopoHDShader`, etc.) — post-process pixel color
73+
- Lighting (`DefaultHDLighting`, `ShadowHDLighting`, etc.) — light level calculation
74+
- `storage/` — Storage backends (FileTree, MySQL, MariaDB, PostgreSQL, SQLite, MSSQL, AWS S3)
75+
- `web/` — Embedded Jetty 9 server with custom HTTP routing (no standard servlet container)
76+
- `markers/impl/` — Full marker system implementation; public interface is in `DynmapCoreAPI`
77+
- `utils/MapChunkCache` + `utils/MapIterator` — Abstract interfaces that each platform implements to feed world data into the renderer
78+
79+
### Platform Integration Pattern
80+
81+
Each platform module (Spigot `bukkit-helper-*`, Fabric, Forge) must implement:
82+
- `MapChunkCache` — Loads and caches chunk data for a tile's required chunks
83+
- `MapIterator` — Block-by-block iteration over the loaded chunk cache
84+
- A platform entry point (e.g., `DynmapPlugin` for Spigot) that bootstraps `DynmapCore`
85+
86+
The `bukkit-helper-*` modules contain version-specific NMS code; `spigot/` delegates to the appropriate helper at runtime via reflection.
7587

7688
## Testing
7789

78-
No automated tests exist. Verification is done by:
79-
1. Building all platforms successfully (`./gradlew setup build` AND `cd oldgradle && ./gradlew setup build`)
90+
Unit tests exist in `DynmapCore/src/test/` (JUnit 4) covering `Matrix3D`, `Vector3D`, `IpAddressMatcher`, `DynIntHashMap`, and `BufferInputStream`. Run with `./gradlew :DynmapCore:test`.
91+
92+
Full verification requires:
93+
1. Building all platforms: `./gradlew setup build` AND `cd oldgradle && ./gradlew setup build`
8094
2. Manual testing on target Minecraft server platforms
8195

82-
## Storage Backends
96+
## Critical Contribution Rules
97+
98+
**PRs must build and test on ALL platforms including oldgradle. Changes to DynmapCore/DynmapCoreAPI require testing on all platforms.**
8399

84-
Dynmap supports: FileTree (default), MySQL/MariaDB, PostgreSQL, SQLite, MS SQL Server, AWS S3
100+
- **Java 8 compatibility required** — Code must compile and run on Java 8
101+
- **Java only** — No Kotlin, Scala, or other JVM languages
102+
- **No dependency updates** — Library versions are tied to platform compatibility
103+
- **No platform-specific code** — Must work on Windows, Linux (x86/ARM), macOS, Docker
104+
- **Small PRs only** — One feature per PR, no style/formatting changes
105+
- **No mod-specific code** — Use Dynmap APIs instead; external mods should depend on DynmapCoreAPI
106+
- **Apache License v2** — All code must be compatible
107+
- **DynmapCoreAPI is the only stable API** — Do not add external dependencies on DynmapCore internals

DynmapCore/src/main/java/org/dynmap/hdmap/HDMapTile.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,13 @@ protected String saveTileData() {
5252

5353
@Override
5454
public int hashCode() {
55-
return tx ^ ty ^ perspective.hashCode() ^ world.hashCode() ^ boostzoom ^ tilescale;
55+
int h = perspective.hashCode();
56+
h = h * 31 + world.hashCode();
57+
h = h * 31 + tx;
58+
h = h * 31 + ty;
59+
h = h * 31 + boostzoom;
60+
h = h * 31 + tilescale;
61+
return h;
5662
}
5763

5864
@Override
@@ -87,19 +93,24 @@ public String toString() {
8793
@Override
8894
public boolean isBlockTypeDataNeeded() { return MapManager.mapman.hdmapman.isBlockTypeDataNeeded(this); }
8995

96+
@Override
9097
public boolean render(MapChunkCache cache, String mapname) {
9198
return perspective.render(cache, this, mapname);
9299
}
93100

101+
@Override
94102
public List<DynmapChunk> getRequiredChunks() {
95103
return perspective.getRequiredChunks(this);
96104
}
97105

106+
@Override
98107
public MapTile[] getAdjecentTiles() {
99108
return perspective.getAdjecentTiles(this);
100109
}
101110

111+
@Override
102112
public int tileOrdinalX() { return tx; }
113+
@Override
103114
public int tileOrdinalY() { return ty; }
104115

105116
}

DynmapCore/src/main/java/org/dynmap/hdmap/IsoHDPerspective.java

Lines changed: 28 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ private class OurPerspectiveState implements HDPerspectiveState {
9090
/* Scaled models for non-cube blocks */
9191
private final HDScaledBlockModels scalemodels;
9292
private final int modscale;
93+
private final int modscale2; /* modscale * modscale, precomputed for raytraceSubblock */
9394

9495
/* Section-level raytrace variables */
9596
int sx, sy, sz;
@@ -153,6 +154,7 @@ public OurPerspectiveState(MapIterator mi, boolean isnether, int scaled) {
153154
custom_meshes = new DynLongHashMap(4096);
154155
custom_fluid_meshes = new DynLongHashMap(4096);
155156
modscale = basemodscale << scaled;
157+
modscale2 = modscale * modscale;
156158
scalemodels = HDBlockModels.getModelsForScale(basemodscale << scaled);
157159
}
158160

@@ -250,18 +252,22 @@ public final void getLightLevelsAtStep(BlockStep step, LightLevels ll) {
250252
/**
251253
* Get pixel X coordinate
252254
*/
255+
@Override
253256
public final int getPixelX() { return px; }
254257
/**
255258
* Get pixel Y coordinate
256259
*/
260+
@Override
257261
public final int getPixelY() { return py; }
258262
/**
259263
* Get map iterator
260264
*/
265+
@Override
261266
public final MapIterator getMapIterator() { return mapiter; }
262267
/**
263268
* Return submodel alpha value (-1 if no submodel rendered)
264269
*/
270+
@Override
265271
public int getSubmodelAlpha() {
266272
return subalpha;
267273
}
@@ -283,9 +289,9 @@ private void raytrace_init() {
283289
n = 1;
284290

285291
/* Initial section coord */
286-
sx = fastFloor(top.x/16.0);
287-
sy = fastFloor(top.y/16.0);
288-
sz = fastFloor(top.z/16.0);
292+
sx = fastFloor(top.x * 0.0625);
293+
sy = fastFloor(top.y * 0.0625);
294+
sz = fastFloor(top.z * 0.0625);
289295
/* Compute parametric step (dt) per step on each axis */
290296
sdt_dx = 16.0 / dx;
291297
sdt_dy = 16.0 / dy;
@@ -302,15 +308,15 @@ private void raytrace_init() {
302308
else if (bottom.x > top.x) {
303309
x_inc = 1;
304310
n += fastFloor(bottom.x) - x;
305-
st_next_x = (fastFloor(top.x/16.0) + 1 - (top.x/16.0)) * sdt_dx;
311+
st_next_x = (fastFloor(top.x * 0.0625) + 1 - (top.x * 0.0625)) * sdt_dx;
306312
stepx = BlockStep.X_PLUS;
307313
mxout = modscale;
308314
}
309315
/* Top is right of bottom */
310316
else {
311317
x_inc = -1;
312318
n += x - fastFloor(bottom.x);
313-
st_next_x = ((top.x/16.0) - fastFloor(top.x/16.0)) * sdt_dx;
319+
st_next_x = ((top.x * 0.0625) - fastFloor(top.x * 0.0625)) * sdt_dx;
314320
stepx = BlockStep.X_MINUS;
315321
mxout = -1;
316322
}
@@ -325,15 +331,15 @@ else if (bottom.x > top.x) {
325331
else if (bottom.y > top.y) {
326332
y_inc = 1;
327333
n += fastFloor(bottom.y) - y;
328-
st_next_y = (fastFloor(top.y/16.0) + 1 - (top.y/16.0)) * sdt_dy;
334+
st_next_y = (fastFloor(top.y * 0.0625) + 1 - (top.y * 0.0625)) * sdt_dy;
329335
stepy = BlockStep.Y_PLUS;
330336
myout = modscale;
331337
}
332338
/* If top is above bottom */
333339
else {
334340
y_inc = -1;
335341
n += y - fastFloor(bottom.y);
336-
st_next_y = ((top.y/16.0) - fastFloor(top.y/16.0)) * sdt_dy;
342+
st_next_y = ((top.y * 0.0625) - fastFloor(top.y * 0.0625)) * sdt_dy;
337343
stepy = BlockStep.Y_MINUS;
338344
myout = -1;
339345
}
@@ -348,15 +354,15 @@ else if (bottom.y > top.y) {
348354
else if (bottom.z > top.z) {
349355
z_inc = 1;
350356
n += fastFloor(bottom.z) - z;
351-
st_next_z = (fastFloor(top.z/16.0) + 1 - (top.z/16.0)) * sdt_dz;
357+
st_next_z = (fastFloor(top.z * 0.0625) + 1 - (top.z * 0.0625)) * sdt_dz;
352358
stepz = BlockStep.Z_PLUS;
353359
mzout = modscale;
354360
}
355361
/* If bottom left of top */
356362
else {
357363
z_inc = -1;
358364
n += z - fastFloor(bottom.z);
359-
st_next_z = ((top.z/16.0) - fastFloor(top.z/16.0)) * sdt_dz;
365+
st_next_z = ((top.z * 0.0625) - fastFloor(top.z * 0.0625)) * sdt_dz;
360366
stepz = BlockStep.Z_MINUS;
361367
mzout = -1;
362368
}
@@ -810,7 +816,7 @@ private final boolean raytraceSubblock(short[] model, boolean firsttime) {
810816
while(mt <= mtend) {
811817
if(!skip) {
812818
try {
813-
int blkalpha = model[modscale*modscale*my + modscale*mz + mx];
819+
int blkalpha = model[modscale2*my + modscale*mz + mx];
814820
if(blkalpha > 0) {
815821
subalpha = blkalpha;
816822
return false;
@@ -857,6 +863,7 @@ else if((mt_next_y <= mt_next_x) && (mt_next_y <= mt_next_z)) {
857863
return true;
858864
}
859865

866+
@Override
860867
public final int[] getSubblockCoord() {
861868
if(cur_patch >= 0) { /* If patch hit */
862869
double tt = cur_patch_t;
@@ -885,6 +892,7 @@ else if(subalpha < 0) {
885892
}
886893

887894
// Is the hit on a cullable face?
895+
@Override
888896
public final boolean isOnFace() {
889897
double tt;
890898
if(cur_patch >= 0) { /* If patch hit */
@@ -1294,11 +1302,14 @@ public boolean render(MapChunkCache cache, HDMapTile tile, String mapname) {
12941302
miny = tile.getDynmapWorld().minY;
12951303
}
12961304

1297-
for(int x = 0; x < tileSize * sizescale; x++) {
1305+
final int tilePixelSize = tileSize * sizescale;
1306+
final double invSizescale = 1.0 / sizescale;
1307+
for(int x = 0; x < tilePixelSize; x++) {
12981308
ps.px = x;
1299-
for(int y = 0; y < tileSize * sizescale; y++) {
1300-
ps.top.x = ps.bottom.x = xbase + (x + 0.5) / sizescale; /* Start at center of pixel at Y=height+0.5, bottom at Y=-0.5 */
1301-
ps.top.y = ps.bottom.y = ybase + (y + 0.5) / sizescale;
1309+
final double px_center = xbase + (x + 0.5) * invSizescale;
1310+
for(int y = 0; y < tilePixelSize; y++) {
1311+
ps.top.x = ps.bottom.x = px_center;
1312+
ps.top.y = ps.bottom.y = ybase + (y + 0.5) * invSizescale;
13021313
ps.top.z = height + 0.5; ps.bottom.z = miny - 0.5;
13031314
map_to_world.transform(ps.top); /* Transform to world coordinates */
13041315
map_to_world.transform(ps.bottom);
@@ -1314,6 +1325,7 @@ public boolean render(MapChunkCache cache, HDMapTile tile, String mapname) {
13141325
Log.severe("Error while raytracing tile: perspective=" + this.name + ", coord=" + mapiter.getX() + "," + mapiter.getY() + "," + mapiter.getZ() + ", blockid=" + mapiter.getBlockType() + ", lighting=" + mapiter.getBlockSkyLight() + ":" + mapiter.getBlockEmittedLight() + ", biome=" + mapiter.getBiome().toString(), ex);
13151326
ex.printStackTrace();
13161327
}
1328+
final int rowOffset = (tilePixelSize - y - 1) * tilePixelSize + x;
13171329
for(int i = 0; i < numshaders; i++) {
13181330
if(shaderdone[i] == false) {
13191331
shaderstate[i].rayFinished(ps);
@@ -1325,21 +1337,11 @@ public boolean render(MapChunkCache cache, HDMapTile tile, String mapname) {
13251337
shaderstate[i].getRayColor(rslt, 0);
13261338
int c_argb = rslt.getARGB();
13271339
if (c_argb != 0) rendered[i] = true;
1328-
if (isOpaque[i] && (c_argb == 0)) {
1329-
argb_buf[i][(tileSize*sizescale-y-1)*tileSize*sizescale + x] = bgnight[i];
1330-
}
1331-
else {
1332-
argb_buf[i][(tileSize*sizescale-y-1)*tileSize*sizescale + x] = c_argb;
1333-
}
1340+
argb_buf[i][rowOffset] = (isOpaque[i] && (c_argb == 0)) ? bgnight[i] : c_argb;
13341341
if (day_argb_buf[i] != null) {
13351342
shaderstate[i].getRayColor(rslt, 1);
13361343
c_argb = rslt.getARGB();
1337-
if (isOpaque[i] && (c_argb == 0)) {
1338-
day_argb_buf[i][(tileSize*sizescale-y-1)*tileSize*sizescale + x] = bgday[i];
1339-
}
1340-
else {
1341-
day_argb_buf[i][(tileSize*sizescale-y-1)*tileSize*sizescale + x] = c_argb;
1342-
}
1344+
day_argb_buf[i][rowOffset] = (isOpaque[i] && (c_argb == 0)) ? bgday[i] : c_argb;
13431345
}
13441346
}
13451347
}

0 commit comments

Comments
 (0)