Fixed Timer tasks being run after cancellation. #7393
Merged
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Timer considers a task "executed" when the timer thread posts it via
Gdx.app.postRunnable
. Code on the app thread may cancel a task that has been posted but not executed yet. It is then very confusing for the app to see a taskrun
after it has been cancelled. To be safe, app code needs to check state for every task execution to make sure it is valid. This can be difficult, eg if a task is cancelled and rescheduled, the app could see both the cancelled and rescheduled execution.We need a way for Timer to post runnables but still be able to remove them before they execute, if needed. This PR does that by having the timer post a single runnable
runPostedTasks
whenever there are one or more tasks that need to be executed.runPostedTasks
then executes each task inArray<Task> postedTasks
. When a task is cancelled, it is removed frompostedTasks
, solving the "executed after cancel" problem. From the app code perspective, everything should work as it did before.postedTasks
is stored in TimerThread (ie globally) rather than Timer because once a task is no longer scheduled to execute in the future, Taskreset
is called andtimer
set to null. It no longer knows about the timer, so wouldn't be able to remove itself frompostedTasks
on the timer.New synchronization was added around
postedTasks
, which is synchronized when running tasks. That means a timer posting a new task must wait while all thepostedTasks
execute. This is probably OK, as tasks aren't generally long running and a newly added task won't be serviced until next frame anyway. If it were important, we could do like the LWJGL backends that useArray<Runnable> runnables, executedRunnables
, synchronizing only long enough to copy the tasks that will run.This PR also adds more lovely Timer tests.