Skip to content

refactor: unify move interfaces#2127

Merged
triceo merged 1 commit intoTimefoldAI:mainfrom
triceo:moves
Feb 19, 2026
Merged

refactor: unify move interfaces#2127
triceo merged 1 commit intoTimefoldAI:mainfrom
triceo:moves

Conversation

@triceo
Copy link
Collaborator

@triceo triceo commented Feb 17, 2026

This PR takes both the old and new Move interfaces, and merges them into one.

In order to not require large changes to the existing generic move selectors,
it also introduces AbstractSelectorBasedMove, a non-public extension of Move which allows to deal with move doability. I am adding the SelectoBased prefix to all such move implementations, to distinguish them in profiling and stack traces from the Neighborhoods-based equivalents we either already have or will create.

It also makes large changes to the documentation around moves, and finally exposes Neighborhoods as a documented preview feature.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR unifies the old and new Move interfaces into a single interface hierarchy. It introduces AbstractSelectorBasedMove as a non-public extension to handle move doability for existing generic move selectors. The refactoring involves extensive renaming of move classes (prefixing with "SelectorBased"), updating imports from core.impl.heuristic.move to core.preview.api.move, and updating method signatures throughout the codebase. Additionally, it exposes Neighborhoods as a preview feature and updates documentation references.

Changes:

  • Unified move interfaces with AbstractSelectorBasedMove for backward compatibility
  • Renamed move classes with "SelectorBased" prefix (e.g., ChangeMoveSelectorBasedChangeMove)
  • Updated imports from legacy to preview API package
  • Changed method signatures (rebase, execute, describe)
  • Updated documentation links and preview feature status
  • Removed deprecated classes and adapters

Reviewed changes

Copilot reviewed 157 out of 157 changed files in this pull request and generated no comments.

Show a summary per file
File Description
Documentation files (*.adoc) Updated cross-references and changed Neighborhoods from "research project" to "preview feature"
Benchmark statistic files Updated import statements and class references to use new naming
Test files Extensive renaming of move classes with "SelectorBased" prefix and method signature updates
Core move classes Replaced AbstractMove with AbstractSelectorBasedMove, updated rebase() and execute() signatures
Move selector files Updated to use new move class names and imports
Utility files Removed adapter classes, simplified move handling

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 180 out of 180 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (2)

core/src/test/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SelectorBasedListUnassignMoveTest.java:34

  • This test calls move.execute(scoreDirector) with scoreDirector being an InnerScoreDirector mock, but Move.execute expects a MutableSolutionView (typically MoveDirector), so this should not compile or will fail at runtime if forced via casts.
    core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SelectorBasedSubListUnassignMove.java:103
  • SelectorBasedSubListUnassignMove.getPlanningValues() returns the planningValues field which is nullable and only set during execute(); this violates the non-null return contract and can cause NPEs if getPlanningValues() is called before execute().

Copy link
Contributor

@Christopher-Chianelli Christopher-Chianelli left a comment

Choose a reason for hiding this comment

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

I believe there is a bug in the new SelectorBasedCompositeMove; it has a field Move[] moves, but its rebase expects AbstractSelectorBasedMove (whereas its execute work with both AbstractSelectorBasedMove moves and normal moves).

A lot of suggestions to change Type x = ... to var x = ... where possible.

Once the bug is fixed, LGTM.

Copy link
Contributor

@Christopher-Chianelli Christopher-Chianelli left a comment

Choose a reason for hiding this comment

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

(Double approval since apparently my first approval cause an error on my client)

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 180 out of 180 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (4)

core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SelectorBasedListSwapMove.java:119

  • SelectorBasedListSwapMove should use reference equality (leftEntity == rightEntity) when deciding move doability; using Objects.equals() can incorrectly treat distinct planning entities as the same if the domain overrides equals().
    core/src/main/java/ai/timefold/solver/core/impl/heuristic/selector/move/generic/list/SelectorBasedListSwapMove.java:127
  • SelectorBasedListSwapMove should use reference equality (leftEntity == rightEntity) when deciding whether to treat this as an in-list swap vs cross-entity swap; Objects.equals() may route execution down the wrong branch for entities with custom equals().
    core/src/main/java/ai/timefold/solver/core/impl/heuristic/move/SelectorBasedCompositeMove.java:79
  • SelectorBasedCompositeMove.isMoveDoable() returns false when all child moves are "new" (non-AbstractSelectorBasedMove) moves, even though those moves are considered always doable; include new moves in the doability calculation.
    core/src/main/java/ai/timefold/solver/core/impl/heuristic/move/SelectorBasedCompositeMove.java:91
  • SelectorBasedCompositeMove.execute() calls legacyMove.execute(scoreDirector) directly, which skips triggerVariableListeners() between child moves and can break child moves that rely on up-to-date shadow variables; execute each child via the MoveDirector (or trigger listeners after each child) to preserve the previous behavior.

@sonarqubecloud
Copy link

@triceo triceo merged commit 8f37ec3 into TimefoldAI:main Feb 19, 2026
21 of 22 checks passed
@triceo triceo deleted the moves branch February 19, 2026 07:33
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.

Publish Neighborhoods as a Preview feature

2 participants

Comments