More implementation of the block restore queue functionality

This commit is contained in:
zontreck 2024-04-23 15:40:43 -07:00
parent b20e56a7d8
commit 40ce8774fb
9 changed files with 363 additions and 6 deletions

View file

@ -9,14 +9,18 @@ import java.util.Map;
import java.util.UUID;
import dev.zontreck.libzontreck.chestgui.ChestGUIRegistry;
import dev.zontreck.libzontreck.config.ServerConfig;
import dev.zontreck.libzontreck.currency.Bank;
import dev.zontreck.libzontreck.currency.CurrencyHelper;
import dev.zontreck.libzontreck.events.BlockRestoreQueueRegistrationEvent;
import dev.zontreck.libzontreck.items.ModItems;
import dev.zontreck.libzontreck.memory.world.BlockRestoreQueue;
import dev.zontreck.libzontreck.memory.world.BlockRestoreQueueRegistry;
import dev.zontreck.libzontreck.menus.ChestGUIScreen;
import dev.zontreck.libzontreck.types.ModMenuTypes;
import dev.zontreck.libzontreck.networking.NetworkEvents;
import net.minecraft.client.gui.screens.MenuScreens;
import net.minecraft.server.level.ServerLevel;
import org.slf4j.Logger;
import com.mojang.logging.LogUtils;
@ -106,9 +110,16 @@ public class LibZontreck {
public void onServerStarted(final ServerStartedEvent event)
{
ALIVE=true;
ServerConfig.init();
CURRENT_SIDE = LogicalSide.SERVER;
MinecraftForge.EVENT_BUS.post(new BlockRestoreQueueRegistrationEvent());
for(ServerLevel level : event.getServer().getAllLevels())
{
// Queues have been registered, but we now need to initialize the queue's data from saveddata
}
}
@SubscribeEvent

View file

@ -0,0 +1,35 @@
package dev.zontreck.libzontreck.config;
import dev.zontreck.libzontreck.LibZontreck;
import dev.zontreck.libzontreck.config.sections.DatabaseSection;
import dev.zontreck.libzontreck.util.SNbtIo;
import net.minecraft.nbt.CompoundTag;
import java.nio.file.Path;
public class ServerConfig
{
public static final Path BASE = LibZontreck.BASE_CONFIG.resolve("server.snbt");
public static DatabaseSection database;
public static void init()
{
if(BASE.toFile().exists())
{
var config = SNbtIo.loadSnbt(BASE);
database = DatabaseSection.deserialize(config.getCompound(DatabaseSection.TAG_NAME));
} else {
database = new DatabaseSection();
}
}
public static void commit()
{
CompoundTag tag = new CompoundTag();
tag.put(DatabaseSection.TAG_NAME, database.serialize());
SNbtIo.writeSnbt(BASE, tag);
}
}

View file

@ -0,0 +1,91 @@
package dev.zontreck.libzontreck.config.sections;
import dev.zontreck.libzontreck.config.ServerConfig;
import net.minecraft.nbt.CompoundTag;
public class DatabaseSection
{
public static final String TAG_NAME = "database";
public static final String TAG_USER = "username";
public static final String TAG_PASSWORD = "password";
public static final String TAG_HOST = "host";
public static final String TAG_DATABASE = "database";
public static final String TAG_VERSION = "rev";
public String user = "root";
public String password = "";
public String host = "localhost:3306"; // IP:port
public String database = "savedBlocks";
public int version;
public static final int VERSION = 1;
private boolean migrated=false;
public static DatabaseSection deserialize(CompoundTag tag)
{
int ver = tag.getInt(TAG_VERSION);
DatabaseSection section = new DatabaseSection();
section.doMigrations(ver, tag);
return section;
}
public void doMigrations(int ver, CompoundTag tag)
{
switch (ver)
{
case 0:
{
ver++;
user = tag.getString(TAG_USER);
password = tag.getString(TAG_PASSWORD);
host = tag.getString(TAG_HOST);
database = tag.getString(TAG_DATABASE);
version = ver;
migrated=true;
break;
}
default:{
if(migrated)
{
ServerConfig.commit();
migrated=false;
return;
}
break;
}
}
doMigrations(ver, tag);
}
public DatabaseSection(){
}
public DatabaseSection(String user, String password, String host, String database)
{
this.user = user;
this.password = password;
this.host = host;
this.database = database;
}
public String getAsSQLFileName()
{
return database + ".sql";
}
public CompoundTag serialize()
{
CompoundTag tag = new CompoundTag();
tag.putString(TAG_USER, user);
tag.putString(TAG_PASSWORD, password);
tag.putString(TAG_HOST, host);
tag.putString(TAG_DATABASE, database);
return tag;
}
}

View file

@ -2,6 +2,7 @@ package dev.zontreck.libzontreck.events;
import dev.zontreck.libzontreck.memory.world.BlockRestoreQueue;
import dev.zontreck.libzontreck.memory.world.BlockRestoreQueueRegistry;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.eventbus.api.Event;
public class BlockRestoreQueueRegistrationEvent extends Event
@ -13,5 +14,7 @@ public class BlockRestoreQueueRegistrationEvent extends Event
public void register(BlockRestoreQueue queue)
{
BlockRestoreQueueRegistry.addQueue(queue);
MinecraftForge.EVENT_BUS.register(queue);
}
}

