Commit fd479439 authored by Yann Boucher's avatar Yann Boucher
Browse files

100x performance increase with Griffeath!

parent 2660013d
Pipeline #79621 passed with stages
in 58 seconds
This diff is collapsed.
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -55,7 +55,7 @@ public:
void newEtat(const state& s);
//! Renvoie l'état en fonction de son identifiant
state getState(unsigned int it) const;
const state& getState(unsigned int it) const;
//! Affecte l'identifiant d'état à un état
void setState(unsigned int i, const state& s);
......
......@@ -159,7 +159,7 @@ private:
void click_on(Coord coord);
void update_current_mouse_pos(Coord coord);
const QPixmap &grid_pixmap() const;
const QImage &grid_image() const;
private slots:
void handle_rubberband(QRect, QPointF, QPointF);
......@@ -173,8 +173,8 @@ private:
QGraphicsScene* m_scene;
detail::Graphics_view_zoom* m_zoom;
History m_undo_history;
std::vector<uint32_t> m_image_data;
QImage m_grid_image;
QPixmap m_grid_pixmap;
QLabel* m_mouse_pos_label;
Grid m_grid;
Coord m_last_mouse_pos;
......
......@@ -13,6 +13,27 @@ Ce fichier contient une fonction permettant d'exécuter une expression mathémat
#include <map>
#include <exception>
#include <vector>
#include <climits>
namespace detail
{
struct rpl_token_t
{
enum : uint8_t
{
LITERAL,
VARIABLE,
OP
} type;
union
{
int8_t value;
uint8_t var_alphabet_idx;
char op;
};
};
}
//! \brief Exception lancée lors de l'évaluation d'une expression mathématique.
class MathExprException : public std::exception
......@@ -29,11 +50,37 @@ private:
std::string m_what;
};
//! \brief Représente une expression mathématique parsée et mise en cache.
class MathExpr
{
public:
//! \brief Initialise l'expression à partir d'une chaîne.
MathExpr(const std::string& expr = "0");
//! \brief Retourne une évaluation de l'expression mathématique.
int eval() const;
//! \brief Permet de spécifier la valeur d'une variable.
//! La constance symbolise ici l'immutabilité de l'expression mathématique en elle-même, pas son contexte d'évaluation.
void set_var(char var, int val) const
{
if (var < 'a' || var > 'z')
throw MathExprException("Invalid variable name");
variable_map[var - 'a'] = val;
}
private:
std::vector<detail::rpl_token_t> rpl_stack;
mutable std::array<int8_t, 26> variable_map;
bool is_constant; // true if the expression contains no variables
int cached_result;
};
//! Evalue une expression mathématique selon une liste de variables fournies, et retourne un entier.
//! Les opérateurs supportés sont les parenthèses, +, -, *, /, %.
//! \param expr L'expression mathématique, en tant que std::string
//! \param variables un std::map associant à chaque nom de variable une valeur entière
//! \returns La valeur de résultat de l'expression mathématique évaluée.
int eval_math(const std::string& expr, const std::map<std::string, int>& variables = {});
int eval_math(const std::string& expr, const std::map<char, int> &variables = {});
#endif // MATHEXPR_HPP
......@@ -26,7 +26,7 @@ public:
state(stateColor c, const std::string& l = "sans nom"):color(c),label(l){}
//! \brief Accesseur en lecture de la couleur
stateColor getColor() const { return color; }
const stateColor& getColor() const { return color; }
//! \brief Accesseur en lecture du label
std::string getStateLabel() const { return label; }
......
......@@ -4,6 +4,7 @@
#include <exception>
#include "transitionrule.hpp"
#include "mathexpr.hpp"
//! \brief Exception lancée lors de l'évaluation d'une règle totalistique.
class NonIsotropicRuleException : public std::exception
......@@ -40,10 +41,10 @@ private:
bool m_initial_state_is_variable;
// could use a union here, but it's a bit tricky as we'd have to call the correct destructor
std::string m_initial_variable;
char m_initial_variable;
unsigned m_initial_state;
std::vector<unsigned> m_constraints;
std::string m_result_state;
MathExpr m_result_state;
};
//! \brief Représente une règle de transition totalistique configurable textuellement.
......
......@@ -18,6 +18,7 @@ Cette classe représente une règle de transition totalistique configurable.
#include "neighborhood.hpp"
#include "mathexpr.hpp"
#include "transitionrule.hpp"
#include "constantes.hpp"
//! \brief Exception lancée lors de l'évaluation d'une règle totalistique.
class TotalisticRuleException : public std::exception
......@@ -37,14 +38,26 @@ private:
//! \brief Représente un intervalle [low;high] composé d'expressions mathématiques
struct Interval
{
std::string low, high;
public:
Interval(const MathExpr& i_low = MathExpr("0"))
{
has_upper_bound = false;
low = i_low;
}
Interval(const MathExpr& i_low, const MathExpr& i_high)
{
has_upper_bound = true;
low = i_low;
high = i_high;
}
//! Evalue l'intervalle pour vérifier si val est inclus dedans
//! \returns true si inclus, false sinon
bool contains(unsigned val) const
{
unsigned low_v = eval_math(low , {});
unsigned high_v = !high.empty() ? eval_math(high, {}) : std::numeric_limits<unsigned>::max();
unsigned low_v = low.eval();
unsigned high_v = has_upper_bound ? high.eval() : std::numeric_limits<unsigned>::max();
// sanity check
if (low_v > high_v)
......@@ -52,6 +65,10 @@ struct Interval
return val >= low_v && val <= high_v;
}
private:
bool has_upper_bound;
MathExpr low, high;
};
//! \brief Représente une entrée de règle de transition totalistique configurable.
......@@ -69,13 +86,51 @@ public:
//! \returns true si accepté, false sinon.
bool accept(unsigned initial_state, const Neighborhood& neighborhood, unsigned& next) const;
private:
class Constraint
{
public:
Constraint(const MathExpr& e, const Interval& i)
: state(e), interval(i)
{
std::fill(cache_bitmap.begin(), cache_bitmap.end(), false);
}
// initial_variable == '\0' si il n'y a pas de variable initiale
bool valid(char initial_variable, unsigned initial_state, const Neighborhood& n) const
{
unsigned state_to_test;
// valeur d'état à tester déjà dans le cache
if (cache_bitmap[initial_state])
{
state_to_test = cache[initial_state];
}
else
{
cache_bitmap[initial_state] = true;
if (initial_variable != '\0')
state.set_var(initial_variable, initial_state);
state_to_test = cache[initial_state] = state.eval();
}
return interval.contains(n.getNb(state_to_test));
}
private:
MathExpr state;
Interval interval;
mutable std::array<bool, MAX_ETATS> cache_bitmap;
mutable std::array<uint8_t, MAX_ETATS> cache;
};
private:
bool m_initial_state_is_variable;
// could use a union here, but it's a bit tricky as we'd have to call the correct destructor
std::string m_initial_variable;
char m_initial_variable;
unsigned m_initial_state;
std::map<std::string, Interval> m_constraints;
std::string m_result_state;
std::vector<Constraint> m_constraints;
MathExpr m_result_state;
mutable std::array<bool, MAX_ETATS> result_cache_bitmap;
mutable std::array<uint8_t, MAX_ETATS> result_cache;
};
//! \brief Représente une règle de transition totalistique configurable textuellement.
......
This diff is collapsed.
......@@ -14,7 +14,7 @@ void Alphabet::newEtat(const state& s){ // Définition de la méthode de créati
etats.push_back(s);
}
state Alphabet::getState(unsigned int it) const{ // Définition de la méthode de récupération d'un état en fonction de son identifiant
const state &Alphabet::getState(unsigned int it) const{ // Définition de la méthode de récupération d'un état en fonction de son identifiant
if (it < etats.size())
{
return etats[it];
......
......@@ -59,9 +59,14 @@ void Automaton::runOnce() {
// On s'assure d'avoir une stratégie de frontière de réseau à jour
grid.set_boundary_policy(border_policy);
Grid tempGrid(grid);
for(int i=0; i<static_cast<int>(grid.get_col()); ++i) {
for(int j=0; j<static_cast<int>(grid.get_rows()); ++j) {
tempGrid.set_cell({i, j}, transitionRule->getState(grid.get_state({i,j}),neighbourhoodRule->getNeighborhood(grid, {i, j}))%alphabet.taille());
for(int i=0; i<static_cast<int>(grid.get_rows()); ++i) {
for(int j=0; j<static_cast<int>(grid.get_col()); ++j) {
unsigned prev = grid.get_state({j,i});
unsigned next = transitionRule->getState(prev,neighbourhoodRule->getNeighborhood(grid, {j, i}));
if (next >= alphabet.taille()) // embrace the magic of branch prediction
next %= alphabet.taille();
if (prev != next)
tempGrid.set_cell({j, i}, next);
}
}
grid = tempGrid;
......
......@@ -247,7 +247,8 @@ void GridGraphicsView::drawBackground(QPainter *painter, const QRectF &rect)
{
QGraphicsView::drawBackground(painter, rect);
painter->drawPixmap(0, 0, m_gridview.grid_pixmap());
//painter->drawPixmap(0, 0, m_gridview.grid_image());
painter->drawImage(0, 0, m_gridview.grid_image());
}
// Algorithme de Bresenham pour tracer une ligne entre les deux points et dessiner sur ces items
......@@ -448,17 +449,31 @@ void GridView::load_grid(const Grid &grid)
m_width = grid.get_col();
m_grid = grid;
m_grid_image = QImage(m_width, m_height, QImage::Format_ARGB32);
for (int i = 0; i < (int)m_width; ++i)
m_image_data.resize(m_width*m_height);
for (int i = 0; i < (int)m_height; ++i)
{
for (int j = 0; j < (int)m_height; ++j)
for (int j = 0; j < (int)m_width; ++j)
{
QColor color = stateColor_to_QColor(m_alph.getState(grid.get_state(Coord{i, j}) % m_alph.taille()).getColor());
m_grid_image.setPixelColor(i, j, color);
m_grid.set_cell(Coord{i, j}, grid.get_state(Coord{i, j}) % m_alph.taille());
unsigned state_id = grid.get_state(Coord{j, i});
if (state_id < m_alph.taille())
{
const auto& state_color = m_alph.getState(state_id).getColor();
m_image_data[i * m_width + j] = 0xff000000 | ((uint32_t)state_color.getRed() << 16)
| ((uint32_t)state_color.getGreen() << 8)
| ((uint32_t)state_color.getBlue() << 0);
}
else
{
const auto& state_color = m_alph.getState(state_id % m_alph.taille()).getColor();
m_image_data[i * m_width + j] = 0xff000000 | ((uint32_t)state_color.getRed() << 16)
| ((uint32_t)state_color.getGreen() << 8)
| ((uint32_t)state_color.getBlue() << 0);
m_grid.set_cell(Coord{j, i}, state_id % m_alph.taille());
}
}
}
m_grid_pixmap.convertFromImage(m_grid_image);
m_grid_image = QImage((const uchar*)m_image_data.data(), m_width, m_height, QImage::Format_ARGB32);
m_scene->setSceneRect(QRectF(0, 0, m_width, m_height));
m_drag_drop_handler->setRect(QRectF(0, 0, m_width, m_height));
update_current_mouse_pos(m_last_mouse_pos);
......@@ -467,14 +482,16 @@ void GridView::load_grid(const Grid &grid)
void GridView::set_cell_state(Coord pos, unsigned state)
{
state %= m_alph.taille();
QColor color = stateColor_to_QColor(m_alph.getState(state).getColor());
m_grid_image.setPixelColor(pos.x, pos.y, color);
auto state_color = m_alph.getState(state).getColor();
m_image_data[pos.y * m_width + pos.x] = 0xff000000 | ((uint32_t)state_color.getRed() << 16)
| ((uint32_t)state_color.getGreen() << 8)
| ((uint32_t)state_color.getBlue() << 0);
m_grid.set_cell(pos, state);
}
void GridView::update_gridview()
{
m_grid_pixmap.convertFromImage(m_grid_image);
m_grid_image = QImage((const uchar*)m_image_data.data(), m_width, m_height, QImage::Format_ARGB32);
m_scene->update();
}
......@@ -583,9 +600,9 @@ void GridView::paste_structure_at(Coord origin, const Structure &s)
update_gridview();
}
const QPixmap &GridView::grid_pixmap() const
const QImage &GridView::grid_image() const
{
return m_grid_pixmap;
return m_grid_image;
}
void GridView::handle_rubberband(QRect rubberBandRect, QPointF fromScenePoint, QPointF toScenePoint)
......
......@@ -17,19 +17,7 @@ Ce fichier contient une fonction permettant d'exécuter une expression mathémat
#include <algorithm>
#include <cctype>
struct rpl_token_t
{
enum
{
LITERAL,
OP
} type;
union
{
int value;
char op;
};
};
using detail::rpl_token_t;
//! Priorité de l'opérateur
static int precedence(char op)
......@@ -45,9 +33,6 @@ static int precedence(char op)
//! Evalue une opération binaire entre x et y, dénotée par op.
static int eval_int_binop(char op, int x, int y)
{
if ((op == '/' || op == '%') && y == 0)
throw MathExprException("Divsion by zero error");
switch (op)
{
case '+':
......@@ -57,49 +42,60 @@ static int eval_int_binop(char op, int x, int y)
case '*':
return x*y;
case '/':
if (y == 0)
throw MathExprException("Divsion by zero error");
return x/y;
case '%':
if (y == 0)
throw MathExprException("Divsion by zero error");
return x%y;
default:
throw MathExprException("Unknown operator");
}
}
#define MAX_DATA_STACK_SIZE 256
//! Evalue une expression RPL
static int evaluate_rpl_input(const std::vector<rpl_token_t>& rpl_stack)
static int evaluate_rpl_input(const std::vector<rpl_token_t>& rpl_stack, const std::array<int8_t, 26>& variable_map)
{
std::vector<int> data_stack;
static std::array<int, MAX_DATA_STACK_SIZE> data_stack;
unsigned stack_len = 0;
for (unsigned i = 0; i < rpl_stack.size(); ++i)
{
if (stack_len+1 >= MAX_DATA_STACK_SIZE)
throw MathExprException("Internal error: Exceeded the maximum amount of RPL tokens");
if (rpl_stack[i].type == rpl_token_t::LITERAL)
data_stack.push_back(rpl_stack[i].value);
data_stack[stack_len++] = rpl_stack[i].value;
else if (rpl_stack[i].type == rpl_token_t::VARIABLE)
data_stack[stack_len++] = variable_map[rpl_stack[i].var_alphabet_idx];
else
{
int op = rpl_stack[i].op;
if (data_stack.size() <= 1)
if (stack_len <= 1)
throw MathExprException("Invalid math expression error");
int x = data_stack[data_stack.size()-2];
int y = data_stack[data_stack.size()-1];
int x = data_stack[stack_len-2];
int y = data_stack[stack_len-1];
int result = eval_int_binop(op, x, y);
data_stack.pop_back();
data_stack[data_stack.size()-1] = result;
--stack_len;
data_stack[stack_len-1] = result;
}
}
if (data_stack.size() != 1)
if (stack_len != 1)
throw MathExprException("Invalid math expression error");
return data_stack[0];
}
int eval_math(const std::string &in_expr, const std::map<std::string, int> &variables)
std::vector<rpl_token_t> parse_math(const std::string& in_expr)
{
std::string expr = in_expr;
// remove whitespace out of the equation altogether
expr.erase(std::remove_if(expr.begin(), expr.end(), ::isspace), expr.end());
expr.erase(std::remove_if(expr.begin(), expr.end(), ::isspace), expr.end());
std::vector<rpl_token_t> rpl_stack;
std::stack<char> op_stack;
......@@ -130,19 +126,19 @@ int eval_math(const std::string &in_expr, const std::map<std::string, int> &vari
case '/':
case '%':
// binary operator
{
while (!op_stack.empty() &&
(op_stack.top() != '(') &&
(precedence(op_stack.top()) > precedence(expr[idx])))
{
rpl_token.type = rpl_token_t::OP;
rpl_token.op = op_stack.top();
rpl_stack.push_back(rpl_token);
op_stack.pop();
while (!op_stack.empty() &&
(op_stack.top() != '(') &&
(precedence(op_stack.top()) > precedence(expr[idx])))
{
rpl_token.type = rpl_token_t::OP;
rpl_token.op = op_stack.top();
rpl_stack.push_back(rpl_token);
op_stack.pop();
}
op_stack.push(expr[idx]);
++idx;
}
op_stack.push(expr[idx]);
++idx;
}
break;
case '(':
op_stack.push('(');
......@@ -166,21 +162,16 @@ int eval_math(const std::string &in_expr, const std::map<std::string, int> &vari
break;
default:
// assume it's a variable name then
{
std::string var_name;
while (isalnum(expr[idx]))
{
var_name += expr[idx];
++idx;
}
char var_name = expr[idx++];
if (var_name < 'a' || var_name > 'z')
throw MathExprException("Invalid variable name");
if (!variables.count(var_name))
throw MathExprException("No such variable '" + var_name + "' found");
rpl_token.type = rpl_token_t::LITERAL;
rpl_token.value = variables.at(var_name);
rpl_stack.push_back(rpl_token);
break;
}
rpl_token.type = rpl_token_t::VARIABLE;
rpl_token.var_alphabet_idx = var_name - 'a';
rpl_stack.push_back(rpl_token);
break;
}
}
}
// while there are remaining operators on the op stack
......@@ -192,6 +183,43 @@ int eval_math(const std::string &in_expr, const std::map<std::string, int> &vari
rpl_stack.push_back(rpl_token);
}
return rpl_stack;
}
int eval_math(const std::string &in_expr, const std::map<char, int> &variables)
{
auto rpl_stack = parse_math(in_expr);
std::array<int8_t, 26> variable_map;
for (const auto& pair : variables)
{
if (pair.second < 'a' || pair.second > 'z')
throw MathExprException("Invalid variable name");
variable_map[pair.first] = pair.second - 'a';
}
return evaluate_rpl_input(rpl_stack, variable_map);
}
return evaluate_rpl_input(rpl_stack);
MathExpr::MathExpr(const std::string &expr)
{
is_constant = false;
rpl_stack = parse_math(expr);
// Constant expression, cache the result
if (std::find_if(expr.begin(), expr.end(), ::isalpha) == expr.end())
{
is_constant = true;
cached_result = evaluate_rpl_input(rpl_stack, variable_map);
}
}
int MathExpr::eval() const
{
if (is_constant)
return cached_result;
else
{
int result = evaluate_rpl_input(rpl_stack, variable_map);
return result;
}
}
......@@ -18,16 +18,37 @@ Neighborhood mooreNeighborhoodRule::getNeighborhood(const Grid& grid, Coord pos)
update_format();
Neighborhood newNeighborhood;
// Coordonnées des voisins dans la grille
Coord gridCoord;
std::vector<Coord>::const_iterator it = format.positions.begin();
for (it = format.positions.begin() ; it != format.positions.end(); ++it) {
// Calcul des coordonnées du voisin sur la grille
gridCoord.x = pos.x + it->x;
gridCoord.y = pos.y + it->y;
// Ajout du nouveau voisin <Coordonnée_relative, état>
newNeighborhood.addNeighbor(*it , grid.get_state(gridCoord));
// fast path
if (radius.val == 1)
{
newNeighborhood.addNeighbor(pos + Coord{-1, -1}, grid.get_state(pos + Coord{-1, -1}));
newNeighborhood.addNeighbor(pos + Coord{0, -1}, grid.get_state(pos + Coord{0, -1}));
newNeighborhood.addNeighbor(pos + Coord{+1, -1}, grid.get_state(pos + Coord{+1, -1}));
newNeighborhood.addNeighbor(pos + Coord{-1, 0}, grid.get_state(pos + Coord{-1, 0}));
newNeighborhood.addNeighbor(pos + Coord{+1, 0}, grid.get_state(pos + Coord{+1, 0}));
newNeighborhood.addNeighbor(pos + Coord{-1, +1}, grid.get_state(pos + Coord{-1, +1}));
newNeighborhood.addNeighbor(pos + Coord{0, +1}, grid.get_state(pos + Coord{0, +1}));
newNeighborhood.addNeighbor(pos + Coord{+1, +1}, grid.get_state(pos + Coord{+1, +1}));
}
else
{
// Coordonnées des voisins dans la grille
Coord gridCoord;
std::vector<Coord>::const_iterator it = format.positions.begin();
for (it = format.positions.begin() ; it != format.positions.end(); ++it) {
// Calcul des coordonnées du voisin sur la grille
gridCoord.x = pos.x + it->x;
gridCoord.y = pos.y + it->y;
// Ajout du nouveau voisin <Coordonnée_relative, état>