196 lines
6.9 KiB
Java
196 lines
6.9 KiB
Java
package net.minecraft.client;
|
|
|
|
import com.google.common.collect.ImmutableMap;
|
|
import com.google.common.math.LongMath;
|
|
import com.google.gson.JsonParser;
|
|
import com.mojang.logging.LogUtils;
|
|
import com.mojang.serialization.Codec;
|
|
import com.mojang.serialization.JsonOps;
|
|
import com.mojang.serialization.codecs.RecordCodecBuilder;
|
|
import it.unimi.dsi.fastutil.objects.Object2BooleanFunction;
|
|
import java.io.Reader;
|
|
import java.util.Collection;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Timer;
|
|
import java.util.TimerTask;
|
|
import java.util.Map.Entry;
|
|
import java.util.concurrent.TimeUnit;
|
|
import java.util.concurrent.atomic.AtomicLong;
|
|
import java.util.stream.Collectors;
|
|
import net.fabricmc.api.EnvType;
|
|
import net.fabricmc.api.Environment;
|
|
import net.minecraft.Util;
|
|
import net.minecraft.client.gui.components.toasts.SystemToast;
|
|
import net.minecraft.network.chat.Component;
|
|
import net.minecraft.resources.ResourceLocation;
|
|
import net.minecraft.server.packs.resources.ResourceManager;
|
|
import net.minecraft.server.packs.resources.SimplePreparableReloadListener;
|
|
import net.minecraft.util.profiling.ProfilerFiller;
|
|
import org.jetbrains.annotations.Nullable;
|
|
import org.slf4j.Logger;
|
|
|
|
@Environment(EnvType.CLIENT)
|
|
public class PeriodicNotificationManager
|
|
extends SimplePreparableReloadListener<Map<String, List<PeriodicNotificationManager.Notification>>>
|
|
implements AutoCloseable {
|
|
private static final Codec<Map<String, List<PeriodicNotificationManager.Notification>>> CODEC = Codec.unboundedMap(
|
|
Codec.STRING,
|
|
RecordCodecBuilder.<PeriodicNotificationManager.Notification>create(
|
|
instance -> instance.group(
|
|
Codec.LONG.optionalFieldOf("delay", 0L).forGetter(PeriodicNotificationManager.Notification::delay),
|
|
Codec.LONG.fieldOf("period").forGetter(PeriodicNotificationManager.Notification::period),
|
|
Codec.STRING.fieldOf("title").forGetter(PeriodicNotificationManager.Notification::title),
|
|
Codec.STRING.fieldOf("message").forGetter(PeriodicNotificationManager.Notification::message)
|
|
)
|
|
.apply(instance, PeriodicNotificationManager.Notification::new)
|
|
)
|
|
.listOf()
|
|
);
|
|
private static final Logger LOGGER = LogUtils.getLogger();
|
|
private final ResourceLocation notifications;
|
|
private final Object2BooleanFunction<String> selector;
|
|
@Nullable
|
|
private Timer timer;
|
|
@Nullable
|
|
private PeriodicNotificationManager.NotificationTask notificationTask;
|
|
|
|
public PeriodicNotificationManager(ResourceLocation notifications, Object2BooleanFunction<String> selector) {
|
|
this.notifications = notifications;
|
|
this.selector = selector;
|
|
}
|
|
|
|
protected Map<String, List<PeriodicNotificationManager.Notification>> prepare(ResourceManager resourceManager, ProfilerFiller profilerFiller) {
|
|
try {
|
|
Reader reader = resourceManager.openAsReader(this.notifications);
|
|
|
|
Map var4;
|
|
try {
|
|
var4 = (Map)CODEC.parse(JsonOps.INSTANCE, JsonParser.parseReader(reader)).result().orElseThrow();
|
|
} catch (Throwable var7) {
|
|
if (reader != null) {
|
|
try {
|
|
reader.close();
|
|
} catch (Throwable var6) {
|
|
var7.addSuppressed(var6);
|
|
}
|
|
}
|
|
|
|
throw var7;
|
|
}
|
|
|
|
if (reader != null) {
|
|
reader.close();
|
|
}
|
|
|
|
return var4;
|
|
} catch (Exception var8) {
|
|
LOGGER.warn("Failed to load {}", this.notifications, var8);
|
|
return ImmutableMap.of();
|
|
}
|
|
}
|
|
|
|
protected void apply(Map<String, List<PeriodicNotificationManager.Notification>> map, ResourceManager resourceManager, ProfilerFiller profilerFiller) {
|
|
List<PeriodicNotificationManager.Notification> list = (List<PeriodicNotificationManager.Notification>)map.entrySet()
|
|
.stream()
|
|
.filter(entry -> this.selector.apply((String)entry.getKey()))
|
|
.map(Entry::getValue)
|
|
.flatMap(Collection::stream)
|
|
.collect(Collectors.toList());
|
|
if (list.isEmpty()) {
|
|
this.stopTimer();
|
|
} else if (list.stream().anyMatch(notification -> notification.period == 0L)) {
|
|
Util.logAndPauseIfInIde("A periodic notification in " + this.notifications + " has a period of zero minutes");
|
|
this.stopTimer();
|
|
} else {
|
|
long l = this.calculateInitialDelay(list);
|
|
long m = this.calculateOptimalPeriod(list, l);
|
|
if (this.timer == null) {
|
|
this.timer = new Timer();
|
|
}
|
|
|
|
if (this.notificationTask == null) {
|
|
this.notificationTask = new PeriodicNotificationManager.NotificationTask(list, l, m);
|
|
} else {
|
|
this.notificationTask = this.notificationTask.reset(list, m);
|
|
}
|
|
|
|
this.timer.scheduleAtFixedRate(this.notificationTask, TimeUnit.MINUTES.toMillis(l), TimeUnit.MINUTES.toMillis(m));
|
|
}
|
|
}
|
|
|
|
public void close() {
|
|
this.stopTimer();
|
|
}
|
|
|
|
private void stopTimer() {
|
|
if (this.timer != null) {
|
|
this.timer.cancel();
|
|
}
|
|
}
|
|
|
|
private long calculateOptimalPeriod(List<PeriodicNotificationManager.Notification> notifications, long delay) {
|
|
return notifications.stream().mapToLong(notification -> {
|
|
long m = notification.delay - delay;
|
|
return LongMath.gcd(m, notification.period);
|
|
}).reduce(LongMath::gcd).orElseThrow(() -> new IllegalStateException("Empty notifications from: " + this.notifications));
|
|
}
|
|
|
|
private long calculateInitialDelay(List<PeriodicNotificationManager.Notification> notifications) {
|
|
return notifications.stream().mapToLong(notification -> notification.delay).min().orElse(0L);
|
|
}
|
|
|
|
@Environment(EnvType.CLIENT)
|
|
public record Notification(long delay, long period, String title, String message) {
|
|
|
|
public Notification(final long delay, final long period, final String title, final String message) {
|
|
this.delay = delay != 0L ? delay : period;
|
|
this.period = period;
|
|
this.title = title;
|
|
this.message = message;
|
|
}
|
|
}
|
|
|
|
@Environment(EnvType.CLIENT)
|
|
static class NotificationTask extends TimerTask {
|
|
private final Minecraft minecraft = Minecraft.getInstance();
|
|
private final List<PeriodicNotificationManager.Notification> notifications;
|
|
private final long period;
|
|
private final AtomicLong elapsed;
|
|
|
|
public NotificationTask(List<PeriodicNotificationManager.Notification> notifications, long elapsed, long period) {
|
|
this.notifications = notifications;
|
|
this.period = period;
|
|
this.elapsed = new AtomicLong(elapsed);
|
|
}
|
|
|
|
public PeriodicNotificationManager.NotificationTask reset(List<PeriodicNotificationManager.Notification> notifications, long period) {
|
|
this.cancel();
|
|
return new PeriodicNotificationManager.NotificationTask(notifications, this.elapsed.get(), period);
|
|
}
|
|
|
|
public void run() {
|
|
long l = this.elapsed.getAndAdd(this.period);
|
|
long m = this.elapsed.get();
|
|
|
|
for (PeriodicNotificationManager.Notification notification : this.notifications) {
|
|
if (l >= notification.delay) {
|
|
long n = l / notification.period;
|
|
long o = m / notification.period;
|
|
if (n != o) {
|
|
this.minecraft
|
|
.execute(
|
|
() -> SystemToast.add(
|
|
Minecraft.getInstance().getToastManager(),
|
|
SystemToast.SystemToastId.PERIODIC_NOTIFICATION,
|
|
Component.translatable(notification.title, n),
|
|
Component.translatable(notification.message, n)
|
|
)
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|