View file

@ -1,13 +1,21 @@
package dev.zontreck.libzontreck.memory.world;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtIo;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.block.Block;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.TickEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public abstract class BlockRestoreQueue
{
private List<PrimitiveBlock> BLOCK_QUEUE = new ArrayList<>();
@ -109,7 +117,7 @@ public abstract class BlockRestoreQueue
/**
* Executes a tick. Spawns a thread which will modify 1 block from the queue
*/
public void tick()
private void tick()
{
Thread tx = new Thread(RUNNER);
tx.start();
@ -123,4 +131,28 @@ public abstract class BlockRestoreQueue
tick();
}
}
/**
* Initialize a restore Queue for a specific level. This will load and import the blocks in the save data into this queue
* @param level The level to load for
* @throws IOException On failure to read a file
*/
public void initialize(ServerLevel level) throws IOException {
var file = SaveDataFactory.builder().withDimension(level).withQueueID(this).withPosition(null).build();
CompoundTag tag = NbtIo.read(file.getSaveDataPath().toFile());
SaveDataFactory.SaveDataManifest manifest = SaveDataFactory.SaveDataManifest.deserialize(tag);
List<SaveDataCoordinates> files = manifest.get();
for(SaveDataCoordinates chunk : files)
{
var saved = SaveDataFactory.builder().withDimension(level).withQueueID(this).withPosition(chunk.toBlockPos()).build();
var saveData = saved.getInstance();
for(SavedBlock sb : saveData.blocks)
{
enqueueBlock(sb);
}
}
}
}

View file

