1

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;

    }
});
Alykoff Gali
  • 1,121
  • 17
  • 32
Pythogen
  • 591
  • 5
  • 25
  • You shouldn't use `ArrayList` in cuncurrent programm. See alternative classes `CopyOnWriteArrayLis`, `ConcurrentLinkedQueue`, ... – Alykoff Gali Nov 18 '16 at 05:54

4 Answers4

1

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.

teppic
  • 7,051
  • 1
  • 29
  • 35
  • Very interesting idea about abstracting the game timeline, but when you say a scheduled event could be pulled out and executed... I am not exactly sure how to tell the event what to execute without running into my current problem. Could you maybe give an example and or reference some links about how to go about this? This seems like a good route. – Pythogen Nov 19 '16 at 01:32
0

I'm not exactly sure what you're trying to accomplish, but it sounds like you may be interested in the ScheduledExecutorService. This allows you to submit Runnables or Callables to be played at specific times in the future. They also automatically remove themselves from the Queue when the event is finished.

DeeV
  • 35,865
  • 9
  • 108
  • 95
0

This doesn't address it? It sounds like canceling and recreating TimerTasks to simulate a pause is what folks have done:

Pausing/stopping and starting/resuming Java TimerTask continuously?

Community
  • 1
  • 1
Jim Weaver
  • 983
  • 5
  • 15
0

As this is Android you can't be sure that after an onPause call your application is not completely removed from memory. So the best way is to use something like ScheduledExecutorService. Cancel a scheduled Runnable when onPause event occures. When onResume is called, simply schedule the same Runnable again.

Anything else could result in problems with Android's application model. Applications on pause state can be removed from memory. So you might have to implement save state for your custom timer.

brummfondel
  • 1,202
  • 1
  • 8
  • 11
  • Good point, but fortunately I only need the pause feature for if the user switches apps real quick, or if I make a pause button, etc. I do not need to resume from the exact time the application is recycled, for the game will have checkpoints it is saved at. – Pythogen Nov 18 '16 at 14:35