bukkit - Waiting? - java

Recently I started developing a Bukkit Plugin and I need help with sleeping/waiting in my code. I'm kinda new to java so if you could explain it, I would appreciate. Here is my code:
#EventHandler
public void onTnt(PlayerInteractEntityEvent e) {
Player clicker = e.getPlayer();
Player rightclick = (Player) e.getRightClicked();
rightclick.sendMessage(ChatColor.GRAY + "~" + ChatColor.DARK_PURPLE + "AN INSTAKILLER HAS YOU AS A TARGET! SNEAK NOW!");
rightclick.sendMessage(ChatColor.GRAY + "~" + ChatColor.DARK_RED + "AN INSTAKILLER HAS YOU AS A TARGET! SNEAK NOW!");
rightclick.sendMessage(ChatColor.GRAY + "~" + ChatColor.GOLD + "AN INSTAKILLER HAS YOU AS A TARGET! SNEAK NOW!");
/* Then wait 3 sec. I've tried Thread.Sleep, wait() and sleep(), no sucess*/
if (!(rightclick.isSneaking())) {
rightclick.sendMessage(ChatColor.GRAY + "~" + ChatColor.GOLD + "Bye bye.");
rightclick.setHealth(0);
}
}

While normally you'd use Thread.sleep for something like this, that would freeze the entire server, and events wouldn't be processed in that time (so even if the player stopped sneaking on their side, they'd still be sneaking from what the server sees).
What you need to do is use a BukkitRunnable (or alternatively a regular Runnable and getServer().getScheduler().scheduleSyncDelayedTask):
#EventHandler
public void onTnt(PlayerInteractEntityEvent e) {
Player clicker = e.getPlayer();
// Needs to be final to reference it later
final Player rightclick = (Player) e.getRightClicked();
rightclick.sendMessage(ChatColor.GRAY + "~" + ChatColor.DARK_PURPLE + "AN INSTAKILLER HAS YOU AS A TARGET! SNEAK NOW!");
rightclick.sendMessage(ChatColor.GRAY + "~" + ChatColor.DARK_RED + "AN INSTAKILLER HAS YOU AS A TARGET! SNEAK NOW!");
rightclick.sendMessage(ChatColor.GRAY + "~" + ChatColor.GOLD + "AN INSTAKILLER HAS YOU AS A TARGET! SNEAK NOW!");
BukkitRunnable task = new BukkitRunnable() {
#Override
public void run() {
if (!(rightclick.isSneaking())) {
rightclick.sendMessage(ChatColor.GRAY + "~" + ChatColor.GOLD + "Bye bye.");
rightclick.setHealth(0);
}
}
};
// Run the task on this plugin in 3 seconds (60 ticks)
task.runTaskLater(this, 20 * 3);
}

I am aware of the fact that this is an old question, yet I'd like to add something so that users who will be looking for a way to do may find this.
The simplest say to do this is creating a scheduled task:
Bukkit.getServer().getScheduler().scheduleSyncDelayedTask(this, new Runnable() {
// The stuff which should be accomplished after the timer runs out goes here.
// Example:
player.sendMessage("The time has ran out!");
}, time);
Now, let's get through the process of understanding what's going on here. Basically, se have created a delayed task (there are two types of scheduled tasks: delayed and repeating tasks).
The first argument of the method above is this. This is supposed to be an instance of your main class (a subclass of org.bukkit.plugin.java.JavaPlugin). This means that if the scheduled task is not in your main class, you'll need to replace this with an instance of it. The second argument is new Runnable() { ... } here we are actually creating the runnable. Whatever should be executed when the timer runs out, should go inside the curly braces. Lastly, the last parameter: this is the actual delay, measured in TICKS. This means, that if you wish to wait x seconds, you'll have to multiply x * 20, as a tick is equal 1/20 seconds, hence 5 seconds will be equal to 100 ticks.
If you know how to use lambdas, you can replace the anonymous class:
Bukkit.getServer().getScheduler().scheduleSyncDelayedTask(this, () -> player.sendMessage("The time has ran out!"), time);

Related

Bukkit; How to trigger multiple ClickEvent RUN_COMMAND actions with a single click on a single chat message

I've written a rather simple method for my paper/spigot Minecraft server plugin that detects potential lag machines under construction.
The issue I have is that I want to send a single chat message that, when clicked once, will first run a /vanish command on behalf of the clicker.
Then if (and only if) the vanish was successful, I want to run a teleport command to a location included along with the specific instance of the ClickEvent.
Both of those commands need to be completed from the single user click event.
For reference here is the method that calls notifyOps() and includes the TextComponent in question, msg
if (LagMats.contains(blockType) || mat.contains("MINECART") || mat.contains("DOOR")) {
int counter = 0;
for (Material thisMat: LagMats) {
if (thisMat != Material.GRAVEL) {
counter += Utilities.blockCounter(block.getChunk(), thisMat);
}
}
TextComponent warn = new TextComponent("WARN "); warn.setBold(true);
warn.setColor(ChatColor.RED);
TextComponent msg = new TextComponent("Potential lag-machine at " +
block.getX() + ", " + block.getY() + ", " + block.getZ() + " in " + dimension +
" by " + placer_name + " with UUID: " + placer.getUniqueId());
String cmd = "/execute in " + env + " run tp #s " +
block.getX() + " " + block.getY() + " " + block.getZ();
msg.setClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, cmd));
if (counter > 256) {
Utilities.notifyOps(new TextComponent(warn, msg));
}
}
Oh and the actual little code of notifyOps where the TextComponent is used in a message:
// send a message to all online ops and console
public static boolean notifyOps(TextComponent msg) {
if (msg == null) return false;
for (Player thisPlayer: Bukkit.getOnlinePlayers()) {
try {
if (thisPlayer.isOp()) thisPlayer.spigot().sendMessage(msg);
} catch (Exception e) {return false;}
}
System.out.println(msg.getText());
return true;
}
So I want to have the user click just once, run two commands, the second only if the first succeeds, and would be best if it could be done within the try block the message is sent from.
I could write a custom command for this purpose, and then just run that command, but I rather avoid adding classes for such a small addition if it's actually possible and I just have no idea.
Thanks for any advice or help!
There is no way of doing that without writing a custom command...
This is impossible because the ClickEvent and HoverEvent are entirely client-side. That means that there are no packets sent from the Player to the server. Therefore, it is impossible to callback the click of the Player and call a method to perform what you are trying to do.
You may notice that all the ClickEvent.Actions do not affect the server. OPEN_URL, OPEN_FILE, RUN_COMMAND, SUGGEST_COMMAND, CHANGE_PAGE and COPY_TO_CLIPBOARD are all actions taken on the client-side.
The only way here is to make the client send a command to the server which will trigger a method.

