Scheduler and countdown

Sponge Build: 1000
Forge Build: 1577
Java Version: 1.8.0_65

Plugin Source: BitBucket

Hi!

I’m developing a Hunger Games plugin and I’m currently struggling with the scheduler to run a countdown task but I don’t want it to be call after the countdown finished.

LobbyCountdownTask taskToExecute = new LobbyCountdownTask(plugin, 10); Task t = taskBuilder.execute(taskToExecute) .interval(1, TimeUnit.SECONDS) .name("HGPlugin - LobbyCountdownTask").submit(plugin);

As you can see, the LobbyCountDownTask should be call only for ten times + one to start a new scheduler task. I was previously using Thread but it wasn’t synchronous with the game, so I couldn’t use the API.

1 Like

After the last execution of your task, put this line:

Task t = myTask;
t.cancel();

Should work

Oh yes, this is obviously the answer. I’ll try this in a minute, thanks a lot. :slightly_smiling:
I should be very tired yesterday…

Make TaskToExecute a task consumer instead of a runnable, then you have access to the task, and can cancel it after it’s counter reaches a value.

Do you have a simple example please? I tried to find what you’re meaning on Google but I didn’t. I don’t know where the timer is share with the Task… By passing arguments? If it is, how can I instantiate a static plugin object ?

EDIT: I can cancel only one task because it’s call in an other task which is repeated. So the task called by the command can’t be stop by task.cancel();… Here’s the command code:

    plugin.setGameStarted(false);
    LobbyCountdownTask taskToExecute = new LobbyCountdownTask(plugin, 10);
    Task t = taskBuilder.execute(taskToExecute)
    	      .interval(1, TimeUnit.SECONDS)
    	      .name("HGPlugin - LobbyCountdownTask").submit(plugin);
    plugin.getLogger().info(t.getName(), " has been called");
    if(taskToExecute.isFinish()){
    	t.cancel();
    }

And the code of this called task:

if(seconds > 0) {
            game.getServer().getBroadcastChannel().send(Text.of(
                    TextColors.GOLD, seconds/60, ":", seconds,
                    TextColors.GREEN, " left to prepare yourself."));
            seconds--;
            return;
        } else if(!gameStartCountdownStarted){
            Task t = taskBuilder.execute(taskToExecute)
                    .interval(1, TimeUnit.SECONDS)
                    .name("HGPlugin - GameStartCountdownTask").submit(plugin);
            
            if(taskToExecute.isFinish()) {
                t.cancel();
                this.isFinish = Boolean.TRUE;
            } else {
                plugin.setPreparationEnded(true);
                game.getServer().getBroadcastChannel().send(Text.of(
                        TextColors.GREEN, "Preparation time ended!"));
                
                plugin.getLogger().info(t.getName() + " has been called");
                gameStartCountdownStarted = Boolean.TRUE;
            }
        }

It makes it hard to provide an example that matches your code, if you arn’t providing where the task is defined, but I’ll try to show an example of what I mean.

import org.spongepowered.api.Sponge;
import org.spongepowered.api.command.CommandException;
import org.spongepowered.api.command.CommandResult;
import org.spongepowered.api.command.CommandSource;
import org.spongepowered.api.command.args.CommandContext;
import org.spongepowered.api.command.spec.CommandExecutor;
import org.spongepowered.api.command.spec.CommandSpec;
import org.spongepowered.api.event.Listener;
import org.spongepowered.api.event.game.state.GameStartingServerEvent;
import org.spongepowered.api.plugin.Plugin;
import org.spongepowered.api.scheduler.Task;
import org.spongepowered.api.text.Text;

import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;

@Plugin(id="au.id.rleach.forumcountdown", name="Forum Countdown", version = "0.0.1")
public class ForumCountdown {
    @Listener
    public void onStart(GameStartingServerEvent init){
        Sponge.getCommandManager()
              .register(
                      this,
                      CommandSpec.builder()
                                         .description(Text.of("Broadcasts a 60 second timer"))
                                         .executor(new TimerCommandExecutor())
                                         .build(),
                      "timer"
              );
    }

    private class TimerCommandExecutor implements CommandExecutor {
        @Override
        public CommandResult execute(CommandSource commandSource, CommandContext commandContext) throws CommandException {
            Sponge  .getScheduler()
                    .createTaskBuilder()
                    .interval(1, TimeUnit.SECONDS)
                    .delay(1, TimeUnit.SECONDS)
                    .execute(new ForumTimerTask())
                    .submit(ForumCountdown.this);
            return CommandResult.success();
        }
    }

