Skip to content

feat: TimerActionComponent to schedule actions per entity.#1469

Open
michqql wants to merge 2 commits intoAlmasB:devfrom
michqql:dev
Open

feat: TimerActionComponent to schedule actions per entity.#1469
michqql wants to merge 2 commits intoAlmasB:devfrom
michqql:dev

Conversation

@michqql
Copy link
Copy Markdown

@michqql michqql commented Mar 23, 2026

Addresses issue #1439.

Summary

Component that wraps a Timer object and updates timer actions dependent on the entities time per frame value. This component can be used to schedule actions with either a delay or interval, with overloaded functions matching the underlying Timer class.

A PongVsComputerApp sample is included to see example usage of the TimerActionComponent.

Features

Includes the same overloaded methods as Timer:

  • runAtInterval(Runnable action, Duration interval)
  • runAtInterval(Runnable action, Duration interval, int limit)
  • runAtIntervalWhile(Runnable action, Duration interval, ReadOnlyBooleanProperty whileCondition)
  • runOnceAfter(Runnable action, Duration delay)
  • clear()

Testing

TimerActionComponentTest includes 12 tests to verify the actions are executed. However these tests try not to duplicate TimerActionTest.

Example Usage

(From the sample)

TimerActionComponent timerComponent = new TimerActionComponent();

/* Move the Y position of the computer bat to the Y position of the ball */
timerComponent.runAtInterval(() -> {
    Entity ball = timerComponent.getEntity().getObject("ballEntity");
    timerComponent.getEntity().setY(ball.getY() - timerComponent.getEntity().getHeight() / 2f);
}, Duration.seconds(1 / 60f));

entityBuilder()
        .at(x, y)
        .viewWithBBox(new Rectangle(PADDLE_WIDTH, PADDLE_HEIGHT))
        .with(timerComponent)
        .with("ballEntity", ball)
        .buildAndAttach();

With TimerActionComponentTest for tests and sample application (PongVsComputerApp) to see usage.
Copy link
Copy Markdown
Owner

@AlmasB AlmasB left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Many thanks for this PR, it looks good.

  1. Please can you check the comments below.
  2. Also the checks seem to be failing, could you take a look please.

*
* @author Michael Pearson (<a href="https://github.com/michqql/">https://github.com/michqql/</a>)
*/
public class TimerActionComponent extends Component {
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be fine to make it final by default. It can be opened in the future if required


@Override
public void onUpdate(double tpf) {
super.onUpdate(tpf);
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

super.onUpdate(tpf); calls Component onUpdate(), which I think may be empty. If it is, then we can safely remove this line.

import javafx.util.Duration;

/**
* Component to schedule the execution of actions.
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Worth noting that the timer's clock will be tied to the entity's clock. So 1 second in this TimerActionComponent will depend on how long 1 second is for the entity, e.g. TimeComponent may alter that behaviour (which is the correct desirable behaviour). Not quite sure exactly the wording for the doc, but something along these lines should do the trick.

private static class Counter {
private int value;

int get () { return value ; }
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This formatting is largely inconsistent with the rest of the project's formatting. This will need to be updated to follow the standard Java guidelines. Though I think the fix for the above comment will remove the need for this altogether.

}

private final Counter executionCounter = new Counter();
private final Runnable action = executionCounter::increment;
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment about formatting Line 34 and 35 as above. There is no need to align elements.

public class TimerActionComponentTest {

/* Counter class to allow for atomic operations from Runnable action */
private static class Counter {
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's fine to use such a helper class, but best to avoid using the same object from multiple tests in this way. For example, if the test environment runs the tests in parallel (I don't think it does, however we should try best practice where it's easy to do so), there may be an issue with multiple threads writing and reading the same data.

This would mean simply using an int count = 0. If this doesn't work because of lambda functions, you can use var count = new SimpleIntegerProperty(), in each test (like you have done in public void testRunAtIntervalConditionalCancelledEarly()). This should give you more or less the same functionality, but in a relatively safer way.

Amended comments in TimerActionComponent and changed the TimerActionComponentTest.
@michqql
Copy link
Copy Markdown
Author

michqql commented Apr 14, 2026

Commit today:

  • Made TimerActionComponent class final
  • Removed call to super#onUpdate in TimerActionComponent
  • Reworded documentation in TimerActionComponent class
  • Fixed formatting in TimerActionComponentTest
  • Removed member variables from TimerActionComponentTest to support parallelism

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants