Skip to content
This repository has been archived by the owner on Sep 2, 2024. It is now read-only.

Commit

Permalink
fix /importfile (finally)
Browse files Browse the repository at this point in the history
  • Loading branch information
homchom committed Jun 17, 2024
1 parent 3c9f9d5 commit 48fd237
Show file tree
Hide file tree
Showing 4 changed files with 201 additions and 70 deletions.
20 changes: 20 additions & 0 deletions src/main/java/io/github/homchom/recode/game/GameTime.kt
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
@file:JvmName("GameTime")

package io.github.homchom.recode.game

import io.github.homchom.recode.Power
import io.github.homchom.recode.RecodeDispatcher
import io.github.homchom.recode.event.listen
import io.github.homchom.recode.event.listenEach
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.take
import kotlinx.coroutines.flow.takeWhile
import kotlinx.coroutines.future.asCompletableFuture
import kotlinx.coroutines.launch
import java.util.concurrent.CompletionStage

/**
* The current client tick.
Expand All @@ -21,6 +29,18 @@ val currentTick get() = TickRecorder.currentTick
*/
suspend fun waitTicks(ticks: Int) = AfterClientTickEvent.notifications.take(ticks).collect()

/**
* @return A [CompletionStage] of a future that completes after the given number of [ticks].
*
* @see waitTicks
*/
@Deprecated("Only for use in Java code",
ReplaceWith("launch { waitTicks(ticks) }", "kotlinx.coroutines.launch")
)
@DelicateCoroutinesApi
fun waitTicksAsync(ticks: Int): CompletionStage<Unit> =
GlobalScope.launch(RecodeDispatcher) { waitTicks(ticks) }.asCompletableFuture()

