This site requires JavaScript, please enable it in your browser!
Greenfoot back
lehrerfreund
lehrerfreund wrote ...

2022/7/23

Check for mouse click without act()-method running

lehrerfreund lehrerfreund

2022/7/23

#
The title says it all :-) My scenario is operated purely by clicking the Act-button, the Run-button is never used. But I need buttons e.g. to start and stop music playing. I tried the following in my Button-class:
    public Button()
    {
        this.waitForButtonClicked();
    }
    private void waitForButtonClicked() {
       while(!Greenfoot.mouseClicked(this)) {
            this.waitForButtonClicked();
        }
        System.out.println("Button was clicked");
        this.waitForButtonClicked();
    }
But this leads to a Stackoverflow-error. Is there any other solution in sight? Thanks in advance!
RcCookie RcCookie

2022/7/23

#
Well the StackOverflowError could be easily fixed: just use a loop instead of recursion (can always be done on end-recursion). However, that doesn’t fix the main problem: while that method is running, nothing else can. Also, Greenfoot.mousePressed() is designed to be used while the scenario is running; it will only reset to false in the next frame. You should try to not use the act button, instead you should use a custom button to execute whatever you have in the act method currently. You could also try to set the execution speed to the minimum using Greenfoot.setSpeed(1) and, on a mouse click, Set the speed to 100 for a single frame to update the mouse. But I think that would be unnecessary complicated.
lehrerfreund lehrerfreund

2022/7/24

#
Thanks, RcCookie, this …
RcCookie wrote...
You should try to not use the act button, instead you should use a custom button to execute whatever you have in the act method currently.
is a very smart and appealing idea, and I immediately would realize it. So you could pretend that the scenario is not running although it does! :-) My problem is that the act-methode must not run because I want to use loops. And using for-loops while the scenario is running leads to problems (because the execution of the act-cycles are blocked until the loops are finished). I thought about solving the problem with Threads but I lack the approach …
RcCookie RcCookie

2022/7/24

#
lehrerfreund wrote...
My problem is that the act-methode must not run because I want to use loops. And using for-loops while the scenario is running leads to problems (because the execution of the act-cycles are blocked until the loops are finished).
I see the point. Could you explain further what these loops do? Maybe you should try using counters instead of loops and run one (or multiple) iterations in each act call. Normally, you don’t want any blocking code in your Greenfoot scenario, at least not on the main thread. Which brings us to your second point.
lehrerfreund wrote...
I thought about solving the problem with Threads but I lack the approach …
Threads could be a solution. Here‘s how to to start a thread:
new Thread(() -> {
    // Your code here
}).start();
However, watch out not to produce any race conditions if you work with multiple threads.
RcCookie RcCookie

2022/7/24

#
lehrerfreund wrote...
because the execution of the act-cycles are blocked until the loops are finished
I don’t think that’s a problem, because you don’t use the act method for anything else
lehrerfreund lehrerfreund

2022/7/24

#
I need the loops because I have to teach my students how for- and while-loops work. So no workaround possible :-) Actually loops block the act-cycle. While the loop is executed you are not able to interact with the scenario (neither are other methods executed). How could the approach with the Thread realized? I mean, I want the Thread to run permanently and to listen if the button is clicked. Do you have any idea?
RcCookie RcCookie

2022/7/24

#
The simplest idea would be to just run the code in the act method in a new thread. Then you can continue to use the act button to initiate the execution, but all it does is running a different method. This method should then actually contain the loop code. Thus, your students would need to extend a subclass of Actor that contains a different method:
import greenfoot.Actor;
public abstract class AsyncActor extends Actor implements Runnable {
    // Final to prevent accidental override of this method
    public final void act() {
        // 'this' can be passed because we are a Runnable
        new Thread(this).start();
    }
}
Your students would need this class in their scenario. Then, they should code like this:
public class MyActor extends AsyncActor {
    public void run() {
        // Some blocking loop stuff
    }
}
RcCookie RcCookie

2022/7/24

#
However, there raise a few problems when using your own threads. First, when the act button is clicked again before the old execution is done, they will run at the same time, which you will likely not want, because that may lead to unexpected output and race conditions. Thus, kill the old thread before you start a new one:
public abstract class AsyncActor extends Actor implements Runnable {
    private Thread thread = null;
    public final void act() {
        if(thread != null && thread.isAlive())
            thread.stop();
        (thread = new Thread(this)).start();
    }
}
RcCookie RcCookie

2022/7/24

#
The other problem is that, because Greenfoot does not restart the JVM every time you press "Reset", threads will remain running until you restart Greenfoot. To fix this, we have to keep track of the actors that were created and stop threads of actors that are not on the active world. Putting it all together:
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import greenfoot.Actor;
import greenfoot.World;
import greenfoot.core.WorldHandler;

/**
 * An actor that executes its act method in a new thread and manages
 * created threads.
 * 
 * @author RcCookie
 */
public abstract class AsyncActor extends Actor implements Runnable {

    private static Set<AsyncActor> ACTIVE = new HashSet<>();

    static {
        Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(() -> {
            World w = WorldHandler.getInstance().getWorld();
            ACTIVE.removeIf(a -> {
            if(a.getWorld() != w) {
                if(a.thread != null && a.thread.isAlive())
                    a.thread.stop();
                return true;
            }
            return false;
        });
        }, 100, 100, TimeUnit.MILLISECONDS);
    }

    private Thread thread = null;

    protected void addedToWorld(World w) {
        ACTIVE.add(this);
    }

    public final void act() {
        if(thread != null && thread.isAlive())
            thread.stop();
        (thread = new Thread(this)).start();
    }
}
You need to login to post a reply.