    private static class ForumTimerTask implements Consumer<Task> {
        private int seconds = 60;
        @Override
        public void accept(Task task) {
            seconds--;
            Sponge  .getGame()
                    .getServer()
                    .getBroadcastChannel()
                    .send(Text.of("Remaining Time: "+seconds+"s"));
            if(seconds < 1) {
                task.cancel();
            }
        }
    }
}

2 Likes

I understand your example ans my code is working well now but I’m just wondering how the Task task is given to the accept method.
Does the Sponge Scheduler API do it for me when it calls the accept method?

Yep.

1 Like

So “.submit();” doesn’t seem to exist in 4.2.0. Has it been replaced with something else? I’ve tried running this code without it, but it’s not functional.

Not sure what you mean, it’s right here.
Perhaps you’re forgetting to provide the plugin instance?

@pie_flavor Oh. I thought I was implementing it correctly, so I assumed it was no longer implemented in 4.2.0 when my code wouldn’t compile. The name of my main class is “Main”, but when I entered that in the “.submit(Main.this)” I got the error “The method submit(Main) is undefined for the type Main.ForumTimerTask”

Could you post full code?
Also, if you click ‘reply’ on someone’s message, the message will auto-notify them, no @-ing required.

1 Like

My code is very identical to the example provided by @ryantheleach. I’ve also changed the name of my class from “Main” to “MainTest”.

package Main;

import java.io.File;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.logging.Logger;

import org.spongepowered.api.Game;
import org.spongepowered.api.Sponge;
import org.spongepowered.api.command.CommandException;
import org.spongepowered.api.command.CommandResult;
import org.spongepowered.api.command.CommandSource;
import org.spongepowered.api.command.args.CommandContext;
import org.spongepowered.api.command.args.GenericArguments;
import org.spongepowered.api.command.spec.CommandExecutor;
import org.spongepowered.api.command.spec.CommandSpec;
import org.spongepowered.api.config.DefaultConfig;
import org.spongepowered.api.event.Listener;
import org.spongepowered.api.event.game.state.GameInitializationEvent;
import org.spongepowered.api.event.game.state.GamePostInitializationEvent;
import org.spongepowered.api.event.game.state.GameStoppedServerEvent;
import org.spongepowered.api.plugin.Plugin;
import org.spongepowered.api.scheduler.Task;
import org.spongepowered.api.text.Text;

import com.google.inject.Inject;

import Executors.JoinExecuter;
import Executors.MeteoriteExecuter;
import Executors.NodeCreatorExecuter;
import Listeners.NodeListener;
import ninja.leaping.configurate.commented.CommentedConfigurationNode;
import ninja.leaping.configurate.loader.ConfigurationLoader;

@Plugin(id = "dz", name = "DZ", version = "0.0.1")

public class MainTest {
@Inject
Game game;

@Listener
public void onInit (GameInitializationEvent event){
    game.getCommandManager().register(this, CommandSpec.builder().description(Text.of("Broadcasts a 60 second node capture timer")).executor(new TimerCommandExecutor()).build(),"timer");
}
public class TimerCommandExecutor implements CommandExecutor {
	@Override
	public CommandResult execute(CommandSource commandSource, CommandContext commandContext) throws CommandException {
	    game.getScheduler().createTaskBuilder().interval(1, TimeUnit.SECONDS).delay(1, TimeUnit.SECONDS).execute(new ForumTimerTask().submit(MainTest.this));
	    return CommandResult.success();
	}
}
public class ForumTimerTask implements Consumer<Task> {
    private int seconds = 60;
    @Override
    public void accept(Task task) {
        seconds--;
        game.getServer().getBroadcastChannel().send(Text.of("Node Capture Remaining Time: "+seconds));
        if(seconds < 1) {
            task.cancel();
        }
    }
}
}

Misplaced )

game.getScheduler().createTaskBuilder().interval(1, TimeUnit.SECONDS).delay(1, TimeUnit.SECONDS).execute(new ForumTimerTask()).submit(MainTest.this);

1 Like

Ah! Thank you. I should have payed attention to when I was organizing my code.

This is why we linebreak method calls on Builders.
e.g.

Task.builder()
        .interval(1, TimeUnit.SECONDS)
        .delay(1, TimeUnit.SECONDS)
        .execute(new ForumTimerTask())
        .submit(MainTest.this);
1 Like

Is it possible to separate the TimerCommandExecutor nested class, into a separate class file? I’ve been trying to split up this class from ForumCountdown, but I can not solve the No enclosing instance of the type ForumCountdown is accessible in scope error, once I move the class over to a new package. I’m trying to keep the class files in their own files, to keep my Main class clean. Thanks.

