Skip to content

Commit 3f8f705

Browse files
committed
Add configuration and optimization transformers for Hytale plugin
- Introduced CatalystConfigFile to manage plugin configuration via JSON. - Implemented ChunkRateTransformer to allow configurable chunk loading rates. - Added EntityDistanceTransformer for adjustable entity view distance multipliers. - Created PathfindingConfigTransformer to configure A* pathfinding parameters. - Added utility class OptimizationMetrics to read runtime metrics from transformed classes. - Updated service loader configuration to include new transformers.
1 parent 6c291d0 commit 3f8f705

19 files changed

Lines changed: 2563 additions & 83 deletions

CHANGELOG.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,34 @@
22

33
All notable changes to Catalyst will be documented in this file.
44

5+
## [0.2.0] - 2026-01-21
6+
7+
### Added
8+
- **Entity Distance Control**: Configurable entity view distance multiplier (default: 1.0)
9+
- Adjusts how far entities are visible/processed
10+
- Lower values improve server performance
11+
- **Chunk Rate Control**: Configurable chunks processed per tick (default: 1)
12+
- Limits chunk loading speed to prevent server lag spikes
13+
- **Pathfinding Optimization**: Configurable pathfinding timeout reduction (default: 50%)
14+
- Reduces CPU usage from entity pathfinding calculations
15+
- **Reset to Defaults Button**: One-click reset in settings GUI to restore all defaults
16+
- **Optimization Metrics**: Track performance impact of enabled optimizations
17+
18+
### Changed
19+
- **UI Redesign**: Complete settings UI overhaul following Hytale design patterns
20+
- Organized into logical sections with color-coded headers
21+
- Fixed position elements (reset button at bottom)
22+
- Removed missing texture references that caused red backgrounds
23+
- Improved layout with proper scrolling sections
24+
- **Slider Values Fixed**: Corrected property type from `.Text` to `.Value` for numeric sliders
25+
- **Improved transformer base class**: Better error handling and logging
26+
27+
### Fixed
28+
- Fixed slider value setting error (now uses correct `.Value` property)
29+
- Fixed red background in UI sections (removed missing texture reference)
30+
- Fixed reset button position (now at fixed bottom position, not scrolling)
31+
- Fixed checkbox value binding in settings UI
32+
533
## [0.1.1] - 2026-01-19
634

735
### Added

build.gradle

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,39 @@ task deployEarlyPlugin(type: Copy) {
165165
}
166166
}
167167

168+
// Task to deploy to both earlyplugins and mods directories
169+
task deploy {
170+
group = 'Hytale'
171+
description = 'Deploys the mod to both earlyplugins (transformers) and mods (commands/GUI) directories'
172+
dependsOn shadowJar
173+
174+
doLast {
175+
def earlyPluginsDir = file("${hytaleBaseDir}/earlyplugins")
176+
def modsDir = file("${hytaleBaseDir}/mods")
177+
178+
if (!earlyPluginsDir.exists()) {
179+
earlyPluginsDir.mkdirs()
180+
}
181+
if (!modsDir.exists()) {
182+
modsDir.mkdirs()
183+
}
184+
185+
copy {
186+
from shadowJar.archiveFile
187+
into earlyPluginsDir
188+
}
189+
copy {
190+
from shadowJar.archiveFile
191+
into modsDir
192+
}
193+
194+
logger.lifecycle("Deployed Catalyst to:")
195+
logger.lifecycle(" - ${earlyPluginsDir} (transformers)")
196+
logger.lifecycle(" - ${modsDir} (commands/GUI)")
197+
logger.lifecycle("Restart the Hytale server to apply changes.")
198+
}
199+
}
200+
168201
test {
169202
useJUnitPlatform()
170203
}
@@ -296,16 +329,24 @@ task runServer(type: JavaExec) {
296329
// Create run directory structure
297330
runDir.mkdirs()
298331
file("$runDir/earlyplugins").mkdirs()
332+
file("$runDir/mods").mkdirs()
299333

300-
// Copy JAR to earlyplugins directory
334+
// Copy JAR to earlyplugins directory (for transformers)
301335
copy {
302336
from shadowJar.archiveFile
303337
into file("$runDir/earlyplugins")
304338
}
305339

340+
// Copy JAR to mods directory (for commands/GUI)
341+
copy {
342+
from shadowJar.archiveFile
343+
into file("$runDir/mods")
344+
}
345+
306346
logger.lifecycle("Starting Hytale server...")
307347
logger.lifecycle("Working directory: ${runDir}")
308348
logger.lifecycle("Early plugins: ${runDir}/earlyplugins/")
349+
logger.lifecycle("Mods: ${runDir}/mods/")
309350
logger.lifecycle("Server data (universe/, logs/) will be created in ${runDir}")
310351
}
311352