private object TickRecorder {
var currentTick = 0L

Expand Down
72 changes: 72 additions & 0 deletions src/main/java/io/github/homchom/recode/io/NativeIO.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
@file:JvmName("NativeIO")

package io.github.homchom.recode.io

import io.github.homchom.recode.mc
import org.lwjgl.PointerBuffer
import org.lwjgl.system.MemoryStack
import org.lwjgl.util.tinyfd.TinyFileDialogs
import java.io.IOException
import java.nio.file.Path
import kotlin.io.path.Path
import kotlin.io.path.pathString

/**
* Opens a native file picker, blocks the current thread until the dialog closes, and returns the [Path] to
* the picked file if one was picked.
*
* @param defaultPath The path to start at. If `null`, the operating system will choose.
* @param filter The [FileFilter] to apply. If `null`, all files will be permitted.
*
* @throws IOException If the native file dialog errors for any reason.
*/
@JvmOverloads
fun pickFile(title: String? = null, defaultPath: Path? = null, filter: FileFilter? = null) =
openFileDialog(filter?.patterns) { filterBuffer ->
TinyFileDialogs.tinyfd_openFileDialog(
title,
defaultPath?.pathString,
filterBuffer,
filter?.description,
false
)?.let(::Path)
}

/**
* Opens a native file picker, blocks the current thread until the dialog closes, and returns the [Path]s to
* the picked files if any were picked. The opened file picker can pick multiple files.
*
* @see pickFile
*/
@JvmOverloads
fun pickMultipleFiles(title: String? = null, defaultPath: Path? = null, filter: FileFilter? = null) =
openFileDialog(filter?.patterns) { filterBuffer ->
val joinedPaths = TinyFileDialogs.tinyfd_openFileDialog(
title,
defaultPath?.pathString,
filterBuffer,
filter?.description,
true
)
joinedPaths?.split('|')?.map(::Path)
}

/**
* A file filter, to be passed to [pickFile] or [pickMultipleFiles].
*/
data class FileFilter(val patterns: List<String>, val description: String? = null)

private inline fun <R : Any> openFileDialog(
filterPatterns: List<String>?,
nativeDialogFunction: (PointerBuffer?) -> R?
): R? {
MemoryStack.stackPush().use { stack ->
val filterPatternBuffer = filterPatterns?.let { patterns ->
val buffer = stack.mallocPointer(patterns.size)
for (pattern in patterns) buffer.put(stack.UTF8(pattern))
buffer.flip()
}
if (mc.isRunning) mc.mouseHandler.releaseMouse()
return nativeDialogFunction(filterPatternBuffer)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,33 @@
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.mojang.brigadier.CommandDispatcher;
import io.github.homchom.recode.LegacyRecode;
import io.github.homchom.recode.ModConstants;
import io.github.homchom.recode.game.GameTime;
import io.github.homchom.recode.io.NativeIO;
import io.github.homchom.recode.mod.commands.Command;
import io.github.homchom.recode.mod.commands.arguments.ArgBuilder;
import io.github.homchom.recode.sys.hypercube.templates.CompressionUtil;
import io.github.homchom.recode.sys.hypercube.templates.TemplateUtil;
import io.github.homchom.recode.sys.player.chat.ChatType;
import io.github.homchom.recode.sys.player.chat.ChatUtil;
import io.github.homchom.recode.sys.util.ItemUtil;
import io.github.homchom.recode.ui.screen.DummyScreen;
import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource;
import net.kyori.adventure.text.Component;
import net.minecraft.client.Minecraft;
import net.minecraft.commands.CommandBuildContext;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;

import java.awt.*;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

// TODO: clean up further
public class ImportFileCommand extends Command {
private final String TITLE = "Pick a file";

@Override
public void register(Minecraft mc, CommandDispatcher<FabricClientCommandSource> cd, CommandBuildContext context) {
Expand All @@ -35,78 +39,94 @@ public void register(Minecraft mc, CommandDispatcher<FabricClientCommandSource>
if (!isCreative(mc)) return -1;

ChatUtil.sendMessage("Opening File Picker", ChatType.INFO_BLUE);
LegacyRecode.executor.submit(() -> {
try {
FileDialog fd = new FileDialog((Dialog) null, "Choose a text file", FileDialog.LOAD);
fd.setMultipleMode(true);
fd.setVisible(true);
File[] files = fd.getFiles();
fd.dispose();
if (files == null || files.length == 0) {
ChatUtil.sendMessage("You didnt choose a file!", ChatType.FAIL);
return;
}

int valid = 0;
files:
for (File f : files) {
if (files.length != 1)
ChatUtil.sendMessage("Loading file: " + f.getName(), ChatType.INFO_BLUE);
Scanner sc = new Scanner(f, StandardCharsets.UTF_8);

List<String> lines = new ArrayList<>();

while (sc.hasNextLine()) {
String line = sc.nextLine();
if (line.length() > 2000) {
ChatUtil.sendMessage("Line " + (lines.size() + 1) + " is too long! (" + line.length() + " > 2000)", ChatType.FAIL);
continue files;
}
lines.add(line);
if (lines.size() > 10000) {
ChatUtil.sendMessage("File contains contains too many lines! (Max: 10000)", ChatType.FAIL);
continue files;
}
}

List<JsonObject> blocks = new ArrayList<>();
List<String> current = new ArrayList<>();

boolean first = true;
for (String line : lines) {
current.add(line);
if (current.size() >= 26) {
blocks.add(block(current, first));
first = false;
current = new ArrayList<>();
}
}
if (!current.isEmpty()) blocks.add(block(current, first));

String template = template(blocks);
if (template.getBytes().length > 65536) {//i have no idea what the actual limit is it just seems to be close to this
ChatUtil.sendMessage("Your file is too large!", ChatType.FAIL);
} else {
ItemStack item = new ItemStack(Items.ENDER_CHEST);
TemplateUtil.applyRawTemplateNBT(item, f.getName(), "CodeUtilities", template);
ItemUtil.giveCreativeItem(item, files.length == 1);
if (files.length != 1) Thread.sleep(500);
valid++;
}
}
if (files.length != 1 && valid > 0)
ChatUtil.sendMessage("Loaded " + valid + " files!", ChatType.SUCCESS);

} catch (Exception err) {
err.printStackTrace();
ChatUtil.sendMessage("Unexpected Error.", ChatType.FAIL);
}
});
Minecraft.getInstance().tell(this::openFilePicker);

return 1;
})
);
}

private void openFilePicker() {
var screen = new DummyScreen(Component.text(TITLE), true);
Minecraft.getInstance().setScreen(screen);

GameTime.waitTicksAsync(1).thenRun(() -> {
var paths = NativeIO.pickMultipleFiles(TITLE);
Minecraft.getInstance().setScreen(null);
if (paths == null || paths.isEmpty()) {
ChatUtil.sendMessage("You didnt choose a file!", ChatType.FAIL);
return;
}

int valid = 0;
files: for (var path : paths) {
if (paths.size() != 1) {
ChatUtil.sendMessage("Loading file: " + path.getFileName(), ChatType.INFO_BLUE);
}
Scanner sc;
try {
sc = new Scanner(path, StandardCharsets.UTF_8);
} catch (IOException e) {
ChatUtil.sendMessage("Failed to load file: " + path.getFileName(), ChatType.FAIL);
continue;
}

List<String> lines = new ArrayList<>();

while (sc.hasNextLine()) {
String line = sc.nextLine();
if (line.length() > 10000) {
ChatUtil.sendMessage("Line " + (lines.size() + 1) + " is too long! (" + line.length() + " > 10000)", ChatType.FAIL);
continue files;
}
lines.add(line);
if (lines.size() > 10000) {
ChatUtil.sendMessage("File contains contains too many lines! (Max: 10,000)", ChatType.FAIL);
continue files;
}
}

List<JsonObject> blocks = new ArrayList<>();
List<String> current = new ArrayList<>();

boolean first = true;
for (String line : lines) {
current.add(line);
if (current.size() >= 26) {
blocks.add(block(current, first));
first = false;
current = new ArrayList<>();
}
}
if (!current.isEmpty()) blocks.add(block(current, first));

String template;
try {
template = template(blocks);
} catch (IOException e) {
ChatUtil.sendMessage("Failed to generate template for file: " + path.getFileName(), ChatType.FAIL);
continue;
}
if (template.getBytes().length > 65536) { // i have no idea what the actual limit is it just seems to be close to this TODO: nice
ChatUtil.sendMessage("Your file is too large!", ChatType.FAIL);
} else {
ItemStack item = new ItemStack(Items.ENDER_CHEST);
TemplateUtil.applyRawTemplateNBT(item, path.getFileName().toString(), ModConstants.MOD_NAME, template);
ItemUtil.giveCreativeItem(item, paths.size() == 1);
if (paths.size() != 1) try {
Thread.sleep(500);
} catch (InterruptedException e) {
// temporary TODO: what
throw new RuntimeException(e);
}
valid++;
}
}
if (paths.size() != 1 && valid > 0)
ChatUtil.sendMessage("Loaded " + valid + " files!", ChatType.SUCCESS);
});
}

private String template(List<JsonObject> iblocks) throws IOException {
JsonArray blocks = new JsonArray();
blocks.add(JsonParser.parseString("{\"id\":\"block\",\"block\":\"func\",\"args\":{\"items\":[]},\"data\":\"file\"}"));
Expand Down
19 changes: 19 additions & 0 deletions src/main/java/io/github/homchom/recode/ui/screen/DummyScreen.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package io.github.homchom.recode.ui.screen

import io.github.homchom.recode.ui.text.toVanilla
import net.kyori.adventure.text.Component
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.screens.Screen

/**
* A dummy [Screen] whose sole purpose is to release the mouse. This is, for example, useful in conjunction
* with native IO functions like [io.github.homchom.recode.io.pickFile].
*/
class DummyScreen(
title: Component,
private val renderBackground: Boolean = true
) : Screen(title.toVanilla()) {
override fun render(guiGraphics: GuiGraphics, mouseX: Int, mouseY: Int, tickDelta: Float) {
if (renderBackground) renderBackground(guiGraphics, mouseX, mouseY, tickDelta)
}
}

0 comments on commit 48fd237

Please sign in to comment.