diff --git a/GPA675Lab1GOL/GOLTeamH.cpp b/GPA675Lab1GOL/GOLTeamH.cpp index 0a7a308..00d903a 100644 --- a/GPA675Lab1GOL/GOLTeamH.cpp +++ b/GPA675Lab1GOL/GOLTeamH.cpp @@ -1,4 +1,4 @@ -#include "GOLTeamH.h" +#include "GOLTeamH.h" @@ -42,11 +42,11 @@ GOL::Statistics GOLTeamH::statistics() const .width = width(), .height = height(), .totalCells = size(), - .iteration = mIteration, - .totalDeadAbs = mData.totalDead(), - .totalAliveAbs = mData.totalAlive(), - .totalDeadRel = mData.totalDeadRel(), - .totalAliveRel = mData.totalAliveRel() + .iteration = mIteration + //.totalDeadAbs = mData.totalDead(), + //.totalAliveAbs = mData.totalAlive(), + //.totalDeadRel = mData.totalDeadRel(), + //.totalAliveRel = mData.totalAliveRel() // .tendencyAbs = ..., // .tendencyRel = ... }); @@ -146,11 +146,11 @@ bool GOLTeamH::setRule(std::string const& rule) { mRule = rule; bool firstPart{ true }; - std::bitset<9> parsedRuleRevive, parsedRuleSurvive; + uint16_t parsedRuleRevive{}, parsedRuleSurvive{}; // On vérifie que la chaine de charactères contient un B au début. // 5 = taille minimale - if (rule.size() < 5 || !(rule[0] == 'B' || rule[0] == 'b')) + if (rule.size() < 3 || !(rule[0] == 'B' || rule[0] == 'b')) return false; for (size_t i{ 1 }; i < rule.length(); i++) { @@ -160,9 +160,9 @@ bool GOLTeamH::setRule(std::string const& rule) // Si c'est un chiffre, on continue en enregistrant la valeur. if (opt.has_value()) { if (firstPart) - parsedRuleRevive.set(opt.value()); + parsedRuleRevive |= 1u << opt.value(); else - parsedRuleSurvive.set(opt.value()); + parsedRuleSurvive |= 1u << opt.value(); continue; } @@ -179,14 +179,9 @@ bool GOLTeamH::setRule(std::string const& rule) return false; } - // Si les les deux règles ont au moins un nombre, alors OK. - if (parsedRuleRevive.any() && parsedRuleSurvive.any()) { - mParsedRuleRevive = parsedRuleRevive; - mParsedRuleSurvive = parsedRuleSurvive; - return true; - } - else - return false; + mParsedRuleRevive = parsedRuleRevive; + mParsedRuleSurvive = parsedRuleSurvive; + return true; } //! \brief Mutateur modifiant la stratégie de gestion de bord. @@ -219,31 +214,23 @@ bool GOLTeamH::setRule(std::string const& rule) //! **grille**. void GOLTeamH::setBorderManagement(BorderManagement borderManagement) { - mBorderManagement = borderManagement; // On assigne la nouvelle stratégie - // Logique spécifique à chaque stratégie de gestion de bord - switch (borderManagement) - { - case BorderManagement::immutableAsIs: - - break; + mBorderManagement = borderManagement; + mIteration = 0; - case BorderManagement::foreverDead: - mData.setBorderValue(State::dead); + switch (borderManagement) { + case GOL::BorderManagement::foreverDead: + mData.fillBorder(GridTeamH::CellType::dead); break; - - case BorderManagement::foreverAlive: - mData.setBorderValue(State::alive); + case GOL::BorderManagement::foreverAlive: + mData.fillBorder(GridTeamH::CellType::alive); break; - - case BorderManagement::warping: - + case GOL::BorderManagement::warping: + mData.fillBorderWarped(); break; - - case BorderManagement::mirror: - + case GOL::BorderManagement::mirror: + mData.fillBorderMirror(); break; } - mIteration = 0; } @@ -272,7 +259,7 @@ void GOLTeamH::setState(int x, int y, State state) //! \param state L'état d'initialisation des cellules. void GOLTeamH::fill(State state) { - mData.fill(state); + mData.fill(state, mBorderManagement == GOL::BorderManagement::immutableAsIs); mIteration = 0; } @@ -288,7 +275,7 @@ void GOLTeamH::fill(State state) //! \param firstCell L'état de la première cellule. void GOLTeamH::fillAlternately(State firstCell) { - mData.fillAternately(firstCell); + mData.fillAternately(firstCell, mBorderManagement == GOL::BorderManagement::immutableAsIs); mIteration = 0; } @@ -305,7 +292,7 @@ void GOLTeamH::fillAlternately(State firstCell) //! vivante. La valeur doit être comprise entre 0.0 et 1.0 inclusivement. void GOLTeamH::randomize(double percentAlive) { - mData.randomize(percentAlive); + mData.randomize(percentAlive, mBorderManagement == GOL::BorderManagement::immutableAsIs); mIteration = 0; } @@ -326,7 +313,9 @@ void GOLTeamH::randomize(double percentAlive) //! \param centerX La coordonnée en x de la grille où se trouve centré le patron. //! \param centerY La coordonnée en y de la grille où se trouve centré le patron. //! \return true si le patron est valide, false sinon. - //! + //! + +// TODO bool GOLTeamH::setFromPattern(std::string const& pattern, int centerX, int centerY) { mIteration = 0; @@ -343,6 +332,8 @@ bool GOLTeamH::setFromPattern(std::string const& pattern, int centerX, int cente //! //! \param pattern Le patron à appliquer. //! \return true si le patron est valide, false sinon. + +// TODO bool GOLTeamH::setFromPattern(std::string const& pattern) { mIteration = 0; @@ -383,14 +374,71 @@ void GOLTeamH::setSolidColor(State state, Color const& color) //! nouvel état de chaque cellule suivant l'état précédent. Les statistiques //! doivent tenir compte de cette évolution. -// TODO: performance void GOLTeamH::processOneStep() { - // On commence à itérer sur les côtés. En règlant ces cas particuliers, on - // peut éviter des branches dans la boucle principale. Le branch predictor - // aime cela. - for (size_t i{}; i < mData.width(); ++i) { - + // Pour des raisons de performance, on accède à la grille interne en faisant + // des calculs pour le border manuellement. + auto& grid{ mData.data() }; + auto& intGrid{ mData.intData() }; + + if (mBorderManagement == GOL::BorderManagement::foreverDead) { + size_t aliveCount{}; + size_t offset{ width() + 2 }; + auto* ptrGrid{ &grid[0] }; // Pointeur qui se promène en mémoire. + auto* const ptrGridInt{ &intGrid[0] }; // Pointeur statique pour des calculs. + + for (size_t i{}; i < mData.realSize() - offset - 1; ++i) { + + if (mData.isInBorder(i)) { + ptrGrid++; + continue; + } + + aliveCount = 0; + + // On prend avantage du fait que GOL::State::alive = 1 et donc + // on retire des branches if. + + // Top + ptrGrid -= offset + 1; + aliveCount += static_cast (*ptrGrid); + ptrGrid++; + aliveCount += static_cast (*ptrGrid); + ptrGrid++; + aliveCount += static_cast (*ptrGrid); + + // Milieu + ptrGrid += offset - 2; + aliveCount += static_cast (*ptrGrid); + ptrGrid += 2; + aliveCount += static_cast (*ptrGrid); + + + // Dessous + ptrGrid += offset - 2; + aliveCount += static_cast (*ptrGrid); + ptrGrid++; + aliveCount += static_cast (*ptrGrid); + ptrGrid++; + aliveCount += static_cast (*ptrGrid); + + // On retourne à une place plus loin qu'à l'origine. + ptrGrid -= offset; + + // On prend avantage du fait que GOL::State::alive = 1. + // + // On évite aussi d'utiliser l'opérateur []. En profilant, nous avons vu un + // impact de performance de ~5%. + if (*(ptrGrid - 1) == GOL::State::alive) + *(ptrGridInt + i) = static_cast(static_cast(mParsedRuleSurvive & (1u << aliveCount))); + else + *(ptrGridInt + i) = static_cast(static_cast(mParsedRuleRevive & (1u << aliveCount))); + + } + ptrGrid = nullptr; + + mData.switchToIntermediate(); + mIteration.value()++; } } @@ -436,29 +484,30 @@ void GOLTeamH::updateImage(uint32_t* buffer, size_t buffer_size) const auto s_ptr = buffer; auto e_ptr = &buffer[buffer_size]; + auto& grid = mData.data(); - // On itère sur chaque éléments du tableau et on associe la couleur. - for (const auto& i : mData.data()) { - if (i == GridTeamH::CellType::alive) { - *s_ptr &= 0; // Clear - *s_ptr |= MAX_ALPHA << 24; // Alpha = 255 - *s_ptr |= mAliveColor.red << 16; - *s_ptr |= mAliveColor.green << 8; - *s_ptr |= mAliveColor.blue; - } - else { - *s_ptr &= 0; - *s_ptr |= MAX_ALPHA << 24; - *s_ptr |= mDeadColor.red << 16; - *s_ptr |= mDeadColor.green << 8; - *s_ptr |= mDeadColor.blue; - } + // On itère sur chaque éléments du tableau et on associe la couleur. + for (size_t index{ width() + 2 }; // On ignore la ligne du bas. + index < (width() + 2) * (height() + 1); // On ignore la ligne du haut. + index++) { + + if (mData.isInBorder(index)) + continue; + + auto var = static_cast(grid[index]); + + *s_ptr &= 0; // Clear + *s_ptr |= MAX_ALPHA << 24; // Alpha = 255 + + *s_ptr |= mAliveColor.red * var << 16; + *s_ptr |= mAliveColor.green * var << 8; + *s_ptr |= mAliveColor.blue * var; + + *s_ptr |= mDeadColor.red * (1 - var) << 16; + *s_ptr |= mDeadColor.green * (1 - var) << 8; + *s_ptr |= mDeadColor.blue * (1 - var); s_ptr++; - - // Sanity check, pour éviter des problèmes - if (s_ptr >= e_ptr) - break; } } diff --git a/GPA675Lab1GOL/GOLTeamH.h b/GPA675Lab1GOL/GOLTeamH.h index a4aee27..faff16a 100644 --- a/GPA675Lab1GOL/GOLTeamH.h +++ b/GPA675Lab1GOL/GOLTeamH.h @@ -1,4 +1,5 @@ -#pragma once +#pragma once + #include #include #include @@ -11,14 +12,13 @@ constexpr unsigned char MAX_ALPHA = 255; class GOLTeamH : public GOL { public: - size_t width() const override { return mData.width(); } - size_t height() const override { return mData.height(); } - size_t size() const override { return mData.size(); } - State state(int x, int y) const override { return mData.value(x, y); } //details L'état d'une cellule peut être mort ou vivant. - std::string rule() const override { return mRule.value_or(std::move(std::string())); } //details La règle est une chaîne de caractères qui contient les règles de survie et de naissance. - BorderManagement borderManagement() const override { return mBorderManagement.value_or(GOL::BorderManagement::immutableAsIs);} //details La gestion des bords est une énumération qui contient les différents types de gestion des bords. - Color color(State state) const override { return state == GOL::State::alive ? mAliveColor : mDeadColor; } - //Accesseur dans le .cpp + size_t width() const override; + size_t height() const override; + size_t size() const override; + State state(int x, int y) const override; + std::string rule() const override; + BorderManagement borderManagement() const override; + Color color(State state) const override; Statistics statistics() const override; ImplementationInformation information() const override; @@ -41,10 +41,12 @@ private: std::optional mIteration; // On utilise un bitset qui contient les règles de chaque nombre. - std::bitset<9> mParsedRuleRevive, mParsedRuleSurvive; + // On n'utilise pas std::bitset pour des raisons de performance. + uint16_t mParsedRuleRevive, mParsedRuleSurvive; + GridTeamH mData; Color mDeadColor, mAliveColor; - // Fonction utilisée à l'interne. + // Fonctions utilisées à l'interne. std::optional convertCharToNumber(const char c); }; \ No newline at end of file diff --git a/GPA675Lab1GOL/GridTeamH.cpp b/GPA675Lab1GOL/GridTeamH.cpp index 1cfd1df..ef739f7 100644 --- a/GPA675Lab1GOL/GridTeamH.cpp +++ b/GPA675Lab1GOL/GridTeamH.cpp @@ -1,10 +1,8 @@ -#include "GridTeamH.h" +#include "GridTeamH.h" #include "GOL.h" #include -//constructeur Grid par défaut - - +// Constructeur Grid par défaut GridTeamH::GridTeamH() : GridTeamH(100, 100, CellType::alive) { @@ -22,54 +20,50 @@ GridTeamH::~GridTeamH() } -//Inlining de ces assesseur dans GridTeam.h -// Accesseur retournant la largeur de la grille. -// Accesseur retournant la hauteur de la grille. -// Accesseur retournant le nombre de cellule de la grille (la taille de la grille). - - // Mutateur modifiant la taille de la grille et initialise le contenu par la valeur spécifiée. void GridTeamH::resize(size_t width, size_t height, CellType initValue) { - // TODO: Performance de resize avec beaucoup d'appel? - // Investiguer reserve + resize - mData.resize(width * height); + mData.resize((width + 2) * (height + 2)); + mIntermediateData.resize((width + 2) * (width + 2)); mWidth = width; mHeight = height; - fill(initValue); + fill(initValue, false); } // Accesseur retournant la valeur d'une cellule à une certaine coordonnée. GridTeamH::CellType GridTeamH::value(int column, int row) const - { - return mData[(column - 1) * (row - 1)]; + size_t offset{ mWidth + 2 }; + return mData[offset * row + (column - 1)]; } // Mutateur modifiant la valeur d'une cellule à une certaine coordonnée. void GridTeamH::setValue(int column, int row, CellType value) { - mData[(column - 1) * (row - 1)] = value; + size_t offset{ mWidth + 2 }; + mData[offset * row + (column - 1)] = value; } // Accesseur retournant la valeur d'une cellule à une certaine coordonnée. std::optional GridTeamH::at(int column, int row) const { - if (column > mWidth || row > mHeight) + if (column >= mWidth || row >= mHeight) return std::nullopt; - return mData[(column - 1) * (row - 1)]; + size_t offset{ mWidth + 2 }; + return mData[offset * row + (column - 1)]; } -// Mutateur modifiant la valeur d'une cellule à une certaine coordonn�e. +// Mutateur modifiant la valeur d'une cellule à une certaine coordonn e. void GridTeamH::setAt(int column, int row, CellType value) { if (column > mWidth || row > mHeight) return; - mData[(column - 1) * (row - 1)] = value; + size_t offset{ mWidth + 2 }; + mData[offset * row + (column - 1)] = value; } @@ -79,6 +73,23 @@ GridTeamH::DataType const& GridTeamH::data() const return mData; } +// Accesseur en lecture/écriture sur le "buffer" de la grille. +GridTeamH::DataType& GridTeamH::data() +{ + return mData; +} + +GridTeamH::DataType const& GridTeamH::intData() const +{ + return mIntermediateData; +} + +GridTeamH::DataType& GridTeamH::intData() +{ + return mIntermediateData; +} + +// TODO: FIX // https://en.cppreference.com/w/cpp/algorithm/count size_t GridTeamH::totalDead() const { @@ -90,6 +101,7 @@ float GridTeamH::totalDeadRel() const return static_cast(totalDead()) / static_cast(size()); } +// TODO: FIX size_t GridTeamH::totalAlive() const { return std::count_if(mData.begin(), mData.end(), [](auto& i) { return i == CellType::alive; }); @@ -100,55 +112,90 @@ float GridTeamH::totalAliveRel() const return static_cast(totalAlive()) / static_cast(size()); } -void GridTeamH::fill(CellType value) +void GridTeamH::fill(CellType value, bool fillBorder) { - for (auto& i : mData) - i = value; + if (fillBorder) { + for (auto& i : mData) + i = value; + } + else { + for (size_t index{}; index < mData.size(); index++) { + if (isInBorder(index)) + continue; + + mData[index] = value; + } + } } -void GridTeamH::fillAternately(CellType initValue) +// TODO: FIX LES COLONNES EN FAISANT ATTENTION AU BORDER +// TIMOTHEE: Je m'en occupe. +void GridTeamH::fillAternately(CellType initValue, bool fillBorder) { - // Détermine la valeur opposée à initValue auto otherValue = (initValue == CellType::alive) ? CellType::dead : CellType::alive; - // Boucle pour parcourir chaque ligne de la grille - for (size_t row = 0; row < mHeight; row++) { - // Boucle pour parcourir chaque colonne de la grille - for (size_t col = 0; col < mWidth; col++) { - // Calcul de l'index dans la grille bidimensionnelle - size_t index = row * mWidth + col; + if (fillBorder) { + for (size_t i{}; i < mData.size(); i++) + mData[i] = !(i % 2) ? initValue : otherValue; + } + else { + for (size_t index{}; index < mData.size(); index++) { + if (isInBorder(index)) + continue; - // Alternance de la valeur pour chaque cellule en fonction de la ligne et de la colonne - mData[index] = ((row + col) % 2 == 0) ? initValue : otherValue; + mData[index] = !(index % 2) ? initValue : otherValue; } } } -void GridTeamH::randomize(double percentAlive) +void GridTeamH::randomize(double percentAlive, bool fillBorder) { - for (auto& i : mData) { - if (mDistribution(mEngine) < percentAlive) - i = CellType::alive; - else - i = CellType::dead; + if (fillBorder) { + for (auto& i : mData) { + if (mDistribution(mEngine) < percentAlive) + i = CellType::alive; + else + i = CellType::dead; + } } -} + else { + for (size_t index{}; index < mData.size() - 1; index++) { + if (isInBorder(index)) + continue; -//Fonction qui attribue CellType aux case de la grille présent en bordure -//sans changer les autres case -void GridTeamH::setBorderValue(CellType initValue) -{ - - // Boucle pour parcourir chaque ligne de la grille - for (size_t row = 0; row < mHeight; row++) { - // Boucle pour parcourir chaque colonne de la grille - for (size_t col = 0; col < mWidth; col++) { - // Calcul de l'index dans la grille bidimensionnelle - size_t index = row * mWidth + col; - - // Alternance de la valeur pour chaque cellule en fonction de la ligne et de la colonne - if (row == 0 || row == mHeight - 1 || col == 0 || col == mWidth - 1) - mData[index] = initValue; + if (mDistribution(mEngine) < percentAlive) + mData[index] = CellType::alive; + else + mData[index] = CellType::dead; } } } + + +// Performance non nécessaire. +void GridTeamH::fillBorder(CellType value) +{ + for (size_t index{}; index < mData.size(); index++) { + if (isInBorder(index)) + mData[index] = value; + } +} + +// TODO +void GridTeamH::fillBorderWarped() +{ + +} + +// TODO +void GridTeamH::fillBorderMirror() +{ + +} + +void GridTeamH::switchToIntermediate() +{ + // Swap pour la performance. + mData.swap(mIntermediateData); +} + diff --git a/GPA675Lab1GOL/GridTeamH.h b/GPA675Lab1GOL/GridTeamH.h index 792143d..dd8fa71 100644 --- a/GPA675Lab1GOL/GridTeamH.h +++ b/GPA675Lab1GOL/GridTeamH.h @@ -1,10 +1,20 @@ -#pragma once +#pragma once #include #include #include #include "GOL.h" +/* + Cette classe fonctionne en ayant deux std::vector qui contiennent les statuts + des cellules. Un vecteur intermédiaire est échangé avec .swap() avec le vecteur + réel lors des calculs. + + On utilise une grandeur de width + 2 par height + 2. Cela nous permet d'avoir un + contour qui sera modifié de façon dynamique par la règle du border. Cela est + complètement transparent aux fonctions publiques autres que certaines fonctions + spécialisées. +*/ class GridTeamH { @@ -23,9 +33,11 @@ public: ~GridTeamH(); // Accesseurs et mutateurs de la grille - size_t width() const {return mWidth; } + // inline puisque trivial. + size_t width() const { return mWidth; } size_t height() const { return mHeight; } - size_t size() const { return mWidth * mHeight; } + size_t size() const { return mHeight * mWidth; } + size_t realSize() const { return (mHeight + 2) * (mWidth + 2); } void resize(size_t width, size_t height, CellType initValue = CellType{}); @@ -38,6 +50,10 @@ public: // Accesseurs du "buffer" de la grille DataType const& data() const; + DataType& data(); + + DataType const& intData() const; + DataType& intData(); size_t totalDead() const; float totalDeadRel() const; @@ -45,16 +61,33 @@ public: size_t totalAlive() const; float totalAliveRel() const; - void fill(CellType value); - void fillAternately(CellType initValue); - void randomize(double percentAlive); + void fill(CellType value, bool fillBorder); + void fillAternately(CellType initValue, bool fillBorder); + void randomize(double percentAlive, bool fillBorder); - void setBorderValue(CellType value); + void fillBorder(CellType value); + void fillBorderWarped(); + void fillBorderMirror(); -private: - DataType mData; - size_t mWidth, mHeight; //hauteur et largeur de la grid + void switchToIntermediate(); + bool isInBorder(size_t index) const; + +private: + DataType mData, mIntermediateData; + size_t mWidth, mHeight; + + // Pour la génération de nombres aléatoires std::random_device mRandomDevice; std::mt19937 mEngine; std::uniform_real_distribution<> mDistribution; -}; \ No newline at end of file +}; + +// Attention: performance terrible si utilisation. Seulement lorsque vitesse +// n'est pas demandée. +inline bool GridTeamH::isInBorder(size_t index) const +{ + return(index % (mWidth + 2) < 1 + || index % (mWidth + 2) > mWidth + || index < mWidth + 2 + || index >(mWidth + 2) * (mHeight + 1)); +} \ No newline at end of file