Refactor the Solver class
ClosedPublic

Authored by fabiank on Mar 30 2018, 6:58 PM.

Details

Summary

This is a set of 3 changes:

  1. Simplify the structure of the solver class:
    • make firstMoves and winMoves private members and add getter for them
    • replace mutex protected bool with atomic bool
    • do not directly expose m_shouldEnd, but provide a stopExecution function
    • use in-class initialization of member variables
    • remove debug variable, and the DEBUG_HINTS define which controlled it, use #ifndef NDEBUG to print the debug messages for now
    • Solver: use a unique_ptr for the MemoryManager
  1. Introduce a smaller SolverInterface, hiding the complexity of Solver

This change:

  • extract all the publically used Solver functionality into SolverInterface, which consists only of virtual functions
  • move solverinterface into its own header, to minimize includes
  • converts users which used Solver directly to SolverInterface
  1. Convert Solver into a template, templated over the number of piles
    • The number of piles is known in each Solver subclass at compile time, thus a subclass with n piles does now inherit from Solver<n>.
    • m_number_piles and its setter become redundant with this change, and are removed
    • This enable the conversion of member variables which were dynamically allocated arrays into std::arrays, increasing type safety and causing an abort/exception on out-of-boundary access. Furthermore, the compiler can now better reason about some of the loops (though this doesn't really matter performance wise)
Test Plan

Everything still compiles, and the Solver can still solve each game

Diff Detail

Repository
R410 KPatience
Lint
Automatic diff as part of commit; lint not applicable.
Unit
Automatic diff as part of commit; unit tests not applicable.
fabiank created this revision.Mar 30 2018, 6:58 PM
Restricted Application added a reviewer: KDE Games. · View Herald TranscriptMar 30 2018, 6:58 PM
Restricted Application added a subscriber: KDE Games. · View Herald Transcript
fabiank requested review of this revision.Mar 30 2018, 6:58 PM
fabiank retitled this revision from Simplify the structure of the solver class: to Refactor the Solver class.Mar 30 2018, 7:00 PM
fabiank edited the summary of this revision. (Show Details)
fabiank edited the summary of this revision. (Show Details)
fabiank edited the summary of this revision. (Show Details)Mar 30 2018, 7:02 PM
fabiank edited the summary of this revision. (Show Details)
aacid added a subscriber: aacid.EditedApr 1 2018, 6:34 PM

I'm not totally sold on Solver being a template, imho

class YukonSolver : public Solver<7>

look a bit weird, but i guess i can be convinced :D

patsolve/freecellsolver.cpp
402 ↗(On Diff #30961)

you already have this on the header?

patsolve/patsolve.cpp
986 ↗(On Diff #30961)

Why do you need all this lines?

Anything I can do to convince you that templates are awesome, and we should use them ;-)? My main argument is that this improves type safety and allows to catch off by one errors when indexing the arrays (and it enables iteration with range based for, which I haven't implemented yet, though). It also helped me catch that Q_ASSERT weirdness in D11814, as with the std::array, that assignment became illegal.

However, if you think that the template makes the code more complicated than it was before, I'm willing to remove that part of the change set – the goal of my changes is to make the code easier to maintain after all.

patsolve/patsolve.cpp
986 ↗(On Diff #30961)

I assume you mean all the template class<number> lines? This is mostly about compile times. This file contains the implementation of multiple templated methods, and those must be instantiated at some point, else we'll get an error at link time. There are generally 2 possible ways to achieve this:

  1. This file gets included together with its header file, and the method definitions are visible to its users when the template is instantiated, so everything is alright.
  2. The way I've done it: The method definitions are only visible here, and for each possible user, we instantiate the template. Then at link time, the compiler has emitted the necessary code and the linker can find it. Drawback: Those lines look weird to uninitiated, as you've just demonstrated. Pro: The code doesn't get included multiple times, and when 2 Solver subclasses have the same number of piles, the code gets only generated once. (With the 1st approach, the compiler would create the code once per translation unit, and at link time it would be merged.)

I'm fine with either changing the approach to 1, or adding some documentation to make the code more understandable.

aacid accepted this revision.Apr 23 2018, 8:35 PM

Ok, i guess, after all you're the maintainer now so i shouldn't be blocking your changes ;)

This revision is now accepted and ready to land.Apr 23 2018, 8:35 PM
This revision was automatically updated to reflect the committed changes.