Skip to content

Commit 4c90666

Browse files
committed
Rewrite animation engine in Java
The animator threads are now daemon threads.
1 parent a196c91 commit 4c90666

13 files changed

+219
-177
lines changed

common/src/main/java/juuxel/adorn/client/gui/widget/ScrollEnvelope.java

+1-3
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
import juuxel.adorn.util.animation.AnimatedPropertyWrapper;
77
import juuxel.adorn.util.animation.AnimationEngine;
88
import juuxel.adorn.util.animation.Interpolator;
9-
import kotlin.Unit;
109
import net.minecraft.client.gui.DrawContext;
1110
import net.minecraft.client.gui.Element;
1211
import net.minecraft.util.math.MathHelper;
@@ -47,9 +46,8 @@ public ScrollEnvelope(int x, int y, int width, int height, SizedElement element,
4746
);
4847
}
4948

50-
private Unit setOffset(double offset) {
49+
private void setOffset(double offset) {
5150
this.offset = MathHelper.clamp(offset, 0.0, heightDifference());
52-
return Unit.INSTANCE;
5351
}
5452

5553
private int heightDifference() {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package juuxel.adorn.util.animation;
2+
3+
import org.jetbrains.annotations.Nullable;
4+
5+
import java.util.Objects;
6+
7+
public abstract class AbstractAnimatedProperty<T> {
8+
private final AnimationEngine engine;
9+
private final int duration;
10+
private final Interpolator<T> interpolator;
11+
private @Nullable Task currentTask = null;
12+
13+
protected AbstractAnimatedProperty(AnimationEngine engine, int duration, Interpolator<T> interpolator) {
14+
this.engine = engine;
15+
this.duration = duration;
16+
this.interpolator = interpolator;
17+
}
18+
19+
protected abstract void setRawValue(T value);
20+
21+
public abstract T get();
22+
23+
public synchronized void set(T value) {
24+
T oldValue = get();
25+
var oldTask = currentTask;
26+
if (oldTask != null) engine.remove(oldTask);
27+
28+
if (!Objects.equals(oldValue, value)) {
29+
var task = new Task(oldValue, value);
30+
currentTask = task;
31+
engine.add(task);
32+
}
33+
}
34+
35+
// https://easings.net/#easeOutQuint
36+
private static float ease(float delta) {
37+
return (float) Math.pow(1 - (1 - delta), 5);
38+
}
39+
40+
private class Task implements AnimationTask {
41+
private final T from;
42+
private final T to;
43+
private int age = 0;
44+
45+
private Task(T from, T to) {
46+
this.from = from;
47+
this.to = to;
48+
}
49+
50+
@Override
51+
public boolean isAlive() {
52+
return age < duration;
53+
}
54+
55+
@Override
56+
public void tick() {
57+
age++;
58+
float delta = ease((float) age / (float) duration);
59+
T newValue = interpolator.interpolate(delta, from, to);
60+
setRawValue(newValue);
61+
}
62+
63+
@Override
64+
public void removed() {
65+
synchronized (AbstractAnimatedProperty.this) {
66+
if (currentTask == this) {
67+
currentTask = null;
68+
}
69+
}
70+
}
71+
}
72+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package juuxel.adorn.util.animation;
2+
3+
public final class AnimatedProperty<T> extends AbstractAnimatedProperty<T> {
4+
private T value;
5+
6+
public AnimatedProperty(T initial, AnimationEngine engine, int duration, Interpolator<T> interpolator) {
7+
super(engine, duration, interpolator);
8+
value = initial;
9+
}
10+
11+
@Override
12+
public T get() {
13+
return value;
14+
}
15+
16+
@Override
17+
protected void setRawValue(T value) {
18+
this.value = value;
19+
}
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package juuxel.adorn.util.animation;
2+
3+
import java.util.function.Consumer;
4+
import java.util.function.Supplier;
5+
6+
public final class AnimatedPropertyWrapper<T> extends AbstractAnimatedProperty<T> {
7+
private final Supplier<T> getter;
8+
private final Consumer<T> setter;
9+
10+
public AnimatedPropertyWrapper(AnimationEngine engine, int duration, Interpolator<T> interpolator, Supplier<T> getter, Consumer<T> setter) {
11+
super(engine, duration, interpolator);
12+
this.getter = getter;
13+
this.setter = setter;
14+
}
15+
16+
@Override
17+
public T get() {
18+
return getter.get();
19+
}
20+
21+
@Override
22+
protected void setRawValue(T value) {
23+
setter.accept(value);
24+
}
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package juuxel.adorn.util.animation;
2+
3+
import org.jetbrains.annotations.Nullable;
4+
5+
import java.util.ArrayList;
6+
import java.util.List;
7+
8+
public final class AnimationEngine {
9+
private final List<AnimationTask> tasks = new ArrayList<>();
10+
private @Nullable AnimatorThread thread = null;
11+
12+
public void add(AnimationTask task) {
13+
synchronized (tasks) {
14+
tasks.add(task);
15+
}
16+
}
17+
18+
public void remove(AnimationTask task) {
19+
synchronized (tasks) {
20+
tasks.remove(task);
21+
}
22+
}
23+
24+
public void start() {
25+
// Null check to make sure that this function can be called in Screen.init.
26+
// It's also called when the screen is resized, so creating a new thread each time
27+
// 1. leaks animator threads
28+
// 2. causes the animations to speed up unreasonably
29+
if (thread == null) {
30+
var thread = new AnimatorThread();
31+
thread.start();
32+
this.thread = thread;
33+
}
34+
}
35+
36+
public void stop() {
37+
var current = thread;
38+
if (current != null) current.interrupt();
39+
thread = null;
40+
}
41+
42+
private class AnimatorThread extends Thread {
43+
private AnimatorThread() {
44+
super("Adorn animator");
45+
setDaemon(true);
46+
}
47+
48+
@Override
49+
public void run() {
50+
while (!interrupted()) {
51+
synchronized (tasks) {
52+
var iter = tasks.iterator();
53+
while (iter.hasNext()) {
54+
var task = iter.next();
55+
if (task.isAlive()) {
56+
task.tick();
57+
} else {
58+
task.removed();
59+
iter.remove();
60+
}
61+
}
62+
}
63+
64+
try {
65+
// noinspection BusyWait
66+
sleep(10L);
67+
} catch (InterruptedException e) {
68+
break;
69+
}
70+
}
71+
}
72+
}
73+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package juuxel.adorn.util.animation;
2+
3+
public interface AnimationTask {
4+
boolean isAlive();
5+
void tick();
6+
default void removed() {
7+
}
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package juuxel.adorn.util.animation;
2+
3+
import juuxel.adorn.util.Colors;
4+
import juuxel.adorn.util.ColorsKt;
5+
import net.minecraft.util.math.MathHelper;
6+
7+
@FunctionalInterface
8+
public interface Interpolator<T> {
9+
Interpolator<Float> FLOAT = MathHelper::lerp;
10+
Interpolator<Double> DOUBLE = MathHelper::lerp;
11+
Interpolator<Integer> COLOR = (delta, from, to) -> {
12+
float alpha = FLOAT.interpolate(delta, Colors.alphaOf(from), Colors.alphaOf(to));
13+
float red = FLOAT.interpolate(delta, Colors.redOf(from), Colors.redOf(to));
14+
float green = FLOAT.interpolate(delta, Colors.greenOf(from), Colors.greenOf(to));
15+
float blue = FLOAT.interpolate(delta, Colors.blueOf(from), Colors.blueOf(to));
16+
return ColorsKt.color(red, green, blue, alpha);
17+
};
18+
19+
T interpolate(float delta, T from, T to);
20+
}

common/src/main/kotlin/juuxel/adorn/util/animation/AbstractAnimatedProperty.kt

-56
This file was deleted.

common/src/main/kotlin/juuxel/adorn/util/animation/AnimatedProperty.kt

-15
This file was deleted.

common/src/main/kotlin/juuxel/adorn/util/animation/AnimatedPropertyWrapper.kt

-12
This file was deleted.

common/src/main/kotlin/juuxel/adorn/util/animation/AnimationEngine.kt

-58
This file was deleted.

common/src/main/kotlin/juuxel/adorn/util/animation/AnimationTask.kt

-7
This file was deleted.

0 commit comments

Comments
 (0)