void () { stat.count.incrementAndGet(); // There's a penalty for run a task. // To decrease cumulative penalty difference between real start // and expected is subtracted from the next delay long wasLate = System.nanoTime() - nextScheduleTime; if (wasLate < 0) { LOG.warn("Negative animation late value"); wasLate = 0; } if (taskId < myRunning.get()) { finalizeRunning(); for (Animation animation : scheduledAnimations) { animation.fireEvent(Animation.Phase.CANCELLED); } return; } if (frameCounter == null) { prepareAnimations(); } long totalFrames = frameCounter.getTotalFrames(); long currentFrame = Math.min(frameCounter.getNextFrame(cycle), totalFrames); long currentDelay = frameCounter.getDelay(currentFrame); double timeline = (double) currentFrame / totalFrames; if (currentFrame >= totalFrames && cycle) { frameCounter = null; } final var expired = new LinkedList<Animation>(); for (Animation animation : scheduledAnimations) { double start = (double) (animation.getDelay() - delay) / duration; double end = start + (double) animation.getDuration() / duration; if (start <= timeline) try { double current = (timeline - start) / (end - start); animation.update(MathUtil.clamp(current, 0.0, 1.0)); animation.fireEvent(Animation.Phase.UPDATED); } catch (Throwable t) { LOG.error(t); } if (timeline > end) { expired.add(animation); } } expired.forEach(scheduledAnimations::remove); boolean isProceed = currentFrame < totalFrames || cycle; if (isProceed) { long nextDelay = Math.max(TimeUnit.MILLISECONDS.toNanos(currentDelay) - wasLate, TimeUnit.MILLISECONDS.toNanos(1)); nextScheduleTime = System.nanoTime() + nextDelay; myCurrentAnimatorFuture = myService.schedule(this, nextDelay, TimeUnit.NANOSECONDS); } else { // There's a situation when a new task is submitted but current is already in progress. // For example, the task can be submitted with Animation#runWhenExpired, // but this code synchronously can fire animate, therefore myRunning increases. // If this situation happens the current one ID is abandoned, // because the value is increased somewhere else. myRunning.compareAndSet(taskId, taskId + 1); //var debugInfo = "Animation total time is " + // TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - animationStarted) + // " ms; requested time is " + // (delay + duration); //LOG.info(debugInfo); finalizeRunning(); } // we should fire events after taskId is updated // and some final activity is done by calling finalizeRunning for (Animation animation : isProceed ? expired : scheduledAnimations) { animation.fireEvent(Animation.Phase.EXPIRED); } }