Why the Spring Boot controller can't handle other requests if Thread.sleep is used?

When we use the Thread.sleep(...) method in the Spring Boot controller, we cannot accept requests concurrently. All requests can only be processed one by one. Why not every request is processed in parallel?
For example, there is a Thread.sleep(...) in the sleep method. In order to simulate the parallel access to the sleep method, I used the Chrome browser to open multiple tabs to visit http://localhost:8080/async. Observe that multiple tabs are executed in series. Why is it not executed in parallel here?
#RestController
public class AsyncController {
#RequestMapping("async")
public String sleep() throws InterruptedException {
long startTime = System.currentTimeMillis();
System.out.println("[before]name is " + Thread.currentThread().getName() + " time is " + startTime);
Thread.sleep(10000);
long endTime = System.currentTimeMillis();
System.out.println("[after]name is " + Thread.currentThread().getName() + " time is " + endTime + " cos " + (endTime-startTime));
return Thread.currentThread().getName();
}
}
Console printing
[before]name is http-nio-8080-exec-2 time is 1602751326997
[after]name is http-nio-8080-exec-2 time is 1602751337000 cos 10003
[before]name is http-nio-8080-exec-3 time is 1602751337004
[after]name is http-nio-8080-exec-3 time is 1602751347008 cos 10004

Java executor.scheduleAtFixedRate adds up Delay

