Plex-FAWE/worldedit-core/src/main/java/com/boydti/fawe/Fawe.java

427 lines
14 KiB
Java
Raw Normal View History

package com.boydti.fawe;
2019-11-02 11:13:42 +00:00
import com.boydti.fawe.beta.implementation.queue.QueueHandler;
import com.boydti.fawe.config.Settings;
import com.boydti.fawe.object.brush.visualization.VisualQueue;
import com.boydti.fawe.util.CachedTextureUtil;
import com.boydti.fawe.util.CleanTextureUtil;
import com.boydti.fawe.util.FaweTimer;
import com.boydti.fawe.util.MainUtil;
import com.boydti.fawe.util.MemUtil;
import com.boydti.fawe.util.RandomTextureUtil;
import com.boydti.fawe.util.TaskManager;
import com.boydti.fawe.util.TextureUtil;
import com.boydti.fawe.util.WEManager;
import com.github.luben.zstd.util.Native;
import com.sk89q.worldedit.WorldEdit;
import com.sk89q.worldedit.extension.platform.Actor;
import com.sk89q.worldedit.session.request.Request;
import com.sk89q.worldedit.util.formatting.text.Component;
import com.sk89q.worldedit.util.formatting.text.TextComponent;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.MemoryPoolMXBean;
import java.lang.management.MemoryUsage;
import java.util.Date;
import java.util.List;
2019-11-21 20:37:56 +00:00
import java.util.Locale;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nullable;
import javax.management.InstanceAlreadyExistsException;
import javax.management.NotificationEmitter;
2019-11-11 21:02:28 +00:00
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* [ WorldEdit action]
2019-04-28 15:44:59 +00:00
* |
* \|/
* [ EditSession ] - The change is processed (area restrictions, change limit, block type)
2019-04-28 15:44:59 +00:00
* |
* \|/
* [Block change] - A block change from some location
2019-04-28 15:44:59 +00:00
* |
* \|/
* [ Set Queue ] - The SetQueue manages the implementation specific queue
2019-04-28 15:44:59 +00:00
* |
* \|/
* [ Fawe Queue] - A queue of chunks - check if the queue has the chunk for a change
2019-04-28 15:44:59 +00:00
* |
* \|/
* [ Fawe Chunk Implementation ] - Otherwise create a new FaweChunk object which is a wrapper around the Chunk object
2019-04-28 15:44:59 +00:00
* |
* \|/
* [ Execution ] - When done, the queue then sets the blocks for the chunk, performs lighting updates and sends the chunk packet to the clients
* <p>
* Why it's faster:
* - The chunk is modified directly rather than through the API
* \ Removes some overhead, and means some processing can be done async
* - Lighting updates are performed on the chunk level rather than for every block
2019-11-11 21:02:28 +00:00
* \ e.g., A blob of stone: only the visible blocks need to have the lighting calculated
* - Block changes are sent with a chunk packet
* \ A chunk packet is generally quicker to create and smaller for large world edits
* - No physics updates
* \ Physics updates are slow, and are usually performed on each block
* - Block data shortcuts
2019-11-11 21:02:28 +00:00
* \ Some known blocks don't need to have the data set or accessed (e.g., air is never going to have data)
* - Remove redundant extents
* \ Up to 11 layers of extents can be removed
* - History bypassing
* \ FastMode bypasses history and means blocks in the world don't need to be checked and recorded
*/
public class Fawe {
2019-11-11 21:02:28 +00:00
private static final Logger log = LoggerFactory.getLogger(Fawe.class);
/**
* The FAWE instance;
*/
private static Fawe INSTANCE;
/**
* TPS timer
*/
private final FaweTimer timer;
private FaweVersion version;
private VisualQueue visualQueue;
private TextureUtil textures;
// TODO: Ping @MattBDev to reimplement 2020-02-04
// private DefaultTransformParser transformParser;
2019-04-30 16:19:10 +00:00
private QueueHandler queueHandler;
/**
* Get the implementation specific class
*
* @return
*/
@SuppressWarnings("unchecked")
public static <T extends IFawe> T imp() {
return INSTANCE != null ? (T) INSTANCE.IMP : null;
}
/**
* Get the implementation independent class
*
* @return
*/
public static Fawe get() {
return INSTANCE;
}
/**
* Setup Fawe
*
* @param implementation
* @throws InstanceAlreadyExistsException
*/
public static void set(final IFawe implementation) throws InstanceAlreadyExistsException, IllegalArgumentException {
if (INSTANCE != null) {
throw new InstanceAlreadyExistsException("FAWE has already been initialized with: " + INSTANCE.IMP);
}
if (implementation == null) {
throw new IllegalArgumentException("Implementation may not be null.");
}
INSTANCE = new Fawe(implementation);
}
public static void debugPlain(String s) {
if (INSTANCE != null) {
INSTANCE.IMP.debug(s);
} else {
2019-08-06 15:28:12 +00:00
System.out.println(s);
}
}
/**
* Write something to the console
*
* @param s
*/
2020-03-05 21:07:20 +00:00
public static void debug(String s) {
Actor actor = Request.request().getActor();
if (actor != null && actor.isPlayer()) {
2020-03-05 21:07:20 +00:00
actor.printInfo(TextComponent.of(s));
return;
}
2020-03-05 21:07:20 +00:00
debugPlain(s);
}
/**
* Write something to the console
*
* @param c The Component to be printed
*/
public static void debug(Component c) {
Actor actor = Request.request().getActor();
if (actor != null && actor.isPlayer()) {
actor.printDebug(c);
return;
}
debugPlain(c.toString());
}
/**
* The platform specific implementation
*/
private final IFawe IMP;
private Thread thread;
private Fawe(final IFawe implementation) {
INSTANCE = this;
this.IMP = implementation;
this.thread = Thread.currentThread();
/*
* Implementation dependent stuff
*/
this.setupConfigs();
TaskManager.IMP = this.IMP.getTaskManager();
TaskManager.IMP.async(() -> {
MainUtil.deleteOlder(MainUtil.getFile(IMP.getDirectory(), Settings.IMP.PATHS.HISTORY), TimeUnit.DAYS.toMillis(Settings.IMP.HISTORY.DELETE_AFTER_DAYS), false);
MainUtil.deleteOlder(MainUtil.getFile(IMP.getDirectory(), Settings.IMP.PATHS.CLIPBOARD), TimeUnit.DAYS.toMillis(Settings.IMP.CLIPBOARD.DELETE_AFTER_DAYS), false);
});
/*
* Instance independent stuff
*/
this.setupMemoryListener();
this.timer = new FaweTimer();
// Delayed worldedit setup
TaskManager.IMP.later(() -> {
try {
// TODO: Ping @MattBDev to reimplement 2020-02-04
// transformParser = new DefaultTransformParser(getWorldEdit());
visualQueue = new VisualQueue(3);
WEManager.IMP.managers.addAll(Fawe.this.IMP.getMaskManagers());
} catch (Throwable ignored) {}
}, 0);
TaskManager.IMP.repeat(timer, 1);
}
public void onDisable() {
}
2019-04-30 16:19:10 +00:00
public QueueHandler getQueueHandler() {
if (queueHandler == null) {
synchronized (this) {
if (queueHandler == null) {
queueHandler = IMP.getQueueHandler();
}
}
}
return queueHandler;
}
// TODO: Ping @MattBDev to reimplement 2020-02-04
// public DefaultTransformParser getTransformParser() {
// return transformParser;
// }
public TextureUtil getCachedTextureUtil(boolean randomize, int min, int max) {
2019-07-22 06:02:51 +00:00
// TODO NOT IMPLEMENTED - optimize this by caching the default true/0/100 texture util
TextureUtil tu = getTextureUtil();
try {
tu = min == 0 && max == 100 ? tu : new CleanTextureUtil(tu, min, max);
tu = randomize ? new RandomTextureUtil(tu) : new CachedTextureUtil(tu);
} catch (FileNotFoundException neverHappens) {
neverHappens.printStackTrace();
}
return tu;
}
public TextureUtil getTextureUtil() {
TextureUtil tmp = textures;
if (tmp == null) {
synchronized (this) {
tmp = textures;
if (tmp == null) {
try {
textures = tmp = new TextureUtil();
tmp.loadModTextures();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
return tmp;
}
/**
* The FaweTimer is a useful class for monitoring TPS
*
* @return FaweTimer
*/
public FaweTimer getTimer() {
return timer;
}
/**
* The visual queue is used to queue visualizations
*
* @return
*/
public VisualQueue getVisualQueue() {
return visualQueue;
}
/**
* The FAWE version
* - Unofficial jars may be lacking version information
*
* @return FaweVersion
*/
public
@Nullable
FaweVersion getVersion() {
return version;
}
public double getTPS() {
return timer.getTPS();
}
public void setupConfigs() {
2020-02-23 17:48:22 +00:00
MainUtil.copyFile(MainUtil.getJarFile(), "lang" + File.separator + "strings.json", null);
// Setting up config.yml
File file = new File(this.IMP.getDirectory(), "config.yml");
Settings.IMP.PLATFORM = IMP.getPlatform().replace("\"", "");
2020-02-23 17:48:22 +00:00
try (InputStream stream = getClass().getResourceAsStream(File.separator + "fawe.properties");
BufferedReader br = new BufferedReader(new InputStreamReader(stream))) {
String versionString = br.readLine();
String commitString = br.readLine();
String dateString = br.readLine();
2019-06-06 22:39:51 +00:00
br.close();
this.version = FaweVersion.tryParse(versionString, commitString, dateString);
Settings.IMP.DATE = new Date(100 + version.year, version.month, version.day).toGMTString();
Settings.IMP.BUILD = "https://ci.athion.net/job/FastAsyncWorldEdit-1.15/" + version.build;
Settings.IMP.COMMIT = "https://github.com/IntellectualSites/FastAsyncWorldEdit-1.13/commit/" + Integer.toHexString(version.hash);
} catch (Throwable ignore) {}
try {
Settings.IMP.reload(file);
} catch (Throwable e) {
debug("====== Failed to load config ======");
debug("Please validate your yaml files:");
e.printStackTrace();
debug("====================================");
}
}
public WorldEdit getWorldEdit() {
return WorldEdit.getInstance();
}
public static void setupInjector() {
/*
* Modify the sessions
2019-11-11 21:02:28 +00:00
* - EditSession supports a custom queue, and a lot of optimizations
* - LocalSession supports VirtualPlayers and undo on disk
*/
if (!Settings.IMP.EXPERIMENTAL.DISABLE_NATIVES) {
try {
Native.load();
} catch (Throwable e) {
if (Settings.IMP.CLIPBOARD.COMPRESSION_LEVEL > 6 || Settings.IMP.HISTORY.COMPRESSION_LEVEL > 6) {
Settings.IMP.CLIPBOARD.COMPRESSION_LEVEL = Math.min(6, Settings.IMP.CLIPBOARD.COMPRESSION_LEVEL);
Settings.IMP.HISTORY.COMPRESSION_LEVEL = Math.min(6, Settings.IMP.HISTORY.COMPRESSION_LEVEL);
debug("====== ZSTD COMPRESSION BINDING NOT FOUND ======");
debug(e.getMessage());
debug("FAWE will work but won't compress data as much");
debug("===============================================");
}
}
try {
net.jpountz.util.Native.load();
} catch (Throwable e) {
e.printStackTrace();
debug("====== LZ4 COMPRESSION BINDING NOT FOUND ======");
debug(e.getMessage());
debug("FAWE will work but compression will be slower");
debug(" - Try updating your JVM / OS");
debug(" - Report this issue if you cannot resolve it");
debug("===============================================");
}
}
2020-03-29 22:47:32 +00:00
// Check Base OS Arch for Mismatching Architectures
boolean x86OS = System.getProperty("sun.arch.data.model").contains("32");
boolean x86JVM = System.getProperty("os.arch").contains("32");
if (x86OS != x86JVM) {
debug("====== UPGRADE TO 64-BIT JAVA ======");
debug("You are running 32-bit Java on a 64-bit machine");
debug("====================================");
}
}
private void setupMemoryListener() {
if (Settings.IMP.MAX_MEMORY_PERCENT < 1 || Settings.IMP.MAX_MEMORY_PERCENT > 99) {
return;
}
try {
final MemoryMXBean memBean = ManagementFactory.getMemoryMXBean();
final NotificationEmitter ne = (NotificationEmitter) memBean;
ne.addNotificationListener((notification, handback) -> {
final long heapSize = Runtime.getRuntime().totalMemory();
final long heapMaxSize = Runtime.getRuntime().maxMemory();
if (heapSize < heapMaxSize) {
return;
}
MemUtil.memoryLimitedTask();
}, null, null);
final List<MemoryPoolMXBean> memPools = ManagementFactory.getMemoryPoolMXBeans();
for (final MemoryPoolMXBean mp : memPools) {
if (mp.isUsageThresholdSupported()) {
final MemoryUsage mu = mp.getUsage();
final long max = mu.getMax();
if (max < 0) {
continue;
}
final long alert = (max * Settings.IMP.MAX_MEMORY_PERCENT) / 100;
mp.setUsageThreshold(alert);
}
}
} catch (Throwable ignored) {
debug("====== MEMORY LISTENER ERROR ======");
debug("FAWE needs access to the JVM memory system:");
debug(" - Change your Java security settings");
debug(" - Disable this with `max-memory-percent: -1`");
debug("===================================");
}
}
/**
* Get the main thread
*
* @return
*/
public Thread getMainThread() {
return this.thread;
}
public static boolean isMainThread() {
return INSTANCE == null || INSTANCE.thread == Thread.currentThread();
}
/**
* Sets the main thread to the current thread
*
* @return
*/
public Thread setMainThread() {
return this.thread = Thread.currentThread();
}
}