Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve console implementation #728

Merged
merged 1 commit into from
Jun 11, 2017
Merged

Improve console implementation #728

merged 1 commit into from
Jun 11, 2017

Conversation

stephan-gh
Copy link
Contributor

@stephan-gh stephan-gh commented Jun 9, 2017

This is a complete rewrite of CraftBukkit's implementation of various console improvements (console colors, tab completion, persistent input line, ...) using JLine 3.x and TerminalConsoleAppender.

Summary

New features:

  • Support console colors for Vanilla commands
  • Add console colors for warnings and errors
  • Server can now be turned off safely using CTRL + C. JLine catches the signal and the implementation shuts down the server cleanly.
  • Support console colors and persistent input line when running in IntelliJ IDEA

Other changes:

  • Update JLine to 3.3.1 (from 2.12.1)
  • Server starts 1-2 seconds faster thanks to optimizations in Log4j configuration

New features

Console colors for Vanilla commands

CraftBukkit supports colors in the console for Bukkit commands, but not for the commands included with the Vanilla server. The new implementation handles both, adding colors in various situations:

Click me for screenshots

screenshot_20170609_193303

screenshot_20170609_192958

Add console colors for warnings and errors

I guess this could be something subjective, but in my opinion it makes it much easier to spot warnings and errors in the log because they are now marked in yellow and red. It's entirely optional so I can easily disable it if you don't want it.

Click me for screenshots

screenshot_20170609_194411

screenshot_20170609_194438

Server can now be turned off safely using CTRL + C

Previously this relied entirely on shutdown hooks that are often not given enough time to be able to shut down the server cleanly and save all the changes. JLine will now catch the signal which allows us to shut down the server cleanly.

Console colors and persistent input line when running in IntelliJ IDEA

It seems like this was entirely missing in the CraftBukkit implementation. I don't know how many developers actually run servers directly in their IDE but IntelliJ IDEA has wonderful support for console colors and the persistent input line now:

Click me for screenshots

screenshot_20170609_195700

screenshot_20170609_195235

Implementation details

In my opinion, the old implementation never really survived the switch to Log4J. It was implemented with a Log4J appender included in Mojang's authlib which pushes all messages into a queue that was polled in another thread and then written to the output stream using JLine's API.

TerminalConsoleAppender is a much more simple Log4J appender, which handles the initialization of the JLine terminal and directly writes the messages to the JLine terminal. It checks the environment to detect which features can be supported and enables them if possible. I think the documentation and Javadocs of it are quite well so I'm not going to explain it further.

I've originally written it a few years ago for SpongeVanilla and improved it over the time. An older version of it is also in use by Forge since Minecraft 1.8.8. For Minecraft 1.12 I've decided to refactor it into a separate project which allows other projects to use it easily. I thought I'd try how well it would work for Paper and it seems to work great.

Server startup optimization

TL;DR Paper starts 1 or 2 seconds faster now. This is only a very small part of the PR and yet it took the most space to document (I tried to keep it short, I guess I failed). It's not really relevant for anything above so feel free to skip if you're not interested.

I've mentioned above that the server starts about 1-2 seconds faster with these changes. The reason for this is the way Log4J loads its plugins. Log4J is more or less modular, the core consists mostly out of independent plugins.

There are two ways it can find them at runtime:

  • By scanning the classpath. This is not really optimized so it just loads all classes in a certain package and checks if they are annotated with the plugin annotation.
  • For performance they have an annotation processor that writes the available plugins into a Log4J2Plugins.dat file.

In the old Log4J version before Minecraft 1.12 the annotation processor was not yet included, so only the log4j-core actually used the Log4J2Plugins.dat file. Everything was working nicely, though there was a slight performance impact because the packages specified in the log4j configuration had to be scanned for plugins. (For CraftBukkit this is only com.mojang.util)

In newer log4j versions the metadata file is also generated for custom plugins now, theoretically removing the need for any classpath scanning. However, Mojang run into an issue with the server JAR (see MC-115797) because their shaded JAR now included Log4J2Plugins.dat twice, once for log4j-core and once for authlib.

