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.minimessage.Template;
034import org.checkerframework.checker.nullness.qual.NonNull;
035
036import javax.annotation.Nullable;
037import java.util.concurrent.atomic.AtomicBoolean;
038
039/**
040 * The default PlotSquared Progress Subscriber. Can be used for both console and player tasks.
041 * It is the {@link ProgressSubscriber} returned by {@link com.plotsquared.core.inject.factory.ProgressSubscriberFactory}.
042 * Runs a repeating synchronous task notifying the given actor about any updates, saving updates notified by the ChunkCoordinator.
043 */
044public class DefaultProgressSubscriber implements ProgressSubscriber {
045
046    @NonNull
047    private final AtomicDouble progress = new AtomicDouble(0);
048    @NonNull
049    private final AtomicBoolean started = new AtomicBoolean(false);
050    @NonNull
051    private final AtomicBoolean cancelled = new AtomicBoolean(false);
052    @NonNull
053    private final TaskTime interval;
054    @NonNull
055    private final TaskTime wait;
056    @NonNull
057    private final PlotPlayer<?> actor;
058    @NonNull
059    private final Caption caption;
060    private PlotSquaredTask task;
061
062    @AssistedInject
063    public DefaultProgressSubscriber() {
064        throw new UnsupportedOperationException("DefaultProgressSubscriber cannot be used without an actor.");
065    }
066
067    @AssistedInject
068    public DefaultProgressSubscriber(@Nullable @Assisted("subscriber") final PlotPlayer<?> actor) {
069        Preconditions.checkNotNull(
070                actor,
071                "Actor cannot be null when using DefaultProgressSubscriber! Make sure if attempting to use custom Subscribers it is correctly parsed to the queue!"
072        );
073        this.actor = actor;
074        this.interval = TaskTime.ms(Settings.QUEUE.NOTIFY_INTERVAL);
075        this.wait = TaskTime.ms(Settings.QUEUE.NOTIFY_WAIT);
076        this.caption = TranslatableCaption.of("working.progress");
077    }
078
079    @AssistedInject
080    public DefaultProgressSubscriber(
081            @Nullable @Assisted("subscriber") final PlotPlayer<?> actor,
082            @Assisted("progressInterval") final long interval,
083            @Assisted("waitBeforeStarting") final long wait,
084            @Nullable @Assisted("caption") final Caption caption
085    ) {
086        Preconditions.checkNotNull(
087                actor,
088                "Actor cannot be null when using DefaultProgressSubscriber! Make sure if attempting to use custom Subscribers it is correctly parsed to the queue!"
089        );
090        this.actor = actor;
091        this.interval = TaskTime.ms(interval);
092        this.wait = TaskTime.ms(wait);
093        if (caption == null) {
094            this.caption = TranslatableCaption.of("working.progress");
095        } else {
096            this.caption = caption;
097        }
098    }
099
100    @Override
101    public void notifyProgress(@NonNull ChunkCoordinator coordinator, double progress) {
102        this.progress.set(progress);
103        if (started.compareAndSet(false, true)) {
104            TaskManager.getPlatformImplementation().taskLater(() -> task = TaskManager
105                    .getPlatformImplementation()
106                    .taskRepeat(() -> {
107                        if (!started.get()) {
108                            return;
109                        }
110                        if (cancelled.get()) {
111                            task.cancel();
112                            return;
113                        }
114                        actor.sendMessage(
115                                caption,
116                                Template.of("progress", String.format("%.2f", this.progress.doubleValue() * 100))
117                        );
118                    }, interval), wait);
119        }
120    }
121
122    public void notifyEnd() {
123        cancel();
124    }
125
126    public void cancel() {
127        this.cancelled.set(true);
128        if (this.task != null) {
129            task.cancel();
130        }
131    }
132
133}