The important thing is to not copy and paste code, but to understand what it does. submit() is asking for an Object which represents the main plugin class. When it’s a member class of the main class to begin with, you can just use <main class>.this. Once you’ve moved the CommandExecutor to another class, you’d provide it the standard way - put a MainTest argument to the constructor of the executor, and pass that into submit().

1 Like

For the record the main reason everything was in a single class to begin with was so I could make a forum example that didn’t need to be split into multiple files.

import org.spongepowered.api.Sponge;
import org.spongepowered.api.command.CommandException;
import org.spongepowered.api.command.CommandResult;
import org.spongepowered.api.command.CommandSource;
import org.spongepowered.api.command.args.CommandContext;
import org.spongepowered.api.command.spec.CommandExecutor;
import org.spongepowered.api.command.spec.CommandSpec;
import org.spongepowered.api.event.Listener;
import org.spongepowered.api.event.game.state.GameStartingServerEvent;
import org.spongepowered.api.plugin.Plugin;
import org.spongepowered.api.scheduler.Task;
import org.spongepowered.api.text.Text;

import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;

@Plugin(id="au.id.rleach.forumcountdown", name="Forum Countdown", version = "0.0.1")
public class ForumCountdown {
    @Listener
    public void onStart(GameStartingServerEvent init){
        Sponge.getCommandManager()
                .register(
                        this,
                        CommandSpec.builder()
                                .description(Text.of("Broadcasts a 60 second timer"))
                                .executor(new TimerCommandExecutor(this))
                                .build(),
                        "timer"
                );
    }

    private static class TimerCommandExecutor implements CommandExecutor {

        private final ForumCountdown plugin;

        public TimerCommandExecutor(ForumCountdown plugin) {
            this.plugin = plugin;
        }

        @Override
        public CommandResult execute(CommandSource commandSource, CommandContext commandContext) throws CommandException {
            Sponge  .getScheduler()
                    .createTaskBuilder()
                    .interval(1, TimeUnit.SECONDS)
                    .delay(1, TimeUnit.SECONDS)
                    .execute(new ForumTimerTask())
                    .submit(plugin);
            return CommandResult.success();
        }
    }

    private static class ForumTimerTask implements Consumer<Task> {
        private int seconds = 60;
        @Override
        public void accept(Task task) {
            seconds--;
            Sponge  .getGame()
                    .getServer()
                    .getBroadcastChannel()
                    .send(Text.of("Remaining Time: "+seconds+"s"));
            if(seconds < 1) {
                task.cancel();
            }
        }
    }
}

That said, it could also be rewritten to just be inline using java 8 lambda.

package au.id.rleach.testdata;
import org.spongepowered.api.Sponge;
import org.spongepowered.api.command.CommandResult;
import org.spongepowered.api.command.spec.CommandSpec;
import org.spongepowered.api.event.Listener;
import org.spongepowered.api.event.game.state.GameStartingServerEvent;
import org.spongepowered.api.plugin.Plugin;
import org.spongepowered.api.scheduler.Task;
import org.spongepowered.api.text.Text;

import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;

@Plugin(id="au.id.rleach.forumcountdown", name="Forum Countdown", version = "0.0.1")
public class ForumCountdown {
    @Listener
    public void onStart(GameStartingServerEvent init){
        Sponge.getCommandManager()
                .register(
                        this,
                        CommandSpec.builder()
                                .description(Text.of("Broadcasts a 60 second timer"))
                                .executor(
                                        (source, commandContext) -> {
                                            Sponge.getScheduler()
                                                    .createTaskBuilder()
                                                    .interval(1, TimeUnit.SECONDS)
                                                    .delay(1, TimeUnit.SECONDS)
                                                    .execute(new ForumTimerTask())
                                                    .submit(this);
                                            return CommandResult.success();
                                        }
                                )
                                .build(),
                        "timer"
                );
    }
    
    private static class ForumTimerTask implements Consumer<Task> {
        private int seconds = 60;
        @Override
        public void accept(Task task) {
            seconds--;
            Sponge  .getGame()
                    .getServer()
                    .getBroadcastChannel()
                    .send(Text.of("Remaining Time: "+seconds+"s"));
            if(seconds < 1) {
                task.cancel();
            }
        }
    }
}

I can’t remember why I didn’t to begin with, maybe the API has changed or It wasn’t yet supporting Java8 which is now mandatory.

The ForumTimerTask however can’t be factored out to be a lambda, as it affects state that isn’t effectively final, it could probably adjust a field though I’m not 100% sure.

1 Like

I myself would use an AtomicInteger in that case.