Skip to content
This repository was archived by the owner on May 12, 2024. It is now read-only.

Commit 9abdde4

Browse files
committed
Add a multithreaded-info Gradle-style progress bar, as well as a thread_count argument
1 parent b57fe0a commit 9abdde4

6 files changed

Lines changed: 326 additions & 12 deletions

File tree

standalone/build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ dependencies {
3030
compileOnly "org.jetbrains:annotations:24.0.1"
3131

3232
include "me.tongfei:progressbar:0.9.5"
33+
34+
include("org.jline:jline-terminal-jansi:3.21.0")
3335
}
3436

3537
application {

standalone/src/main/java/net/raphimc/javadowngrader/standalone/Main.java

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@
1818
package net.raphimc.javadowngrader.standalone;
1919

2020
import joptsimple.*;
21-
import me.tongfei.progressbar.ProgressBar;
2221
import me.tongfei.progressbar.ProgressBarBuilder;
2322
import me.tongfei.progressbar.ProgressBarStyle;
2423
import net.lenni0451.classtransform.TransformerManager;
2524
import net.lenni0451.classtransform.utils.tree.BasicClassProvider;
25+
import net.raphimc.javadowngrader.standalone.progress.MultiThreadedProgressBar;
2626
import net.raphimc.javadowngrader.standalone.transform.JavaDowngraderTransformer;
2727
import net.raphimc.javadowngrader.standalone.transform.LazyFileClassProvider;
2828
import net.raphimc.javadowngrader.standalone.transform.PathClassProvider;
@@ -75,6 +75,10 @@ public static void main(String[] args) throws Throwable {
7575
final OptionSpec<List<File>> libraryPath = parser.acceptsAll(asList("library_path", "library", "l"), "Additional libraries to add to the classpath (required for stack frames)")
7676
.withRequiredArg()
7777
.withValuesConvertedBy(new PathConverter());
78+
final OptionSpec<Integer> threadCount = parser.acceptsAll(asList("thread_count", "threads", "t"), "The number of threads to use for the downgrading")
79+
.withRequiredArg()
80+
.ofType(Integer.class)
81+
.defaultsTo(Math.min(Runtime.getRuntime().availableProcessors(), 255));
7882

7983
final OptionSet options;
8084
try {
@@ -112,7 +116,12 @@ public static void main(String[] args) throws Throwable {
112116

113117
try {
114118
final long start = System.nanoTime();
115-
doConversion(inputFile, outputFile, options.valueOf(version), GeneralUtil.flatten(options.valuesOf(libraryPath)));
119+
doConversion(
120+
inputFile, outputFile,
121+
options.valueOf(version),
122+
GeneralUtil.flatten(options.valuesOf(libraryPath)),
123+
Math.min(options.valueOf(threadCount), 255)
124+
);
116125
final long end = System.nanoTime();
117126
LOGGER.info(
118127
"Done in {}.",
@@ -132,7 +141,8 @@ private static void doConversion(
132141
final File inputFile,
133142
final File outputFile,
134143
final JavaVersion targetVersion,
135-
List<File> libraryPath
144+
List<File> libraryPath,
145+
int threadCount
136146
) throws Throwable {
137147
LOGGER.info("Downgrading {} to Java {}", inputFile, targetVersion.getName());
138148
if (outputFile.isFile() && !outputFile.canWrite()) {
@@ -196,14 +206,14 @@ private static void doConversion(
196206
});
197207
}
198208
}
199-
final int threadCount = Runtime.getRuntime().availableProcessors();
200-
LOGGER.info("Downgrading classes with {} threads", threadCount);
209+
LOGGER.info("Downgrading classes with {} thread(s)", threadCount);
201210
final ExecutorService threadPool = Executors.newFixedThreadPool(threadCount);
202211
final List<Callable<Void>> tasks;
203-
final ProgressBar[] pb = new ProgressBar[1];
212+
final MultiThreadedProgressBar[] pb = new MultiThreadedProgressBar[1];
204213
try (Stream<Path> stream = Files.walk(inRoot)) {
205214
tasks = stream.map(path -> (Callable<Void>) () -> {
206215
final String relative = GeneralUtil.slashName(inRoot.relativize(path));
216+
pb[0].setThreadTask(relative);
207217
final Path inOther = outRoot.resolve(relative);
208218
if (Files.isDirectory(path)) {
209219
Files.createDirectories(inOther);
@@ -234,12 +244,13 @@ private static void doConversion(
234244
}).collect(Collectors.toList());
235245
}
236246
try {
237-
pb[0] = new ProgressBarBuilder()
238-
.setTaskName("Downgrading")
239-
.setStyle(ProgressBarStyle.ASCII)
240-
.setInitialMax(tasks.size())
241-
.setUpdateIntervalMillis(100)
242-
.build();
247+
pb[0] = MultiThreadedProgressBar.create(
248+
new ProgressBarBuilder()
249+
.setTaskName("Downgrading")
250+
.setStyle(ProgressBarStyle.ASCII)
251+
.setInitialMax(tasks.size())
252+
.setUpdateIntervalMillis(100)
253+
);
243254
threadPool.invokeAll(tasks);
244255
} finally {
245256
if (pb[0] != null) {
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* This file is part of JavaDowngrader - https://github.com/RaphiMC/JavaDowngrader
3+
* Copyright (C) 2023 RK_01/RaphiMC and contributors
4+
*
5+
* This program is free software; you can redistribute it and/or
6+
* modify it under the terms of the GNU Lesser General Public
7+
* License as published by the Free Software Foundation; either
8+
* version 3 of the License, or (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
*/
18+
package net.raphimc.javadowngrader.standalone.progress;
19+
20+
import me.tongfei.progressbar.ProgressBarBuilder;
21+
22+
public interface MultiThreadedProgressBar extends AutoCloseable {
23+
void step();
24+
25+
void setThreadTask(String task);
26+
27+
@Override
28+
void close();
29+
30+
static MultiThreadedProgressBar create(ProgressBarBuilder bar) {
31+
System.out.println("Supports cursor movement: " + TerminalUtils.hasCursorMovementSupport());
32+
return TerminalUtils.hasCursorMovementSupport()
33+
? new ThreadedLineProgressBar(bar)
34+
: new SimpleProgressBar(bar.build());
35+
}
36+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* This file is part of JavaDowngrader - https://github.com/RaphiMC/JavaDowngrader
3+
* Copyright (C) 2023 RK_01/RaphiMC and contributors
4+
*
5+
* This program is free software; you can redistribute it and/or
6+
* modify it under the terms of the GNU Lesser General Public
7+
* License as published by the Free Software Foundation; either
8+
* version 3 of the License, or (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
*/
18+
package net.raphimc.javadowngrader.standalone.progress;
19+
20+
import me.tongfei.progressbar.ProgressBar;
21+
22+
class SimpleProgressBar implements MultiThreadedProgressBar {
23+
protected final ProgressBar bar;
24+
25+
public SimpleProgressBar(ProgressBar bar) {
26+
this.bar = bar;
27+
}
28+
29+
@Override
30+
public void step() {
31+
bar.step();
32+
}
33+
34+
@Override
35+
public void setThreadTask(String task) {
36+
}
37+
38+
@Override
39+
public void close() {
40+
bar.close();
41+
}
42+
}
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
/*
2+
* The MIT License (MIT)
3+
*
4+
* Copyright (c) 2015--2020 Tongfei Chen and contributors
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in all
14+
* copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
* SOFTWARE.
23+
*/
24+
package net.raphimc.javadowngrader.standalone.progress;
25+
26+
import me.tongfei.progressbar.ProgressBarConsumer;
27+
import org.jline.terminal.Terminal;
28+
import org.jline.terminal.TerminalBuilder;
29+
import org.jline.utils.InfoCmp;
30+
31+
import java.io.IOException;
32+
import java.util.Queue;
33+
import java.util.concurrent.ConcurrentLinkedQueue;
34+
import java.util.stream.Stream;
35+
36+
/**
37+
* @author Martin Vehovsky
38+
* @since 0.9.0
39+
*/
40+
class TerminalUtils {
41+
42+
static final char CARRIAGE_RETURN = '\r';
43+
static final char ESCAPE_CHAR = '\u001b';
44+
static final int DEFAULT_TERMINAL_WIDTH = 80;
45+
46+
private static Terminal terminal = null;
47+
private static boolean cursorMovementSupported = false;
48+
49+
static Queue<ProgressBarConsumer> activeConsumers = new ConcurrentLinkedQueue<>();
50+
51+
synchronized static int getTerminalWidth() {
52+
Terminal terminal = getTerminal();
53+
int width = terminal.getWidth();
54+
55+
// Workaround for issue #23 under IntelliJ
56+
return (width >= 10) ? width : DEFAULT_TERMINAL_WIDTH;
57+
}
58+
59+
static boolean hasCursorMovementSupport() {
60+
if (terminal == null)
61+
terminal = getTerminal();
62+
return cursorMovementSupported;
63+
}
64+
65+
synchronized static void closeTerminal() {
66+
try {
67+
if (terminal != null) {
68+
terminal.close();
69+
terminal = null;
70+
}
71+
} catch (IOException ignored) { /* noop */ }
72+
}
73+
74+
static <T extends ProgressBarConsumer> Stream<T> filterActiveConsumers(Class<T> clazz) {
75+
return activeConsumers.stream()
76+
.filter(clazz::isInstance)
77+
.map(clazz::cast);
78+
}
79+
80+
static String moveCursorUp(int count) {
81+
return ESCAPE_CHAR + "[" + count + "A" + CARRIAGE_RETURN;
82+
}
83+
84+
static String moveCursorDown(int count) {
85+
return ESCAPE_CHAR + "[" + count + "B" + CARRIAGE_RETURN;
86+
}
87+
88+
/**
89+
* <ul>
90+
* <li>Creating terminal is relatively expensive, usually takes between 5-10ms.
91+
* <ul>
92+
* <li>If updateInterval is set under 10ms creating new terminal for on every re-render of progress bar could be a problem.</li>
93+
* <li>Especially when multiple progress bars are running in parallel.</li>
94+
* </ul>
95+
* </li>
96+
* <li>Another problem with {@link Terminal} is that once created you can create another instance (say from different thread), but this instance will be
97+
* "dumb". Until previously created terminal will be closed.
98+
* </li>
99+
* </ul>
100+
*/
101+
static Terminal getTerminal() {
102+
if (terminal == null) {
103+
try {
104+
// Issue #42
105+
// Defaulting to a dumb terminal when a supported terminal can not be correctly created
106+
// see https://github.com/jline/jline3/issues/291
107+
terminal = TerminalBuilder.builder().dumb(true).build();
108+
cursorMovementSupported = (
109+
terminal.getStringCapability(InfoCmp.Capability.cursor_up) != null &&
110+
terminal.getStringCapability(InfoCmp.Capability.cursor_down) != null
111+
);
112+
} catch (IOException e) {
113+
throw new RuntimeException("This should never happen! Dumb terminal should have been created.");
114+
}
115+
}
116+
return terminal;
117+
}
118+
119+
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/*
2+
* This file is part of JavaDowngrader - https://github.com/RaphiMC/JavaDowngrader
3+
* Copyright (C) 2023 RK_01/RaphiMC and contributors
4+
*
5+
* This program is free software; you can redistribute it and/or
6+
* modify it under the terms of the GNU Lesser General Public
7+
* License as published by the Free Software Foundation; either
8+
* version 3 of the License, or (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
*/
18+
package net.raphimc.javadowngrader.standalone.progress;
19+
20+
import me.tongfei.progressbar.ConsoleProgressBarConsumer;
21+
import me.tongfei.progressbar.ProgressBarBuilder;
22+
23+
import java.io.FileDescriptor;
24+
import java.io.FileOutputStream;
25+
import java.io.PrintStream;
26+
import java.util.*;
27+
28+
class ThreadedLineProgressBar extends SimpleProgressBar {
29+
private final Map<Thread, String> tasks = new LinkedHashMap<>();
30+
31+
public ThreadedLineProgressBar(ProgressBarBuilder builder) {
32+
this(builder, new TheConsumer(new PrintStream(new FileOutputStream(FileDescriptor.err))));
33+
}
34+
35+
public ThreadedLineProgressBar(ProgressBarBuilder builder, TheConsumer consumer) {
36+
super(builder.setConsumer(consumer).build());
37+
consumer.bar = this;
38+
}
39+
40+
@Override
41+
public void step() {
42+
setThreadTask("IDLE");
43+
super.step();
44+
}
45+
46+
@Override
47+
public void setThreadTask(String task) {
48+
tasks.put(Thread.currentThread(), task);
49+
}
50+
51+
private static class TheConsumer extends ConsoleProgressBarConsumer {
52+
private final PrintStream out;
53+
private ThreadedLineProgressBar bar;
54+
private List<String> oldTasks;
55+
56+
public TheConsumer(PrintStream out) {
57+
super(out);
58+
this.out = out;
59+
}
60+
61+
@Override
62+
public void accept(String str) {
63+
if (oldTasks != null) {
64+
out.print(TerminalUtils.moveCursorUp(oldTasks.size()));
65+
} else {
66+
oldTasks = Collections.emptyList();
67+
}
68+
super.accept(str);
69+
if (bar == null) return;
70+
final List<String> tasks = new ArrayList<>(bar.tasks.values());
71+
int i = 0;
72+
for (final String task : tasks) {
73+
final String oldTask = i < oldTasks.size() ? oldTasks.get(i) : "";
74+
final StringBuilder display = new StringBuilder("\n> ").append(task);
75+
if (oldTask.length() > task.length()) {
76+
for (int j = 0, l = oldTask.length() - task.length(); j < l; j++) {
77+
display.append(' ');
78+
}
79+
}
80+
out.print(display);
81+
i++;
82+
}
83+
oldTasks = tasks;
84+
out.flush();
85+
}
86+
87+
@Override
88+
public void close() {
89+
if (oldTasks != null) {
90+
final StringBuilder clear = new StringBuilder();
91+
for (int i = oldTasks.size() - 1; i >= 0; i--) {
92+
final int taskLen = oldTasks.get(i).length() + 2;
93+
clear.append('\r');
94+
for (int j = 0; j < taskLen + 2; j++) {
95+
clear.append(' ');
96+
}
97+
clear.append(TerminalUtils.moveCursorUp(1));
98+
}
99+
out.print(clear);
100+
}
101+
super.close();
102+
}
103+
}
104+
}

0 commit comments

Comments
 (0)