Merge branch 'feature/ChunkedFileTransfer'

This commit is contained in:
Frank 2021-08-23 15:24:15 +02:00
commit 0816fd032d
21 changed files with 667 additions and 63 deletions

View file

@ -7,6 +7,7 @@ import net.minecraft.resources.ResourceLocation;
import ru.bclib.api.TagAPI;
import ru.bclib.api.WorldDataAPI;
import ru.bclib.api.dataexchange.DataExchangeAPI;
import ru.bclib.api.dataexchange.handler.autosync.Chunker;
import ru.bclib.api.dataexchange.handler.autosync.HelloClient;
import ru.bclib.api.dataexchange.handler.autosync.HelloServer;
import ru.bclib.api.dataexchange.handler.autosync.RequestFiles;
@ -17,6 +18,7 @@ import ru.bclib.recipes.CraftingRecipes;
import ru.bclib.registry.BaseBlockEntities;
import ru.bclib.registry.BaseRegistry;
import ru.bclib.util.Logger;
import ru.bclib.util.ModUtil;
import ru.bclib.world.generator.BCLibEndBiomeSource;
import ru.bclib.world.generator.BCLibNetherBiomeSource;
import ru.bclib.world.generator.GeneratorOptions;
@ -30,6 +32,7 @@ public class BCLib implements ModInitializer {
@Override
public void onInitialize() {
ModUtil.convertModVersion("1.3.0-pre1-1.17.1");
BaseRegistry.register();
GeneratorOptions.init();
BaseBlockEntities.register();
@ -45,7 +48,8 @@ public class BCLib implements ModInitializer {
HelloClient.DESCRIPTOR,
HelloServer.DESCRIPTOR,
RequestFiles.DESCRIPTOR,
SendFiles.DESCRIPTOR
SendFiles.DESCRIPTOR,
Chunker.DESCRIPTOR
));
DataFixerAPI.registerPatch(() -> new BCLibPatch());

View file

@ -55,6 +55,7 @@ public abstract class BaseDataHandler {
@Environment(EnvType.CLIENT)
abstract void sendToServer(Minecraft client);
protected boolean isBlocking() { return false; }
@Override
public String toString() {

View file

@ -15,6 +15,11 @@ import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.server.network.ServerGamePacketListenerImpl;
import ru.bclib.BCLib;
import ru.bclib.api.dataexchange.handler.autosync.Chunker;
import ru.bclib.api.dataexchange.handler.autosync.Chunker.PacketChunkSender;
import java.util.Collection;
import java.util.List;
public abstract class DataHandler extends BaseDataHandler {
public abstract static class WithoutPayload extends DataHandler {
@ -51,7 +56,10 @@ public abstract class DataHandler extends BaseDataHandler {
@Override
void receiveFromServer(Minecraft client, ClientPacketListener handler, FriendlyByteBuf buf, PacketSender responseSender) {
deserializeIncomingData(buf, responseSender, true);
client.execute(() -> runOnGameThread(client, null, true));
final Runnable runner = () -> runOnGameThread(client, null, true);
if (isBlocking()) client.executeBlocking(runner);
else client.execute(runner);
}
@Override
@ -59,7 +67,10 @@ public abstract class DataHandler extends BaseDataHandler {
super.receiveFromClient(server, player, handler, buf, responseSender);
deserializeIncomingData(buf, responseSender, false);
server.execute(() -> runOnGameThread(null, server, false));
final Runnable runner = () -> runOnGameThread(null, server, false);
if (isBlocking()) server.executeBlocking(runner);
else server.execute(runner);
}
@Override
@ -68,9 +79,7 @@ public abstract class DataHandler extends BaseDataHandler {
FriendlyByteBuf buf = PacketByteBufs.create();
serializeData(buf, false);
for (ServerPlayer player : PlayerLookup.all(server)) {
ServerPlayNetworking.send(player, getIdentifier(), buf);
}
_sendToClient(getIdentifier(), server, PlayerLookup.all(server), buf);
}
}
@ -79,7 +88,20 @@ public abstract class DataHandler extends BaseDataHandler {
if (prepareData(false)) {
FriendlyByteBuf buf = PacketByteBufs.create();
serializeData(buf, false);
ServerPlayNetworking.send(player, getIdentifier(), buf);
_sendToClient(getIdentifier(), server, List.of(player), buf);
}
}
public static void _sendToClient(ResourceLocation identifier, MinecraftServer server, Collection<ServerPlayer> players, FriendlyByteBuf buf) {
if (buf.readableBytes()> Chunker.MAX_PACKET_SIZE) {
final PacketChunkSender sender = new PacketChunkSender(buf, identifier);
sender.sendChunks(players);
} else {
for (ServerPlayer player : players) {
ServerPlayNetworking.send(player, identifier, buf);
}
}
}
@ -140,7 +162,10 @@ public abstract class DataHandler extends BaseDataHandler {
super.receiveFromClient(server, player, handler, buf, responseSender);
deserializeIncomingDataOnServer(buf, responseSender);
server.execute(() -> runOnServerGameThread(server));
final Runnable runner = () -> runOnServerGameThread(server);
if (isBlocking()) server.executeBlocking(runner);
else server.execute(runner);
}
@Override
@ -204,7 +229,10 @@ public abstract class DataHandler extends BaseDataHandler {
@Override
final void receiveFromServer(Minecraft client, ClientPacketListener handler, FriendlyByteBuf buf, PacketSender responseSender) {
deserializeIncomingDataOnClient(buf, responseSender);
client.execute(() -> runOnClientGameThread(client));
final Runnable runner = () -> runOnClientGameThread(client);
if (isBlocking()) client.executeBlocking(runner);
else client.execute(runner);
}
@Override
@ -213,15 +241,17 @@ public abstract class DataHandler extends BaseDataHandler {
BCLib.LOGGER.error("[Internal Error] The message '" + getIdentifier() + "' must originate from the server!");
}
public void receiveFromMemory(FriendlyByteBuf buf){
receiveFromServer(Minecraft.getInstance(), null, buf, null);
}
@Override
final void sendToClient(MinecraftServer server) {
if (prepareDataOnServer()) {
FriendlyByteBuf buf = PacketByteBufs.create();
serializeDataOnServer(buf);
for (ServerPlayer player : PlayerLookup.all(server)) {
ServerPlayNetworking.send(player, getIdentifier(), buf);
}
_sendToClient(getIdentifier(), server, PlayerLookup.all(server), buf);
}
}
@ -230,7 +260,8 @@ public abstract class DataHandler extends BaseDataHandler {
if (prepareDataOnServer()) {
FriendlyByteBuf buf = PacketByteBufs.create();
serializeDataOnServer(buf);
ServerPlayNetworking.send(player, getIdentifier(), buf);
_sendToClient(getIdentifier(), server, List.of(player), buf);
}
}

View file

@ -1,18 +1,20 @@
package ru.bclib.api.dataexchange;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.NotNull;
import java.util.Objects;
import java.util.function.Supplier;
public class DataHandlerDescriptor {
public DataHandlerDescriptor(ResourceLocation identifier, Supplier<BaseDataHandler> instancer){
public DataHandlerDescriptor(@NotNull ResourceLocation identifier, @NotNull Supplier<BaseDataHandler> instancer){
this(identifier, instancer, instancer, false, false);
}
public DataHandlerDescriptor(ResourceLocation identifier, Supplier<BaseDataHandler> instancer, boolean sendOnJoin, boolean sendBeforeEnter){
public DataHandlerDescriptor(@NotNull ResourceLocation identifier,@NotNull Supplier<BaseDataHandler> instancer, boolean sendOnJoin, boolean sendBeforeEnter){
this(identifier, instancer, instancer, sendOnJoin, sendBeforeEnter);
}
public DataHandlerDescriptor(ResourceLocation identifier, Supplier<BaseDataHandler> receiv_instancer, Supplier<BaseDataHandler> join_instancer, boolean sendOnJoin, boolean sendBeforeEnter){
public DataHandlerDescriptor(@NotNull ResourceLocation identifier, @NotNull Supplier<BaseDataHandler> receiv_instancer, @NotNull Supplier<BaseDataHandler> join_instancer, boolean sendOnJoin, boolean sendBeforeEnter){
this.INSTANCE = receiv_instancer;
this.JOIN_INSTANCE = join_instancer;
this.IDENTIFIER = identifier;
@ -23,7 +25,25 @@ public class DataHandlerDescriptor {
public final boolean sendOnJoin;
public final boolean sendBeforeEnter;
@NotNull
public final ResourceLocation IDENTIFIER;
@NotNull
public final Supplier<BaseDataHandler> INSTANCE;
@NotNull
public final Supplier<BaseDataHandler> JOIN_INSTANCE;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o instanceof ResourceLocation){
return o.equals(IDENTIFIER);
}
if (!(o instanceof DataHandlerDescriptor that)) return false;
return IDENTIFIER.equals(that.IDENTIFIER);
}
@Override
public int hashCode() {
return Objects.hash(IDENTIFIER);
}
}

View file

@ -4,6 +4,7 @@ import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents;
import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents;
import net.minecraft.resources.ResourceLocation;
import ru.bclib.api.dataexchange.BaseDataHandler;
import ru.bclib.api.dataexchange.ConnectorClientside;
import ru.bclib.api.dataexchange.ConnectorServerside;
@ -43,6 +44,9 @@ abstract public class DataExchange {
public Set<DataHandlerDescriptor> getDescriptors() { return descriptors; }
public static DataHandlerDescriptor getDescriptor(ResourceLocation identifier){
return getInstance().descriptors.stream().filter(d -> d.equals(identifier)).findFirst().orElse(null);
}
@Environment(EnvType.CLIENT)
protected void initClientside() {

View file

@ -36,7 +36,7 @@ public class AutoSync {
@ConfigUI(leftPadding =8)
public static final DependendConfigToken<Boolean> ACCEPT_FILES = DependendConfigToken.Boolean(true,"acceptFiles", SYNC_CATEGORY, (config)->config.get(ENABLED));
@ConfigUI(leftPadding =8)
public static final DependendConfigToken<Boolean> ACCEPT_MODS = DependendConfigToken.Boolean(true,"acceptMods", SYNC_CATEGORY, (config)->config.get(ENABLED));
public static final DependendConfigToken<Boolean> ACCEPT_MODS = DependendConfigToken.Boolean(false,"acceptMods", SYNC_CATEGORY, (config)->config.get(ENABLED));
@ConfigUI(topPadding = 12)
public static final ConfigToken<Boolean> DEBUG_HASHES = ConfigToken.Boolean(false, "debugHashes", SYNC_CATEGORY);

View file

@ -0,0 +1,270 @@
package ru.bclib.api.dataexchange.handler.autosync;
import net.fabricmc.fabric.api.networking.v1.PacketByteBufs;
import net.fabricmc.fabric.api.networking.v1.PacketSender;
import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking;
import net.minecraft.client.Minecraft;
import net.minecraft.util.ProgressListener;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import ru.bclib.BCLib;
import ru.bclib.api.dataexchange.BaseDataHandler;
import ru.bclib.api.dataexchange.DataHandler;
import ru.bclib.api.dataexchange.DataHandlerDescriptor;
import ru.bclib.api.dataexchange.handler.DataExchange;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
/**
* Used to seperate large data transfers into multiple smaller messages.
* <p>
* {@link DataHandler} will automatically convert larger messages into Chunks on the Server
* and assemble the original message from those chunks on the client.
*/
public class Chunker extends DataHandler.FromServer {
/**
* Responsible for assembling the original ByteBuffer created by {@link PacketChunkSender} on the
* receiving end. Automatically created from the header {@link Chunker}-Message (where the serialNo==-1)
*/
static class PacketChunkReceiver {
@NotNull
public final UUID uuid;
public final int chunkCount;
@NotNull
private final FriendlyByteBuf networkedBuf;
@Nullable
private final DataHandlerDescriptor descriptor;
private static List<PacketChunkReceiver> active = new ArrayList<>(1);
private static PacketChunkReceiver newReceiver(@NotNull UUID uuid, int chunkCount, ResourceLocation origin){
DataHandlerDescriptor desc = DataExchange.getDescriptor(origin);
final PacketChunkReceiver r = new PacketChunkReceiver(uuid, chunkCount, desc);
active.add(r);
return r;
}
private static PacketChunkReceiver getOrCreate(@NotNull UUID uuid, int chunkCount, ResourceLocation origin){
return active.stream().filter(r -> r.uuid.equals(uuid)).findFirst().orElse(newReceiver(uuid, chunkCount, origin));
}
public static PacketChunkReceiver get(@NotNull UUID uuid){
return active.stream().filter(r -> r.uuid.equals(uuid)).findFirst().orElse(null);
}
private PacketChunkReceiver(@NotNull UUID uuid, int chunkCount, @Nullable DataHandlerDescriptor descriptor){
this.uuid = uuid;
this.chunkCount = chunkCount;
networkedBuf = PacketByteBufs.create();
this.descriptor = descriptor;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof PacketChunkReceiver)) return false;
PacketChunkReceiver that = (PacketChunkReceiver) o;
return uuid.equals(that.uuid);
}
@Override
public int hashCode() {
return Objects.hash(uuid);
}
public boolean testFinished(){
ProgressListener listener = ChunkerProgress.getProgressListener();
if (listener!=null){
listener.progressStagePercentage((100*receivedCount)/chunkCount);
}
if (incomingBuffer == null){
return true;
} if (lastReadSerial>=chunkCount-1){
onFinish();
return true;
}
return false;
}
private void addBuffer(FriendlyByteBuf input){
final int size = input.readableBytes();
final int cap = networkedBuf.capacity()-networkedBuf.writerIndex();
if (cap < size){
networkedBuf.capacity(networkedBuf.writerIndex() + size);
}
input.readBytes(networkedBuf, size);
input.clear();
}
protected void onFinish(){
incomingBuffer.clear();
incomingBuffer = null;
final BaseDataHandler baseHandler = descriptor.INSTANCE.get();
if (baseHandler instanceof DataHandler.FromServer handler){
handler.receiveFromMemory(networkedBuf);
}
}
Map<Integer, FriendlyByteBuf> incomingBuffer = new HashMap<>();
int lastReadSerial = -1;
int receivedCount = 0;
public void processReceived(FriendlyByteBuf buf, int serialNo, int size){
receivedCount++;
if (lastReadSerial == serialNo-1){
addBuffer(buf);
lastReadSerial = serialNo;
} else {
//not sure if order is guaranteed by the underlying system!
boolean haveAll = true;
for (int nr = lastReadSerial+1; nr < serialNo-1; nr++){
if (incomingBuffer.get(nr) == null){
haveAll = false;
break;
}
}
if (haveAll){
for (int nr = lastReadSerial+1; nr < serialNo-1; nr++){
addBuffer(incomingBuffer.get(nr));
incomingBuffer.put(nr, null);
}
addBuffer(buf);
lastReadSerial = serialNo;
} else {
incomingBuffer.put(serialNo, buf);
}
}
}
}
/**
* Responsible for splitting an outgoing ByteBuffer into several smaller Chunks and
* send them as seperate messages to the {@link Chunker}-Channel
*/
public static class PacketChunkSender {
private final FriendlyByteBuf networkedBuf;
public final UUID uuid;
public final int chunkCount;
public final int size;
public final ResourceLocation origin;
public PacketChunkSender(FriendlyByteBuf buf, ResourceLocation origin){
networkedBuf = buf;
size = buf.readableBytes();
chunkCount = (int)Math.ceil((double)size / MAX_PAYLOAD_SIZE);
uuid = UUID.randomUUID();
this.origin = origin;
}
public void sendChunks(Collection<ServerPlayer> players){
BCLib.LOGGER.info("Sending Request in " + chunkCount + " Packet-Chunks");
for (int i=-1; i<chunkCount; i++){
Chunker c = new Chunker(i, uuid, networkedBuf, chunkCount, origin);
FriendlyByteBuf buf = PacketByteBufs.create();
c.serializeDataOnServer(buf);
for (ServerPlayer player : players){
ServerPlayNetworking.send(player, DESCRIPTOR.IDENTIFIER, buf);
}
}
}
}
//header = version + UUID + serialNo + size, see serializeDataOnServer
private static final int HEADER_SIZE = 1 + 16 + 4 + 4;
public static final int MAX_PACKET_SIZE = 1024*1024;
private static final int MAX_PAYLOAD_SIZE = MAX_PACKET_SIZE - HEADER_SIZE;
public static DataHandlerDescriptor DESCRIPTOR = new DataHandlerDescriptor(new ResourceLocation(BCLib.MOD_ID, "chunker"), Chunker::new, false, false);
private int serialNo;
private UUID uuid;
private int chunkCount;
private FriendlyByteBuf networkedBuf;
private ResourceLocation origin;
protected Chunker(int serialNo, UUID uuid, FriendlyByteBuf networkedBuf, int chunkCount, ResourceLocation origin) {
super(DESCRIPTOR.IDENTIFIER);
this.serialNo = serialNo;
this.uuid = uuid;
this.networkedBuf = networkedBuf;
this.chunkCount = chunkCount;
this.origin = origin;
}
protected Chunker(){
super(DESCRIPTOR.IDENTIFIER);
}
@Override
protected void serializeDataOnServer(FriendlyByteBuf buf) {
//Sending Header. Make sure to change HEADER_SIZE if you change this!
buf.writeByte(0);
buf.writeLong(uuid.getMostSignificantBits());
buf.writeLong(uuid.getLeastSignificantBits());
buf.writeInt(serialNo);
//sending Payload
if (serialNo == -1){
//this is our header-Chunk that transports status information
buf.writeInt(chunkCount);
writeString(buf, origin.getNamespace());
writeString(buf, origin.getPath());
} else {
//this is an actual payload chunk
buf.capacity(MAX_PACKET_SIZE);
final int size = Math.min(MAX_PAYLOAD_SIZE, networkedBuf.readableBytes());
buf.writeInt(size);
networkedBuf.readBytes(buf, size);
}
}
private PacketChunkReceiver receiver;
@Override
protected void deserializeIncomingDataOnClient(FriendlyByteBuf buf, PacketSender responseSender) {
final int version = buf.readByte();
uuid = new UUID(buf.readLong(), buf.readLong());
serialNo = buf.readInt();
if (serialNo == -1){
chunkCount = buf.readInt();
final String namespace = readString(buf);
final String path = readString(buf);
ResourceLocation ident = new ResourceLocation(namespace, path);
BCLib.LOGGER.info("Receiving " + chunkCount + " + Packet-Chunks for " + ident);
receiver = PacketChunkReceiver.getOrCreate(uuid, chunkCount, ident);
} else {
receiver = PacketChunkReceiver.get(uuid);
if (receiver!=null) {
final int size = buf.readInt();
receiver.processReceived(buf, serialNo, size);
} else {
BCLib.LOGGER.error("Unknown Packet-Chunk Transfer for " + uuid);
}
}
}
@Override
protected void runOnClientGameThread(Minecraft client) {
if (receiver!=null){
receiver.testFinished();
}
}
}

View file

@ -0,0 +1,26 @@
package ru.bclib.api.dataexchange.handler.autosync;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.util.ProgressListener;
import ru.bclib.gui.screens.ProgressScreen;
@Environment(EnvType.CLIENT)
public class ChunkerProgress {
private static ProgressScreen progressScreen;
@Environment(EnvType.CLIENT)
public static void setProgressScreen(ProgressScreen scr){
progressScreen = scr;
}
@Environment(EnvType.CLIENT)
public static ProgressScreen getProgressScreen(){
return progressScreen;
}
@Environment(EnvType.CLIENT)
public static ProgressListener getProgressListener(){
return progressScreen;
}
}

View file

@ -6,6 +6,7 @@ import net.fabricmc.fabric.api.networking.v1.PacketSender;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.chat.TranslatableComponent;
import net.minecraft.resources.ResourceLocation;
import ru.bclib.BCLib;
import ru.bclib.api.dataexchange.DataExchangeAPI;
@ -14,6 +15,7 @@ import ru.bclib.api.dataexchange.DataHandlerDescriptor;
import ru.bclib.api.dataexchange.handler.autosync.AutoSyncID.WithContentOverride;
import ru.bclib.api.dataexchange.handler.autosync.SyncFolderDescriptor.SubFile;
import ru.bclib.config.Configs;
import ru.bclib.gui.screens.ProgressScreen;
import ru.bclib.gui.screens.SyncFilesScreen;
import ru.bclib.gui.screens.WarnBCLibVersionMismatch;
import ru.bclib.util.ModUtil;
@ -300,6 +302,10 @@ public class HelloClient extends DataHandler.FromServer {
}
}
@Override
protected boolean isBlocking() {
return true;
}
@Environment(EnvType.CLIENT)
@Override
@ -371,8 +377,6 @@ public class HelloClient extends DataHandler.FromServer {
}
client.setScreen(new SyncFilesScreen(modFiles, configFiles, singleFiles, folderFiles, filesToRemove.size(), (downloadMods, downloadConfigs, downloadFiles, removeFiles) -> {
Minecraft.getInstance()
.setScreen((Screen) null);
if (downloadMods || downloadConfigs || downloadFiles) {
BCLib.LOGGER.info("Updating local Files:");
List<AutoSyncID.WithContentOverride> localChanges = new ArrayList<>(files.toArray().length);
@ -398,8 +402,15 @@ public class HelloClient extends DataHandler.FromServer {
aid.relFile.delete();
});
}
this.onCloseSyncFilesScreen();
}));
}
@Environment(EnvType.CLIENT)
private void onCloseSyncFilesScreen(){
Minecraft.getInstance()
.setScreen(ChunkerProgress.getProgressScreen());
}
private void processOfferedFile(List<AutoSyncID> requestFiles, AutoSyncID aid) {
@ -420,8 +431,14 @@ public class HelloClient extends DataHandler.FromServer {
whenFinished.accept(true);
}
@Environment(EnvType.CLIENT)
private void requestFileDownloads(List<AutoSyncID> files) {
BCLib.LOGGER.info("Starting download of Files:" + files.size());
final ProgressScreen progress = new ProgressScreen(null, new TranslatableComponent("title.bclib.filesync.progress"), new TranslatableComponent("message.bclib.filesync.progress"));
progress.progressStart(new TranslatableComponent("message.bclib.filesync.progress.stage.empty"));
ChunkerProgress.setProgressScreen(progress);
DataExchangeAPI.send(new RequestFiles(files));
}
}

View file

@ -65,7 +65,6 @@ public class HelloServer extends DataHandler.FromClient {
super(DESCRIPTOR.IDENTIFIER);
}
@Environment(EnvType.CLIENT)
@Override
protected boolean prepareDataOnClient() {
@ -73,6 +72,7 @@ public class HelloServer extends DataHandler.FromClient {
BCLib.LOGGER.info("Auto-Sync was disabled on the client.");
return false;
}
return true;
}

View file

@ -12,7 +12,7 @@ import java.util.function.Function;
class GridCell extends GridCellDefinition {
public final float height;
Function<GridTransform, Object> componentPlacer;
final TriConsumer<PoseStack, GridTransform, Object> customRender;
TriConsumer<PoseStack, GridTransform, Object> customRender;
GridCell(double width, double height, GridLayout.GridValueType widthType, Function<GridTransform, Object> componentPlacer, TriConsumer<PoseStack, GridTransform, Object> customRender) {
super(width, widthType);

View file

@ -0,0 +1,17 @@
package ru.bclib.gui.gridlayout;
import com.mojang.blaze3d.vertex.PoseStack;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import ru.bclib.gui.gridlayout.GridLayout.GridValueType;
@Environment(EnvType.CLIENT)
public abstract class GridCustomRenderCell extends GridCell{
protected GridCustomRenderCell(double width, GridValueType widthType, double height) {
super(width, height, widthType, null, null);
this.customRender = this::onRender;
}
public abstract void onRender(PoseStack poseStack, GridTransform transform, Object context);
}

View file

@ -86,6 +86,7 @@ public class GridLayout extends GridColumn {
public static final int COLOR_RED = 0x00FF0000;
public static final int COLOR_GREEN = 0x0000FF00;
public static final int COLOR_BLUE = 0x000000FF;
public static final int COLOR_GRAY = 0x007F7F7F;
public final GridScreen screen;
public final int screenHeight;

View file

@ -11,41 +11,51 @@ import ru.bclib.gui.gridlayout.GridLayout.GridValueType;
import java.util.List;
@Environment(EnvType.CLIENT)
class GridMessageCell extends GridCell {
public class GridMessageCell extends GridCell {
private final Font font;
private final Component text;
private MultiLineLabel label;
private Component text;
private MultiLineLabel lastLabel;
private GridTransform lastTransform;
GridMessageCell(double width, GridValueType widthType, Alignment contentAlignment, Font font, Component text) {
this(width, widthType, contentAlignment, font, text, GridLayout.COLOR_WHITE);
}
GridMessageCell(double width, GridValueType widthType, Alignment contentAlignment, Font font, Component text, int color) {
super(width, -1, widthType, null, (poseStack, transform, context) -> {
MultiLineLabel label = (MultiLineLabel) context;
if (contentAlignment == Alignment.CENTER) {
label.renderCentered(poseStack, transform.width / 2 + transform.left, transform.top, font.lineHeight, color);
}
else if (contentAlignment == Alignment.LEFT) {
label.renderLeftAligned(poseStack, transform.left, transform.top, font.lineHeight, color);
}
});
super(width, -1, widthType, null, null);
this.font = font;
this.text = text;
customRender = (poseStack, transform, context) -> {
//MultiLineLabel label = (MultiLineLabel) context;
if (contentAlignment == Alignment.CENTER) {
lastLabel.renderCentered(poseStack, transform.width / 2 + transform.left, transform.top, font.lineHeight, color);
}
else if (contentAlignment == Alignment.LEFT) {
lastLabel.renderLeftAligned(poseStack, transform.left, transform.top, font.lineHeight, color);
}
};
}
public void setText(Component text){
this.text = text;
if (lastTransform!=null) {
create(lastTransform);
}
}
private MultiLineLabel getLabel(GridTransform transform) {
return label;
return lastLabel;
}
protected void create(GridTransform transform) {
this.label = MultiLineLabel.create(font, text, transform.width);
this.lastTransform = transform;
this.lastLabel = MultiLineLabel.create(font, text, transform.width);
}
@Override
protected GridElement buildElementAt(int left, int top, int width, List<GridElement> collector) {
create(new GridTransform(left, top, width, 0));
int promptLines = this.label.getLineCount() + 1;
int promptLines = this.lastLabel.getLineCount() + 1;
int height = promptLines * 9;
return new GridElement(left, top, width, height, this::getLabel, customRender);

View file

@ -122,6 +122,10 @@ public class GridRow extends GridContainer {
return cell;
}
public GridCustomRenderCell addCustomRender(GridCustomRenderCell cell) {
this.cells.add(cell);
return cell;
}
public GridCell addImage(ResourceLocation location, int width, int height) {
return addImage(location, 1.0f, width, height);
@ -170,48 +174,48 @@ public class GridRow extends GridContainer {
}
public GridCell addMessage(Component text, Font font, Alignment contentAlignment) {
public GridMessageCell addMessage(Component text, Font font, Alignment contentAlignment) {
return addMessage(text, font, GridLayout.COLOR_WHITE, contentAlignment);
}
public GridCell addMessage(Component text, Font font, int color, Alignment contentAlignment) {
public GridMessageCell addMessage(Component text, Font font, int color, Alignment contentAlignment) {
return addMessage(text, 1.0, GridLayout.GridValueType.PERCENTAGE, font, color, contentAlignment);
}
public GridCell addMessage(Component text, double width, GridValueType widthType, Font font, Alignment contentAlignment) {
public GridMessageCell addMessage(Component text, double width, GridValueType widthType, Font font, Alignment contentAlignment) {
return addMessage(text, width, widthType, font, GridLayout.COLOR_WHITE, contentAlignment);
}
public GridCell addMessage(Component text, double width, GridValueType widthType, Font font, int color, Alignment contentAlignment) {
GridCell cell = new GridMessageCell(width, widthType, Alignment.LEFT, font, text, color);
public GridMessageCell addMessage(Component text, double width, GridValueType widthType, Font font, int color, Alignment contentAlignment) {
GridMessageCell cell = new GridMessageCell(width, widthType, contentAlignment, font, text, color);
this.cells.add(cell);
return cell;
}
public GridCell addString(Component text, GridScreen parent) {
public GridStringCell addString(Component text, GridScreen parent) {
return this.addString(text, GridLayout.COLOR_WHITE, parent);
}
public GridCell addString(Component text, int color, GridScreen parent) {
final int width = parent.getFont()
.width(text.getVisualOrderText());
public GridStringCell addString(Component text, int color, GridScreen parent) {
final int width = parent.getWidth(text);
return this.addString(text, width, GridValueType.CONSTANT, GridLayout.COLOR_WHITE, Alignment.CENTER, parent);
}
public GridCell addString(Component text, Alignment contentAlignment, GridScreen parent) {
public GridStringCell addString(Component text, Alignment contentAlignment, GridScreen parent) {
return this.addString(text, GridLayout.COLOR_WHITE, contentAlignment, parent);
}
public GridCell addString(Component text, int color, Alignment contentAlignment, GridScreen parent) {
public GridStringCell addString(Component text, int color, Alignment contentAlignment, GridScreen parent) {
return this.addString(text, 1.0, GridLayout.GridValueType.PERCENTAGE, color, contentAlignment, parent);
}
public GridCell addString(Component text, double width, GridValueType widthType, Alignment contentAlignment, GridScreen parent) {
public GridStringCell addString(Component text, double width, GridValueType widthType, Alignment contentAlignment, GridScreen parent) {
return addString(text, width, widthType, GridLayout.COLOR_WHITE, contentAlignment, parent);
}
public GridCell addString(Component text, double width, GridValueType widthType, int color, Alignment contentAlignment, GridScreen parent) {
GridCell cell = new GridStringCell(width, widthType, parent.getFont().lineHeight, contentAlignment, parent, text, color);
public GridStringCell addString(Component text, double width, GridValueType widthType, int color, Alignment contentAlignment, GridScreen parent) {
GridStringCell cell = new GridStringCell(width, widthType, parent.getFont().lineHeight, contentAlignment, parent, text, color);
this.cells.add(cell);
return cell;
}

View file

@ -96,4 +96,12 @@ public abstract class GridScreen extends Screen {
super.render(poseStack, i, j, f);
}
public static int getWidth(Component text, Font font) {
return font.width(text.getVisualOrderText());
}
public int getWidth(Component text) {
return getWidth(text, getFont());
}
}

View file

@ -7,19 +7,26 @@ import ru.bclib.gui.gridlayout.GridLayout.Alignment;
import ru.bclib.gui.gridlayout.GridLayout.GridValueType;
@Environment(EnvType.CLIENT)
class GridStringCell extends GridCell {
public class GridStringCell extends GridCell {
private Component text;
GridStringCell(double width, GridValueType widthType, int height, Alignment contentAlignment, GridScreen parent, Component text) {
this(width, widthType, height, contentAlignment, parent, text, GridLayout.COLOR_WHITE);
}
GridStringCell(double width, GridValueType widthType, int height, Alignment contentAlignment, GridScreen parent, Component text, int color) {
super(width, height, widthType, null, (poseStack, transform, context) -> {
super(width, height, widthType, null, null);
this.text = text;
this.customRender = (poseStack, transform, context) -> {
if (contentAlignment == Alignment.CENTER) {
parent.drawCenteredString(poseStack, parent.getFont(), text, transform.width / 2 + transform.left, transform.top, color);
parent.drawCenteredString(poseStack, parent.getFont(), this.text, transform.width / 2 + transform.left, transform.top, color);
}
else if (contentAlignment == Alignment.LEFT) {
parent.drawString(poseStack, parent.getFont(), text, transform.left, transform.top, color);
parent.drawString(poseStack, parent.getFont(), this.text, transform.left, transform.top, color);
}
});
};
}
public void setText(Component newText){
this.text = newText;
}
}

View file

@ -0,0 +1,186 @@
package ru.bclib.gui.screens;
import com.mojang.blaze3d.platform.GlStateManager;
import com.mojang.blaze3d.systems.RenderSystem;
import com.mojang.blaze3d.vertex.PoseStack;
import net.minecraft.client.gui.GuiComponent;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.client.renderer.GameRenderer;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.TextComponent;
import net.minecraft.network.chat.TranslatableComponent;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.ProgressListener;
import org.jetbrains.annotations.Nullable;
import ru.bclib.BCLib;
import ru.bclib.gui.gridlayout.GridColumn;
import ru.bclib.gui.gridlayout.GridCustomRenderCell;
import ru.bclib.gui.gridlayout.GridLayout;
import ru.bclib.gui.gridlayout.GridLayout.Alignment;
import ru.bclib.gui.gridlayout.GridLayout.GridValueType;
import ru.bclib.gui.gridlayout.GridLayout.VerticalAlignment;
import ru.bclib.gui.gridlayout.GridMessageCell;
import ru.bclib.gui.gridlayout.GridRow;
import ru.bclib.gui.gridlayout.GridScreen;
import ru.bclib.gui.gridlayout.GridStringCell;
import ru.bclib.gui.gridlayout.GridTransform;
class ProgressLogoRender extends GridCustomRenderCell {
public static final int SIZE = 64;
public static final int LOGO_SIZE = 512;
public static final int PIXELATED_SIZE = 512;
float percentage = 0;
double time = 0;
protected ProgressLogoRender() {
super(SIZE, GridValueType.CONSTANT, SIZE);
}
@Override
public void onRender(PoseStack poseStack, GridTransform transform, Object context) {
time += 0.03;
RenderSystem.setShader(GameRenderer::getPositionTexShader);
RenderSystem.enableBlend();
RenderSystem.blendFunc(GlStateManager.SourceFactor.SRC_ALPHA, GlStateManager.DestFactor.ONE_MINUS_SRC_ALPHA);
RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0f);
final float fScale = (float)(0.3*((Math.sin(time)+1.0)*0.5) + 0.7);
int height = (int)(transform.height*fScale);
int width = (int)(transform.width*fScale);
width -= ((transform.width-width)%2);
height -= ((transform.height-height)%2);
final int yOffset = (transform.height-height)/2;
final int xOffset = (transform.width-width)/2;
final int yBarLocal = (int)(transform.height*percentage);
final int yBar = transform.top + yBarLocal;
final float relativeY = ((float)(yBarLocal - yOffset)/height);
final int uvTopLogo = (int)(relativeY * LOGO_SIZE);
final int uvTopPixelated = (int)(relativeY * PIXELATED_SIZE);
if (uvTopLogo>0) {
RenderSystem.setShaderTexture(0, BCLibScreen.BCLIB_LOGO_LOCATION);
GuiComponent.blit(poseStack,
xOffset + transform.left,
yOffset + transform.top,
width,
yBarLocal - yOffset,
0, 0, LOGO_SIZE, uvTopLogo,
LOGO_SIZE, LOGO_SIZE
);
}
if (uvTopPixelated<PIXELATED_SIZE) {
RenderSystem.setShaderTexture(0, ProgressScreen.BCLIB_LOGO_PIXELATED_LOCATION);
GuiComponent.blit(poseStack,
xOffset + transform.left,
yBar,
width,
height-(yBarLocal - yOffset),
0, uvTopPixelated, PIXELATED_SIZE, PIXELATED_SIZE-uvTopPixelated,
PIXELATED_SIZE, PIXELATED_SIZE
);
}
if (percentage>0 && percentage<1.0){
GuiComponent.fill(poseStack,
transform.left,
yBar,
transform.left+transform.width,
yBar+1,
0x3FFFFFFF
);
}
}
}
public class ProgressScreen extends GridScreen implements ProgressListener {
static final ResourceLocation BCLIB_LOGO_PIXELATED_LOCATION = new ResourceLocation(BCLib.MOD_ID, "iconpixelated.png");
public ProgressScreen(@Nullable Screen parent, Component title, Component description) {
super(parent, title, 20, false);
this.description = description;
}
Component description;
private Component stageComponent;
private GridMessageCell stage;
private GridStringCell progress;
private ProgressLogoRender progressImage;
private int currentProgress = 0;
public boolean shouldCloseOnEsc() {
return false;
}
public Component getProgressComponent(){
return getProgressComponent(currentProgress);
}
private Component getProgressComponent(int pg){
return new TranslatableComponent("title.bclib.progress").append(": " + pg + "%");
}
@Override
protected void initLayout() {
grid.addSpacerRow();
GridRow row = grid.addRow(VerticalAlignment.CENTER);
row.addFiller();
progressImage = new ProgressLogoRender();
progressImage.percentage = currentProgress / 100.0f;
row.addCustomRender(progressImage);
row.addSpacer();
int textWidth = Math.max(getWidth(description), getWidth(getProgressComponent(100)));
GridColumn textCol = row.addColumn(textWidth, GridValueType.CONSTANT);
textCol.addRow().addString(description, this);
textCol.addSpacerRow();
progress = textCol.addRow().addString(getProgressComponent(), GridLayout.COLOR_GRAY, Alignment.LEFT, this);
row.addFiller();
grid.addSpacerRow(20);
row = grid.addRow();
stage = row.addMessage(stageComponent!=null?stageComponent:new TextComponent(""), font, Alignment.CENTER);
}
@Override
public void progressStartNoAbort(Component component) {
this.progressStage(component);
}
@Override
public void progressStart(Component component) {
this.progressStage(component);
this.progressStagePercentage(30);
}
@Override
public void progressStage(Component component) {
stageComponent = component;
if (stage!=null) stage.setText(component);
}
@Override
public void progressStagePercentage(int i) {
if (i!=currentProgress) {
currentProgress = i;
if (progressImage!=null) progressImage.percentage = currentProgress / 100.0f;
BCLib.LOGGER.info(" -> progress: " + i + "%");
if (progress!=null) progress.setText(getProgressComponent());
}
}
@Override
public void stop() {
}
double time = 0;
@Override
public void render(PoseStack poseStack, int i, int j, float f) {
//time += 0.05;
//progressStagePercentage(((int)time)%100);
//progressStagePercentage(1);
super.render(poseStack, i, j, f);
}
}

View file

@ -56,16 +56,10 @@ public abstract class MinecraftMixin {
@Final
private LevelStorageSource levelSource;
@Shadow
public abstract void loadLevel(String string);
private final String BCLIB_RECURSION = "$@BCLIB:";
@Inject(method = "loadLevel", cancellable = true, at = @At("HEAD"))
private void bclib_callFixerOnLoad(String levelID, CallbackInfo ci) {
DataExchangeAPI.prepareServerside();
if (DataFixerAPI.fixData(this.levelSource, levelID, true, (appliedFixes) -> {
this.doLoadLevel(levelID, RegistryAccess.builtin(), Minecraft::loadDataPacks, Minecraft::loadWorldData, false, Minecraft.ExperimentalDialogType.BACKUP);
})) {

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

View file

@ -17,6 +17,10 @@
"message.bclib.confirmrestart": "The requested content was synchronized. You need to restart Minecraft now.",
"title.link.bclib.discord": "Discord",
"title.bclib.modmenu.main": "BCLib Settings",
"title.bclib.progress": "Progress",
"title.bclib.filesync.progress": "File Transfer",
"message.bclib.filesync.progress": "Syncing File-Content with Server",
"message.bclib.filesync.progress.stage.empty": "",
"title.config.bclib.client.auto_sync.enabled": "Enable Auto-Sync",
"title.config.bclib.client.auto_sync.acceptConfigs": "Accept incoming Confog Files",