-
-
Notifications
You must be signed in to change notification settings - Fork 72
Expand file tree
/
Copy pathKeepStashLoaded.java
More file actions
executable file
·209 lines (189 loc) · 9.21 KB
/
KeepStashLoaded.java
File metadata and controls
executable file
·209 lines (189 loc) · 9.21 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
package me.xginko.aef.modules.lagpreventions;
import io.github.thatsmusic99.configurationmaster.api.ConfigSection;
import io.papermc.paper.threadedregions.scheduler.ScheduledTask;
import me.xginko.aef.modules.AEFModule;
import me.xginko.aef.utils.ChunkUtil;
import me.xginko.aef.utils.MaterialUtil;
import me.xginko.aef.utils.models.ChunkUID;
import org.bukkit.Chunk;
import org.bukkit.Material;
import org.bukkit.block.BlockState;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
import org.bukkit.event.world.ChunkLoadEvent;
import org.bukkit.util.NumberConversions;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.stream.Collectors;
/**
* Credits to the initial idea of just keeping big chunks loaded to reduce lag
* created from loading them go to kumori (Soft1k) of 3b3t.org.
*/
public class KeepStashLoaded extends AEFModule implements Consumer<ScheduledTask>, Listener {
private final Map<String, Double> worldsAndTheirRadiuses;
private final Set<Material> storageTypes;
private final long minInhabitedTime, keepLoadedMillis, checkDelayTicks;
private final int stashCount;
private final boolean logIsEnabled, onlyTileEntities;
private Map<ChunkUID, Long> forceLoadedChunks;
private ScheduledTask chunkUnloadTask;
public KeepStashLoaded() {
super("lag-preventions.keep-stash-chunks-loaded", false, """
Idea by 3b3t admin kumori (Soft1k)\s
Improves lag generated by large stash chunks constantly loading and\s
unloading by setting them force loaded. This might cause increased ram\s
usage, so keep an eye out for that.""");
this.logIsEnabled = config.getBoolean(configPath + ".log", false);
this.stashCount = config.getInt(configPath + ".container-block-threshold", 50, """
How many container blocks have to be in a chunk for it to be seen\s
as a stash chunk to keep force loaded.""");
this.checkDelayTicks = config.getInt(configPath + ".check-delay-ticks", 200, """
Ticks to wait after a chunk is loaded before it will be checked.\s
Reduces lag by fast travelling players.""");
this.minInhabitedTime = config.getInt(configPath + ".min-chunk-inhabited-time-ticks", 1000, """
The minimum time in ticks a chunk has to have been inhabited to be checked.""");
this.keepLoadedMillis = TimeUnit.MINUTES.toMillis(config.getInt(configPath + ".keep-loaded-minutes", 120, """
The time in minutes a stash chunks will be kept force loaded before\s
setting it back to normal."""));
this.onlyTileEntities = config.getBoolean(configPath + ".only-check-tile-entities", true, """
Set to false if you want to check more blocks than just tile entities.\s
Makes the overall speed of the module faster if set to true.""");
this.storageTypes = config.getList(configPath + ".container-types", MaterialUtil.INVENTORY_HOLDERS.get()
.stream()
.map(Enum::name)
.collect(Collectors.toList()))
.stream()
.map(configuredType -> {
try {
return Material.valueOf(configuredType);
} catch (IllegalArgumentException e) {
notRecognized(Material.class, configuredType);
return null;
}
})
.filter(Objects::nonNull)
.collect(Collectors.toCollection(() -> EnumSet.noneOf(Material.class)));
Map<String, Object> defaults = new HashMap<>();
defaults.put("world", 100);
defaults.put("world_nether", 100);
defaults.put("world_the_end", 100);
ConfigSection section = config.getConfigSection(configPath + ".worlds", defaults,
"Radiuses around spawn in chunks (not blocks) that should not be checked.\n" +
"Worlds not on this list are exempt from all checking.");
List<String> worlds = section.getKeys(false);
this.worldsAndTheirRadiuses = new HashMap<>(worlds.size());
for (String world : worlds) {
try {
int radius = Integer.parseInt(section.getString(world));
this.worldsAndTheirRadiuses.put(world, NumberConversions.square(radius));
} catch (NumberFormatException e) {
warn("Radius for world '" + world + "' is not a valid integer.");
}
}
}
@Override
public void enable() {
forceLoadedChunks = new ConcurrentHashMap<>();
plugin.getServer().getPluginManager().registerEvents(this, plugin);
chunkUnloadTask = plugin.getServer().getAsyncScheduler()
.runAtFixedRate(plugin, this, 1L, 1L, TimeUnit.MINUTES);
}
@Override
public void disable() {
HandlerList.unregisterAll(this);
if (chunkUnloadTask != null) {
chunkUnloadTask.cancel();
chunkUnloadTask = null;
}
if (forceLoadedChunks != null) {
for (Map.Entry<ChunkUID, Long> entry : forceLoadedChunks.entrySet()) {
entry.getKey().getChunkAsync(false).thenAccept(chunk -> {
if (chunk != null) {
ChunkUtil.setForceLoaded(chunk, false);
}
});
}
forceLoadedChunks.clear();
forceLoadedChunks = null;
}
}
@Override
public void accept(ScheduledTask task) {
for (Map.Entry<ChunkUID, Long> entry : forceLoadedChunks.entrySet()) {
if (System.currentTimeMillis() < entry.getValue()) {
continue;
}
entry.getKey().getChunkAsync(false).thenAccept(chunk -> {
if (chunk == null) {
forceLoadedChunks.remove(entry.getKey());
if (logIsEnabled)
info("Removing key that returned a null chunk: "+entry.getKey()+".");
return;
}
plugin.getServer().getGlobalRegionScheduler().execute(plugin, () -> {
chunk.setForceLoaded(false);
forceLoadedChunks.remove(entry.getKey());
if (logIsEnabled)
info("Set chunk "+entry.getKey()+" to no longer force loaded.");
});
});
}
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
private void onChunkLoad(ChunkLoadEvent event) {
if (event.isNewChunk()) return;
final String world = event.getWorld().getName();
if (!worldsAndTheirRadiuses.containsKey(world)) return;
Chunk chunk = event.getChunk();
if (chunk.getInhabitedTime() < minInhabitedTime) return;
if (NumberConversions.square(chunk.getX()) + NumberConversions.square(chunk.getZ()) < worldsAndTheirRadiuses.get(world)) return;
plugin.getServer().getRegionScheduler().runDelayed(plugin, chunk.getWorld(), chunk.getX(), chunk.getZ(), delayedCheck -> {
if (!chunk.isLoaded()) return;
int count = 0;
if (onlyTileEntities) {
for (BlockState tileEntity : chunk.getTileEntities(false)) {
if (storageTypes.contains(tileEntity.getType())) {
count++;
if (count > stashCount) {
chunk.setForceLoaded(true);
forceLoadedChunks.computeIfAbsent(ChunkUID.of(chunk), chunkUID -> {
if (logIsEnabled) info("Set chunk " + chunkUID + " to force loaded.");
return System.currentTimeMillis() + keepLoadedMillis;
});
return;
}
}
}
return;
}
final int minY = chunk.getWorld().getMinHeight();
final int maxY = chunk.getWorld().getMaxHeight();
for (int x = 0; x < 16; x++) {
for (int z = 0; z < 16; z++) {
for (int y = minY; y < maxY; y++) {
if (storageTypes.contains(chunk.getBlock(x, y, z).getType())) {
count++;
if (count > stashCount) {
chunk.setForceLoaded(true);
forceLoadedChunks.computeIfAbsent(ChunkUID.of(chunk), chunkUID -> {
if (logIsEnabled) info("Set chunk " + chunkUID + " to force loaded.");
return System.currentTimeMillis() + keepLoadedMillis;
});
return;
}
}
}
}
}
}, checkDelayTicks);
}
}