What is the best way to handle delayed events in Java?

advertisements

I am trying to make a simple game in android studio, and I need to be able to delay certain events.

I started out by doing some googling and found that Timer with TimerTask seemed to be a pretty good option. However, if my activity calls onPause there is no Timer.pause, I have to cancel the whole thing.

So, I decided to go ahead and create my own class that would handle the events for me, and support pausing.

I made a simple class (EventHandler) that creates "event(s)" on users command, and cycles through an ArrayList of events every 10 milliseconds to see if System.currentTimeMillis >= eventFinishedTime. If the event is complete, then it calls an inteface method and the event removes itself from the ArrayList.

But now I run into a new issue.

The EventHandler calls an interface method (onFinished) when the event is finished, so I can't use a variable that isn't declared final in onFinished. The only way around this, that I can find, is to make a new method for every time I want to delay an event, which seems like bad practice.

So my question is, What is the best way to do this, or how would you do it?

Feel free to ask if you would like to see my code, just specify which part:) Also feel free to ask for more information... I summed up quite a bit and am more than willing to try to explain further, with examples.

Thanks!

Here is the EventHandler.class (I didn't include the imports...Scroll to the bottom to see where the interface method is called):

public class EventHandler extends Thread {
    //Constants
    final String TAG = "EventHandler";
    final long WAIT_TIME = 10;

    public ArrayList<Event> events = new ArrayList<>(); //Every WAIT_TIME the run() funtion cycles through this list and checks if any events are complete
    public boolean runChecks = true; //If true, the run() function goes (It's only false while the DoDayActivity tells it to pause
    public long pauseStartTime; //This value tags the System.currentTimeMillis() @pauseCheck
    public long totalPausedTime = 0; //This value contains how long the EventHandler was paused
    Activity activity;

    public EventHandler(Activity activity) {
        this.activity = activity;
    }

    public void run() {
        //checking the listeners
        while (true) {
            if (runChecks) {//Making sure the timer isn't paused
                checkListeners();
            }
            try {
                Thread.sleep(WAIT_TIME); //Yes I am using Thread.sleep(), kill me
            } catch (Exception ignore) {
            }
        }

    }

    public interface OnEventListener {
        void onFinished();
    }

    public void createEvent(String name, long milliseconds, OnEventListener eventListener) {//This is how an event is created, see the private Event class below
        new Event(this, name, milliseconds, eventListener);
    }

    public void checkListeners() {
        for (Event event : events) {
            event.amIFinished();//A method that checks if the event has reached its end time
        }
    }

    public void pauseCheck() { //"Pauses" the timer (Probably not the best way, but it is simple and does what I need it to
        runChecks = false;
        pauseStartTime = System.currentTimeMillis();
    }

    public void resumeCheck() {//Resumes the timer by adding the amount of time the EventHandler was paused for to the end if each event
        try {
            if ((pauseStartTime > 99999999)) {//For some reason, when an activity is created, onResume is called, so I added this in there to prevent glicthes
                totalPausedTime = System.currentTimeMillis() - pauseStartTime;
                Log.d(TAG, "Resuming, adding " + String.valueOf(totalPausedTime) + " milliseconds to each event");
                for (Event e : events) {
                    e.end += totalPausedTime;
                }
            }
        } catch (Exception e) {
            Log.w(TAG, "During resume, EventHandler tried to add time to each of the events, but couldn't!");
            e.printStackTrace();
        }

        runChecks = true;

    }

    private class Event { //Here is the class for the event
        public long start;
        public long end;
        OnEventListener listener;
        EventHandler parent;
        String name;

        public Event(EventHandler parent, String name, long milliseconds, OnEventListener listener) {
            start = System.currentTimeMillis();
            end = start + milliseconds;
            this.listener = listener;
            this.parent = parent;
            this.name = name;

            //Adding itself to the ArrayList
            parent.events.add(this);
        }

        public void amIFinished() {//Method that checks if the event is completed
            if (System.currentTimeMillis() >= end) {//Removes itself from the arraylist and calls onFinished
                Log.d(TAG, "Completed " + name);
                parent.events.remove(this);
                activity.runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        listener.onFinished(); //This is where the interface method is called!
                    }
                });
            }
        }
    }
}

Here is where I try to use it (This is just an example using int x):

int x = 0;

eventHandler = new EventHandler(this);
eventHandler.start();
eventHandler.createEvent("Change X Value", 800, new EventHandler.OnEventListener() {
    @Override
    public void onFinished() {
        //x is not declared final so it will not work
        x = 5;

    }
});


It's only the the reference that needs to be effectively final; it's OK to change the state of an object from an inner class:

AtomicInteger x = new AtomicInteger(0);

eventHandler = new EventHandler(this);
eventHandler.start();
eventHandler.createEvent("Change X Value", 800, new EventHandler.OnEventListener() {
    @Override
    public void onFinished() {
        // x is effectively final so we can reference it
        x.set(5);
    }
});

Or... using a lambda

eventHandler.createEvent("Change X Value", 800, () -> x.set(5));

If I was doing it I'd abstract the game time line. Increment a tick counter in your main loop and process events as they become due in game time, not real-time.

Events could then be scheduled by adding them to a TreeSet that sorts by game time, and pulled out of the set and executed by the main loop when they become due.