Skip to content

Commit

Permalink
Small imporvements
Browse files Browse the repository at this point in the history
Made progressMap independent of description, so multiple descriptions can be the same
Added AtomicInteger instead, so indices are distinct
Added descriptionMap for descriptions
Added current index to each Progress object to identify it in progressMap
Made description and list publicly available
Added SafeVarargs annotation to varargs methods
Inlined updateProgress method to directly access progressMap
Used Thread.ofVirtual() so execution is suspended while printer thread sleeps
Moved startTime inside the printer thread, so it only starts after the Progress object is actually created, not when Progress.reset() is called
Removed description if size < 0
Added 0 to seconds if seconds are only single digit and minutes are present
Updated Demo
Updated README
  • Loading branch information
danielbinder committed Oct 21, 2023
1 parent 9a7ff06 commit 6f735ef
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 61 deletions.
12 changes: 8 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Progress

This is a simple library for adding one or multiple console progress counter(s) to your project.
This is a simple library for adding one or multiple console progress counters to your project.

![demo.png](demo.png)
### I want to use and/or adapt this project
Expand All @@ -12,20 +12,24 @@ Regardless of the license, it would be cool if you somehow mentioned, that you g
1) Add as a library (here shown in IntelliJ) or add the code directly to your project
![lib.png](lib.png)
2) Wrap the Object you want to track in `Progresss.of("optional description", someObj)` wherever you want to consume it e.g.
- You can use it anywhere, where you would use an iterator of a Collection or an Array, since it literally returns one
- The DESCRIPTIONS are used as a key to the progress map, SO THEY NEED TO BE UNIQUE
- You can use it anywhere, where you would use an `Iterator`, since the Iterator returned triggers the progress update
- If you don't add a description, an incrementing counter will be added instead
- ```
for(Thing t : Progress.of(thingList)) {
doSomethingWith(t);
}
```
3) You can also register multiple Collections in multiple Threads
4) If you want to reset the counter use `Progress.reset()`
4) If you want to reset the counter use `Progress.reset()` (as late as possible)
5) You can find more details in the [Demo.java](https://github.com/danielbinder/Progress/blob/master/src/Demo.java)

### Implementation details
- Update rate of the counter is hard coded to 100ms.
- The progress amount itself is not threadsafe, since syncing the numbers would unnecessarily take additional time, **since**
- either something is very fast, and you
- don't really need a progress counter or
- it's too fast for you to notice the slightly wrong numbers
- or it's slow enough that the progress is basically up2date after 100ms
- The goal of complicated progress update loop is to reduce IO access as much as possible.
- The progress will be shown as a percentage (int)
- Time is `elapsed time | estimated time left`
Expand Down
Binary file modified demo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
28 changes: 16 additions & 12 deletions src/Demo.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,21 @@
import java.util.stream.IntStream;

public class Demo {
public void count() {
Random r = new Random();
private static final int MAX_SLEEP_MS = 1000;
private static final int MAX_ELEMENTS_IN_LIST = 1234;
private static final Random r = new Random();


// Simulate real progress
public void count() {
try {
// Sleep up to 1s
Thread.sleep(r.nextInt(1, 1000));
// Simulate real work by sleeping
Thread.sleep(r.nextInt(1, MAX_SLEEP_MS));
} catch(InterruptedException ignored) {}
}

public static void main(String[] args) {
System.out.println("Single counter demo:");
List<Demo> demos = IntStream.range(0, 100)
List<Demo> demos = IntStream.range(0, 123)
.mapToObj(i -> new Demo())
.toList();

Expand All @@ -29,13 +31,15 @@ public static void main(String[] args) {
System.out.println("\n\nMulticounter demo:");
Progress.reset();
IntStream.range(0, 5)
.mapToObj(l -> IntStream.range(0, 100)
// Random amount of list elements
.mapToObj(l -> IntStream.range(0, r.nextInt(MAX_ELEMENTS_IN_LIST))
.mapToObj(i -> new Demo())
.toList())
.parallel()
// Register multiple lists
.forEach(list -> {
for(Demo d : Progress.of(list)) d.count();
});
// Register multiple counters in different threads
.forEach(list ->
Thread.ofPlatform().start(() -> {
for(Demo d : Progress.of(list)) d.count();
})
);
}
}
98 changes: 53 additions & 45 deletions src/Progress.java
Original file line number Diff line number Diff line change
@@ -1,30 +1,43 @@
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

public class Progress<T> implements Iterable<T> {
private static final Map<String, Integer> progressMap = new HashMap<>();
private static int i = 0;
private static long startTime;
private final String description;
private final List<T> list;
private static final Map<Integer, Integer> progressMap = new HashMap<>();
private static final Map<Integer, String> descriptionMap = new HashMap<>();
private static final AtomicInteger i = new AtomicInteger();
private final int counterIndex;
public final String description;
public final List<T> list;

static {
reset();
}

private Progress(String description, List<T> list) {
progressMap.put(description, 0);
counterIndex = i.getAndIncrement();
progressMap.put(counterIndex, 0);
descriptionMap.put(counterIndex, description);

this.description = description;
this.list = list;
}

private Progress(List<T> list) {
counterIndex = i.getAndIncrement();
progressMap.put(counterIndex, 0);

description = String.valueOf(counterIndex);
descriptionMap.put(counterIndex, description);
this.list = list;
}

public static <T> Progress<T> of(String description, List<T> list) {
return new Progress<>(description, list);
}

public static <T> Progress<T> of(List<T> list) {
return of(String.valueOf(i++), list);
return new Progress<>(list);
}

public static <T> Progress<T> of(String description, Collection<T> collection) {
Expand All @@ -35,10 +48,12 @@ public static <T> Progress<T> of(Collection<T> collection) {
return of(collection.stream().toList());
}

@SafeVarargs
public static <T> Progress<T> of(String description, T...array) {
return of(description, Arrays.asList(array));
}

@SafeVarargs
public static <T> Progress<T> of(T...array) {
return of(Arrays.asList(array));
}
Expand All @@ -55,62 +70,55 @@ public boolean hasNext() {

@Override
public T next() {
updateProgress(description, (100 * i) / Math.max(1, list.size() - 1));
progressMap.put(counterIndex, (100 * i) / Math.max(1, list.size() - 1));

return list.get(i++);
}
};
}

private static void updateProgress(String description, int percentage) {
progressMap.put(description, percentage);
}

public static void reset() {
progressMap.clear();
i = 0;
startTime = System.currentTimeMillis();
new Thread() {
@Override
public void run() {
super.run();
i.set(1);

while(progressMap.isEmpty()) {
try {
Thread.sleep(100);
} catch(InterruptedException ignored) {}
}
Thread.ofVirtual().start(() -> {
while(progressMap.isEmpty()) {
try {
Thread.sleep(100);
} catch(InterruptedException ignored) {}
}

long startTime = System.currentTimeMillis();

String oldString = "";
int minProgress = progressMap.values().stream()
.min(Integer::compareTo)
// stop if no minimum found
.orElse(100);

String oldString = "";
int minProgress = progressMap.values().stream()
while(minProgress < 100) {
minProgress = progressMap.values().stream()
.min(Integer::compareTo)
// stop if no minimum found
.orElse(100);

while(minProgress < 100) {
minProgress = progressMap.values().stream()
.min(Integer::compareTo)
// stop if no minimum found
.orElse(100);
String currString = "\u001B[32m" + progressMap.keySet().stream()
.map(i -> (progressMap.size() > 1 || !descriptionMap.get(i).equals("1") ? "[" + descriptionMap.get(i) + "] " : "") + progressMap.get(i) + "% ")
.collect(Collectors.joining("", "", getTimeString(startTime, minProgress)));

String currString = "\u001B[32m" + progressMap.keySet().stream()
.map(d -> "[" + d + "] " + progressMap.get(d) + "% ")
.collect(Collectors.joining("", "", getTimeString(startTime, minProgress)));
if(!currString.equals(oldString)) {
System.out.print("\b".repeat(oldString.length()));

if(!currString.equals(oldString)) {
System.out.print("\b".repeat(oldString.length()));
oldString = currString;

oldString = currString;

System.out.print(currString);
}

try {
Thread.sleep(100);
} catch(InterruptedException ignored) {}
System.out.print(currString);
}

try {
Thread.sleep(100);
} catch(InterruptedException ignored) {}
}
}.start();
});
}

private static String getTimeString(long startTime, int minProgress) {
Expand All @@ -135,6 +143,6 @@ private static String formatTimeMillis(long timeMillis) {
(d == 0 ? "" : (y > 0 ? "0".repeat(3 - String.valueOf(d).length()) + d : d) + "d") +
(h == 0 ? "" : (d > 0 ? "0".repeat(2 - String.valueOf(h).length()) + h : h) + ":") +
(min == 0 ? "" : (h > 0 ? "0".repeat(2 - String.valueOf(min).length()) + min : min) + ":") +
(h == 0 && min == 0 ? s + "s" : s);
(h == 0 && min == 0 ? s + "s" : (s < 10 ? "0" + s : s));
}
}

0 comments on commit 6f735ef

Please sign in to comment.