The solution for that is to merge them, and there are tools available to do this for Maven and Gradle. However, Mojang chose to exclude both, even the one from log4j-core! Log4J handles that gracefully, however the consequence is that is forced to classload literally every single of its classes to check if they are plugins, not only a few classes from custom plugins.

You can easily see that if you enable Log4J's debug logging. It even shows several exceptions because it loads classes with missing dependencies (which are never used in Minecraft or Paper): https://gist.github.com/Minecrell/e39deb871eb63457331a012e0a53b3d1#file-gistfile1-txt-L138-L3273

Except the delay to load and check all the classes included in Log4J it doesn't start causing issues until you add the first custom appender with a Log4J2Plugins.dat file, which is the case for TerminalConsoleAppender. As soon as one Log4J2Plugins.dat is on the classpath, Log4j will no longer scan its own classes.

The solution for this in Paper is extremely simply, all we need to do is to shade log4j-core again to get its plugin map file and merge it with the one from TerminalConsoleAppender. No classpath scanning is necessary at runtime, the delay after Loading libraries, please wait... is reduced about 1 or 2 seconds (sometimes more).

Profiler screenshots

Here is a screenshot from a profiler without Log4j2Plugins.dat. In this case it takes about 3 seconds to initialize:

screenshot_20170609_203259

Now let's add Log4JPlugins.dat and disable all classpath scanning. Now it takes about 800 ms to load instead of 3000ms!:

screenshot_20170609_203711

@DenWav
Copy link
Member

DenWav commented Jun 9, 2017

Without having time to comment on the actual implementation, I definitely support the changes presented in this PR. Thanks!

+ </TerminalConsole>
<RollingRandomAccessFile name="File" fileName="logs/latest.log" filePattern="logs/%d{yyyy-MM-dd}-%i.log.gz">
- <PatternLayout pattern="[%d{HH:mm:ss}] [%t/%level]: %msg%n" />
+ <PatternLayout pattern="[%d{HH:mm:ss}] [%t/%level]: %minecraftFormatting{%msg}{strip}%n" />
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two spaces?

@kashike kashike requested a review from zachbr June 9, 2017 19:27
@zachbr
Copy link
Contributor

zachbr commented Jun 9, 2017

I have not done a proper review, however from my initial glance through of the proposal I am very interested in seeing this added 👍

@aikar
Copy link
Member

aikar commented Jun 9, 2017

Is there any risks in breaking compat with plugins? Def on board with it though, and clear winner if no breakages are expected and it's one of those "Just makes things better"

Demon mentioned option to disable, but I would say let's not waste effort on that and only worry about that IF it becomes a problem.

And that would only be the case if the problem isn't fixable.

@stephan-gh
Copy link
Contributor Author

stephan-gh commented Jun 9, 2017

I could only imagine that some plugins may break if they rely on certain implementation details:

  • The old JLine and jansi are no longer included in the Paper JAR (They've been even relocated so I don't think that counts)
  • The implementation class for the default console sender changed entirely, if someone casts Bukkit.getConsoleSender() to CB's implementation class ColouredConsoleSender it won't work anymore because I've replaced it using a different class. It still extends CraftConsoleCommandSender, but ColouredConsoleSender doesn't exist anymore. I'm not sure why someone would do that so I don't think it should cause problems (and if it does they should fix their plugins)

