001/*
002 * PlotSquared, a land and world management plugin for Minecraft.
003 * Copyright (C) IntellectualSites <https://intellectualsites.com>
004 * Copyright (C) IntellectualSites team and contributors
005 *
006 * This program is free software: you can redistribute it and/or modify
007 * it under the terms of the GNU General Public License as published by
008 * the Free Software Foundation, either version 3 of the License, or
009 * (at your option) any later version.
010 *
011 * This program is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
014 * GNU General Public License for more details.
015 *
016 * You should have received a copy of the GNU General Public License
017 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
018 */
019package com.plotsquared.core.queue.subscriber;
020
021import com.google.common.base.Preconditions;
022import com.google.common.util.concurrent.AtomicDouble;
023import com.google.inject.assistedinject.Assisted;
024import com.google.inject.assistedinject.AssistedInject;
025import com.plotsquared.core.configuration.Settings;
026import com.plotsquared.core.configuration.caption.Caption;
027import com.plotsquared.core.configuration.caption.TranslatableCaption;
028import com.plotsquared.core.player.PlotPlayer;
029import com.plotsquared.core.queue.ChunkCoordinator;
030import com.plotsquared.core.util.task.PlotSquaredTask;
031import com.plotsquared.core.util.task.TaskManager;
032import com.plotsquared.core.util.task.TaskTime;
033import net.kyori.adventure.text.Component;
034import net.kyori.adventure.text.minimessage.tag.Tag;
035import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
036import org.checkerframework.checker.nullness.qual.NonNull;
037
038import javax.annotation.Nullable;
039import java.util.Objects;
040import java.util.concurrent.atomic.AtomicBoolean;
041
042/**
043 * The default PlotSquared Progress Subscriber. Can be used for both console and player tasks.
044 * It is the {@link ProgressSubscriber} returned by {@link com.plotsquared.core.inject.factory.ProgressSubscriberFactory}.
045 * Runs a repeating synchronous task notifying the given actor about any updates, saving updates notified by the ChunkCoordinator.
046 */
047public class DefaultProgressSubscriber implements ProgressSubscriber {
048
049    @NonNull
050    private final AtomicDouble progress = new AtomicDouble(0);
051    @NonNull
052    private final AtomicBoolean started = new AtomicBoolean(false);
053    @NonNull
054    private final AtomicBoolean cancelled = new AtomicBoolean(false);
055    @NonNull
056    private final TaskTime interval;
057    @NonNull
058    private final TaskTime wait;
059    @NonNull
060    private final PlotPlayer<?> actor;
061    @NonNull
062    private final Caption caption;
063    private PlotSquaredTask task;
064
065    @AssistedInject
066    public DefaultProgressSubscriber() {
067        throw new UnsupportedOperationException("DefaultProgressSubscriber cannot be used without an actor.");
068    }
069
070    @AssistedInject
071    public DefaultProgressSubscriber(@Nullable @Assisted("subscriber") final PlotPlayer<?> actor) {
072        Preconditions.checkNotNull(
073                actor,
074                "Actor cannot be null when using DefaultProgressSubscriber! Make sure if attempting to use custom Subscribers it is correctly parsed to the queue!"
075        );
076        this.actor = actor;
077        this.interval = TaskTime.ms(Settings.QUEUE.NOTIFY_INTERVAL);
078        this.wait = TaskTime.ms(Settings.QUEUE.NOTIFY_WAIT);
079        this.caption = TranslatableCaption.of("working.progress");
080    }
081
082    @AssistedInject
083    public DefaultProgressSubscriber(
084            @Nullable @Assisted("subscriber") final PlotPlayer<?> actor,
085            @Assisted("progressInterval") final long interval,
086            @Assisted("waitBeforeStarting") final long wait,
087            @Nullable @Assisted("caption") final Caption caption
088    ) {
089        Preconditions.checkNotNull(
090                actor,
091                "Actor cannot be null when using DefaultProgressSubscriber! Make sure if attempting to use custom Subscribers it is correctly parsed to the queue!"
092        );
093        this.actor = actor;
094        this.interval = TaskTime.ms(interval);
095        this.wait = TaskTime.ms(wait);
096        this.caption = Objects.requireNonNullElseGet(caption, () -> TranslatableCaption.of("working.progress"));
097    }
098
099    @Override
100    public void notifyProgress(@NonNull ChunkCoordinator coordinator, double progress) {
101        this.progress.set(progress);
102        if (started.compareAndSet(false, true)) {
103            TaskManager.getPlatformImplementation().taskLater(() -> task = TaskManager
104                    .getPlatformImplementation()
105                    .taskRepeat(() -> {
106                        if (!started.get()) {
107                            return;
108                        }
109                        if (cancelled.get()) {
110                            task.cancel();
111                            return;
112                        }
113                        actor.sendMessage(
114                                caption,
115                                TagResolver.resolver(
116                                        "progress",
117                                        Tag.inserting(Component.text(String.format("%.2f", this.progress.doubleValue() * 100)))
118                                )
119                        );
120                    }, interval), wait);
121        }
122    }
123
124    public void notifyEnd() {
125        cancel();
126    }
127
128    public void cancel() {
129        this.cancelled.set(true);
130        if (this.task != null) {
131            task.cancel();
132        }
133    }
134
135}