From fa546b3f264973dba0a3c4ec0f344fb5bf29ebd4 Mon Sep 17 00:00:00 2001 From: JFronny Date: Tue, 18 Jul 2023 23:25:17 +0200 Subject: [PATCH] feat: use async on client --- .../client/mixin/ChatScreenMixin.java | 5 +- .../client/mixin/ClientPlayerEntityMixin.java | 5 +- .../client/mixin/MessageHandlerMixin.java | 96 +++++++-- .../gitlab/jfronny/googlechat/GoogleChat.java | 191 ++++++++++-------- .../jfronny/googlechat/GoogleChatCache.java | 41 ---- .../jfronny/googlechat/GoogleChatConfig.java | 3 +- .../googlechat/TranslationDirection.java | 38 ++++ .../googlechat/server/GoogleChatServer.java | 14 +- .../assets/google-chat/lang/en_us.json | 4 +- 9 files changed, 237 insertions(+), 160 deletions(-) delete mode 100644 src/main/java/io/gitlab/jfronny/googlechat/GoogleChatCache.java create mode 100644 src/main/java/io/gitlab/jfronny/googlechat/TranslationDirection.java diff --git a/src/client/java/io/gitlab/jfronny/googlechat/client/mixin/ChatScreenMixin.java b/src/client/java/io/gitlab/jfronny/googlechat/client/mixin/ChatScreenMixin.java index 0543833..91c71ef 100644 --- a/src/client/java/io/gitlab/jfronny/googlechat/client/mixin/ChatScreenMixin.java +++ b/src/client/java/io/gitlab/jfronny/googlechat/client/mixin/ChatScreenMixin.java @@ -1,6 +1,7 @@ package io.gitlab.jfronny.googlechat.client.mixin; -import io.gitlab.jfronny.googlechat.GoogleChatCache; +import io.gitlab.jfronny.googlechat.GoogleChat; +import io.gitlab.jfronny.googlechat.TranslationDirection; import net.minecraft.client.gui.screen.ChatScreen; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; @@ -11,6 +12,6 @@ public class ChatScreenMixin { @ModifyVariable(method = "sendMessage(Ljava/lang/String;Z)Z", at = @At(value = "HEAD"), argsOnly = true, ordinal = 0) String googlechat$translateChatText(String chatText) { if (chatText.startsWith("/")) return chatText; // Bypass for client-side commands (Carpet, ...) - return GoogleChatCache.c2s(chatText); + return GoogleChat.translateIfNeeded(chatText, TranslationDirection.C2S, true); } } diff --git a/src/client/java/io/gitlab/jfronny/googlechat/client/mixin/ClientPlayerEntityMixin.java b/src/client/java/io/gitlab/jfronny/googlechat/client/mixin/ClientPlayerEntityMixin.java index 9486e1b..053c68f 100644 --- a/src/client/java/io/gitlab/jfronny/googlechat/client/mixin/ClientPlayerEntityMixin.java +++ b/src/client/java/io/gitlab/jfronny/googlechat/client/mixin/ClientPlayerEntityMixin.java @@ -1,6 +1,7 @@ package io.gitlab.jfronny.googlechat.client.mixin; -import io.gitlab.jfronny.googlechat.GoogleChatCache; +import io.gitlab.jfronny.googlechat.GoogleChat; +import io.gitlab.jfronny.googlechat.TranslationDirection; import net.minecraft.client.network.ClientPlayerEntity; import net.minecraft.text.Text; import org.spongepowered.asm.mixin.Mixin; @@ -11,6 +12,6 @@ import org.spongepowered.asm.mixin.injection.ModifyVariable; public class ClientPlayerEntityMixin { @ModifyVariable(method = "sendMessage(Lnet/minecraft/text/Text;)V", at = @At("HEAD"), argsOnly = true, ordinal = 0) Text googlechat$translateMessage(Text source) { - return GoogleChatCache.c2s(source); + return GoogleChat.translateIfNeeded(source, TranslationDirection.C2S, true); } } diff --git a/src/client/java/io/gitlab/jfronny/googlechat/client/mixin/MessageHandlerMixin.java b/src/client/java/io/gitlab/jfronny/googlechat/client/mixin/MessageHandlerMixin.java index 95523f5..8b3cacf 100644 --- a/src/client/java/io/gitlab/jfronny/googlechat/client/mixin/MessageHandlerMixin.java +++ b/src/client/java/io/gitlab/jfronny/googlechat/client/mixin/MessageHandlerMixin.java @@ -1,40 +1,100 @@ package io.gitlab.jfronny.googlechat.client.mixin; import com.mojang.authlib.GameProfile; -import io.gitlab.jfronny.googlechat.GoogleChatCache; +import io.gitlab.jfronny.googlechat.*; import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.hud.*; import net.minecraft.client.network.message.MessageHandler; -import net.minecraft.network.message.MessageType; -import net.minecraft.network.message.SignedMessage; +import net.minecraft.client.util.NarratorManager; +import net.minecraft.network.message.*; import net.minecraft.text.Text; import org.spongepowered.asm.mixin.*; import org.spongepowered.asm.mixin.injection.*; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.time.Instant; +import java.util.concurrent.CompletableFuture; @Mixin(MessageHandler.class) -public class MessageHandlerMixin { +public abstract class MessageHandlerMixin { @Shadow @Final private MinecraftClient client; + @Shadow protected abstract void narrate(MessageType.Parameters params, Text message); - @Redirect(method = "onChatMessage(Lnet/minecraft/network/message/SignedMessage;Lcom/mojang/authlib/GameProfile;Lnet/minecraft/network/message/MessageType$Parameters;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/network/message/MessageType$Parameters;applyChatDecoration(Lnet/minecraft/text/Text;)Lnet/minecraft/text/Text;")) - Text googlechat$applyDecoration(MessageType.Parameters instance, Text content, SignedMessage args$signed, GameProfile args$sender) { - return instance.applyChatDecoration(googlechat$shouldTranslate(args$sender) ? GoogleChatCache.s2c(content) : content); + @Unique CompletableFuture googlechat$currentFuture = CompletableFuture.completedFuture(null); + @Unique ThreadLocal sender = new ThreadLocal<>(); + + @Redirect(method = "onGameMessage(Lnet/minecraft/text/Text;Z)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/hud/InGameHud;setOverlayMessage(Lnet/minecraft/text/Text;Z)V")) + private void googlechat$onGameMessage$setOverlayMessage(InGameHud instance, Text message, boolean tinted) { + if (googlechat$shouldTranslate()) googlechat$schedule(() -> instance.setOverlayMessage(googlechat$s2c(message), tinted)); + else instance.setOverlayMessage(message, tinted); } - @Redirect(method = "processChatMessageInternal(Lnet/minecraft/network/message/MessageType$Parameters;Lnet/minecraft/network/message/SignedMessage;Lnet/minecraft/text/Text;Lcom/mojang/authlib/GameProfile;ZLjava/time/Instant;)Z", at = @At(value = "INVOKE", target = "Lnet/minecraft/network/message/MessageType$Parameters;applyChatDecoration(Lnet/minecraft/text/Text;)Lnet/minecraft/text/Text;")) - Text googlechat$applyDecoration2(MessageType.Parameters instance, Text content, MessageType.Parameters args$params, SignedMessage args$message, Text args$decorated, GameProfile args$sender) { - return instance.applyChatDecoration(googlechat$shouldTranslate(args$sender) ? GoogleChatCache.s2c(content) : content); + @Redirect(method = "onGameMessage(Lnet/minecraft/text/Text;Z)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/hud/ChatHud;addMessage(Lnet/minecraft/text/Text;)V")) + private void googlechat$onGameMessage$addMessage(ChatHud instance, Text text) { + if (googlechat$shouldTranslate()) googlechat$schedule(() -> instance.addMessage(googlechat$s2c(text))); + else instance.addMessage(text); } - @ModifyVariable(method = "onProfilelessMessage(Lnet/minecraft/text/Text;Lnet/minecraft/network/message/MessageType$Parameters;)V", at = @At(value = "HEAD"), argsOnly = true, ordinal = 0) - Text googlechat$applyTranslation(Text origin) { - return GoogleChatCache.s2c(origin); + @Redirect(method = "onGameMessage(Lnet/minecraft/text/Text;Z)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/util/NarratorManager;narrateSystemMessage(Lnet/minecraft/text/Text;)V")) + private void googlechat$onGameMessage$narrateSystemMessage(NarratorManager instance, Text text) { + if (googlechat$shouldTranslate()) googlechat$schedule(() -> instance.narrateSystemMessage(googlechat$s2c(text))); + else instance.narrateSystemMessage(text); } - @ModifyVariable(method = "onGameMessage(Lnet/minecraft/text/Text;Z)V", at = @At(value = "HEAD"), argsOnly = true, ordinal = 0) - Text googlechat$applyTranslation2(Text origin) { - return GoogleChatCache.s2c(origin); + @Redirect(method = "method_45745(Lnet/minecraft/network/message/MessageType$Parameters;Lnet/minecraft/text/Text;Ljava/time/Instant;)Z", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/hud/ChatHud;addMessage(Lnet/minecraft/text/Text;)V")) + private void googlechat$onProfilelessMessage$addMessage(ChatHud instance, Text text) { + if (googlechat$shouldTranslate()) googlechat$schedule(() -> instance.addMessage(googlechat$s2c(text))); + else instance.addMessage(text); } - private boolean googlechat$shouldTranslate(GameProfile sender) { - return client != null && client.player != null && sender != null && !sender.getId().equals(client.player.getUuid()); + @Redirect(method = "method_45745(Lnet/minecraft/network/message/MessageType$Parameters;Lnet/minecraft/text/Text;Ljava/time/Instant;)Z", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/network/message/MessageHandler;narrate(Lnet/minecraft/network/message/MessageType$Parameters;Lnet/minecraft/text/Text;)V")) + private void googlechat$onProfilelessMessage$narrate(MessageHandler instance, MessageType.Parameters params, Text message) { + if (instance != (Object) this) GoogleChat.LOGGER.warn("Mismatched message handler in onGameMessage"); + if (googlechat$shouldTranslate()) googlechat$schedule(() -> narrate(params, googlechat$s2c(message))); + else narrate(params, message); + } + + @Inject(method = "onChatMessage(Lnet/minecraft/network/message/SignedMessage;Lcom/mojang/authlib/GameProfile;Lnet/minecraft/network/message/MessageType$Parameters;)V", at = @At("HEAD")) + private void googlechat$onChatMessage$extractSender(SignedMessage message, GameProfile sender, MessageType.Parameters params, CallbackInfo ci) { + this.sender.set(sender); + } + + @Redirect(method = "processChatMessageInternal(Lnet/minecraft/network/message/MessageType$Parameters;Lnet/minecraft/network/message/SignedMessage;Lnet/minecraft/text/Text;Lcom/mojang/authlib/GameProfile;ZLjava/time/Instant;)Z", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/hud/ChatHud;addMessage(Lnet/minecraft/text/Text;Lnet/minecraft/network/message/MessageSignatureData;Lnet/minecraft/client/gui/hud/MessageIndicator;)V")) + private void googlechat$processChatMessageInternal$addMessage(ChatHud instance, Text message, MessageSignatureData signature, MessageIndicator indicator) { + if (googlechat$shouldTranslate()) googlechat$schedule(() -> instance.addMessage(googlechat$s2c(message), signature, indicator)); + else instance.addMessage(message, signature, indicator); + } + + @Redirect(method = "processChatMessageInternal(Lnet/minecraft/network/message/MessageType$Parameters;Lnet/minecraft/network/message/SignedMessage;Lnet/minecraft/text/Text;Lcom/mojang/authlib/GameProfile;ZLjava/time/Instant;)Z", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/network/message/MessageHandler;narrate(Lnet/minecraft/network/message/MessageType$Parameters;Lnet/minecraft/text/Text;)V")) + private void googlechat$processChatMessageInternal$narrate(MessageHandler instance, MessageType.Parameters params, Text message) { + if (instance != (Object) this) GoogleChat.LOGGER.warn("Mismatched message handler in onGameMessage"); + if (googlechat$shouldTranslate()) googlechat$schedule(() -> narrate(params, googlechat$s2c(message))); + else narrate(params, message); + } + + @Inject(method = "processChatMessageInternal(Lnet/minecraft/network/message/MessageType$Parameters;Lnet/minecraft/network/message/SignedMessage;Lnet/minecraft/text/Text;Lcom/mojang/authlib/GameProfile;ZLjava/time/Instant;)Z", at = @At("RETURN")) + private void googlechat$processChatMessageInternal$clearSender(MessageType.Parameters params, SignedMessage message, Text decorated, GameProfile sender, boolean onlyShowSecureChat, Instant receptionTimestamp, CallbackInfoReturnable cir) { + this.sender.remove(); + } + + private Text googlechat$s2c(Text message) { + return GoogleChat.translateIfNeeded(message, TranslationDirection.S2C, true); + } + + private void googlechat$schedule(Runnable runnable) { + if (!GoogleChatConfig.Advanced.async) runnable.run(); + else googlechat$currentFuture.whenCompleteAsync((_1, _2) -> runnable.run()).exceptionally(throwable -> { + GoogleChat.LOGGER.error("Something went wrong while processing a message", throwable); + return null; + }); + } + + private boolean googlechat$shouldTranslate() { + if (!GoogleChatConfig.General.enabled) return false; + if (client == null || client.player == null) return false; + var sender = this.sender.get(); + if (sender == null) return true; + return !sender.getId().equals(client.player.getUuid()); } } diff --git a/src/main/java/io/gitlab/jfronny/googlechat/GoogleChat.java b/src/main/java/io/gitlab/jfronny/googlechat/GoogleChat.java index 056da04..b8975a3 100644 --- a/src/main/java/io/gitlab/jfronny/googlechat/GoogleChat.java +++ b/src/main/java/io/gitlab/jfronny/googlechat/GoogleChat.java @@ -1,49 +1,79 @@ package io.gitlab.jfronny.googlechat; +import io.gitlab.jfronny.commons.cache.FixedSizeMap; import io.gitlab.jfronny.commons.log.Logger; import io.gitlab.jfronny.libjf.translate.api.Language; import io.gitlab.jfronny.libjf.translate.api.TranslateService; -import net.fabricmc.api.EnvType; import net.fabricmc.api.ModInitializer; -import net.fabricmc.loader.api.FabricLoader; import net.minecraft.text.*; -import java.util.Arrays; -import java.util.Optional; +import java.util.*; import java.util.concurrent.ForkJoinPool; +import java.util.function.Function; public class GoogleChat implements ModInitializer { public static final String MOD_ID = "google-chat"; public static final Logger LOGGER = Logger.forName(MOD_ID); public static TranslateService TRANSLATE_SERVICE; + private static final Map s2ct = new FixedSizeMap<>(GoogleChatConfig.Advanced.cacheSize); + private static final Map c2st = new FixedSizeMap<>(GoogleChatConfig.Advanced.cacheSize); + private static final Map s2cc = new FixedSizeMap<>(GoogleChatConfig.Advanced.cacheSize); + private static final Map c2sc = new FixedSizeMap<>(GoogleChatConfig.Advanced.cacheSize); + private static final Map s2cs = new FixedSizeMap<>(GoogleChatConfig.Advanced.cacheSize); + private static final Map c2ss = new FixedSizeMap<>(GoogleChatConfig.Advanced.cacheSize); + @Override public void onInitialize() { ForkJoinPool.commonPool().execute(() -> TRANSLATE_SERVICE = TranslateService.getConfigured()); } - public static Text translateIfNeeded(Text source, Direction direction, boolean respectRegex) { - if (shouldSkipOutright(direction)) return source; + public static void onConfigChange() { + synchronized (s2ct) { + s2ct.clear(); + } + synchronized (c2st) { + c2st.clear(); + } + synchronized (s2cc) { + s2cc.clear(); + } + synchronized (c2sc) { + c2sc.clear(); + } + synchronized (s2cs) { + s2cs.clear(); + } + synchronized (c2ss) { + c2ss.clear(); + } + } + + public static Text translateIfNeeded(Text source, TranslationDirection direction, boolean respectRegex) { + if (source == null) return null; + if (direction.shouldSkipOutright()) return source; String sourceString = toString(source); - if (respectRegex && failsRegex(sourceString, direction)) return source; - MutableText translated; - if (GoogleChatConfig.Processing.desugar) { - translated = Text.literal(translateIfNeeded(sourceString, direction, true)); - } else { - translated = MutableText.of(translateIfNeeded(source.getContent(), direction, false)) - .setStyle(source.getStyle()); - for (Text sibling : source.getSiblings()) { - translated.append(translateIfNeeded(sibling, direction, false)); + if (respectRegex && direction.failsRegex(sourceString)) return source; + return computeIfAbsent2(direction == TranslationDirection.C2S ? c2st : s2ct, source, t -> { + MutableText translated; + if (GoogleChatConfig.Processing.desugar) { + translated = Text.literal(translateIfNeeded(sourceString, direction, true)); + } else { + translated = MutableText.of(translateIfNeeded(t.getContent(), direction, false)) + .setStyle(t.getStyle()); + for (Text sibling : t.getSiblings()) { + translated.append(translateIfNeeded(sibling, direction, false)); + } } - } - if (GoogleChatConfig.Advanced.debugLogs) LOGGER.info("Translated " + sourceString + " to " + toString(translated)); - if (GoogleChatConfig.General.translationTooltip) { - return source.copy().styled(style -> addHover(style, Text.literal("Translated: ").append(translated))); - } else if (translated.getStyle().getHoverEvent() == null) { - return translated.styled(style -> addHover(style, Text.literal("Original: ").append(source))); - } else { - return translated; - } + if (GoogleChatConfig.Advanced.debugLogs) LOGGER.info("Translated " + sourceString + " to " + toString(translated)); + if (GoogleChatConfig.General.translationTooltip) { + return t.copy().styled(style -> addHover(style, Text.literal("Translated: ").append(translated))); + } else if (translated.getStyle().getHoverEvent() == null) { + return translated.styled(style -> addHover(style, Text.literal("Original: ").append(t))); + } else { + return translated; + } + }); } private static String toString(Text text) { @@ -55,28 +85,31 @@ public class GoogleChat implements ModInitializer { return sb.toString(); } - public static TextContent translateIfNeeded(TextContent source, Direction direction, boolean respectRegex) { - if (shouldSkipOutright(direction)) return source; + public static TextContent translateIfNeeded(TextContent source, TranslationDirection direction, boolean respectRegex) { + if (source == null) return null; + if (direction.shouldSkipOutright()) return source; String sourceString = toString(source); - if (respectRegex && failsRegex(sourceString, direction)) return source; - //TODO This method (and the check for translatable args) should be converted to a switch pattern when available - if (source instanceof TranslatableTextContent tx) { - Object[] args = tx.getArgs(); - args = Arrays.copyOf(args, args.length); - // We're not translating TranslatableText, but are translating arguments - for (int i = 0; i < args.length; i++) { - if (args[i] instanceof Text tx1) args[i] = translateIfNeeded(tx1, direction, false); - else if (args[i] instanceof TextContent tx1) args[i] = translateIfNeeded(tx1, direction, false); - else if (args[i] instanceof String tx1) args[i] = translateIfNeeded(tx1, direction, false); - else args[i] = args[i]; + if (respectRegex && direction.failsRegex(sourceString)) return source; + return computeIfAbsent2(direction == TranslationDirection.C2S ? c2sc : s2cc, source, t -> { + //TODO This method (and the check for translatable args) should be converted to a switch pattern when available + if (t instanceof TranslatableTextContent tx) { + Object[] args = tx.getArgs(); + args = Arrays.copyOf(args, args.length); + // We're not translating TranslatableText, but are translating arguments + for (int i = 0; i < args.length; i++) { + if (args[i] instanceof Text tx1) args[i] = translateIfNeeded(tx1, direction, false); + else if (args[i] instanceof TextContent tx1) args[i] = translateIfNeeded(tx1, direction, false); + else if (args[i] instanceof String tx1) args[i] = translateIfNeeded(tx1, direction, false); + else args[i] = args[i]; + } + return new TranslatableTextContent(tx.getKey(), translateIfNeeded(tx.getFallback(), direction, false), args); + } else if (t instanceof LiteralTextContent tx) { + return new LiteralTextContent(translateIfNeeded(tx.string(), direction, false)); + } else { +// LOGGER.info("Unhandled text type: " + source.getClass() + " (" + source + ")"); + return t; } - return new TranslatableTextContent(tx.getKey(), translateIfNeeded(tx.getFallback(), direction, false), args); - } else if (source instanceof LiteralTextContent tx) { - return new LiteralTextContent(translateIfNeeded(tx.string(), direction, false)); - } else { -// LOGGER.info("Unhandled text type: " + source.getClass() + " (" + source + ")"); - return source; - } + }); } private static String toString(TextContent text) { @@ -92,53 +125,33 @@ public class GoogleChat implements ModInitializer { return style.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, hoverText)); } - public static String translateIfNeeded(String source, Direction direction, boolean respectRegex) { - if (shouldSkipOutright(direction)) return source; - if (respectRegex && failsRegex(source, direction)) return source; - try { - // Ignore generics since this is apparently not something java supports - @SuppressWarnings("rawtypes") TranslateService svc = GoogleChat.TRANSLATE_SERVICE; - if (svc == null) throw new NullPointerException("Translate service uninitialized"); - Language sourceLang = svc.parseLang(direction.source()); - Language targetLang = svc.parseLang(direction.target()); - //noinspection unchecked - return svc.translate(source, sourceLang, targetLang); - } catch (Throwable e) { - LOGGER.error("Could not translate text: " + source, e); - return source; - } + public static String translateIfNeeded(String source, TranslationDirection direction, boolean respectRegex) { + if (source == null) return null; + if (direction.shouldSkipOutright()) return source; + if (respectRegex && direction.failsRegex(source)) return source; + return computeIfAbsent2(direction == TranslationDirection.C2S ? c2ss : s2cs, source, t -> { + try { + // Ignore generics since this is apparently not something java supports + @SuppressWarnings("rawtypes") TranslateService svc = GoogleChat.TRANSLATE_SERVICE; + if (svc == null) throw new NullPointerException("Translate service uninitialized"); + Language sourceLang = svc.parseLang(direction.source()); + Language targetLang = svc.parseLang(direction.target()); + //noinspection unchecked + return svc.translate(source, sourceLang, targetLang); + } catch (Throwable e) { + LOGGER.error("Could not translate text: " + source, e); + return source; + } + }); } - private static boolean failsRegex(String text, Direction direction) { - boolean isSender = (FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT) == (direction == Direction.C2S); - if (isSender) return text.matches(GoogleChatConfig.Processing.sendingRegex) == GoogleChatConfig.Processing.sendingRegexIsBlacklist; - else return text.matches(GoogleChatConfig.Processing.receivingRegex) == GoogleChatConfig.Processing.receivingRegexIsBlacklist; - } - - private static boolean shouldSkipOutright(Direction direction) { - return !GoogleChatConfig.General.enabled || !hasTarget(direction); - } - - public static boolean hasTarget(Direction direction) { - return !direction.target().equals("auto"); - } - - public enum Direction { - C2S, - S2C; - - public String source() { - return switch (this) { - case C2S -> GoogleChatConfig.General.clientLanguage; - case S2C -> GoogleChatConfig.General.serverLanguage; - }; - } - - public String target() { - return switch (this) { - case C2S -> GoogleChatConfig.General.serverLanguage; - case S2C -> GoogleChatConfig.General.clientLanguage; - }; + private static V computeIfAbsent2(Map map, K key, Function compute) { + if (!GoogleChatConfig.Advanced.async) return map.computeIfAbsent(key, compute); + synchronized (map) { + if (map.containsKey(key)) return map.get(key); + V value = compute.apply(key); + map.put(key, value); + return value; } } } diff --git a/src/main/java/io/gitlab/jfronny/googlechat/GoogleChatCache.java b/src/main/java/io/gitlab/jfronny/googlechat/GoogleChatCache.java deleted file mode 100644 index 14437b3..0000000 --- a/src/main/java/io/gitlab/jfronny/googlechat/GoogleChatCache.java +++ /dev/null @@ -1,41 +0,0 @@ -package io.gitlab.jfronny.googlechat; - -import io.gitlab.jfronny.commons.cache.FixedSizeMap; -import net.minecraft.text.Text; - -import java.util.Map; - -public class GoogleChatCache { - private static Map s2ct = new FixedSizeMap<>(GoogleChatConfig.Advanced.cacheSize); - private static Map c2st = new FixedSizeMap<>(GoogleChatConfig.Advanced.cacheSize); - private static Map s2cs = new FixedSizeMap<>(GoogleChatConfig.Advanced.cacheSize); - private static Map c2ss = new FixedSizeMap<>(GoogleChatConfig.Advanced.cacheSize); - - public static void clear() { - s2ct.clear(); - c2st.clear(); - s2cs.clear(); - c2ss.clear(); - } - - public static void onConfigChange() { - GoogleChat.LOGGER.info("Config Change"); - clear(); - } - - public static Text s2c(Text msg) { - return s2ct.computeIfAbsent(msg, m -> GoogleChat.translateIfNeeded(m, GoogleChat.Direction.S2C, true)); - } - - public static Text c2s(Text msg) { - return c2st.computeIfAbsent(msg, m -> GoogleChat.translateIfNeeded(m, GoogleChat.Direction.C2S, true)); - } - - public static String s2c(String msg) { - return s2cs.computeIfAbsent(msg, m -> GoogleChat.translateIfNeeded(m, GoogleChat.Direction.S2C, true)); - } - - public static String c2s(String msg) { - return c2ss.computeIfAbsent(msg, m -> GoogleChat.translateIfNeeded(m, GoogleChat.Direction.C2S, true)); - } -} diff --git a/src/main/java/io/gitlab/jfronny/googlechat/GoogleChatConfig.java b/src/main/java/io/gitlab/jfronny/googlechat/GoogleChatConfig.java index 396d3d3..c642dd5 100644 --- a/src/main/java/io/gitlab/jfronny/googlechat/GoogleChatConfig.java +++ b/src/main/java/io/gitlab/jfronny/googlechat/GoogleChatConfig.java @@ -46,6 +46,7 @@ public class GoogleChatConfig { @Category public static class Advanced { @Entry(min = 1, max = 1024) public static int cacheSize = 256; + @Entry public static boolean async = true; @Entry public static boolean debugLogs = FabricLoader.getInstance().isDevelopmentEnvironment(); } @@ -57,7 +58,7 @@ public class GoogleChatConfig { Your client language is not set to "auto" and you are using a server. This setup is not recommended! Please set up GoogleChat according to its documentation!"""); } - if (!initial) GoogleChatCache.onConfigChange(); + if (!initial) GoogleChat.onConfigChange(); initial = false; } diff --git a/src/main/java/io/gitlab/jfronny/googlechat/TranslationDirection.java b/src/main/java/io/gitlab/jfronny/googlechat/TranslationDirection.java new file mode 100644 index 0000000..769a5c8 --- /dev/null +++ b/src/main/java/io/gitlab/jfronny/googlechat/TranslationDirection.java @@ -0,0 +1,38 @@ +package io.gitlab.jfronny.googlechat; + +import net.fabricmc.api.EnvType; +import net.fabricmc.loader.api.FabricLoader; + +public enum TranslationDirection { + C2S, + S2C; + + public String source() { + return switch (this) { + case C2S -> GoogleChatConfig.General.clientLanguage; + case S2C -> GoogleChatConfig.General.serverLanguage; + }; + } + + public String target() { + return switch (this) { + case C2S -> GoogleChatConfig.General.serverLanguage; + case S2C -> GoogleChatConfig.General.clientLanguage; + }; + } + + public boolean hasTarget() { + return !target().equals("auto"); + } + + public boolean shouldSkipOutright() { + if (!GoogleChatConfig.General.enabled) return true; + return !hasTarget(); + } + + public boolean failsRegex(String text) { + boolean isSender = (FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT) == (this == TranslationDirection.C2S); + if (isSender) return text.matches(GoogleChatConfig.Processing.sendingRegex) == GoogleChatConfig.Processing.sendingRegexIsBlacklist; + else return text.matches(GoogleChatConfig.Processing.receivingRegex) == GoogleChatConfig.Processing.receivingRegexIsBlacklist; + } +} diff --git a/src/main/java/io/gitlab/jfronny/googlechat/server/GoogleChatServer.java b/src/main/java/io/gitlab/jfronny/googlechat/server/GoogleChatServer.java index 26a2cc8..78a46f8 100644 --- a/src/main/java/io/gitlab/jfronny/googlechat/server/GoogleChatServer.java +++ b/src/main/java/io/gitlab/jfronny/googlechat/server/GoogleChatServer.java @@ -8,7 +8,6 @@ import net.minecraft.text.Text; import java.util.concurrent.CompletableFuture; -import static io.gitlab.jfronny.googlechat.GoogleChat.hasTarget; import static io.gitlab.jfronny.libjf.LibJf.LOGGER; public class GoogleChatServer implements DedicatedServerModInitializer { @@ -19,20 +18,23 @@ public class GoogleChatServer implements DedicatedServerModInitializer { // If this causes an incompatibility, I'll add my own phase ServerMessageDecoratorEvent.EVENT.register(Event.DEFAULT_PHASE, (sender, originalMessage) -> { CompletableFuture futureMessage = CompletableFuture.completedFuture(originalMessage); + if (!GoogleChatConfig.General.enabled) return futureMessage; // fast fallthrough if (sender != null) { // Client messages should first be translated to the server language - if (hasTarget(GoogleChat.Direction.C2S) && hasTarget(GoogleChat.Direction.S2C)) { - // Do not translate back and forth - return futureMessage; + if (TranslationDirection.C2S.hasTarget()) { + if (TranslationDirection.S2C.hasTarget()) { + // Do not translate back and forth + return futureMessage; + } } futureMessage = futureMessage.thenApplyAsync(msg -> { - var translated = GoogleChatCache.c2s(msg); + var translated = GoogleChat.translateIfNeeded(msg, TranslationDirection.C2S, true); if (GoogleChatConfig.Advanced.debugLogs) LOGGER.info("Applied C2S translation from " + msg + " to " + translated); return translated; }); } // All messages should be translated to the client language before sending futureMessage = futureMessage.thenApplyAsync(msg -> { - var translated = GoogleChatCache.s2c(msg); + var translated = GoogleChat.translateIfNeeded(msg, TranslationDirection.S2C, true); if (GoogleChatConfig.Advanced.debugLogs) LOGGER.info("Applied S2C translation from " + msg + " to " + translated); return translated; }); diff --git a/src/main/resources/assets/google-chat/lang/en_us.json b/src/main/resources/assets/google-chat/lang/en_us.json index 043c557..0c88b47 100644 --- a/src/main/resources/assets/google-chat/lang/en_us.json +++ b/src/main/resources/assets/google-chat/lang/en_us.json @@ -22,7 +22,9 @@ "google-chat.jfconfig.processing.sendingRegexIsBlacklist.tooltip": "Whether the relevant regex should blacklist messages from translation instead of whitelisting", "google-chat.jfconfig.advanced.title": "Advanced", "google-chat.jfconfig.advanced.cacheSize": "Cache Size", - "google-chat.jfconfig.advanced.cacheSize.tooltips": "The size of each message cache. Since there are four caches, the actual size will be four times this.", + "google-chat.jfconfig.advanced.cacheSize.tooltips": "The size of each message cache. Since there are six caches, the actual size will be six times this.", + "google-chat.jfconfig.advanced.async": "Async", + "google-chat.jfconfig.advanced.async.tooltips": "Whether to asynchronously process messages. Should prevent some stutters, but might cause other issues. Disable if you no longer receive messages", "google-chat.jfconfig.advanced.debugLogs": "Debug Logs", "google-chat.jfconfig.advanced.debugLogs.tooltips": "Log additional information about message processing. Useful for debugging",