-
-public class ColouredConsoleSender extends CraftConsoleCommandSender {
- private final Terminal terminal;
- private final Map<ChatColor, String> replacements = new EnumMap<ChatColor, String>(ChatColor.class);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Id say we should leave this in, so that if a plugin is dumb, it will fail more gracefully (so only the dumb part of the plugin dies)

Also, if upstream changes this file, it would cause conflicts for us.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, it doesn't compile anymore unless I keep the old dependencies (which are then completely unused). Considering the Paper JAR is already 40 MB I'm not sure if that is a good idea. I could keep in the entire file commented out but I'm not sure if that would help?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather see this (and most other deletions) removed entirely rather than commented out. Upstream doesn't change this code frequently and resolving a conflict on a removal is trivial at best.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright. Let me know if I should remove something else that is currently only commented out.


public class Main {
- public static boolean useJline = true;
+ //public static boolean useJline = true; // Paper
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In effort to reduce diff, let's avoid commenting out stuff that won't impact behavior

}

- public ConsoleReader getReader() {
+ // Paper start - JLine update
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In effort to reduce diff, let's avoid commenting out stuff that won't impact behavior

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As below, doesn't compile anymore because the dependency was removed


entityplayer.spawnIn(world);
entityplayer.setPosition(loc.getX(), loc.getY(), loc.getZ());
- entityplayer.setYawPitch(loc.getYaw(), loc.getPitch());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

avoid diff here please


for (JsonListEntry entry : playerList.getProfileBans().getValues()) {
result.add(getOfflinePlayer((GameProfile) entry.getKey()));
- }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

avoid diff here please

diff --git a/src/main/java/org/bukkit/craftbukkit/util/TerminalConsoleWriterThread.java b/src/main/java/org/bukkit/craftbukkit/util/TerminalConsoleWriterThread.java
deleted file mode 100644
index b6409711..00000000
--- a/src/main/java/org/bukkit/craftbukkit/util/TerminalConsoleWriterThread.java
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here, let's avoid deleting the files to keep diff/patch smaller and reduce impact of silly plugins doing silly things.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above

@stephan-gh
Copy link
Contributor Author

I've removed the patch added by @electronicboy in ca62540 because it's no longer needed with the new JLine version (with my changes, JNA is used instead of jansi). I'm not entirely sure if something similar is needed for JNA, but I guess not?

@stephan-gh stephan-gh changed the title [WIP] Improve console implementation Improve console implementation Jun 10, 2017
@electronicboy
Copy link
Member

That patch was only added due to a bug with how jansi extracts it's native component, I have a windows VM (I think) still sitting around that replicates the bug that ca62540 was resolved with, so could consider firing it up to see if we hit some similar stupidity, but we shouldn't*

[*] Windows is stupid, especially when it comes to stuff like this, don't blame me if it somehow breaks on somebodies computer.

Copy link
Contributor

@zachbr zachbr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Broadly speaking, I'm quite happy with the implementation you've done here 👍

I hope to test this myself later across some testing servers I maintain locally.

As far as general patch handling goes, you've been extremely conservative with your removals. While I can certainly appreciate that, many of these are not useful and no harder to maintain than just removing the code outright. I commented most instances of this, I may have missed one or two.

These aren't really blockers, they're simple changes I could make on merge. But I'm going to mark as "Request Changes" anyway, just in case someone gets bored.

Good work :)
Thanks again


- new Thread(new org.bukkit.craftbukkit.util.TerminalConsoleWriterThread(System.out, this.reader)).start();
+ // Paper start - Handled by TerminalConsoleAppender
+ //new Thread(new org.bukkit.craftbukkit.util.TerminalConsoleWriterThread(System.out, this.reader)).start();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be removed entirely.

try {
- reader.getTerminal().restore();
+ // Paper - Use TerminalConsoleAppender
+ // reader.getTerminal().restore();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can be removed

public void sendMessage(IChatBaseComponent ichatbasecomponent) {
- MinecraftServer.LOGGER.info(ichatbasecomponent.toPlainText());
+ // Paper start - Log message with colors
+ //MinecraftServer.LOGGER.info(ichatbasecomponent.toPlainText());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can be removed


- public int complete(final String buffer, final int cursor, final List<CharSequence> candidates) {
+ // Paper start - Change method signature for JLine update
+ //public int complete(final String buffer, final int cursor, final List<CharSequence> candidates) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't need to keep the old signature unless you're interested in backwards compat and want to make that work, keeping it commented doesn't do much for us.

+ // Paper start - Change method signature for JLine update
+ //public int complete(final String buffer, final int cursor, final List<CharSequence> candidates) {
+ public void complete(LineReader reader, ParsedLine line, List<Candidate> candidates) {
+ final CraftServer server = this.server.server;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was this added just so you can stop using this everywhere?

public org.bukkit.command.ConsoleCommandSender console;
public org.bukkit.command.RemoteConsoleCommandSender remoteConsole;
- public ConsoleReader reader;
+ //public ConsoleReader reader; // Paper
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do appreciate you keeping entire fields though 👍

- minecraftserver.reader.addCompleter(new org.bukkit.craftbukkit.command.ConsoleCommandCompleter(minecraftserver.server));
+ // Paper start - Use TerminalConsoleAppender for colors, command completion is registered in TerminalHandler
+ minecraftserver.console = new com.destroystokyo.paper.console.TerminalConsoleCommandSender();
+ //minecraftserver.console = org.bukkit.craftbukkit.command.ColouredConsoleSender.getInstance();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All of this can be switched to removal

import net.minecraft.server.MinecraftServer;
-import org.fusesource.jansi.AnsiConsole;
+import net.minecrell.terminalconsole.TerminalConsoleAppender; // Paper
+//import org.fusesource.jansi.AnsiConsole; // Paper
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as before, no reason to keep imports


-import jline.console.completer.Completer;
+// Paper start - JLine update
+//import jline.console.completer.Completer;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Commented import!

try {
- server.reader.getTerminal().restore();
+ // Paper start - Use TerminalConsoleAppender
+ //server.reader.getTerminal().restore();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove

@stephan-gh
Copy link
Contributor Author

@Zbob750 Thanks for the review. I wasn't quite sure when to keep the old code and when to remove it. I think I have fixed all your comments.

- public int complete(final String buffer, final int cursor, final List<CharSequence> candidates) {
+ // Paper start - Change method signature for JLine update
+ public void complete(LineReader reader, ParsedLine line, List<Candidate> candidates) {
+ final CraftServer server = this.server.server;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Zbob750

Was this added just so you can stop using this everywhere?

No, the command completer is now initialized earlier, directly from the console thread in DedicatedServer. At this point, CraftServer is not available yet so I had to change the field in this class from CraftServer to DedicatedServer. The purpose of this variable is to keep the existing code as original as possible because it references the CraftServer field. It works for some usages (e.g. the ones in the Waitable) but not for the usages prefixed with this..

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the explanation 👍

Rewrite console improvements (console colors, tab completion,
persistent input line, ...) using JLine 3.x and TerminalConsoleAppender.

New features:
  - Support console colors for Vanilla commands
  - Add console colors for warnings and errors
  - Server can now be turned off safely using CTRL + C. JLine catches
    the signal and the implementation shuts down the server cleanly.
  - Support console colors and persistent input line when running in
    IntelliJ IDEA

Other changes:
  - Update JLine to 3.3.1 (from 2.12.1)
  - Server starts 1-2 seconds faster thanks to optimizations in Log4j
    configuration
@zachbr
Copy link
Contributor

zachbr commented Jun 11, 2017

Testing:

  • Windows
  • Linux Bash
  • Various Panels
    • Puffer Panel
    • Pterodactyl Panel
    • McMyAdmin

@stephan-gh
Copy link
Contributor Author

I've tested on Linux (Arch Linux). Also tested AMP (so pretty much McMyAdmin) recently on SpongeVanilla which uses the same system. The other panels should work too.

@zachbr zachbr merged commit 4eff799 into PaperMC:master Jun 11, 2017
@zachbr
Copy link
Contributor

zachbr commented Jun 11, 2017

Thanks again 👍

@stephan-gh stephan-gh deleted the terminalconsoleappender branch June 12, 2017 06:06
- jline.console.ConsoleReader bufferedreader = reader;
+ // Paper start - Use TerminalConsoleAppender implementation
+ if (com.destroystokyo.paper.console.TerminalHandler.handleCommands(DedicatedServer.this)) return;
+ BufferedReader bufferedreader = new BufferedReader(new InputStreamReader(System.in, StandardCharsets.UTF_8));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How to change StandardCharsets.UTF_8? In Windows, any input with cyrillic always appears as ╨┐╨░╨▓╨┐╨▓╨...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Um, this is what Vanilla Minecraft is using.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this case, I don't understand. Server is started in cmd.exe, codepage isn't changed. But instead of "тест" I'm getting "╤В╨╡╤Б╤В"..

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you get a > on the input line before the commands? Did this work on previous Paper versions?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest that you open a new issue about this here: https://github.com/PaperMC/Paper/issues

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants