Skip to content

Commit

Permalink
Improve timeline animation performance.
Browse files Browse the repository at this point in the history
  • Loading branch information
colorizenl committed Oct 23, 2023
1 parent d24a070 commit 0c9b8b7
Show file tree
Hide file tree
Showing 19 changed files with 609 additions and 335 deletions.
17 changes: 10 additions & 7 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,16 @@ apply plugin: "maven-publish"
apply plugin: "signing"

group = "nl.colorize"
version = "2023.15"
sourceCompatibility = "17"
targetCompatibility = "17"
version = "2023.16"
compileJava.options.encoding = "UTF-8"
sourceSets.main.java.srcDirs = ["source"]
sourceSets.main.resources.srcDirs = ["resources"]
sourceSets.test.java.srcDirs = ["test"]

java {
sourceCompatibility = "17"
targetCompatibility = "17"
sourceSets.main.java.srcDirs = ["source"]
sourceSets.main.resources.srcDirs = ["resources"]
sourceSets.test.java.srcDirs = ["test"]
}

repositories {
mavenCentral()
Expand All @@ -37,7 +40,7 @@ test {
jacocoTestReport {
afterEvaluate {
classDirectories.from = files(classDirectories.files.collect {
fileTree(dir: it, exclude: ["**/swing/**"])
fileTree(dir: it, exclude: ["**/swing/**", "**/Platform*"])
})
}
}
Expand Down
4 changes: 2 additions & 2 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,13 @@ to the dependencies section in `pom.xml`:
<dependency>
<groupId>nl.colorize</groupId>
<artifactId>colorize-java-commons</artifactId>
<version>2023.15</version>
<version>2023.16</version>
</dependency>

The library can also be used in Gradle projects:

dependencies {
implementation "nl.colorize:colorize-java-commons:2023.15"
implementation "nl.colorize:colorize-java-commons:2023.16"
}

Documentation
Expand Down
8 changes: 0 additions & 8 deletions source/nl/colorize/util/CSVRecord.java
Original file line number Diff line number Diff line change
Expand Up @@ -70,14 +70,6 @@ public String get(String column) {
return cells.get(columnIndex);
}

/**
* Returns the list of cells in this CSV record. The list will be sorted to
* match the order in which the cells appear in the CSV.
*/
public List<String> getCells() {
return ImmutableList.copyOf(cells);
}

public boolean hasColumnNameInformation() {
return !columns.isEmpty();
}
Expand Down
8 changes: 6 additions & 2 deletions source/nl/colorize/util/FileUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -243,8 +243,12 @@ private static List<File> getDirectoryContents(File dir) {
* with only files matching the filter being returned.
*/
public static List<File> walkFiles(File dir, Predicate<File> filter) throws IOException {
if (!dir.isDirectory() && filter.test(dir)) {
return Collections.singletonList(dir);
if (!dir.isDirectory()) {
if (filter.test(dir)) {
return Collections.singletonList(dir);
} else {
return Collections.emptyList();
}
}

return Files.walk(dir.toPath())
Expand Down
50 changes: 50 additions & 0 deletions source/nl/colorize/util/PropertyDeserializer.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Properties;
import java.util.UUID;
import java.util.function.Function;
import java.util.logging.Logger;
Expand Down Expand Up @@ -173,4 +174,53 @@ public <T> Optional<T> attempt(String value, Class<T> type) {
return Optional.empty();
}
}

// Convenience methods for common data types

public String parseString(String value, String defaultValue) {
return parse(value, String.class, defaultValue);
}

public int parseInt(String value, int defaultValue) {
return parse(value, int.class, defaultValue);
}

public long parseLong(String value, long defaultValue) {
return parse(value, long.class, defaultValue);
}

public float parseFloat(String value, float defaultValue) {
return parse(value, float.class, defaultValue);
}

public double parseDouble(String value, double defaultValue) {
return parse(value, double.class, defaultValue);
}

public boolean parseBool(String value, boolean defaultValue) {
return parse(value, boolean.class, defaultValue);
}

/**
* Returns a {@link PropertyDeserializer} that acts as a live view for
* parsing values from the specified {@link Properties} object.
*/
public static PropertyDeserializer fromProperties(Properties properties) {
PropertyDeserializer propertyDeserializer = new PropertyDeserializer();
propertyDeserializer.registerPreprocessor(properties::getProperty);
return propertyDeserializer;
}

/**
* Returns a {@link PropertyDeserializer} that acts as a live view for
* parsing values from the specified CSV record.
*/
public static PropertyDeserializer fromCSV(CSVRecord record) {
Preconditions.checkArgument(record.hasColumnNameInformation(),
"CSV is missing column name information");

PropertyDeserializer propertyDeserializer = new PropertyDeserializer();
propertyDeserializer.registerPreprocessor(record::get);
return propertyDeserializer;
}
}
61 changes: 61 additions & 0 deletions source/nl/colorize/util/Subscribable.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.logging.Logger;

/**
Expand Down Expand Up @@ -62,6 +63,10 @@ private <S> List<S> prepareList() {
return new CopyOnWriteArrayList<>();
}

/**
* Publishes the next event to all event subscribers. If subscriptions
* have already been disposed, calling this method does nothing.
*/
public void next(T event) {
if (disposed) {
return;
Expand All @@ -74,6 +79,12 @@ public void next(T event) {
history.add(event);
}

/**
* Publishes the next error to all error subscribers. If no error
* subscribers exist, publishing an error will result in the error
* being logged. If subscriptions have already been disposed, calling
* this method does nothing.
*/
public void nextError(Exception error) {
if (disposed) {
return;
Expand All @@ -95,8 +106,15 @@ public void nextError(Exception error) {
* subscribers. If the operation completes normally, the return value is
* published to subscribers. If the operation produces an exception, this
* exception is published to error subscribers.
* <p>
* If subscriptions have already been disposed, calling this method does
* nothing.
*/
public void next(Callable<T> operation) {
if (disposed) {
return;
}

try {
T event = operation.call();
next(event);
Expand All @@ -105,19 +123,41 @@ public void next(Callable<T> operation) {
}
}

/**
* Registers the specified callback functions as event and error subscribers,
* respectively. The subscribers will immediately be notified of previously
* published events and/or errors.
*
* @throws IllegalStateException when trying to subscribe when subscriptions
* have alreasdy been disposed.
*/
public Subscribable<T> subscribe(Consumer<T> onEvent, Consumer<Exception> onError) {
subscribe(onEvent);
subscribeErrors(onError);
return this;
}

/**
* Registers the specified callback function as event subscriber. The
* subscriber will immediately be notified of previously published events.
*
* @throws IllegalStateException when trying to subscribe when subscriptions
* have alreasdy been disposed.
*/
public Subscribable<T> subscribe(Consumer<T> onEvent) {
Preconditions.checkState(!disposed, "Subscribable has already been disposed");
eventSubscribers.add(onEvent);
history.forEach(onEvent);
return this;
}

/**
* Registers the specified callback function as error subscriber. The
* subscriber will immediately be notified of previously published errors.
*
* @throws IllegalStateException when trying to subscribe when subscriptions
* have alreasdy been disposed.
*/
public Subscribable<T> subscribeErrors(Consumer<Exception> onError) {
Preconditions.checkState(!disposed, "Subscribable has already been disposed");
errorSubscribers.add(onError);
Expand All @@ -138,6 +178,11 @@ public void dispose() {
errorSubscribers.clear();
}

/**
* Returns a new {@link Subscribable} that will forward events to its own
* subscribers, but first uses the specified mapping function on each event.
* Errors will be forwarded as-is.
*/
public <S> Subscribable<S> map(Function<T, S> mapper) {
Subscribable<S> mapped = new Subscribable<>();
subscribe(event -> {
Expand All @@ -152,6 +197,22 @@ public <S> Subscribable<S> map(Function<T, S> mapper) {
return mapped;
}

/**
* Returns a new {@link Subscribable} that will forward events to its own
* subscribers, but only if the event matches the specified predicate.
* Errors will be forwarded as-is.
*/
public Subscribable<T> filter(Predicate<T> predicate) {
Subscribable<T> filtered = new Subscribable<>();
subscribe(event -> {
if (predicate.test(event)) {
filtered.next(event);
}
});
subscribeErrors(filtered::nextError);
return filtered;
}

/**
* Returns a {@link Promise} that is based on the first event or the first
* error produced by this {@link Subscribable}. If <em>multiple</em> events
Expand Down
27 changes: 0 additions & 27 deletions source/nl/colorize/util/TextUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -404,33 +404,6 @@ public static float calculateRelativeLevenshteinDistance(String a, String b) {
relativeDistance = Math.min(relativeDistance, 1f);
return relativeDistance;
}

/**
* Returns all candidates that "fuzzy match" the specified string. Fuzzy matching
* is done using the <a href="https://en.wikipedia.org/wiki/Levenshtein_distance">
* Levenshtein distance</a>, relative to the length of the string, against the
* provided threshold.
*
* @throws IllegalArgumentException if the threshold is outside the range between
* 0.0 (strings must be equal) and 1.0 (any string is allowed).
*/
public static List<String> fuzzyMatch(String str, Collection<String> candidates, float threshold) {
Preconditions.checkArgument(threshold >= 0f && threshold <= 1f,
"Threshold is outside range 0.0 - 1.0: " + threshold);

List<String> fuzzyMatches = new ArrayList<>();
for (String candidate : candidates) {
if (candidate != null && isFuzzyMatch(str, candidate, threshold)) {
fuzzyMatches.add(candidate);
}
}
return fuzzyMatches;
}

private static boolean isFuzzyMatch(String str, String candidate, float threshold) {
float distance = calculateRelativeLevenshteinDistance(str, candidate);
return distance <= threshold;
}

private static BufferedReader toBufferedReader(Reader reader) {
if (reader instanceof BufferedReader) {
Expand Down
21 changes: 18 additions & 3 deletions source/nl/colorize/util/TranslationBundle.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public final class TranslationBundle {

/**
* Creates a {@link TranslationBundle} based on the specified default
* translation. Additional translations can be added afterwards.
* translation. Additional translations can be added afterward.
* <p>
* In most cases, translation data will be stored in {@code .properties}
* files. The factory methods {@link #fromProperties(Properties)} and/or
Expand All @@ -52,11 +52,23 @@ public final class TranslationBundle {
* and then using this constructor.
*/
private TranslationBundle(Map<String, String> defaultTranslation) {
//TODO cannot use Map.copyOf() until it's supported by TeaVM.
this.defaultTranslation = ImmutableMap.copyOf(defaultTranslation);
this.translations = new HashMap<>();
}

/**
* Adds a translation for the specified locale. The default translation
* will act as a fallback for any keys that are not included in the
* translation.
*
* @throws IllegalArgumentException if this {@link TranslationBundle}
* already includes a translation for the same locale.
*/
public void addTranslation(Locale locale, TranslationBundle translation) {
Preconditions.checkArgument(!translations.containsKey(locale),
"Translation for locale already exists: " + locale);

translations.put(locale, translation);
}

Expand Down Expand Up @@ -120,20 +132,23 @@ public Set<String> getKeys(Locale locale) {
}

public Set<String> getKeys() {
//TODO cannot use Set.copyOf() until it's supported by TeaVM.
return ImmutableSet.copyOf(defaultTranslation.keySet());
}

/**
* Factory method that creates a {@link TranslationBundle} from a map with
* key/value pairs for the default translation.
* key/value pairs for the default translation. Additional translations
* can be added afterward.
*/
public static TranslationBundle fromMap(Map<String, String> defaultTranslation) {
return new TranslationBundle(defaultTranslation);
}

/**
* Factory method that creates a {@link TranslationBundle} from an existing
* {@link Properties} instance.
* {@link Properties} instance with key/value pairs for the default
* translation. Additional translations can be added afterward.
*/
public static TranslationBundle fromProperties(Properties defaultTranslation) {
return new TranslationBundle(LoadUtils.toMap(defaultTranslation));
Expand Down
12 changes: 9 additions & 3 deletions source/nl/colorize/util/animation/KeyFrame.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@
import com.google.common.base.Preconditions;

/**
* Defines a property's value at a certain point in time. Key frames are placed
* on a timeline, which can then animate the property by interpolating between
* key frames.
* Defines a property's value at a certain point in time, used in conjunction
* with {@link Timeline}. The key frame's {@code time} indicates its position
* on the timeline, in seconds. The timeline will then use the key frame data,
* interpolating between key frames when necessary.
*/
public record KeyFrame(float time, float value) implements Comparable<KeyFrame> {

Expand All @@ -23,4 +24,9 @@ public record KeyFrame(float time, float value) implements Comparable<KeyFrame>
public int compareTo(KeyFrame other) {
return Float.compare(time, other.time);
}

@Override
public String toString() {
return time + ": " + value;
}
}
Loading

0 comments on commit 0c9b8b7

Please sign in to comment.