I am experiencing some issues with the delay of the executor. My thread looks like this:
public void run() {
logger.log("Starting " + tso.getName() + " for " + dataType);
List<String[]> allRows = new ArrayList<String[]>();
String[] lastRow;
LocalDateTime lastDate;;
LocalDateTime lastQuarterHour;
while (true) {
try {
// Make attempt to take CSV data for today. If there is no data wait several seconds and try again
allRows = getCSVRows();
if (allRows == null || allRows.isEmpty()) {
logger.log("Sleeping thread due to empty list for TSO " + tso.getName() + " and data type " + dataType);
Thread.sleep(MILISECONDS_TO_WAIT);
continue;
}
lastRow = allRows.get(allRows.size() - 1);
lastDate = convertStringToUTC(lastRow[0] + " " + lastRow[2]);
lastQuarterHour = takeLastQuorterHourTime();
// If CSV data is available take the last record if it is before last quarter hour wait SEVERAL seconds and try again
if (lastDate.isBefore(lastQuarterHour)) {
logger.log("Sleeping due to lack of information for the current quarter for TSO " + tso.getName() + " and data type " + dataType);
Thread.sleep(MILISECONDS_TO_WAIT);
} else {
break;
}
} catch (InterruptedException e) {
logger.log(e.getMessage());
}
}
}
}
The first time i run my thread the delay is OK, but when the thread sleeps 2 or 3 times, the delay when the next thread cycle starts, is not what is defined in:
executor.scheduleAtFixedRate(extractorThread, 0, WAIT_INTERVAL_MINUTES, TimeUnit.SECONDS);
So, when does the delay start, once the thread finishes its 2-3 sleeps and terminates or when the thread itself is started, no matter how long it works?
It might have something to do with the fact that you're sleeping inside the Thread
Thread.sleep(MILISECONDS_TO_WAIT);
Try removing those and see if the problem still occurs
[EDIT]
The answer to your question is that it will run every X amount of seconds and it counts from when the initial thread/next thread starts but if the thread is not available at the time of the scheduled execution (Because of the thread sleeping or for instance doing very heavy calculations) it will wait for it to become available.

calling a second method while one method call is still in progress causes program to crash

