File size: 8,170 Bytes
d46f4a3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
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 com.mojang.serialization.codecs.RecordCodecBuilder.Instance;
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 javax.annotation.Nullable;
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 net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import org.slf4j.Logger;

@OnlyIn(Dist.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(
                p_205303_ -> p_205303_.group(
                            Codec.LONG.optionalFieldOf("delay", Long.valueOf(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(p_205303_, 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 p_205293_, Object2BooleanFunction<String> p_205294_) {
        this.notifications = p_205293_;
        this.selector = p_205294_;
    }

    protected Map<String, List<PeriodicNotificationManager.Notification>> prepare(ResourceManager p_205300_, ProfilerFiller p_205301_) {
        try {
            Map map;
            try (Reader reader = p_205300_.openAsReader(this.notifications)) {
                map = CODEC.parse(JsonOps.INSTANCE, JsonParser.parseReader(reader)).result().orElseThrow();
            }

            return map;
        } catch (Exception exception) {
            LOGGER.warn("Failed to load {}", this.notifications, exception);
            return ImmutableMap.of();
        }
    }

    protected void apply(Map<String, List<PeriodicNotificationManager.Notification>> p_205318_, ResourceManager p_205319_, ProfilerFiller p_205320_) {
        List<PeriodicNotificationManager.Notification> list = p_205318_.entrySet()
            .stream()
            .filter(p_205316_ -> this.selector.apply(p_205316_.getKey()))
            .map(Entry::getValue)
            .flatMap(Collection::stream)
            .collect(Collectors.toList());
        if (list.isEmpty()) {
            this.stopTimer();
        } else if (list.stream().anyMatch(p_205326_ -> p_205326_.period == 0L)) {
            Util.logAndPauseIfInIde("A periodic notification in " + this.notifications + " has a period of zero minutes");
            this.stopTimer();
        } else {
            long i = this.calculateInitialDelay(list);
            long j = this.calculateOptimalPeriod(list, i);
            if (this.timer == null) {
                this.timer = new Timer();
            }

            if (this.notificationTask == null) {
                this.notificationTask = new PeriodicNotificationManager.NotificationTask(list, i, j);
            } else {
                this.notificationTask = this.notificationTask.reset(list, j);
            }

            this.timer.scheduleAtFixedRate(this.notificationTask, TimeUnit.MINUTES.toMillis(i), TimeUnit.MINUTES.toMillis(j));
        }
    }

    @Override
    public void close() {
        this.stopTimer();
    }

    private void stopTimer() {
        if (this.timer != null) {
            this.timer.cancel();
        }
    }

    private long calculateOptimalPeriod(List<PeriodicNotificationManager.Notification> p_205313_, long p_205314_) {
        return p_205313_.stream().mapToLong(p_205298_ -> {
            long i = p_205298_.delay - p_205314_;
            return LongMath.gcd(i, p_205298_.period);
        }).reduce(LongMath::gcd).orElseThrow(() -> new IllegalStateException("Empty notifications from: " + this.notifications));
    }

    private long calculateInitialDelay(List<PeriodicNotificationManager.Notification> p_205311_) {
        return p_205311_.stream().mapToLong(p_205305_ -> p_205305_.delay).min().orElse(0L);
    }

    @OnlyIn(Dist.CLIENT)
    public static 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;
        }
    }

    @OnlyIn(Dist.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> p_205350_, long p_205351_, long p_205352_) {
            this.notifications = p_205350_;
            this.period = p_205352_;
            this.elapsed = new AtomicLong(p_205351_);
        }

        public PeriodicNotificationManager.NotificationTask reset(List<PeriodicNotificationManager.Notification> p_205357_, long p_205358_) {
            this.cancel();
            return new PeriodicNotificationManager.NotificationTask(p_205357_, this.elapsed.get(), p_205358_);
        }

        @Override
        public void run() {
            long i = this.elapsed.getAndAdd(this.period);
            long j = this.elapsed.get();

            for (PeriodicNotificationManager.Notification periodicnotificationmanager$notification : this.notifications) {
                if (i >= periodicnotificationmanager$notification.delay) {
                    long k = i / periodicnotificationmanager$notification.period;
                    long l = j / periodicnotificationmanager$notification.period;
                    if (k != l) {
                        this.minecraft
                            .execute(
                                () -> SystemToast.add(
                                        Minecraft.getInstance().getToastManager(),
                                        SystemToast.SystemToastId.PERIODIC_NOTIFICATION,
                                        Component.translatable(periodicnotificationmanager$notification.title, k),
                                        Component.translatable(periodicnotificationmanager$notification.message, k)
                                    )
                            );
                        return;
                    }
                }
            }
        }
    }
}