@@ -584,7 +625,7 @@ task vscode {
584625
}
585626

586627
// Eclipse launch configuration generation
587-
task eclipse {
628+
task eclipseConfig {
588629
group = 'IDE'
589630
description = 'Generates Eclipse launch configuration for Hytale Server'
590631

@@ -631,7 +672,7 @@ task eclipse {
631672
task ide {
632673
group = 'IDE'
633674
description = 'Generates run configurations for all supported IDEs (VSCode, Eclipse, IntelliJ)'
634-
dependsOn vscode, eclipse
675+
dependsOn vscode, eclipseConfig
635676
}
636677

637678
// Publishing configuration

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
group=com.criticalrange
22
name=Catalyst
3-
version=0.1.1
3+
version=0.2.0
44
java_version=25
55
mod_description=Hytale Performance Optimization Mod - High-performance Early Plugin using bytecode transformation for server optimization
66
website=https://github.com/CriticalRange/catalyst

src/main/java/com/criticalrange/Catalyst.java

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
* <li>Lazy block entity initialization - defers creation until needed</li>
1818
* <li>Lazy block tick discovery - defers ticking block detection</li>
1919
* <li>Lazy fluid pre-processing - defers fluid simulation during chunk load</li>
20+
* <li>Entity view distance - configurable entity sync distance multiplier</li>
21+
* <li>Chunk rate - configurable chunks per tick</li>
22+
* <li>Pathfinding limits - configurable A* pathfinding parameters</li>
2023
* </ul>
2124
*
2225
* @author CriticalRange
@@ -25,6 +28,7 @@ public class Catalyst extends JavaPlugin {
2528

2629
private static Catalyst instance;
2730
private CatalystTransformerManager transformerManager;
31+
private CatalystConfigFile configFile;
2832

2933
public Catalyst(@Nonnull JavaPluginInit init) {
3034
super(init);
@@ -50,6 +54,13 @@ public String getVersion() {
5054

5155
@Override
5256
protected void setup() {
57+
// Load configuration from file
58+
configFile = new CatalystConfigFile(getDataDirectory());
59+
configFile.load();
60+
61+
// Sync config values to injected fields in transformed classes
62+
syncConfigToInjectedFields();
63+
5364
transformerManager = new CatalystTransformerManager();
5465

5566
try {
@@ -70,6 +81,12 @@ protected void start() {
7081
@Override
7182
protected void shutdown() {
7283
log("Catalyst shutting down...");
84+
85+
// Save configuration to file
86+
if (configFile != null) {
87+
configFile.save();
88+
}
89+
7390
log("\n" + CatalystMetrics.generateReport());
7491
if (transformerManager != null) {
7592
transformerManager.logMetrics();
@@ -83,6 +100,16 @@ private void logConfig() {
83100
log(" - Block Entities: " + s(CatalystConfig.LAZY_BLOCK_ENTITIES_ENABLED));
84101
log(" - Block Tick: " + s(CatalystConfig.LAZY_BLOCK_TICK_ENABLED));
85102
log(" - Fluid: " + s(CatalystConfig.LAZY_FLUID_ENABLED));
103+
log(" Runtime Optimizations:");
104+
log(" - Entity Distance: " + s(CatalystConfig.ENTITY_DISTANCE_ENABLED) +
105+
" (multiplier: " + CatalystConfig.ENTITY_VIEW_MULTIPLIER + ")");
106+
log(" - Chunk Rate: " + s(CatalystConfig.CHUNK_RATE_ENABLED) +
107+
" (chunks/tick: " + CatalystConfig.CHUNKS_PER_TICK + ")");
108+
log(" Pathfinding:");
109+
log(" - Custom Limits: " + s(CatalystConfig.PATHFINDING_ENABLED));
110+
log(" - Max Path Length: " + CatalystConfig.MAX_PATH_LENGTH);
111+
log(" - Open Nodes: " + CatalystConfig.OPEN_NODES_LIMIT);
112+
log(" - Total Nodes: " + CatalystConfig.TOTAL_NODES_LIMIT);
86113
}
87114

88115
private String s(boolean enabled) {
@@ -93,7 +120,97 @@ private void log(String message) {
93120
System.out.println("[Catalyst] " + message);
94121
}
95122

123+
/**
124+
* Syncs CatalystConfig values to the injected fields in transformed Hytale classes.
125+
* This must be called after loading config but before the server starts using these values.
126+
*/
127+
private void syncConfigToInjectedFields() {
128+
// Lazy loading fields
129+
setStaticField("com.hypixel.hytale.server.core.modules.block.BlockModule",
130+
"$catalystLazyBlockEntities", CatalystConfig.LAZY_BLOCK_ENTITIES_ENABLED);
131+
setStaticField("com.hypixel.hytale.builtin.blocktick.BlockTickPlugin",
132+
"$catalystLazyBlockTick", CatalystConfig.LAZY_BLOCK_TICK_ENABLED);
133+
setStaticField("com.hypixel.hytale.builtin.fluid.FluidPlugin",
134+
"$catalystLazyFluid", CatalystConfig.LAZY_FLUID_ENABLED);
135+
136+
// Entity distance fields
137+
setStaticField("com.hypixel.hytale.server.core.universe.Universe",
138+
"$catalystEntityDistEnabled", CatalystConfig.ENTITY_DISTANCE_ENABLED);
139+
setStaticField("com.hypixel.hytale.server.core.universe.Universe",
140+
"$catalystEntityViewMultiplier", CatalystConfig.ENTITY_VIEW_MULTIPLIER);
141+
142+
// Chunk rate fields
143+
setStaticField("com.hypixel.hytale.server.core.modules.entity.player.ChunkTracker",
144+
"$catalystChunkRateEnabled", CatalystConfig.CHUNK_RATE_ENABLED);
145+
setStaticField("com.hypixel.hytale.server.core.modules.entity.player.ChunkTracker",
146+
"$catalystChunksPerTick", CatalystConfig.CHUNKS_PER_TICK);
147+
148+
// Pathfinding fields
149+
setStaticField("com.hypixel.hytale.server.npc.navigation.AStarBase",
150+
"$catalystPathfindingEnabled", CatalystConfig.PATHFINDING_ENABLED);
151+
setStaticField("com.hypixel.hytale.server.npc.navigation.AStarBase",
152+
"$catalystMaxPathLength", CatalystConfig.MAX_PATH_LENGTH);
153+
setStaticField("com.hypixel.hytale.server.npc.navigation.AStarBase",
154+
"$catalystOpenNodesLimit", CatalystConfig.OPEN_NODES_LIMIT);
155+
setStaticField("com.hypixel.hytale.server.npc.navigation.AStarBase",
156+
"$catalystTotalNodesLimit", CatalystConfig.TOTAL_NODES_LIMIT);
157+
}
158+
159+
private void setStaticField(String className, String fieldName, boolean value) {
160+
try {
161+
Class<?> clazz = Class.forName(className);
162+
java.lang.reflect.Field field = clazz.getField(fieldName);
163+
field.setBoolean(null, value);
164+
} catch (ClassNotFoundException e) {
165+
// Class not loaded yet - this is fine, field will use default
166+
log(" [Sync] " + className.substring(className.lastIndexOf('.') + 1) + " not loaded yet");
167+
} catch (NoSuchFieldException e) {
168+
// Field doesn't exist - transformer may not have run
169+
log(" [Sync] Field " + fieldName + " not found in " + className);
170+
} catch (Exception e) {
171+
log(" [Sync] Failed to set " + fieldName + ": " + e.getMessage());
172+
}
173+
}
174+
175+
private void setStaticField(String className, String fieldName, int value) {
176+
try {
177+
Class<?> clazz = Class.forName(className);
178+
java.lang.reflect.Field field = clazz.getField(fieldName);
179+
field.setInt(null, value);
180+
} catch (ClassNotFoundException e) {
181+
// Class not loaded yet - this is fine, field will use default
182+
log(" [Sync] " + className.substring(className.lastIndexOf('.') + 1) + " not loaded yet");
183+
} catch (NoSuchFieldException e) {
184+
// Field doesn't exist - transformer may not have run
185+
log(" [Sync] Field " + fieldName + " not found in " + className);
186+
} catch (Exception e) {
187+
log(" [Sync] Failed to set " + fieldName + ": " + e.getMessage());
188+
}
189+
}
190+
96191
public CatalystTransformerManager getTransformerManager() {
97192
return transformerManager;
98193
}
194+
195+
/**
196+
* Saves the current configuration to the config file.
197+
* Call this after making changes to {@link CatalystConfig} to persist them.
198+
*/
199+
public void saveConfig() {
200+
if (configFile != null) {
201+
configFile.save();
202+
}
203+
}
204+
205+
/**
206+
* Reloads configuration from the config file.
207+
*
208+
* @return true if config was reloaded successfully
209+
*/
210+
public boolean reloadConfig() {
211+
if (configFile != null) {
212+
return configFile.load();
213+
}
214+
return false;
215+
}
99216
}

0 commit comments

Comments
 (0)