I am having some trouble with a couple of methods in my program: I have a method that is called when the user clicks a start button on the GUI. This method will receive all of the network traffic currently being sent/ received over the network, and display information about it to the user. The user can stop this method being called by clicking the stop button.
This method currently looks like this:
public static void retrieveFilteredPdu(){
/*Try/Catch block copied from 'receivePdu()' method in EspduReceiver.java on 07/05/2014
* Now edit it so that it will receive all PDUs, but only display the ones matching the filter values in the top JTextArea */
try{
/*Specify the socket to receive the data */
EspduReceiver.socket = new MulticastSocket(EspduSender.PORT);
EspduReceiver.address = InetAddress.getByName(EspduSender.DEFAULT_MULTICAST_GROUP);
EspduReceiver.socket.joinGroup(EspduReceiver.address);
//stopCapture = false;
/*Loop infinitely, receiving datagrams */
while(true){
/*First, set the value of the 'stopCapture' boolean to 'false', in case it has previously been set to true during the life of
* the program */
/*stopCapture = false; /*For some reason, if I do this here, clicking the 'Get site' button causes the program to start receiving
PDUs again, and you are not able to stop it without manually shutting down the program. Possibly because I am infinitely
setting the value of 'stopCapture' to false?*/
byte buffer[] = new byte[EspduReceiver.MAX_PDU_SIZE];
EspduReceiver.packet = new DatagramPacket(buffer, buffer.length);
EspduReceiver.socket.receive(EspduReceiver.packet);
Pdu pdu = EspduReceiver.pduFactory.createPdu(EspduReceiver.packet.getData()); /*Moved this line to the top of the class to declare as global variable (29/04/2014) */
if(pdu != null){
System.out.print("Got PDU of type: " + pdu.getClass().getName());
if(pdu instanceof EntityStatePdu){
EntityID eid = ((EntityStatePdu)pdu).getEntityID();
Vector3Double position = ((EntityStatePdu)pdu).getEntityLocation();
System.out.println(" EID:[" + eid.getSite() + ", " + eid.getApplication() + ", " + eid.getEntity() + "] ");
System.out.println("Location in DIS coordinates: [" + position.getX() + ", " + position.getY() + ", " + position.getZ() + "] ");
/*Add PDU to ArrayList of PDUs */
EspduReceiver.espdu.add(pdu);
/* System.out.println(" PDU added to arrayList. ");
System.out.println(espdu); /*This is printing out the actual DIS messages (i.e. edu.nps.moves.dis.EntityState...),
maybe try adding the 'eid.getSite()', etc to an ArrayList instead. Use Associative arrays/ map/ hashmap */
EspduReceiver.entitySite.add(eid.getSite());
System.out.println("Entity Site added to ArrayList from Filter.retrieveFilteredPdu() ");
EspduReceiver.entityApplication.add(eid.getApplication());
System.out.println("Entity Application added to ArrayLIst. ");
EspduReceiver.entity.add(eid.getEntity());
System.out.println("Entity ID added to ArrayList");
/*Check that everything is actually in the ArrayLists
for(int i : entity){ /*Substituted 'entity' with 'entitySite' and 'entityApplication'- values are all printed correctly.
System.out.println(i);
} */
/*07/05/2014
* Write a method that will only append the PDUs that match the filter values to the text area,
* call that method here. */
/*Now append each PDU to the text area */
Gui.displayFilteredOutput.append("\n");
Gui.displayFilteredOutput.append("EID: [" + eid.getSite() + ", " + eid.getApplication() + ", " + eid.getEntity() + "]. ");
Gui.displayFilteredOutput.append("Location in DIS coordinates: [" + position.getX() + ", " + position.getY() + ", " + position.getZ() + "] ");
/*Append every PDU that matches the filter criteria to the displayFilteredOutput JTextArea
Gui.displayFilteredOutput.append("\n");
Gui.displayFilteredOutput.append("EID: [" + eid.getSite() + ", " + eid.getApplication() + ", " + eid.getEntity() + "]. ");
Gui.displayFilteredOutput.append("Location in DIS coordinates: [" + position.getX() + ", " + position.getY() + ", " + position.getZ() + "] "); */
} else if(!(pdu instanceof EntityStatePdu)){
System.out.println("There are no PDUs currently being received.");
}
System.out.println();
}
Thread.sleep(1000);
/*Try adding a boolean to allow me to stop the capture by clicking 'stop' button- Look on stackoverflow */
boolean queryStopCapture = EspduReceiver.stopCapture;
if(queryStopCapture == true){
System.out.println("Break clause in 'queryStopCapture' if statement in EspduReceiver.java has been called. ");
break; /*Use a call to receivePdu() to populate the second JTextArea, but don't let it call a 'break' clause at all.
* Find some other way of adding a 'break' to the output displayed in the first JTextArea (01/05/2014)
* Maybe add this code to receivePdu() call in ActionListener instead of here.
*/
}
} /*end while */
} /*end try */
catch(Exception e){
System.out.println(e);
e.printStackTrace();
System.out.println("Error in retrieveFilteredPdu() method. ");
/*09/04/2014 # 17:100
* If this exception gets called, presumably it either means that pdu is not an instance of EntityStatePdu, or
* that pdu does not actually hold a packet. */
}
}
I have another method, which, in theory, should display information about only the network traffic which has attributes matching some values set by the user. This method is called when the user clicks the 'Filter' button on the GUI. The method currently looks like this:
public static void filterPDUs(){
// if(EspduReceiver.startCapture == true){
/*If EspduReceiver.startCapture is true, then the program is already receiving PDUs, so now I need to set it to only display the ones that
* match the filter criteria. Do this by checking the PDU attributes against the filter values before printing- if they match, then print,
* if not, don't. */
if((EspduReceiver.startCapture == true) && (EspduReceiver.stopCapture == false)){
/*Get the size of the sitesToBeFiltered ArrayList, and store in a variable. Will need to update the variable with every iteration
* of the loop, because the ArrayList will keep growing as long as the capture is running. */
int sitesToBeFilteredSize = sitesToBeFiltered.size();
int matchingSitesIterator = 0;
/*First, check if site filter value matches the PDU's site, if it does, then check Application, if it matches again, then check ID.
* If at any point it doesn't match, exit the while loop. */
while(matchingSitesIterator < sitesToBeFilteredSize){
System.out.println("SitesToBeFiltered.size() = " + sitesToBeFilteredSize);
if(sitesToBeFiltered.get(matchingSitesIterator) == Filter.filter1Value){
if(applicationsToBeFiltered.get(matchingSitesIterator) == Filter.filter2Value){
if(IDsToBeFiltered.get(matchingSitesIterator) == Filter.filter3Value){
Gui.displayFilteredOutput.append("Matching PDU found: [" + sitesToBeFiltered.get(matchingSitesIterator) + ", " + applicationsToBeFiltered.get(matchingSitesIterator) + ", " + IDsToBeFiltered.get(matchingSitesIterator) + "] ");
} else {Gui.displayFilteredOutput.append("Sorry, there were no PDUs found with the specified ID value.");}
Gui.displayFilteredOutput.append("Need to display every PDU that had a matching Site & Application here. ");
}else {Gui.displayFilteredOutput.append("Sorry, there were no PDUs found with the specified Application value.");}
Gui.displayFilteredOutput.append("need to display every PDU that had a matching Site here. ");
}else {Gui.displayOutput.append("Sorry, there were no PDUs found with the specified Site value.");}
}
} else {
Gui.displayFilteredOutput.append("Do something if EspduReceiver.startCapture is not true, and EspduReceiver.stopCapture is not false");
}
// }else if(EspduReceiver.startCapture == false){
/*If EspduReceiver.startCapture is false, then the program is not receiving PDUs, so I now need to call the method that will receive PDUs,
* and display only the ones that match the filter criteria. */
// }
}
The problem I'm having is that for some reason, if the user clicks the 'Filter' button to display only the PDUs with matching filter criteria, after clicking 'Start' (to call the first method), then the program crashes completely... (i.e. it appears to stop performing the function that it was carrying out, and will not respond to clicks on any buttons or even the 'Close window' button)
If they click 'Filter' after having clicked 'Start' & 'Stop' (i.e. when the first method is not running, then the second method performs its tasks as expected.
It seems that for some reason, calling the second method while the first method is still running causes the program to crash- and I can't work out why this is... can anyone point out what's going wrong here?
Edit
I have the following code in my main method:
public static void main(String[] args){
SwingUtilities.invokeLater(new Runnable(){
#Override
public void run(){
Gui gui = new Gui();
gui.setVisible(true);
}
});
}
You need to show more of the code, as others have said, but if you're using Swingworkers or other threads to run these methods, then either the methods have to be thread-safe or they need to be synchronized to prevent their execution from being interleaved. If you're directly accessing Swing components outside the event-dispatching thread, then Swing will make demons come out of your nose, so don't do that (there are some special exceptions for some text components, but generally it's prohibited). If you're not sure, stick assert java.awt.EventQueue.isDispatchThread() before every statement that constructs or uses a Swing object, and enable assertions in the runtime system.
I assume Gui is built on Swing. Read the Swing tutorial, and pay attention to the Event Dispatch Thread. I can't see how the Swing portion of you code works, but I'm guessing you are locking up the EDT somehow. A long-running process needs to run in its own thread and use SwingUtilities.invokeLater() to update Swing components, like your text area.