@ -1,5 +1,9 @@
package dev.zontreck.libzontreck.memory.world;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.Level;
import java.io.IOException;
import java.util.*;
/**
@ -27,4 +31,35 @@ public class BlockRestoreQueueRegistry
public static BlockRestoreQueue getQueue(String restoreQueueName) {
return QUEUES.get(restoreQueueName);
}
/**
* Returns a iterator for a list of all the queues. This cannot remove items from the main queue.
* @return
*/
public static Iterator<BlockRestoreQueue> getReadOnlyQueue() {
List<BlockRestoreQueue> queues = new ArrayList<>();
queues.addAll(QUEUES.values());
return queues.iterator();
}
/**
* Initialize a block restore queue.
* <p></p>
* Block Restore Queues are level independent, but blocks are not. This loads the blocks saved for this queue in that particular level's hierarchy
* @param level The level to load the queue for
*/
public static void init(ServerLevel level)
{
Iterator<BlockRestoreQueue> it = BlockRestoreQueueRegistry.getReadOnlyQueue();
while(it.hasNext())
{
BlockRestoreQueue queue = it.next();
try {
queue.initialize(level);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}

View file

@ -0,0 +1,57 @@
package dev.zontreck.libzontreck.memory.world;
import java.sql.*;
public class DatabaseWrapper {
private Connection connection;
public DatabaseWrapper() {
connection = null;
}
public void connect(String url, String username, String password) throws SQLException {
try {
// Try MariaDB JDBC driver
Class.forName("org.mariadb.jdbc.Driver");
connection = DriverManager.getConnection("jdbc:mariadb://" + url, username, password);
} catch (ClassNotFoundException | SQLException e) {
// MariaDB not found or failed to connect, try MySQL
try {
Class.forName("com.mysql.cj.jdbc.Driver");
connection = DriverManager.getConnection("jdbc:mysql://" + url, username, password);
} catch (ClassNotFoundException | SQLException ex) {
// MySQL not found or failed to connect, try SQLite
try {
Class.forName("org.sqlite.JDBC");
connection = DriverManager.getConnection("jdbc:sqlite:" + url);
} catch (ClassNotFoundException | SQLException exc) {
throw new SQLException("Failed to connect to database: " + exc.getMessage());
}
}
}
}
public ResultSet executeQuery(String query) throws SQLException {
if (connection == null) {
throw new SQLException("Connection not established.");
}
Statement statement = connection.createStatement();
return statement.executeQuery(query);
}
public int executeUpdate(String statement) throws SQLException {
if (connection == null) {
throw new SQLException("Connection not established.");
}
Statement stmt = connection.createStatement();
return stmt.executeUpdate(statement);
}
public void disconnect() throws SQLException {
if (connection != null && !connection.isClosed()) {
connection.close();
}
}
}

View file

@ -1,20 +1,47 @@
package dev.zontreck.libzontreck.memory.world;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtUtils;
import net.minecraft.tags.BlockTags;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraftforge.common.Tags;
public class SaveDataCoordinates
{
public int X;
public int Z;
private BlockPos source;
public SaveDataCoordinates(BlockPos pos)
{
int X = pos.getX() >> 4 >> 5;
int Z = pos.getZ() >> 4 >> 5;
source = pos;
}
public String getFileName()
{
return "r." + X + "." + Z + ".dat";
}
public BlockPos toBlockPos()
{
return source;
}
public static final String TAG_COORDS = "sdc";
public CompoundTag toNBT()
{
return NbtUtils.writeBlockPos(source);
}
public static SaveDataCoordinates deserialize(CompoundTag tag)
{
BlockPos pos = NbtUtils.readBlockPos(tag);
return new SaveDataCoordinates(pos);
}
}

View file

@ -1,11 +1,9 @@
package dev.zontreck.libzontreck.memory.world;
import dev.zontreck.libzontreck.LibZontreck;
import dev.zontreck.libzontreck.vectors.WorldPosition;
import net.minecraft.core.BlockPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.NbtIo;
import net.minecraft.nbt.Tag;
import net.minecraft.nbt.*;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.level.Level;
@ -72,6 +70,11 @@ public class SaveDataFactory
return this;
}
public SaveDataManifest getManifest()
{
return new SaveDataManifest();
}
public SaveDataFile build()
{
return new SaveDataFile(modID, levelName, queueID, DBMode, position);
@ -85,6 +88,7 @@ public class SaveDataFactory
String queue;
boolean database;
BlockPos position;
boolean useManifest = false;
public SaveDataFile(String modID, String levelName, String queueID, boolean DBMode, BlockPos pos)
{
@ -93,8 +97,17 @@ public class SaveDataFactory
queue = queueID;
database = DBMode;
position = pos;
if(pos == null)
{
useManifest=true;
}
}
/**
* config/LibZontreck/block_snapshots/[mod]/[dimension]/[queueNickName]/r.x.z.dat
* @return
*/
public Path getSaveDataPath()
{
Path path = LibZontreck.BASE_CONFIG.resolve("block_snapshots");
@ -104,6 +117,8 @@ public class SaveDataFactory
path.toFile().mkdirs();
if(useManifest) return path.resolve("manifest.nbt");
SaveDataCoordinates coordinates = new SaveDataCoordinates(position);
path = path.resolve(coordinates.getFileName());
return path;
@ -131,6 +146,52 @@ public class SaveDataFactory
}
}
static class SaveDataManifest {
private List<SaveDataCoordinates> SAVE_DATA = new ArrayList<>();
public static final String TAG_MANIFEST = "manifest";
private SaveDataManifest()
{
}
public void add(SaveDataCoordinates pos){
SAVE_DATA.add(pos);
}
public List<SaveDataCoordinates> get()
{
return new ArrayList<>(SAVE_DATA);
}
public CompoundTag save()
{
CompoundTag tag = new CompoundTag();
ListTag lst = new ListTag();
for(SaveDataCoordinates str : SAVE_DATA)
{
lst.add(str.toNBT());
}
tag.put(TAG_MANIFEST, lst);
return tag;
}
public static SaveDataManifest deserialize(CompoundTag tag)
{
SaveDataManifest ret = new SaveDataManifest();
ListTag lst = tag.getList(TAG_MANIFEST, Tag.TAG_COMPOUND);
for(Tag entry : lst)
{
if(entry instanceof CompoundTag ct)
{
ret.add(SaveDataCoordinates.deserialize(ct));
}
}
return ret;
}
}
static class SaveData {
SaveDataFile myFile;
public static final String TAG_SAVED_BLOCKS = "sb";
@ -183,6 +244,8 @@ public class SaveDataFactory
/**
* Imports a full queue to the save data file.
* ! WARNING ! This method will overwrite the SaveDataFile's Queue ID
* * * *
* This will only import for the correct dimension. This method is invoked automatically for each level for each queue when the server is shutting down prior to level unload.
* @param queue Queue to import
* @return The current SaveData instance
*/
@ -190,7 +253,10 @@ public class SaveDataFactory
{
for(PrimitiveBlock blk : queue.getQueue())
{
if(WorldPosition.getDim(blk.level) == this.myFile.dimension)
blocks.add(blk.savedBlock);
else
continue; // We only want to add to a save data file, the blocks for the dimension in question.
}
myFile.queue = queue.getRestoreQueueName();