RowVisitor.h 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. /*
  2. * RowVisitor.h
  3. * ------------
  4. * Purpose: Class for recording which rows of a song has already been visited, used for detecting when a module starts to loop.
  5. * Notes : See implementation file.
  6. * Authors: OpenMPT Devs
  7. * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
  8. */
  9. #pragma once
  10. #include "openmpt/all/BuildSettings.hpp"
  11. #include "mpt/base/span.hpp"
  12. #include "Snd_defs.h"
  13. #include <map>
  14. OPENMPT_NAMESPACE_BEGIN
  15. #if defined(MPT_BUILD_DEBUG) || defined(MPT_BUILD_FUZZER)
  16. #define MPT_VERIFY_ROWVISITOR_LOOPSTATE
  17. #endif // MPT_BUILD_DEBUG || MPT_BUILD_FUZZER
  18. class CSoundFile;
  19. class ModSequence;
  20. struct ModChannel;
  21. class RowVisitor
  22. {
  23. protected:
  24. using ChannelStates = mpt::span<const ModChannel>;
  25. class LoopState
  26. {
  27. static constexpr uint64 FNV1a_BASIS = 14695981039346656037ull;
  28. static constexpr uint64 FNV1a_PRIME = 1099511628211ull;
  29. uint64 m_hash = FNV1a_BASIS;
  30. #ifdef MPT_VERIFY_ROWVISITOR_LOOPSTATE
  31. std::vector<std::pair<uint8, uint8>> m_counts; // Actual loop counts to verify equality of hash-based implementation
  32. #endif
  33. public:
  34. LoopState() = default;
  35. LoopState(const ChannelStates &chnState, const bool ignoreRow);
  36. LoopState(const LoopState &) = default;
  37. LoopState(LoopState&&) = default;
  38. LoopState &operator=(const LoopState &) = default;
  39. LoopState &operator=(LoopState&&) = default;
  40. [[nodiscard]] bool operator==(const LoopState &other) const noexcept
  41. {
  42. #ifdef MPT_VERIFY_ROWVISITOR_LOOPSTATE
  43. if((m_counts == other.m_counts) != (m_hash == other.m_hash))
  44. std::abort();
  45. #endif
  46. return m_hash == other.m_hash;
  47. }
  48. [[nodiscard]] bool HasLoops() const noexcept
  49. {
  50. #ifdef MPT_VERIFY_ROWVISITOR_LOOPSTATE
  51. if(m_counts.empty() != (m_hash == FNV1a_BASIS))
  52. std::abort();
  53. #endif
  54. return m_hash != FNV1a_BASIS;
  55. }
  56. };
  57. using LoopStateSet = std::vector<LoopState>;
  58. // Stores for every (order, row) combination in the sequence if it has been visited or not.
  59. std::vector<std::vector<bool>> m_visitedRows;
  60. // Map for each row that's part of a pattern loop which loop states have been visited. Held in a separate data structure because it is sparse data in typical modules.
  61. std::map<std::pair<ORDERINDEX, ROWINDEX>, LoopStateSet> m_visitedLoopStates;
  62. const CSoundFile &m_sndFile;
  63. ROWINDEX m_rowsSpentInLoops = 0;
  64. const SEQUENCEINDEX m_sequence;
  65. public:
  66. RowVisitor(const CSoundFile &sndFile, SEQUENCEINDEX sequence = SEQUENCEINDEX_INVALID);
  67. void MoveVisitedRowsFrom(RowVisitor &other) noexcept;
  68. // Resize / Clear the row vector.
  69. // If reset is true, the vector is not only resized to the required dimensions, but also completely cleared (i.e. all visited rows are unset).
  70. void Initialize(bool reset);
  71. // Mark an order/row combination as visited and returns true if it was visited before.
  72. bool Visit(ORDERINDEX ord, ROWINDEX row, const ChannelStates &chnState, bool ignoreRow);
  73. // Find the first row that has not been played yet.
  74. // The order and row is stored in the order and row variables on success, on failure they contain invalid values.
  75. // If onlyUnplayedPatterns is true (default), only completely unplayed patterns are considered, otherwise a song can start anywhere.
  76. // Function returns true on success.
  77. [[nodiscard]] bool GetFirstUnvisitedRow(ORDERINDEX &order, ROWINDEX &row, bool onlyUnplayedPatterns) const;
  78. // Pattern loops can stack up exponentially, which can cause an effectively infinite amount of time to be spent on evaluating them.
  79. // If this function returns true, module evaluation should be aborted because the pattern loops appear to be too complex.
  80. [[nodiscard]] bool ModuleTooComplex(ROWINDEX threshold) const noexcept { return m_rowsSpentInLoops >= threshold; }
  81. void ResetComplexity() { m_rowsSpentInLoops = 0; }
  82. protected:
  83. // Get the needed vector size for a given pattern.
  84. [[nodiscard]] ROWINDEX VisitedRowsVectorSize(PATTERNINDEX pattern) const noexcept;
  85. [[nodiscard]] const ModSequence &Order() const;
  86. };
  87. OPENMPT_NAMESPACE_END