Java Concurrency issues when updating video control UI while JMF video is playing

I'm building a video player in pure Java around JMF, with completely custom UI controls. Everything was working great until I put in a JLabel which updates the current play time in hh:mm:ss.ss format. The label updates acceptably, but occasionally halts for 10+ seconds at at a time, which is unacceptable.
The JMF Player is created in a SwingUtilities.invokeLater(new Runnable()... block. Here is the UI updating code:
protected void createUIUpdater() {
System.out.println("Creating UI updating worker thread");
new Thread() {
#Override
public void run() {
while(mediaPlayer.getState() == Controller.Started) {
updatePlayPosition();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
System.err.println("Sleep interrupted!");
}
}
System.out.println("UI Updater thread finished.");
}
}.start();
}
protected void updatePlayPosition() {
Movie movie = timelineEditor.getMovie();
movie.setInsertionPoint(Rational.valueOf(mediaPlayer.getMediaTime().getSeconds()));
updateTimeLabel();
}
protected void updateTimeLabel() {
Movie movie = timelineEditor.getMovie();
Rational time = movie == null ? new Rational(0,1) : movie.getInsertionPoint();
// ... hours/minutes/seconds calculated
timeLabel.setText((hours < 10 ? "0" + hours : hours) + ":" + (minutes < 10 ? "0" + minutes : minutes)
+ ":" + (seconds < 10 ? "0" + seconds : seconds)
+ "." + (frame < 10 ? "0" + frame : frame));
}
which is called in a ControllerListener on a StartEvent. During this time the audio/video are playing.
I'm admittedly a bit of a novice when it comes to concurrency in Java, and I'm sure there's a better way of handling this separate thread. Any suggestions?
you could replace your Thread/sleep with ScheduledExecutorService:
http://docs.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/ScheduledExecutorService.html
because when using sleep there is no guarantee as to how long the thread will actually sleep , it may take more the the time you specify to return to the running status.
After closer inspection, it seems that my thread is executing fine, it is just getting blocked occasionally on mediaPlayer.getMediaTime() (where mediaPlayer is a javax.media.Player).
Turns out, calling mediaPlayer.getMediaNanoseconds() does not block.

Categories

Resources