Fix Fabric build script and implement the fabric-plugin

This commit is contained in:
Blue (Lukas Rieger) 2020-08-03 14:15:05 +02:00
parent b6f89ad5a8
commit 7861405f60
13 changed files with 495 additions and 54 deletions

View File

@ -38,15 +38,18 @@
public class MapUpdateHandler implements ServerEventListener {
public Multimap<MapType, Vector2i> updateBuffer;
private Plugin plugin;
public MapUpdateHandler() {
private Multimap<MapType, Vector2i> updateBuffer;
public MapUpdateHandler(Plugin plugin) {
this.plugin = plugin;
updateBuffer = MultimapBuilder.hashKeys().hashSetValues().build();
}
@Override
public void onWorldSaveToDisk(final UUID world) {
RenderManager renderManager = Plugin.getInstance().getRenderManager();
RenderManager renderManager = plugin.getRenderManager();
new Thread(() -> {
try {
@ -106,7 +109,7 @@ private void updateChunk(UUID world, Vector2i chunkPos) {
private void updateBlock(UUID world, Vector3i pos){
synchronized (updateBuffer) {
for (MapType mapType : Plugin.getInstance().getMapTypes()) {
for (MapType mapType : plugin.getMapTypes()) {
if (mapType.getWorld().getUUID().equals(world)) {
mapType.getWorld().invalidateChunkCache(mapType.getWorld().blockPosToChunkPos(pos));
@ -122,7 +125,7 @@ public int getUpdateBufferCount() {
}
public void flushTileBuffer() {
RenderManager renderManager = Plugin.getInstance().getRenderManager();
RenderManager renderManager = plugin.getRenderManager();
synchronized (updateBuffer) {
for (MapType map : updateBuffer.keySet()) {

View File

@ -72,8 +72,6 @@ public class Plugin {
public static final String PLUGIN_ID = "bluemap";
public static final String PLUGIN_NAME = "BlueMap";
private static Plugin instance;
private BlueMapAPIImpl api;
@ -104,8 +102,6 @@ public Plugin(String implementationType, ServerInterface serverInterface) {
this.maps = new HashMap<>();
this.worlds = new HashMap<>();
instance = this;
}
public synchronized void load() throws IOException, ParseResourceException {
@ -270,7 +266,7 @@ public synchronized void load() throws IOException, ParseResourceException {
periodicalSaveThread.start();
//start map updater
this.updateHandler = new MapUpdateHandler();
this.updateHandler = new MapUpdateHandler(this);
serverInterface.registerListener(updateHandler);
//create/update webfiles
@ -427,8 +423,4 @@ public boolean isLoaded() {
return loaded;
}
public static Plugin getInstance() {
return instance;
}
}

View File

@ -5,7 +5,7 @@ plugins {
}
configurations {
compile.extendsFrom include
compile.extendsFrom shadowInclude
}
dependencies {
@ -14,7 +14,7 @@ dependencies {
modImplementation "net.fabricmc:fabric-loader:0.8.2+build.194"
modImplementation "net.fabricmc.fabric-api:fabric-api:0.5.1+build.294-1.15"
compile (project(':BlueMapCommon')) {
shadowInclude (project(':BlueMapCommon')) {
//exclude dependencies provided by fabric
exclude group: 'com.google.guava', module: 'guava'
exclude group: 'com.google.code.gson', module: 'gson'
@ -38,13 +38,7 @@ processResources {
}
shadowJar {
configurations = [project.configurations.compile]
dependencies {
include(dependency(':BlueMapCommon'))
include(dependency(':BlueMapCore'))
include(dependency(':BlueMapAPI'))
}
configurations = [project.configurations.shadowInclude]
//relocate 'com.flowpowered.math', 'de.bluecolored.shadow.flowpowered.math' //DON'T relocate this, because the API depends on it
relocate 'com.typesafe.config', 'de.bluecolored.shadow.typesafe.config'
@ -52,7 +46,7 @@ shadowJar {
relocate 'ninja.leaping.configurate', 'de.bluecolored.shadow.ninja.leaping.configurate'
relocate 'org.yaml.snakeyaml', 'de.bluecolored.shadow.yaml.snakeyaml'
exclude '/mappings/*'
//exclude '/mappings/*'
}
task ramappedShadowJar(type: RemapJarTask) {
@ -62,6 +56,7 @@ task ramappedShadowJar(type: RemapJarTask) {
addNestedDependencies = true
archiveName = "BlueMap-${version}-fabric.jar"
}
build.dependsOn ramappedShadowJar
task sourcesJar(type: Jar, dependsOn: classes) {
classifier = "sources"

View File

@ -0,0 +1,95 @@
package de.bluecolored.bluemap.fabric;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.UUID;
import com.flowpowered.math.vector.Vector2i;
import com.flowpowered.math.vector.Vector3i;
import de.bluecolored.bluemap.common.plugin.serverinterface.ServerEventListener;
import de.bluecolored.bluemap.core.logger.Logger;
import de.bluecolored.bluemap.fabric.events.ChunkFinalizeCallback;
import de.bluecolored.bluemap.fabric.events.WorldSaveCallback;
import net.fabricmc.fabric.api.event.player.AttackBlockCallback;
import net.fabricmc.fabric.api.event.player.UseBlockCallback;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.ActionResult;
import net.minecraft.util.Hand;
import net.minecraft.util.hit.BlockHitResult;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.world.World;
public class FabricEventForwarder {
private FabricMod mod;
private Collection<ServerEventListener> eventListeners;
public FabricEventForwarder(FabricMod mod) {
this.mod = mod;
this.eventListeners = new ArrayList<>(1);
WorldSaveCallback.EVENT.register(this::onWorldSave);
ChunkFinalizeCallback.EVENT.register(this::onChunkFinalize);
AttackBlockCallback.EVENT.register(this::onBlockAttack);
UseBlockCallback.EVENT.register(this::onBlockUse);
}
public void addEventListener(ServerEventListener listener) {
this.eventListeners.add(listener);
}
public void removeAllListeners() {
this.eventListeners.clear();
}
public ActionResult onBlockUse(PlayerEntity player, World world, Hand hand, BlockHitResult hitResult) {
if (world instanceof ServerWorld) {
onBlockChange((ServerWorld) world, hitResult.getBlockPos());
}
return ActionResult.PASS;
}
public ActionResult onBlockAttack(PlayerEntity player, World world, Hand hand, BlockPos pos, Direction direction) {
if (world instanceof ServerWorld) {
onBlockChange((ServerWorld) world, pos);
}
return ActionResult.PASS;
}
public void onBlockChange(ServerWorld world, BlockPos blockPos) {
Vector3i position = new Vector3i(blockPos.getX(), blockPos.getY(), blockPos.getZ());
try {
UUID uuid = mod.getUUIDForWorld(world);
eventListeners.forEach(e -> e.onBlockChange(uuid, position));
} catch (IOException e) {
Logger.global.logError("Failed to get UUID for world: " + world, e);
}
}
public void onWorldSave(ServerWorld world) {
try {
UUID uuid = mod.getUUIDForWorld(world);
eventListeners.forEach(e -> e.onWorldSaveToDisk(uuid));
} catch (IOException e) {
Logger.global.logError("Failed to get UUID for world: " + world, e);
}
}
public void onChunkFinalize(ServerWorld world, Vector2i chunkPos) {
try {
UUID uuid = mod.getUUIDForWorld(world);
eventListeners.forEach(e -> e.onChunkFinishedGeneration(uuid, chunkPos));
} catch (IOException e) {
Logger.global.logError("Failed to get UUID for world: " + world, e);
}
}
}

View File

@ -2,62 +2,125 @@
import java.io.File;
import java.io.IOException;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import org.apache.logging.log4j.LogManager;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import de.bluecolored.bluemap.common.plugin.Plugin;
import de.bluecolored.bluemap.common.plugin.commands.Commands;
import de.bluecolored.bluemap.common.plugin.serverinterface.ServerEventListener;
import de.bluecolored.bluemap.common.plugin.serverinterface.ServerInterface;
import de.bluecolored.bluemap.core.logger.Logger;
import de.bluecolored.bluemap.core.resourcepack.ParseResourceException;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.event.server.ServerStartCallback;
import net.fabricmc.fabric.api.event.server.ServerStopCallback;
import net.fabricmc.fabric.api.registry.CommandRegistry;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.world.ServerWorld;
public class FabricMod implements ModInitializer, ServerInterface {
private Plugin pluginInstance = null;
private Plugin plugin;
private Map<File, UUID> worldUuids;
private FabricEventForwarder eventForwarder;
private LoadingCache<ServerWorld, UUID> worldUuidCache;
public FabricMod() {
Logger.global = new Log4jLogger(LogManager.getLogger(Plugin.PLUGIN_NAME));
pluginInstance = new Plugin("fabric", this);
this.worldUuids = new ConcurrentHashMap<>();
this.eventForwarder = new FabricEventForwarder(this);
this.worldUuidCache = CacheBuilder.newBuilder()
.weakKeys()
.maximumSize(1000)
.build(new CacheLoader<ServerWorld, UUID>() {
@Override
public UUID load(ServerWorld key) throws Exception {
return loadUUIDForWorld(key);
}
});
}
@Override
public void onInitialize() {
Logger.global = new Log4jLogger(LogManager.getLogger(Plugin.PLUGIN_NAME));
this.plugin = new Plugin("forge", this);
//register commands
CommandRegistry.INSTANCE.register(true, dispatcher -> {
new Commands<>(plugin, dispatcher, fabricSource -> new FabricCommandSource(this, plugin, fabricSource));
new Commands<>(pluginInstance, dispatcher, fabricSource -> new FabricCommandSource(this, pluginInstance, fabricSource));
});
ServerStartCallback.EVENT.register((MinecraftServer server) -> {
new Thread(()->{
Logger.global.logInfo("Loading BlueMap...");
try {
pluginInstance.load();
Logger.global.logInfo("BlueMap loaded!");
} catch (IOException | ParseResourceException e) {
Logger.global.logError("Failed to load bluemap!", e);
}
}).start();
});
ServerStopCallback.EVENT.register((MinecraftServer server) -> {
pluginInstance.unload();
Logger.global.logInfo("BlueMap unloaded!");
});
}
public UUID getUUIDForWorld(ServerWorld world) throws IOException {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Not implemented!");
}
@Override
public void registerListener(ServerEventListener listener) {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Not implemented!");
eventForwarder.addEventListener(listener);
}
@Override
public void unregisterAllListeners() {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Not implemented!");
eventForwarder.removeAllListeners();
}
@Override
public UUID getUUIDForWorld(File worldFolder) throws IOException {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Not implemented!");
worldFolder = worldFolder.getCanonicalFile();
UUID uuid = worldUuids.get(worldFolder);
if (uuid == null) {
uuid = UUID.randomUUID();
worldUuids.put(worldFolder, uuid);
}
return uuid;
}
public UUID getUUIDForWorld(ServerWorld world) throws IOException {
try {
return worldUuidCache.get(world);
} catch (ExecutionException e) {
Throwable cause = e.getCause();
if (cause instanceof IOException) throw (IOException) cause;
else throw new IOException(cause);
}
}
private UUID loadUUIDForWorld(ServerWorld world) throws IOException {
File dimensionDir = world.getDimension().getType().getSaveDirectory(world.getSaveHandler().getWorldDir());
return getUUIDForWorld(dimensionDir);
}
@Override
public File getConfigFolder() {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Not implemented!");
return new File("config/bluemap");
}
}

View File

@ -0,0 +1,19 @@
package de.bluecolored.bluemap.fabric.events;
import com.flowpowered.math.vector.Vector2i;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
import net.minecraft.server.world.ServerWorld;
public interface ChunkFinalizeCallback {
Event<ChunkFinalizeCallback> EVENT = EventFactory.createArrayBacked(ChunkFinalizeCallback.class,
(listeners) -> (world, chunkPos) -> {
for (ChunkFinalizeCallback event : listeners) {
event.onChunkFinalized(world, chunkPos);
}
}
);
void onChunkFinalized(ServerWorld world, Vector2i chunkPos);
}

View File

@ -0,0 +1,17 @@
package de.bluecolored.bluemap.fabric.events;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
import net.minecraft.server.world.ServerWorld;
public interface WorldSaveCallback {
Event<WorldSaveCallback> EVENT = EventFactory.createArrayBacked(WorldSaveCallback.class,
(listeners) -> (world) -> {
for (WorldSaveCallback event : listeners) {
event.onWorldSaved(world);
}
}
);
void onWorldSaved(ServerWorld world);
}

View File

@ -0,0 +1,32 @@
package de.bluecolored.bluemap.fabric.mixin;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import com.flowpowered.math.vector.Vector2i;
import de.bluecolored.bluemap.fabric.events.ChunkFinalizeCallback;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.world.ChunkRegion;
import net.minecraft.world.IWorld;
import net.minecraft.world.gen.chunk.ChunkGenerator;
@Mixin(ChunkGenerator.class)
public class MixinChunkGenerator {
@Shadow
@Final
protected IWorld world;
@Inject(at = @At("RETURN"), method = "generateFeatures")
public void generateFeatures(ChunkRegion region, CallbackInfo ci) {
if (world instanceof ServerWorld) {
ChunkFinalizeCallback.EVENT.invoker().onChunkFinalized((ServerWorld) world, new Vector2i(region.getCenterChunkX(), region.getCenterChunkZ()));
}
}
}

View File

@ -0,0 +1,21 @@
package de.bluecolored.bluemap.fabric.mixin;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import de.bluecolored.bluemap.fabric.events.WorldSaveCallback;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.ProgressListener;
import net.minecraft.world.SessionLockException;
@Mixin(ServerWorld.class)
public abstract class MixinServerWorld {
@Inject(at = @At("RETURN"), method = "save")
public void save(ProgressListener progressListener, boolean flush, boolean bl, CallbackInfo ci) throws SessionLockException {
WorldSaveCallback.EVENT.invoker().onWorldSaved((ServerWorld) (Object) this);
}
}

View File

@ -0,0 +1,11 @@
accept-download: false
metrics: true
renderThreadCount: -2
data: "bluemap"
webroot: "bluemap/web"
useCookies: true
webserver {
enabled: true
port: 8100
maxConnectionCount: 100
}

View File

@ -0,0 +1,166 @@
## ##
## BlueMap ##
## ##
## by Blue (Lukas Rieger) ##
## http://bluecolored.de/ ##
## ##
# By changing the setting (accept-download) below to TRUE you are indicating that you have accepted mojang's EULA (https://account.mojang.com/documents/minecraft_eula),
# you confirm that you own a license to Minecraft (Java Edition)
# and you agree that BlueMap will download and use this file for you: %minecraft-client-url%
# (Alternatively you can download the file yourself and store it here: <data>/minecraft-client-%minecraft-client-version%.jar)
# This file contains resources that belong to mojang and you must not redistribute it or do anything else that is not compliant with mojang's EULA.
# BlueMap uses resources in this file to generate the 3D-Models used for the map and texture them. (BlueMap will not work without those resources.)
# %datetime-iso%
accept-download: false
# This changes the amount of threads that BlueMap will use to render the maps.
# A higher value can improve render-speed but could impact performance on the host machine.
# This should be always below or equal to the number of available processor-cores.
# Zero or a negative value means the amount of of available processor-cores subtracted by the value.
# (So a value of -2 with 6 cores results in 4 render-processes)
# Default is -2
renderThreadCount: -2
# If this is true, BlueMap might send really basic metrics reports containg only the implementation-type and the version that is being used to https://metrics.bluecolored.de/bluemap/
# This allows me to track the basic usage of BlueMap and helps me stay motivated to further develop this tool! Please leave it on :)
# An example report looks like this: {"implementation":"forge","version":"%version%"}
metrics: true
# The folder where bluemap saves data-files it needs during runtime or to save e.g. the render-progress to resume it later.
data: "bluemap"
# The webroot of the website that displays the map.
webroot: "bluemap/web"
# Unncomment this to override the path where bluemap stores the data-files.
# Default is "<webroot>/data"
#webdata: "path/to/data/folder"
# If the web-application should use cookies to save the configurations of a user.
useCookies: true
webserver {
# With this setting you can disable the integrated web-server.
# This is usefull if you want to only render the map-data for later use, or if you setup your own webserver.
# Default is enabled
enabled: true
# The IP-Adress that the webserver binds to.
# If this setting is commented out, bluemap tries to find the default ip-adress of your system.
# If you only want to access it locally use "localhost".
#ip: "localhost"
#ip: "127.0.0.1"
# The port that the webserver listenes to.
# Default is 8100
port: 8100
# Max number of simultaneous connections that the webserver allows
# Default is 100
maxConnectionCount: 100
}
# This is an array with multiple configured maps.
# You can define multiple maps, for different worlds with different render-settings here
maps: [
{
# The id of this map
# Should only contain word-charactes: [a-zA-Z0-9_]
# Changing this value breaks your existing renders.
id: "world"
# The name of this map
# This defines the display name of this map, you can change this at any time.
# Default is the id of this map
name: "World"
# The path to the save-folder of the world to render.
world: "world"
# The position on the world where the map will be centered if you open it.
# You can change this at any time.
# This defaults to the world-spawn if you don't set it.
#startPos: [500, -820]
# The color of thy sky as a hex-color
# You can change this at any time.
# Default is "#7dabff"
skyColor: "#7dabff"
# Defines the ambient light-strength that every block is recieving, regardless of the sunlight/blocklight.
# 0 is no ambient light, 1 is fully lighted.
# You can change this at any time.
# Default is 0
ambientLight: 0
# If this is false, BlueMap tries to omit all blocks that are not visible from above-ground.
# More specific: Block-Faces that have a sunlight/skylight value of 0 are removed.
# This improves the performance of the map on slower devices by a lot, but might cause some blocks to disappear that should normally be visible.
# Changing this value requires a re-render of the map.
# Default is false
renderCaves: false
# With the below values you can limit the map-render.
# This can be used to ignore the nethers ceiling or render only a certain part of a world.
# Changing this values might require a re-render of the map, already rendered tiles outside the limits will not be deleted.
# Default is no min or max value (= infinite bounds)
#minX: -4000
#maxX: 4000
#minZ: -4000
#maxZ: 4000
#minY: 50
#maxY: 126
# Using this, BlueMap pretends that every Block out of the defined render-bounds is AIR,
# this means you can see the blocks where the world is cut (instead of having a see-through/xray view).
# This has only an effect if you set some render-bounds above.
# Changing this value requires a re-render of the map.
# Default is true
renderEdges: true
# With this set to true, the generated files for this world are compressed using gzip to save A LOT of space.
# Files will be only 5% as big with compression!
# Note: If you are using NGINX or Apache to host your map, you can configure them to serve the compressed files directly.
# This is much better than disabling the compression.
# Changing this value requires a re-render of the map.
# Default is true
useCompression: true
}
# Here another example for the End-Map
# Things we don't want to change from default we can just omit
{
id: "end"
name: "End"
world: "world/DIM1"
# We dont want a blue sky in the end
skyColor: "#080010"
# In the end is no sky-light, so we need to enable this or we won't see anything.
renderCaves: true
# Same here, we don't want a dark map. But not completely lighted, so we see the effect of e.g torches.
ambientLight: 0.6
}
# Here another example for the Nether-Map
{
id: "nether"
name: "Nether"
world: "world/DIM-1"
skyColor: "#290000"
renderCaves: true
ambientLight: 0.6
# We slice the whole world at y:90 so every block above 90 will be air.
# This way we don't render the nethers ceiling.
maxY: 90
renderEdges: true
}
]

View File

@ -1,13 +1,15 @@
{
"required": true,
"minVersion": "0.8",
"package": "de.bluecolored.bluemap.fabric.mixin",
"compatibilityLevel": "JAVA_8",
"mixins": [
],
"client": [
],
"injectors": {
"defaultRequire": 1
}
}
"required": true,
"minVersion": "0.8",
"package": "de.bluecolored.bluemap.fabric.mixin",
"compatibilityLevel": "JAVA_8",
"mixins": [],
"client": [],
"server": [
"MixinServerWorld",
"MixinChunkGenerator"
],
"injectors": {
"defaultRequire": 1
}
}

View File

@ -31,10 +31,14 @@
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import org.apache.logging.log4j.LogManager;
import com.flowpowered.math.vector.Vector3i;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import de.bluecolored.bluemap.common.plugin.Plugin;
import de.bluecolored.bluemap.common.plugin.commands.Commands;
@ -59,12 +63,23 @@ public class ForgeMod implements ServerInterface {
private Map<String, UUID> worldUUIDs;
private Collection<ServerEventListener> eventListeners;
private LoadingCache<ServerWorld, UUID> worldUuidCache;
public ForgeMod() {
Logger.global = new Log4jLogger(LogManager.getLogger(Plugin.PLUGIN_NAME));
this.bluemap = new Plugin("forge", this);
this.worldUUIDs = new HashMap<>();
this.eventListeners = new ArrayList<>(1);
this.worldUuidCache = CacheBuilder.newBuilder()
.weakKeys()
.maximumSize(1000)
.build(new CacheLoader<ServerWorld, UUID>() {
@Override
public UUID load(ServerWorld key) throws Exception {
return loadUUIDForWorld(key);
}
});
MinecraftForge.EVENT_BUS.register(this);
}
@ -175,6 +190,16 @@ public UUID getUUIDForWorld(File worldFolder) throws IOException {
}
public UUID getUUIDForWorld(ServerWorld world) throws IOException {
try {
return worldUuidCache.get(world);
} catch (ExecutionException e) {
Throwable cause = e.getCause();
if (cause instanceof IOException) throw (IOException) cause;
else throw new IOException(cause);
}
}
private UUID loadUUIDForWorld(ServerWorld world) throws IOException {
synchronized (worldUUIDs) {
String key = getFolderForWorld(world).getPath();