<commit_before>// Copyright (c) 2019-2020 The PIVX developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include "qt/pivx/addresseswidget.h"
#include "qt/pivx/forms/ui_addresseswidget.h"
#include "qt/pivx/addresslabelrow.h"
#include "qt/pivx/addnewaddressdialog.h"
#include "qt/pivx/tooltipmenu.h"

#include "qt/pivx/addnewcontactdialog.h"
#include "qt/pivx/pivxgui.h"
#include "guiutil.h"
#include "qt/pivx/qtutils.h"
#include "walletmodel.h"

#include <QModelIndex>
#include <QRegExpValidator>

#define DECORATION_SIZE 60
#define NUM_ITEMS 3

class ContactsHolder : public FurListRow<QWidget*>
{
public:
    ContactsHolder();

    explicit ContactsHolder(bool _isLightTheme) : FurListRow(), isLightTheme(_isLightTheme){}

    AddressLabelRow* createHolder(int pos) override{
        if (!cachedRow) cachedRow = new AddressLabelRow();
        cachedRow->init(isLightTheme, false);
        return cachedRow;
    }

    void init(QWidget* holder,const QModelIndex &index, bool isHovered, bool isSelected) const override
    {
        AddressLabelRow* row = static_cast<AddressLabelRow*>(holder);

        row->updateState(isLightTheme, isHovered, isSelected);

        QString address = index.data(Qt::DisplayRole).toString();
        if (index.data(AddressTableModel::TypeRole).toString() == AddressTableModel::ShieldedSend) {
            address = address.left(26) + "..." + address.right(26);
        }
        QModelIndex sibling = index.sibling(index.row(), AddressTableModel::Label);
        QString label = sibling.data(Qt::DisplayRole).toString();

        row->updateView(address, label);
    }

    QColor rectColor(bool isHovered, bool isSelected) override
    {
        return getRowColor(isLightTheme, isHovered, isSelected);
    }

    ~ContactsHolder() override{}

    bool isLightTheme;
    AddressLabelRow* cachedRow = nullptr;
};

AddressesWidget::AddressesWidget(PIVXGUI* parent) :
    PWidget(parent),
    ui(new Ui::AddressesWidget)
{
    ui->setupUi(this);

    this->setStyleSheet(parent->styleSheet());

    delegate = new FurAbstractListItemDelegate(
                DECORATION_SIZE,
                new ContactsHolder(isLightTheme()),
                this
    );

    /* Containers */
    setCssProperty(ui->left, "container");
    ui->left->setContentsMargins(0,20,0,20);
    setCssProperty(ui->right, "container-right");
    ui->right->setContentsMargins(20,10,20,20);
    setCssProperty(ui->listAddresses, "container");

    // Title
    setCssTitleScreen(ui->labelTitle);
    setCssSubtitleScreen(ui->labelSubtitle1);

    // Change address option
    ui->btnAddContact->setTitleClassAndText("btn-title-grey", tr("Add new contact"));
    ui->btnAddContact->setSubTitleClassAndText("text-subtitle", tr("Generate a new address to receive tokens."));
    ui->btnAddContact->setRightIconClass("ic-arrow-down");

    // List Addresses
    ui->listAddresses->setItemDelegate(delegate);
    ui->listAddresses->setIconSize(QSize(DECORATION_SIZE, DECORATION_SIZE));
    ui->listAddresses->setMinimumHeight(NUM_ITEMS * (DECORATION_SIZE + 2));
    ui->listAddresses->setAttribute(Qt::WA_MacShowFocusRect, false);
    ui->listAddresses->setSelectionBehavior(QAbstractItemView::SelectRows);
    ui->listAddresses->setUniformItemSizes(true);

    // Sort Controls
    SortEdit* lineEdit = new SortEdit(ui->comboBoxSort);
    connect(lineEdit, &SortEdit::Mouse_Pressed, [this](){ui->comboBoxSort->showPopup();});
    connect(ui->comboBoxSort, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &AddressesWidget::onSortChanged);
    SortEdit* lineEditOrder = new SortEdit(ui->comboBoxSortOrder);
    connect(lineEditOrder, &SortEdit::Mouse_Pressed, [this](){ui->comboBoxSortOrder->showPopup();});
    connect(ui->comboBoxSortOrder, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &AddressesWidget::onSortOrderChanged);
    fillAddressSortControls(lineEdit, lineEditOrder, ui->comboBoxSort, ui->comboBoxSortOrder);

    //Empty List
    ui->emptyContainer->setVisible(false);
    setCssProperty(ui->pushImgEmpty, "img-empty-contacts");
    setCssProperty(ui->labelEmpty, "text-empty");

    // Add Contact
    setCssProperty(ui->layoutNewContact, "container-options");

    // Name
    setCssProperty(ui->labelName, "text-title");
    setCssEditLine(ui->lineEditName, true);

    // Address
    setCssProperty(ui->labelAddress, "text-title");
    setCssEditLine(ui->lineEditAddress, true);
    ui->lineEditAddress->setValidator(new QRegExpValidator(QRegExp("^[A-Za-z0-9]+"), ui->lineEditName));

    // Buttons
    setCssBtnPrimary(ui->btnSave);

    connect(ui->listAddresses, &QListView::clicked, this, &AddressesWidget::handleAddressClicked);
    connect(ui->btnSave, &QPushButton::clicked, this, &AddressesWidget::onStoreContactClicked);
    connect(ui->btnAddContact, &OptionButton::clicked, this, &AddressesWidget::onAddContactShowHideClicked);
}

void AddressesWidget::handleAddressClicked(const QModelIndex &index)
{
    ui->listAddresses->setCurrentIndex(index);
    QRect rect = ui->listAddresses->visualRect(index);
    QPoint pos = rect.topRight();
    pos.setX(pos.x() - (DECORATION_SIZE * 2));
    pos.setY(pos.y() + (DECORATION_SIZE));

    QModelIndex rIndex = filter->mapToSource(index);

    if (!this->menu) {
        this->menu = new TooltipMenu(window, this);
        connect(this->menu, &TooltipMenu::message, this, &AddressesWidget::message);
        connect(this->menu, &TooltipMenu::onEditClicked, this, &AddressesWidget::onEditClicked);
        connect(this->menu, &TooltipMenu::onDeleteClicked, this, &AddressesWidget::onDeleteClicked);
        connect(this->menu, &TooltipMenu::onCopyClicked, this, &AddressesWidget::onCopyClicked);
    } else {
        this->menu->hide();
    }
    this->index = rIndex;
    menu->move(pos);
    menu->show();
}

void AddressesWidget::loadWalletModel()
{
    if (walletModel) {
        addressTablemodel = walletModel->getAddressTableModel();
        this->filter = new AddressFilterProxyModel(
                QStringList({AddressTableModel::Send, AddressTableModel::ColdStakingSend, AddressTableModel::ShieldedSend}),
                this);
        this->filter->setSourceModel(addressTablemodel);
        this->filter->sort(sortType, sortOrder);
        ui->listAddresses->setModel(this->filter);
        ui->listAddresses->setModelColumn(AddressTableModel::Address);

        updateListView();
    }
}

void AddressesWidget::updateListView()
{
    bool empty = addressTablemodel->sizeSend() == 0;
    ui->emptyContainer->setVisible(empty);
    ui->listAddresses->setVisible(!empty);
}

void AddressesWidget::onStoreContactClicked()
{
    if (walletModel) {
        QString label = ui->lineEditName->text();
        QString address = ui->lineEditAddress->text();

        bool isStakingAddress = false;
        auto pivAdd = Standard::DecodeDestination(address.toUtf8().constData(), isStakingAddress);

        if (!Standard::IsValidDestination(pivAdd) || isStakingAddress) {
            setCssEditLine(ui->lineEditAddress, false, true);
            inform(tr("Invalid Contact Address"));
            return;
        }

        if (walletModel->isMine(pivAdd)) {
            setCssEditLine(ui->lineEditAddress, false, true);
            inform(tr("Cannot store your own address as contact"));
            return;
        }

        QString storedLabel = walletModel->getAddressTableModel()->labelForAddress(address);

        if (!storedLabel.isEmpty()) {
            inform(tr("Address already stored, label: %1").arg("\'"+storedLabel+"\'"));
            return;
        }

        bool isShielded = walletModel->IsShieldedDestination(pivAdd);
        if (walletModel->updateAddressBookLabels(pivAdd, label.toUtf8().constData(),
                         isShielded ? AddressBook::AddressBookPurpose::SHIELDED_SEND :
                         isStakingAddress ? AddressBook::AddressBookPurpose::COLD_STAKING_SEND : AddressBook::AddressBookPurpose::SEND)
                ) {
            ui->lineEditAddress->setText("");
            ui->lineEditName->setText("");
            setCssEditLine(ui->lineEditAddress, true, true);
            setCssEditLine(ui->lineEditName, true, true);

            if (ui->emptyContainer->isVisible()) {
                ui->emptyContainer->setVisible(false);
                ui->listAddresses->setVisible(true);
            }
            inform(tr("New Contact Stored"));
        } else {
            inform(tr("Error Storing Contact"));
        }
    }
}

void AddressesWidget::onEditClicked()
{
    QString address = index.data(Qt::DisplayRole).toString();
    QString currentLabel = index.sibling(index.row(), AddressTableModel::Label).data(Qt::DisplayRole).toString();
    showHideOp(true);
    AddNewContactDialog *dialog = new AddNewContactDialog(window);
    dialog->setData(address, currentLabel);
    if (openDialogWithOpaqueBackground(dialog, window)) {
        if (walletModel->updateAddressBookLabels(
                Standard::DecodeDestination(address.toStdString()), dialog->getLabel().toStdString(), addressTablemodel->purposeForAddress(address.toStdString()))){
            inform(tr("Contact edited"));
        } else {
            inform(tr("Contact edit failed"));
        }
    }
    dialog->deleteLater();
}

void AddressesWidget::onDeleteClicked()
{
    if (walletModel) {
        if (ask(tr("Delete Contact"), tr("You are just about to remove the contact:\n\n%1\n\nAre you sure?").arg(index.data(Qt::DisplayRole).toString().toUtf8().constData()))
        ) {
            if (this->walletModel->getAddressTableModel()->removeRows(index.row(), 1, index)) {
                updateListView();
                inform(tr("Contact Deleted"));
            } else {
                inform(tr("Error deleting a contact"));
            }
        }
    }
}

void AddressesWidget::onCopyClicked()
{
    GUIUtil::setClipboard(index.data(Qt::DisplayRole).toString());
    inform(tr("Address copied"));
}

void AddressesWidget::onAddContactShowHideClicked()
{
    if (!ui->layoutNewContact->isVisible()){
        ui->btnAddContact->setRightIconClass("btn-dropdown", true);
        ui->layoutNewContact->setVisible(true);
    } else {
        ui->btnAddContact->setRightIconClass("ic-arrow", true);
        ui->layoutNewContact->setVisible(false);
    }
}

void AddressesWidget::onSortChanged(int idx)
{
    sortType = (AddressTableModel::ColumnIndex) ui->comboBoxSort->itemData(idx).toInt();
    sortAddresses();
}

void AddressesWidget::onSortOrderChanged(int idx)
{
    sortOrder = (Qt::SortOrder) ui->comboBoxSortOrder->itemData(idx).toInt();
    sortAddresses();
}

void AddressesWidget::sortAddresses()
{
    if (this->filter)
        this->filter->sort(sortType, sortOrder);
}

void AddressesWidget::changeTheme(bool isLightTheme, QString& theme)
{
    static_cast<ContactsHolder*>(this->delegate->getRowFactory())->isLightTheme = isLightTheme;
}

AddressesWidget::~AddressesWidget()
{
    delete ui;
}
<commit_msg>[GUI][Trivial] Save cold-staking address in contacts<commit_after>// Copyright (c) 2019-2020 The PIVX developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include "qt/pivx/addresseswidget.h"
#include "qt/pivx/forms/ui_addresseswidget.h"
#include "qt/pivx/addresslabelrow.h"
#include "qt/pivx/addnewaddressdialog.h"
#include "qt/pivx/tooltipmenu.h"

#include "qt/pivx/addnewcontactdialog.h"
#include "qt/pivx/pivxgui.h"
#include "guiutil.h"
#include "qt/pivx/qtutils.h"
#include "walletmodel.h"

#include <QModelIndex>
#include <QRegExpValidator>

#define DECORATION_SIZE 60
#define NUM_ITEMS 3

class ContactsHolder : public FurListRow<QWidget*>
{
public:
    ContactsHolder();

    explicit ContactsHolder(bool _isLightTheme) : FurListRow(), isLightTheme(_isLightTheme){}

    AddressLabelRow* createHolder(int pos) override{
        if (!cachedRow) cachedRow = new AddressLabelRow();
        cachedRow->init(isLightTheme, false);
        return cachedRow;
    }

    void init(QWidget* holder,const QModelIndex &index, bool isHovered, bool isSelected) const override
    {
        AddressLabelRow* row = static_cast<AddressLabelRow*>(holder);

        row->updateState(isLightTheme, isHovered, isSelected);

        QString address = index.data(Qt::DisplayRole).toString();
        if (index.data(AddressTableModel::TypeRole).toString() == AddressTableModel::ShieldedSend) {
            address = address.left(26) + "..." + address.right(26);
        }
        QModelIndex sibling = index.sibling(index.row(), AddressTableModel::Label);
        QString label = sibling.data(Qt::DisplayRole).toString();

        row->updateView(address, label);
    }

    QColor rectColor(bool isHovered, bool isSelected) override
    {
        return getRowColor(isLightTheme, isHovered, isSelected);
    }

    ~ContactsHolder() override{}

    bool isLightTheme;
    AddressLabelRow* cachedRow = nullptr;
};

AddressesWidget::AddressesWidget(PIVXGUI* parent) :
    PWidget(parent),
    ui(new Ui::AddressesWidget)
{
    ui->setupUi(this);

    this->setStyleSheet(parent->styleSheet());

    delegate = new FurAbstractListItemDelegate(
                DECORATION_SIZE,
                new ContactsHolder(isLightTheme()),
                this
    );

    /* Containers */
    setCssProperty(ui->left, "container");
    ui->left->setContentsMargins(0,20,0,20);
    setCssProperty(ui->right, "container-right");
    ui->right->setContentsMargins(20,10,20,20);
    setCssProperty(ui->listAddresses, "container");

    // Title
    setCssTitleScreen(ui->labelTitle);
    setCssSubtitleScreen(ui->labelSubtitle1);

    // Change address option
    ui->btnAddContact->setTitleClassAndText("btn-title-grey", tr("Add new contact"));
    ui->btnAddContact->setSubTitleClassAndText("text-subtitle", tr("Generate a new address to receive tokens."));
    ui->btnAddContact->setRightIconClass("ic-arrow-down");

    // List Addresses
    ui->listAddresses->setItemDelegate(delegate);
    ui->listAddresses->setIconSize(QSize(DECORATION_SIZE, DECORATION_SIZE));
    ui->listAddresses->setMinimumHeight(NUM_ITEMS * (DECORATION_SIZE + 2));
    ui->listAddresses->setAttribute(Qt::WA_MacShowFocusRect, false);
    ui->listAddresses->setSelectionBehavior(QAbstractItemView::SelectRows);
    ui->listAddresses->setUniformItemSizes(true);

    // Sort Controls
    SortEdit* lineEdit = new SortEdit(ui->comboBoxSort);
    connect(lineEdit, &SortEdit::Mouse_Pressed, [this](){ui->comboBoxSort->showPopup();});
    connect(ui->comboBoxSort, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &AddressesWidget::onSortChanged);
    SortEdit* lineEditOrder = new SortEdit(ui->comboBoxSortOrder);
    connect(lineEditOrder, &SortEdit::Mouse_Pressed, [this](){ui->comboBoxSortOrder->showPopup();});
    connect(ui->comboBoxSortOrder, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &AddressesWidget::onSortOrderChanged);
    fillAddressSortControls(lineEdit, lineEditOrder, ui->comboBoxSort, ui->comboBoxSortOrder);

    //Empty List
    ui->emptyContainer->setVisible(false);
    setCssProperty(ui->pushImgEmpty, "img-empty-contacts");
    setCssProperty(ui->labelEmpty, "text-empty");

    // Add Contact
    setCssProperty(ui->layoutNewContact, "container-options");

    // Name
    setCssProperty(ui->labelName, "text-title");
    setCssEditLine(ui->lineEditName, true);

    // Address
    setCssProperty(ui->labelAddress, "text-title");
    setCssEditLine(ui->lineEditAddress, true);
    ui->lineEditAddress->setValidator(new QRegExpValidator(QRegExp("^[A-Za-z0-9]+"), ui->lineEditName));

    // Buttons
    setCssBtnPrimary(ui->btnSave);

    connect(ui->listAddresses, &QListView::clicked, this, &AddressesWidget::handleAddressClicked);
    connect(ui->btnSave, &QPushButton::clicked, this, &AddressesWidget::onStoreContactClicked);
    connect(ui->btnAddContact, &OptionButton::clicked, this, &AddressesWidget::onAddContactShowHideClicked);
}

void AddressesWidget::handleAddressClicked(const QModelIndex &index)
{
    ui->listAddresses->setCurrentIndex(index);
    QRect rect = ui->listAddresses->visualRect(index);
    QPoint pos = rect.topRight();
    pos.setX(pos.x() - (DECORATION_SIZE * 2));
    pos.setY(pos.y() + (DECORATION_SIZE));

    QModelIndex rIndex = filter->mapToSource(index);

    if (!this->menu) {
        this->menu = new TooltipMenu(window, this);
        connect(this->menu, &TooltipMenu::message, this, &AddressesWidget::message);
        connect(this->menu, &TooltipMenu::onEditClicked, this, &AddressesWidget::onEditClicked);
        connect(this->menu, &TooltipMenu::onDeleteClicked, this, &AddressesWidget::onDeleteClicked);
        connect(this->menu, &TooltipMenu::onCopyClicked, this, &AddressesWidget::onCopyClicked);
    } else {
        this->menu->hide();
    }
    this->index = rIndex;
    menu->move(pos);
    menu->show();
}

void AddressesWidget::loadWalletModel()
{
    if (walletModel) {
        addressTablemodel = walletModel->getAddressTableModel();
        this->filter = new AddressFilterProxyModel(
                QStringList({AddressTableModel::Send, AddressTableModel::ColdStakingSend, AddressTableModel::ShieldedSend}),
                this);
        this->filter->setSourceModel(addressTablemodel);
        this->filter->sort(sortType, sortOrder);
        ui->listAddresses->setModel(this->filter);
        ui->listAddresses->setModelColumn(AddressTableModel::Address);

        updateListView();
    }
}

void AddressesWidget::updateListView()
{
    bool empty = addressTablemodel->sizeSend() == 0;
    ui->emptyContainer->setVisible(empty);
    ui->listAddresses->setVisible(!empty);
}

void AddressesWidget::onStoreContactClicked()
{
    if (walletModel) {
        QString label = ui->lineEditName->text();
        QString address = ui->lineEditAddress->text();

        bool isStakingAddress = false;
        auto pivAdd = Standard::DecodeDestination(address.toUtf8().constData(), isStakingAddress);

        if (!Standard::IsValidDestination(pivAdd)) {
            setCssEditLine(ui->lineEditAddress, false, true);
            inform(tr("Invalid Contact Address"));
            return;
        }

        if (walletModel->isMine(pivAdd)) {
            setCssEditLine(ui->lineEditAddress, false, true);
            inform(tr("Cannot store your own address as contact"));
            return;
        }

        QString storedLabel = walletModel->getAddressTableModel()->labelForAddress(address);

        if (!storedLabel.isEmpty()) {
            inform(tr("Address already stored, label: %1").arg("\'"+storedLabel+"\'"));
            return;
        }

        bool isShielded = walletModel->IsShieldedDestination(pivAdd);
        if (walletModel->updateAddressBookLabels(pivAdd, label.toUtf8().constData(),
                         isShielded ? AddressBook::AddressBookPurpose::SHIELDED_SEND :
                         isStakingAddress ? AddressBook::AddressBookPurpose::COLD_STAKING_SEND : AddressBook::AddressBookPurpose::SEND)
                ) {
            ui->lineEditAddress->setText("");
            ui->lineEditName->setText("");
            setCssEditLine(ui->lineEditAddress, true, true);
            setCssEditLine(ui->lineEditName, true, true);

            if (ui->emptyContainer->isVisible()) {
                ui->emptyContainer->setVisible(false);
                ui->listAddresses->setVisible(true);
            }
            inform(tr("New Contact Stored"));
        } else {
            inform(tr("Error Storing Contact"));
        }
    }
}

void AddressesWidget::onEditClicked()
{
    QString address = index.data(Qt::DisplayRole).toString();
    QString currentLabel = index.sibling(index.row(), AddressTableModel::Label).data(Qt::DisplayRole).toString();
    showHideOp(true);
    AddNewContactDialog *dialog = new AddNewContactDialog(window);
    dialog->setData(address, currentLabel);
    if (openDialogWithOpaqueBackground(dialog, window)) {
        if (walletModel->updateAddressBookLabels(
                Standard::DecodeDestination(address.toStdString()), dialog->getLabel().toStdString(), addressTablemodel->purposeForAddress(address.toStdString()))){
            inform(tr("Contact edited"));
        } else {
            inform(tr("Contact edit failed"));
        }
    }
    dialog->deleteLater();
}

void AddressesWidget::onDeleteClicked()
{
    if (walletModel) {
        if (ask(tr("Delete Contact"), tr("You are just about to remove the contact:\n\n%1\n\nAre you sure?").arg(index.data(Qt::DisplayRole).toString().toUtf8().constData()))
        ) {
            if (this->walletModel->getAddressTableModel()->removeRows(index.row(), 1, index)) {
                updateListView();
                inform(tr("Contact Deleted"));
            } else {
                inform(tr("Error deleting a contact"));
            }
        }
    }
}

void AddressesWidget::onCopyClicked()
{
    GUIUtil::setClipboard(index.data(Qt::DisplayRole).toString());
    inform(tr("Address copied"));
}

void AddressesWidget::onAddContactShowHideClicked()
{
    if (!ui->layoutNewContact->isVisible()){
        ui->btnAddContact->setRightIconClass("btn-dropdown", true);
        ui->layoutNewContact->setVisible(true);
    } else {
        ui->btnAddContact->setRightIconClass("ic-arrow", true);
        ui->layoutNewContact->setVisible(false);
    }
}

void AddressesWidget::onSortChanged(int idx)
{
    sortType = (AddressTableModel::ColumnIndex) ui->comboBoxSort->itemData(idx).toInt();
    sortAddresses();
}

void AddressesWidget::onSortOrderChanged(int idx)
{
    sortOrder = (Qt::SortOrder) ui->comboBoxSortOrder->itemData(idx).toInt();
    sortAddresses();
}

void AddressesWidget::sortAddresses()
{
    if (this->filter)
        this->filter->sort(sortType, sortOrder);
}

void AddressesWidget::changeTheme(bool isLightTheme, QString& theme)
{
    static_cast<ContactsHolder*>(this->delegate->getRowFactory())->isLightTheme = isLightTheme;
}

AddressesWidget::~AddressesWidget()
{
    delete ui;
}
<|endoftext|>


<commit_before>#ifndef RDB_PROTOCOL_CHANGEFEED_HPP_
#define RDB_PROTOCOL_CHANGEFEED_HPP_

#include <deque>
#include <exception>
#include <map>

#include "errors.hpp"

#include <boost/variant.hpp>

#include "concurrency/rwlock.hpp"
#include "containers/counted.hpp"
#include "containers/scoped.hpp"
#include "protocol_api.hpp"
#include "repli_timestamp.hpp"
#include "rpc/connectivity/connectivity.hpp"
#include "rpc/mailbox/typed.hpp"
#include "rpc/serialize_macros.hpp"

class auto_drainer_t;
class base_namespace_repo_t;
class mailbox_manager_t;
struct rdb_modification_report_t;

namespace ql {

class base_exc_t;
class batcher_t;
class changefeed_t;
class datum_stream_t;
class datum_t;
class env_t;
class table_t;

namespace changefeed {

struct msg_t {
    struct change_t {
        change_t();
        explicit change_t(counted_t<const datum_t> _old_val,
                          counted_t<const datum_t> _new_val);
        ~change_t();
        counted_t<const datum_t> old_val, new_val;
        RDB_DECLARE_ME_SERIALIZABLE;
    };
    struct stop_t { RDB_DECLARE_ME_SERIALIZABLE; };

    msg_t() { }
    msg_t(msg_t &&msg);
    msg_t(const msg_t &msg) = default;
    explicit msg_t(stop_t &&op);
    explicit msg_t(change_t &&op);

    // Starts with STOP to avoid doing work for default initialization.
    boost::variant<stop_t, change_t> op;

    RDB_DECLARE_ME_SERIALIZABLE;
};

class feed_t;
struct stamped_msg_t;

// The `client_t` exists on the machine handling the changefeed query, in the
// `rdb_context_t`.  When a query subscribes to the changes on a table, it
// should call `new_feed`.  The `client_t` will give it back a stream of rows.
// The `client_t` does this by maintaining an internal map from table UUIDs to
// `feed_t`s.  (It does this so that there is at most one `feed_t` per <table,
// client> pair, to prevent redundant cluster messages.)  The actual logic for
// subscribing to a changefeed server and distributing writes to streams can be
// found in the `feed_t` class.
class client_t : public home_thread_mixin_t {
public:
    typedef mailbox_addr_t<void(stamped_msg_t)> addr_t;
    client_t(mailbox_manager_t *_manager);
    ~client_t();
    // Throws QL exceptions.
    counted_t<datum_stream_t> new_feed(const counted_t<table_t> &tbl, env_t *env);
    void maybe_remove_feed(const uuid_u &uuid);
    scoped_ptr_t<feed_t> detach_feed(const uuid_u &uuid);
private:
    friend class sub_t;
    mailbox_manager_t *const manager;
    std::map<uuid_u, scoped_ptr_t<feed_t> > feeds;
    // This lock manages access to the `feeds` map.  The `feeds` map needs to be
    // read whenever `new_feed` is called, and needs to be written to whenever
    // `new_feed` is called with a table not already in the `feeds` map, or
    // whenever `maybe_remove_feed` or `detach_feed` is called.
    rwlock_t feeds_lock;
    auto_drainer_t drainer;
};

// There is one `server_t` per `store_t`, and it is used to send changes that
// occur on that `store_t` to any subscribed `feed_t`s contained in a
// `client_t`.
class server_t {
public:
    typedef mailbox_addr_t<void(client_t::addr_t)> addr_t;
    server_t(mailbox_manager_t *_manager);
    void add_client(const client_t::addr_t &addr);
    void send_all(msg_t msg);
    addr_t get_stop_addr();
    uint64_t get_stamp(const client_t::addr_t &addr);
    uuid_u get_uuid();
private:
    void stop_mailbox_cb(client_t::addr_t addr);
    void add_client_cb(signal_t *stopped, client_t::addr_t addr);

    // The UUID of the server, used so that `feed_t`s can order changefeed
    // messages on a per-server basis (and drop changefeed messages from before
    // their own creation timestamp on a per-server basis).
    const uuid_u uuid;
    mailbox_manager_t *const manager;

    struct client_info_t {
        scoped_ptr_t<cond_t> cond;
        uint64_t stamp;
    };
    std::map<client_t::addr_t, client_info_t> clients;
    // Controls access to `clients`.  A `server_t` needs to read `clients` when:
    // * `send_all` is called
    // * `get_tamp` is called
    // And needs to write to clients when:
    // * `add_client` is called
    // * A message is received at `stop_mailbox` unsubscribing a client
    // A lock is needed because e.g. `send_all` calls `send`, which can block,
    // while looping over `clients`, and we need to make sure the map doesn't
    // change under it.
    rwlock_t clients_lock;

    mailbox_t<void(client_t::addr_t)> stop_mailbox;
    auto_drainer_t drainer;
};

} // namespace changefeed
} // namespace ql

#endif // RDB_PROTOCOL_CHANGEFEED_HPP_
<commit_msg>Added comments.<commit_after>#ifndef RDB_PROTOCOL_CHANGEFEED_HPP_
#define RDB_PROTOCOL_CHANGEFEED_HPP_

#include <deque>
#include <exception>
#include <map>

#include "errors.hpp"

#include <boost/variant.hpp>

#include "concurrency/rwlock.hpp"
#include "containers/counted.hpp"
#include "containers/scoped.hpp"
#include "protocol_api.hpp"
#include "repli_timestamp.hpp"
#include "rpc/connectivity/connectivity.hpp"
#include "rpc/mailbox/typed.hpp"
#include "rpc/serialize_macros.hpp"

class auto_drainer_t;
class base_namespace_repo_t;
class mailbox_manager_t;
struct rdb_modification_report_t;

namespace ql {

class base_exc_t;
class batcher_t;
class changefeed_t;
class datum_stream_t;
class datum_t;
class env_t;
class table_t;

namespace changefeed {

struct msg_t {
    struct change_t {
        change_t();
        explicit change_t(counted_t<const datum_t> _old_val,
                          counted_t<const datum_t> _new_val);
        ~change_t();
        counted_t<const datum_t> old_val, new_val;
        RDB_DECLARE_ME_SERIALIZABLE;
    };
    struct stop_t { RDB_DECLARE_ME_SERIALIZABLE; };

    msg_t() { }
    msg_t(msg_t &&msg);
    msg_t(const msg_t &msg) = default;
    explicit msg_t(stop_t &&op);
    explicit msg_t(change_t &&op);

    // Starts with STOP to avoid doing work for default initialization.
    boost::variant<stop_t, change_t> op;

    RDB_DECLARE_ME_SERIALIZABLE;
};

class feed_t;
struct stamped_msg_t;

// The `client_t` exists on the machine handling the changefeed query, in the
// `rdb_context_t`.  When a query subscribes to the changes on a table, it
// should call `new_feed`.  The `client_t` will give it back a stream of rows.
// The `client_t` does this by maintaining an internal map from table UUIDs to
// `feed_t`s.  (It does this so that there is at most one `feed_t` per <table,
// client> pair, to prevent redundant cluster messages.)  The actual logic for
// subscribing to a changefeed server and distributing writes to streams can be
// found in the `feed_t` class.
class client_t : public home_thread_mixin_t {
public:
    typedef mailbox_addr_t<void(stamped_msg_t)> addr_t;
    client_t(mailbox_manager_t *_manager);
    ~client_t();
    // Throws QL exceptions.
    counted_t<datum_stream_t> new_feed(const counted_t<table_t> &tbl, env_t *env);
    void maybe_remove_feed(const uuid_u &uuid);
    scoped_ptr_t<feed_t> detach_feed(const uuid_u &uuid);
private:
    friend class sub_t;
    mailbox_manager_t *const manager;
    std::map<uuid_u, scoped_ptr_t<feed_t> > feeds;
    // This lock manages access to the `feeds` map.  The `feeds` map needs to be
    // read whenever `new_feed` is called, and needs to be written to whenever
    // `new_feed` is called with a table not already in the `feeds` map, or
    // whenever `maybe_remove_feed` or `detach_feed` is called.
    rwlock_t feeds_lock;
    auto_drainer_t drainer;
};

// There is one `server_t` per `store_t`, and it is used to send changes that
// occur on that `store_t` to any subscribed `feed_t`s contained in a
// `client_t`.
class server_t {
public:
    typedef mailbox_addr_t<void(client_t::addr_t)> addr_t;
    server_t(mailbox_manager_t *_manager);
    void add_client(const client_t::addr_t &addr);
    void send_all(msg_t msg);
    addr_t get_stop_addr();
    uint64_t get_stamp(const client_t::addr_t &addr);
    uuid_u get_uuid();
private:
    void stop_mailbox_cb(client_t::addr_t addr);
    void add_client_cb(signal_t *stopped, client_t::addr_t addr);

    // The UUID of the server, used so that `feed_t`s can order changefeed
    // messages on a per-server basis (and drop changefeed messages from before
    // their own creation timestamp on a per-server basis).
    const uuid_u uuid;
    mailbox_manager_t *const manager;

    struct client_info_t {
        scoped_ptr_t<cond_t> cond;
        uint64_t stamp;
    };
    std::map<client_t::addr_t, client_info_t> clients;
    // Controls access to `clients`.  A `server_t` needs to read `clients` when:
    // * `send_all` is called
    // * `get_tamp` is called
    // And needs to write to clients when:
    // * `add_client` is called
    // * A message is received at `stop_mailbox` unsubscribing a client
    // A lock is needed because e.g. `send_all` calls `send`, which can block,
    // while looping over `clients`, and we need to make sure the map doesn't
    // change under it.
    rwlock_t clients_lock;

    // Clients send a message to this mailbox with their address when they want
    // to unsubscribe.
    mailbox_t<void(client_t::addr_t)> stop_mailbox;
    auto_drainer_t drainer;
};

} // namespace changefeed
} // namespace ql

#endif // RDB_PROTOCOL_CHANGEFEED_HPP_
<|endoftext|>


<commit_before>//////////////////////////////////////////////////////////////////////////
//
//  Copyright (c) 2010, Image Engine Design Inc. All rights reserved.
//
//  Redistribution and use in source and binary forms, with or without
//  modification, are permitted provided that the following conditions are
//  met:
//
//     * Redistributions of source code must retain the above copyright
//       notice, this list of conditions and the following disclaimer.
//
//     * Redistributions in binary form must reproduce the above copyright
//       notice, this list of conditions and the following disclaimer in the
//       documentation and/or other materials provided with the distribution.
//
//     * Neither the name of Image Engine Design nor the names of any
//       other contributors to this software may be used to endorse or
//       promote products derived from this software without specific prior
//       written permission.
//
//  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
//  IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
//  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
//  PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
//  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
//  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
//  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
//  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
//  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
//  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
//  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
//////////////////////////////////////////////////////////////////////////

#include "boost/shared_ptr.hpp"

#include "IECore/VectorTypedData.h"

#include "IECoreRI/SXExecutor.h"

using namespace IECoreRI;
using namespace IECore;
using namespace Imath;
using namespace std;

SXExecutor::SXExecutor( const ShaderVector *shaders, const ShaderVector *coshaders, const ShaderVector *lights )
	:	m_shaders( shaders ), m_coshaders( coshaders ), m_lights( lights )
{
}

IECore::CompoundDataPtr SXExecutor::execute( const IECore::CompoundData *points ) const
{
	V2i gridSize( 0 );
	return execute( points, gridSize );
}

IECore::CompoundDataPtr SXExecutor::execute( const IECore::CompoundData *points, const Imath::V2i &gridSize ) const
{
	// decide how many points we're shading and validate the grid size

	const V3fVectorData *p = points->member<V3fVectorData>( "P", true /* throw */ );
	size_t numPoints = p->readable().size();

	bool haveGrid = gridSize.x > 0 && gridSize.y > 0;
	if( haveGrid )
	{
		if( numPoints != (size_t)(gridSize.x * gridSize.y) )
		{
			throw Exception( boost::str( boost::format( "Wrong number of points (%d) for grid (%dx%d)." ) % numPoints % gridSize.x % gridSize.y ) );
		}
	}

	// create parameter list and set topology if we can.
	// we use a shared_ptr with a custom deleter to ensure that SxDestroyParameterList is called
	// no matter how we exit this function.

	boost::shared_ptr<void> vars( SxCreateParameterList( 0, numPoints, "current" ), SxDestroyParameterList );

	if( haveGrid )
	{
		unsigned nu = gridSize.x; unsigned nv = gridSize.y;
		SxSetParameterListGridTopology( vars.get(), 1, &nu, &nv );
	}

	// set input variables for coshaders
	if( m_coshaders )
	{
		for( unsigned shaderIndex = 0; shaderIndex < m_coshaders->size(); shaderIndex++ )
		{
			setVariables( vars.get(), (*m_coshaders)[shaderIndex], 0, points, numPoints );
		}
	}

	// loop over the shaders, running them and extracting results as we go
	CompoundDataPtr result = new CompoundData();

	SxShader previousShader = 0;
	for( unsigned shaderIndex = 0; shaderIndex < m_shaders->size(); shaderIndex++ )
	{

		// set parameters from input data

		setVariables( vars.get(), (*m_shaders)[shaderIndex], previousShader, points, numPoints );

		// run shader

		SxCallShader(
			(*m_shaders)[shaderIndex],
			vars.get(),
			m_lights ? (void **)&((*m_lights)[0]) : 0,
			m_lights ? m_lights->size() : 0,
			m_coshaders ? (void **)&((*m_coshaders)[0]) : 0,
			m_coshaders ? m_coshaders->size() : 0
		);

		// extract and store output data

		unsigned numParameters = SxGetNumParameters( (*m_shaders)[shaderIndex] );
		for( unsigned i=0; i<numParameters; i++ )
		{
			SxType type;
			bool varying;
			SxData defaultValue;
			unsigned arraySize;
			const char *spaceName;
			bool output;
			const char *name = SxGetParameterInfo( (*m_shaders)[shaderIndex], i, &type, &varying, &defaultValue, &arraySize, &spaceName, &output );

			if( output )
			{
				switch( type )
				{
					case SxFloat :
					{
						getVariable<FloatVectorData>( (*m_shaders)[shaderIndex], name, result );
						break;
					}
					case SxColor :
					{
						getVariable<Color3fVectorData>( (*m_shaders)[shaderIndex], name, result );
						break;
					}
					case SxVector :
					case SxPoint :
					case SxNormal :
					{
						getVariable<V3fVectorData>( (*m_shaders)[shaderIndex], name, result );
						break;
					}
					default :
						throw Exception( boost::str( boost::format( "Output parameter \"%s\" has unsupported type." ) % name ) );

				}
			}
		}

		getVariable<Color3fVectorData>( (*m_shaders)[shaderIndex], "Ci", result );
		getVariable<Color3fVectorData>( (*m_shaders)[shaderIndex], "Oi", result );
		getVariable<V3fVectorData>( (*m_shaders)[shaderIndex], "P", result );
		getVariable<V3fVectorData>( (*m_shaders)[shaderIndex], "N", result );

		previousShader = (*m_shaders)[shaderIndex];

	}

	return result;
}

IECore::TypeId SXExecutor::predefinedParameterType( const char *name ) const
{
	if(
		strcmp( name, "P" )==0 ||
		strcmp( name, "N" )==0 ||
		strcmp( name, "Ng" )==0 ||
		strcmp( name, "I" )==0
	)
	{
		return V3fVectorDataTypeId;
	}
	else if(
		strcmp( name, "s" )==0 ||
		strcmp( name, "t" )==0 ||
		strcmp( name, "u" )==0 ||
		strcmp( name, "v" )==0 ||
		strcmp( name, "du" )==0 ||
		strcmp( name, "dv" )==0
	)
	{
		return FloatVectorDataTypeId;
	}
	else if(
		strcmp( name, "Cs" )==0 ||
		strcmp( name, "Os" )==0 ||
		strcmp( name, "Ci" )==0 ||
		strcmp( name, "Oi" )==0 ||
		strcmp( name, "Cl" )==0
	)
	{
		return Color3fVectorDataTypeId;
	}

	throw Exception( boost::str( boost::format( "Unknown predefined parameter \"%s\"" ) % name ) );
}

void SXExecutor::setVariables( SxParameterList parameterList, SxShader targetShader, SxShader previousShader, const IECore::CompoundData *points, size_t expectedSize ) const
{
	unsigned numPredefinedParameters = SxGetPredefinedParameters( targetShader, 0, 0 );
	std::vector<const char *> predefinedParameters( numPredefinedParameters );
	SxGetPredefinedParameters( targetShader, &(predefinedParameters[0]), numPredefinedParameters );

	for( unsigned i=0; i<numPredefinedParameters; i++ )
	{
		IECore::TypeId expectedType = predefinedParameterType( predefinedParameters[i] );

		switch( expectedType )
		{
			case FloatVectorDataTypeId :
			{
				setVariable<FloatVectorData>( parameterList, predefinedParameters[i], previousShader, points, expectedSize );
				break;
			}
			case V3fVectorDataTypeId :
			{
				setVariable<V3fVectorData>( parameterList, predefinedParameters[i], previousShader, points, expectedSize );
				break;
			}
			case Color3fVectorDataTypeId :
			{
				setVariable<Color3fVectorData>( parameterList, predefinedParameters[i], previousShader, points, expectedSize );
				break;
			}
			default :
				throw Exception( boost::str( boost::format( "Input parameter \"%s\" has unsupported type." ) % predefinedParameters[i] ) );
		}
	}
}

template<class T>
void SXExecutor::setVariable( SxParameterList parameterList, const char *name, SxShader previousShader, const CompoundData *points, size_t expectedSize ) const
{
	// try to set the variable based on the output of the previous shader
	if( previousShader )
	{
		void *d = 0;
		size_t numPoints = SxGetWritableParameterInfo( previousShader, name, &d );
		if( d && numPoints==expectedSize )
		{
			SxSetPredefinedParameter( parameterList, name, d );
			return;
		}
	}

	// if that failed for any reason then set the variable based on the input data provided by the caller
	const Data *d = points->member<Data>( name );
	if( d )
	{
		const T *td = IECore::runTimeCast<const T>( d );
		if( !td )
		{
			throw( Exception( boost::str( boost::format( "Input parameter \"%s\" has wrong type (%s but should be %s)." ) % d->typeName() % T::staticTypeName() ) ) );
		}

		if( td )
		{
			size_t s = td->readable().size();
			if( s != expectedSize )
			{
				throw( Exception( boost::str( boost::format( "Input parameter \"%s\" has wrong size (%d but should be %d)." ) % name % s % expectedSize ) ) );
			}

			SxSetPredefinedParameter( parameterList, name, (void *)&(td->readable()[0]) );
		}
	}
	else
	{
		// no data available, fall back to default values if we don't think they'll
		// be provided by attributes. for some things (like "t") the Sx library will
		// crash if they're not specified, but for others (like "Cs") it'll provide
		// a default value from the attribute state and we don't want to overwrite that.
		// others still (like "Ci") are for output only as far as i know so we don't
		// want to set them either. others still ("u", "du" etc) are provided by 3delight
		// when there is a grid topology specified. all this should go away in 3delight 9.0.44
		// where the library itself will be providing default values and the crashes will
		// be gone.
		if(
			0==strcmp( name, "Cs" ) || 0==strcmp( name, "Os" ) ||
			0==strcmp( name, "Ci" ) || 0==strcmp( name, "Oi" ) ||
			0==strcmp( name, "u" ) || 0==strcmp( name, "v" ) ||
			0==strcmp( name, "du" ) || 0==strcmp( name, "dv" )
		)
		{
			return;
		}
		typename T::ValueType defaultValue; defaultValue.resize( expectedSize, typename T::ValueType::value_type( 0 ) );
		SxSetPredefinedParameter( parameterList, name, &(defaultValue[0]) );
	}
}

template<class T>
void SXExecutor::getVariable( SxShader shader, const char *name, IECore::CompoundData *container ) const
{
	void *d = 0;
	int numPoints = SxGetWritableParameterInfo( shader, name, &d );
	if( d && numPoints )
	{
		typename T::Ptr data = new T;
		data->writable().resize( numPoints );
		memcpy( &(data->writable()[0]), d, numPoints * sizeof( typename T::ValueType::value_type ) );
		container->writable()[name] = data;
	}
}
<commit_msg>Removing workarounds for previous versions of 3delight - tests fine with 9.0.44.<commit_after>//////////////////////////////////////////////////////////////////////////
//
//  Copyright (c) 2010, Image Engine Design Inc. All rights reserved.
//
//  Redistribution and use in source and binary forms, with or without
//  modification, are permitted provided that the following conditions are
//  met:
//
//     * Redistributions of source code must retain the above copyright
//       notice, this list of conditions and the following disclaimer.
//
//     * Redistributions in binary form must reproduce the above copyright
//       notice, this list of conditions and the following disclaimer in the
//       documentation and/or other materials provided with the distribution.
//
//     * Neither the name of Image Engine Design nor the names of any
//       other contributors to this software may be used to endorse or
//       promote products derived from this software without specific prior
//       written permission.
//
//  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
//  IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
//  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
//  PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
//  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
//  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
//  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
//  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
//  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
//  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
//  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
//////////////////////////////////////////////////////////////////////////

#include "boost/shared_ptr.hpp"

#include "IECore/VectorTypedData.h"

#include "IECoreRI/SXExecutor.h"

using namespace IECoreRI;
using namespace IECore;
using namespace Imath;
using namespace std;

SXExecutor::SXExecutor( const ShaderVector *shaders, const ShaderVector *coshaders, const ShaderVector *lights )
	:	m_shaders( shaders ), m_coshaders( coshaders ), m_lights( lights )
{
}

IECore::CompoundDataPtr SXExecutor::execute( const IECore::CompoundData *points ) const
{
	V2i gridSize( 0 );
	return execute( points, gridSize );
}

IECore::CompoundDataPtr SXExecutor::execute( const IECore::CompoundData *points, const Imath::V2i &gridSize ) const
{
	// decide how many points we're shading and validate the grid size

	const V3fVectorData *p = points->member<V3fVectorData>( "P", true /* throw */ );
	size_t numPoints = p->readable().size();

	bool haveGrid = gridSize.x > 0 && gridSize.y > 0;
	if( haveGrid )
	{
		if( numPoints != (size_t)(gridSize.x * gridSize.y) )
		{
			throw Exception( boost::str( boost::format( "Wrong number of points (%d) for grid (%dx%d)." ) % numPoints % gridSize.x % gridSize.y ) );
		}
	}

	// create parameter list and set topology if we can.
	// we use a shared_ptr with a custom deleter to ensure that SxDestroyParameterList is called
	// no matter how we exit this function.

	boost::shared_ptr<void> vars( SxCreateParameterList( 0, numPoints, "current" ), SxDestroyParameterList );

	if( haveGrid )
	{
		unsigned nu = gridSize.x; unsigned nv = gridSize.y;
		SxSetParameterListGridTopology( vars.get(), 1, &nu, &nv );
	}

	// set input variables for coshaders
	if( m_coshaders )
	{
		for( unsigned shaderIndex = 0; shaderIndex < m_coshaders->size(); shaderIndex++ )
		{
			setVariables( vars.get(), (*m_coshaders)[shaderIndex], 0, points, numPoints );
		}
	}

	// loop over the shaders, running them and extracting results as we go
	CompoundDataPtr result = new CompoundData();

	SxShader previousShader = 0;
	for( unsigned shaderIndex = 0; shaderIndex < m_shaders->size(); shaderIndex++ )
	{

		// set parameters from input data

		setVariables( vars.get(), (*m_shaders)[shaderIndex], previousShader, points, numPoints );

		// run shader

		SxCallShader(
			(*m_shaders)[shaderIndex],
			vars.get(),
			m_lights ? (void **)&((*m_lights)[0]) : 0,
			m_lights ? m_lights->size() : 0,
			m_coshaders ? (void **)&((*m_coshaders)[0]) : 0,
			m_coshaders ? m_coshaders->size() : 0
		);

		// extract and store output data

		unsigned numParameters = SxGetNumParameters( (*m_shaders)[shaderIndex] );
		for( unsigned i=0; i<numParameters; i++ )
		{
			SxType type;
			bool varying;
			SxData defaultValue;
			unsigned arraySize;
			const char *spaceName;
			bool output;
			const char *name = SxGetParameterInfo( (*m_shaders)[shaderIndex], i, &type, &varying, &defaultValue, &arraySize, &spaceName, &output );

			if( output )
			{
				switch( type )
				{
					case SxFloat :
					{
						getVariable<FloatVectorData>( (*m_shaders)[shaderIndex], name, result );
						break;
					}
					case SxColor :
					{
						getVariable<Color3fVectorData>( (*m_shaders)[shaderIndex], name, result );
						break;
					}
					case SxVector :
					case SxPoint :
					case SxNormal :
					{
						getVariable<V3fVectorData>( (*m_shaders)[shaderIndex], name, result );
						break;
					}
					default :
						throw Exception( boost::str( boost::format( "Output parameter \"%s\" has unsupported type." ) % name ) );

				}
			}
		}

		getVariable<Color3fVectorData>( (*m_shaders)[shaderIndex], "Ci", result );
		getVariable<Color3fVectorData>( (*m_shaders)[shaderIndex], "Oi", result );
		getVariable<V3fVectorData>( (*m_shaders)[shaderIndex], "P", result );
		getVariable<V3fVectorData>( (*m_shaders)[shaderIndex], "N", result );

		previousShader = (*m_shaders)[shaderIndex];

	}

	return result;
}

IECore::TypeId SXExecutor::predefinedParameterType( const char *name ) const
{
	if(
		strcmp( name, "P" )==0 ||
		strcmp( name, "N" )==0 ||
		strcmp( name, "Ng" )==0 ||
		strcmp( name, "I" )==0
	)
	{
		return V3fVectorDataTypeId;
	}
	else if(
		strcmp( name, "s" )==0 ||
		strcmp( name, "t" )==0 ||
		strcmp( name, "u" )==0 ||
		strcmp( name, "v" )==0 ||
		strcmp( name, "du" )==0 ||
		strcmp( name, "dv" )==0
	)
	{
		return FloatVectorDataTypeId;
	}
	else if(
		strcmp( name, "Cs" )==0 ||
		strcmp( name, "Os" )==0 ||
		strcmp( name, "Ci" )==0 ||
		strcmp( name, "Oi" )==0 ||
		strcmp( name, "Cl" )==0
	)
	{
		return Color3fVectorDataTypeId;
	}

	throw Exception( boost::str( boost::format( "Unknown predefined parameter \"%s\"" ) % name ) );
}

void SXExecutor::setVariables( SxParameterList parameterList, SxShader targetShader, SxShader previousShader, const IECore::CompoundData *points, size_t expectedSize ) const
{
	unsigned numPredefinedParameters = SxGetPredefinedParameters( targetShader, 0, 0 );
	std::vector<const char *> predefinedParameters( numPredefinedParameters );
	SxGetPredefinedParameters( targetShader, &(predefinedParameters[0]), numPredefinedParameters );

	for( unsigned i=0; i<numPredefinedParameters; i++ )
	{
		IECore::TypeId expectedType = predefinedParameterType( predefinedParameters[i] );

		switch( expectedType )
		{
			case FloatVectorDataTypeId :
			{
				setVariable<FloatVectorData>( parameterList, predefinedParameters[i], previousShader, points, expectedSize );
				break;
			}
			case V3fVectorDataTypeId :
			{
				setVariable<V3fVectorData>( parameterList, predefinedParameters[i], previousShader, points, expectedSize );
				break;
			}
			case Color3fVectorDataTypeId :
			{
				setVariable<Color3fVectorData>( parameterList, predefinedParameters[i], previousShader, points, expectedSize );
				break;
			}
			default :
				throw Exception( boost::str( boost::format( "Input parameter \"%s\" has unsupported type." ) % predefinedParameters[i] ) );
		}
	}
}

template<class T>
void SXExecutor::setVariable( SxParameterList parameterList, const char *name, SxShader previousShader, const CompoundData *points, size_t expectedSize ) const
{
	// try to set the variable based on the output of the previous shader
	if( previousShader )
	{
		void *d = 0;
		size_t numPoints = SxGetWritableParameterInfo( previousShader, name, &d );
		if( d && numPoints==expectedSize )
		{
			SxSetPredefinedParameter( parameterList, name, d );
			return;
		}
	}

	// if that failed for any reason then set the variable based on the input data provided by the caller
	const Data *d = points->member<Data>( name );
	if( d )
	{
		const T *td = IECore::runTimeCast<const T>( d );
		if( !td )
		{
			throw( Exception( boost::str( boost::format( "Input parameter \"%s\" has wrong type (%s but should be %s)." ) % d->typeName() % T::staticTypeName() ) ) );
		}

		if( td )
		{
			size_t s = td->readable().size();
			if( s != expectedSize )
			{
				throw( Exception( boost::str( boost::format( "Input parameter \"%s\" has wrong size (%d but should be %d)." ) % name % s % expectedSize ) ) );
			}

			SxSetPredefinedParameter( parameterList, name, (void *)&(td->readable()[0]) );
		}
	}
}

template<class T>
void SXExecutor::getVariable( SxShader shader, const char *name, IECore::CompoundData *container ) const
{
	void *d = 0;
	int numPoints = SxGetWritableParameterInfo( shader, name, &d );
	if( d && numPoints )
	{
		typename T::Ptr data = new T;
		data->writable().resize( numPoints );
		memcpy( &(data->writable()[0]), d, numPoints * sizeof( typename T::ValueType::value_type ) );
		container->writable()[name] = data;
	}
}
<|endoftext|>


<commit_before>//Copyright (c) 2021 Ultimaker B.V.
//CuraEngine is released under the terms of the AGPLv3 or higher.

#include "ExtruderTrain.h"
#include "FffGcodeWriter.h"
#include "InsetOrderOptimizer.h"
#include "LayerPlan.h"
#include "utils/logoutput.h"
#include "WallToolPaths.h"

namespace cura
{

InsetOrderOptimizer::InsetOrderOptimizer(const FffGcodeWriter& gcode_writer,
                                         const SliceDataStorage& storage,
                                         LayerPlan& gcode_layer,
                                         const Settings& settings,
                                         const int extruder_nr,
                                         const GCodePathConfig& inset_0_non_bridge_config,
                                         const GCodePathConfig& inset_X_non_bridge_config,
                                         const GCodePathConfig& inset_0_bridge_config,
                                         const GCodePathConfig& inset_X_bridge_config,
                                         const bool retract_before_outer_wall,
                                         const coord_t wall_0_wipe_dist,
                                         const coord_t wall_x_wipe_dist,
                                         const size_t wall_0_extruder_nr,
                                         const size_t wall_x_extruder_nr,
                                         const ZSeamConfig& z_seam_config,
                                         const VariableWidthPaths& paths) :
    gcode_writer(gcode_writer),
    storage(storage),
    gcode_layer(gcode_layer),
    settings(settings),
    extruder_nr(extruder_nr),
    inset_0_non_bridge_config(inset_0_non_bridge_config),
    inset_X_non_bridge_config(inset_X_non_bridge_config),
    inset_0_bridge_config(inset_0_bridge_config),
    inset_X_bridge_config(inset_X_bridge_config),
    retract_before_outer_wall(retract_before_outer_wall),
    wall_0_wipe_dist(wall_0_wipe_dist),
    wall_x_wipe_dist(wall_x_wipe_dist),
    wall_0_extruder_nr(wall_0_extruder_nr),
    wall_x_extruder_nr(wall_x_extruder_nr),
    z_seam_config(z_seam_config),
    paths(paths),
    layer_nr(gcode_layer.getLayerNr()),
    added_something(false),
    retraction_region_calculated(false)
{
}

bool InsetOrderOptimizer::addToLayer()
{
    // Settings & configs:
    const bool pack_by_inset = ! settings.get<bool>("optimize_wall_printing_order");
    const InsetDirection inset_direction = settings.get<InsetDirection>("inset_direction");
    const bool center_last = inset_direction == InsetDirection::CENTER_LAST;
    const bool alternate_walls = settings.get<bool>("material_alternate_walls");

    const bool outer_to_inner = inset_direction == InsetDirection::OUTSIDE_IN;

    size_t start_inset;
    size_t end_inset;
    int direction;
    //If the entire wall is printed with the current extruder, print all of it.
    if (wall_0_extruder_nr == wall_x_extruder_nr && wall_x_extruder_nr == extruder_nr)
    {
        //If printing the outer inset first, start with the lowest inset.
        //Otherwise start with the highest inset and iterate backwards.
        if(inset_direction == InsetDirection::OUTSIDE_IN)
        {
            start_inset = 0;
            end_inset = paths.size();
            direction = 1;
        }
        else //INSIDE_OUT or CENTER_LAST.
        {
            start_inset = paths.size() - 1;
            end_inset = -1;
            direction = -1;
        }
    }
    //If the wall is partially printed with the current extruder, print the correct part.
    else if (wall_0_extruder_nr != wall_x_extruder_nr)
    {
        //If the wall_0 and wall_x extruders are different, then only include the insets that should be printed by the
        //current extruder_nr.
        if(extruder_nr == wall_0_extruder_nr)
        {
            start_inset = 0;
            end_inset = 1; // Ignore inner walls
            direction = 1;
        }
        else if(extruder_nr == wall_x_extruder_nr)
        {
            start_inset = paths.size() - 1;
            end_inset = 0; // Ignore outer wall
            direction = -1;
        }
        else
        {
            return added_something;
        }
    }
    else //The wall is not printed with this extruder, not even in part. Don't print anything then.
    {
        return added_something;
    }

    std::vector<const ExtrusionLine*> walls_to_be_added;
    //Add all of the insets one by one.
    for(size_t inset_idx = start_inset; inset_idx != end_inset; inset_idx += direction)
    {
        if (paths[inset_idx].empty())
        {
            continue; //Don't switch extruders either, etc.
        }
        const VariableWidthLines& inset = paths[inset_idx];
        for (const ExtrusionLine& wall : inset)
        {
            walls_to_be_added.emplace_back(&wall);
        }
    }

    std::unordered_set<std::pair<const ExtrusionLine*, const ExtrusionLine*>> order =
        pack_by_inset?
        getInsetOrder(walls_to_be_added, outer_to_inner)
        : getRegionOrder(walls_to_be_added, outer_to_inner);

    if (center_last)
    {
        for (const ExtrusionLine* line : walls_to_be_added)
            if (line->is_odd)
                for (const ExtrusionLine* other_line : walls_to_be_added)
                    if ( ! other_line->is_odd)
                        order.emplace(std::make_pair(other_line, line));
    }

    constexpr Ratio flow = 1.0_r;

    bool added_something = false;

    constexpr bool detect_loops = false;
    constexpr Polygons* combing_boundary = nullptr;
    //When we alternate walls, also alternate the direction at which the first wall starts in.
    //On even layers we start with normal direction, on odd layers with inverted direction.
    constexpr bool reverse_all_paths = false;
    PathOrderOptimizer<const ExtrusionLine*> order_optimizer(gcode_layer.getLastPlannedPositionOrStartingPosition(), z_seam_config, detect_loops, combing_boundary, reverse_all_paths, order);

    for (const ExtrusionLine* line : walls_to_be_added)
    {
        if (line->is_closed)
        {
            order_optimizer.addPolygon(line);
        }
        else
        {
            order_optimizer.addPolyline(line);
        }
    }

    order_optimizer.optimize();

    cura::Point p_end {0, 0};
    for(const PathOrderOptimizer<const ExtrusionLine*>::Path& path : order_optimizer.paths)
    {
        if (path.vertices->empty()) continue;

        const bool is_outer_wall = path.vertices->inset_idx == 0; // or thin wall 'gap filler'
        const bool is_gap_filler = path.vertices->is_odd;
        const GCodePathConfig& non_bridge_config = is_outer_wall ? inset_0_non_bridge_config : inset_X_non_bridge_config;
        const GCodePathConfig& bridge_config = is_outer_wall? inset_0_bridge_config : inset_X_bridge_config;
        const coord_t wipe_dist = is_outer_wall && ! is_gap_filler ? wall_0_wipe_dist : wall_x_wipe_dist;
        const bool retract_before = is_outer_wall ? retract_before_outer_wall : false;

        const bool alternate_direction_modifier = alternate_walls && (path.vertices->inset_idx % 2 == layer_nr % 2);
        const bool backwards = path.backwards != alternate_direction_modifier;

        p_end = path.backwards ? path.vertices->back().p : path.vertices->front().p;
        const cura::Point p_start = path.backwards ? path.vertices->front().p : path.vertices->back().p;
        const bool linked_path = p_start != p_end;

        added_something = true;
        gcode_writer.setExtruder_addPrime(storage, gcode_layer, extruder_nr);
        gcode_layer.setIsInside(true); //Going to print walls, which are always inside.
        gcode_layer.addWall(*path.vertices, path.start_vertex, settings, non_bridge_config, bridge_config, wipe_dist, flow, retract_before, path.is_closed, backwards, linked_path);
        added_something = true;
    }
    return added_something;
}

std::unordered_set<std::pair<const ExtrusionLine*, const ExtrusionLine*>> InsetOrderOptimizer::getRegionOrder(const std::vector<const ExtrusionLine*>& input, const bool outer_to_inner)
{
    std::unordered_set<std::pair<const ExtrusionLine*, const ExtrusionLine*>> order_requirements;

    coord_t max_line_w = 0u;
    { // compute max_line_w
        for (const ExtrusionLine* line : input)
        {
            for (const ExtrusionJunction& junction : *line)
            {
                max_line_w = std::max(max_line_w, junction.w);
            }
        }
    }
    if (max_line_w == 0u) return order_requirements;

    const coord_t searching_radius = max_line_w * 2;
    using GridT = SparsePointGridInclusive<const ExtrusionLine*>;
    GridT grid(searching_radius);

    for (const ExtrusionLine* line : input)
    {
        for (const ExtrusionJunction& junction : *line)
        {
            grid.insert(junction.p, line);
        }
    }
    for (const std::pair<SquareGrid::GridPoint, SparsePointGridInclusiveImpl::SparsePointGridInclusiveElem<const ExtrusionLine*>>& p : grid)
    {
        const ExtrusionLine* here = p.second.val;
        Point loc_here = p.second.point;
        std::vector<const ExtrusionLine*> nearby_verts = grid.getNearbyVals(loc_here, searching_radius);
        for (const ExtrusionLine* nearby : nearby_verts)
        {
            if (nearby == here) continue;
            if (nearby->inset_idx == here->inset_idx) continue;
            if (nearby->inset_idx > here->inset_idx + 1) continue; // not directly adjacent
            if (here->inset_idx > nearby->inset_idx + 1) continue; // not directly adjacent
            if (here->is_odd || nearby->is_odd)
            {
                if (here->is_odd && ! nearby->is_odd && nearby->inset_idx < here->inset_idx)
                {
                    order_requirements.emplace(std::make_pair(nearby, here));
                }
                if (nearby->is_odd && ! here->is_odd && here->inset_idx < nearby->inset_idx)
                {
                    order_requirements.emplace(std::make_pair(here, nearby));
                }
            }
            else if (nearby->inset_idx < here->inset_idx == outer_to_inner)
            {
                order_requirements.emplace(std::make_pair(nearby, here));
            }
            else
            {
                assert(nearby->inset_idx > here->inset_idx == outer_to_inner);
                order_requirements.emplace(std::make_pair(here, nearby));
            }
        }
    }
    return order_requirements;
}

std::unordered_set<std::pair<const ExtrusionLine*, const ExtrusionLine*>> InsetOrderOptimizer::getInsetOrder(const std::vector<const ExtrusionLine*>& input, const bool outer_to_inner)
{
    std::unordered_set<std::pair<const ExtrusionLine*, const ExtrusionLine*>> order;

    std::vector<std::vector<const ExtrusionLine*>> walls_by_inset;
    std::vector<std::vector<const ExtrusionLine*>> fillers_by_inset;

    for (const ExtrusionLine* line : input)
    {
        if (line->is_odd)
        {
            if (line->inset_idx >= fillers_by_inset.size())
            {
                fillers_by_inset.resize(line->inset_idx + 1);
            }
            fillers_by_inset[line->inset_idx].emplace_back(line);
        }
        else
        {
            if (line->inset_idx >= walls_by_inset.size())
            {
                walls_by_inset.resize(line->inset_idx + 1);
            }
            walls_by_inset[line->inset_idx].emplace_back(line);
        }
    }
    for (size_t inset_idx = 0; inset_idx + 1 < walls_by_inset.size(); inset_idx++)
    {
        for (const ExtrusionLine* line : walls_by_inset[inset_idx])
        {
            for (const ExtrusionLine* inner_line : walls_by_inset[inset_idx + 1])
            {
                const ExtrusionLine* before = inner_line;
                const ExtrusionLine* after = line;
                if (outer_to_inner)
                {
                    std::swap(before, after);
                }
                order.emplace(before, after);
            }
        }
    }
    for (size_t inset_idx = 1; inset_idx < fillers_by_inset.size(); inset_idx++)
    {
        for (const ExtrusionLine* line : fillers_by_inset[inset_idx])
        {
            assert(inset_idx - 1 < walls_by_inset.size());
            if (inset_idx - 1 >= walls_by_inset.size()) continue;
            for (const ExtrusionLine* enclosing_wall : walls_by_inset[inset_idx - 1])
            {
                order.emplace(enclosing_wall, line);
            }
        }

    }

    return order;
}

}//namespace cura
<commit_msg>fix order between too far away lines<commit_after>//Copyright (c) 2021 Ultimaker B.V.
//CuraEngine is released under the terms of the AGPLv3 or higher.

#include "ExtruderTrain.h"
#include "FffGcodeWriter.h"
#include "InsetOrderOptimizer.h"
#include "LayerPlan.h"
#include "utils/logoutput.h"
#include "WallToolPaths.h"

namespace cura
{

InsetOrderOptimizer::InsetOrderOptimizer(const FffGcodeWriter& gcode_writer,
                                         const SliceDataStorage& storage,
                                         LayerPlan& gcode_layer,
                                         const Settings& settings,
                                         const int extruder_nr,
                                         const GCodePathConfig& inset_0_non_bridge_config,
                                         const GCodePathConfig& inset_X_non_bridge_config,
                                         const GCodePathConfig& inset_0_bridge_config,
                                         const GCodePathConfig& inset_X_bridge_config,
                                         const bool retract_before_outer_wall,
                                         const coord_t wall_0_wipe_dist,
                                         const coord_t wall_x_wipe_dist,
                                         const size_t wall_0_extruder_nr,
                                         const size_t wall_x_extruder_nr,
                                         const ZSeamConfig& z_seam_config,
                                         const VariableWidthPaths& paths) :
    gcode_writer(gcode_writer),
    storage(storage),
    gcode_layer(gcode_layer),
    settings(settings),
    extruder_nr(extruder_nr),
    inset_0_non_bridge_config(inset_0_non_bridge_config),
    inset_X_non_bridge_config(inset_X_non_bridge_config),
    inset_0_bridge_config(inset_0_bridge_config),
    inset_X_bridge_config(inset_X_bridge_config),
    retract_before_outer_wall(retract_before_outer_wall),
    wall_0_wipe_dist(wall_0_wipe_dist),
    wall_x_wipe_dist(wall_x_wipe_dist),
    wall_0_extruder_nr(wall_0_extruder_nr),
    wall_x_extruder_nr(wall_x_extruder_nr),
    z_seam_config(z_seam_config),
    paths(paths),
    layer_nr(gcode_layer.getLayerNr()),
    added_something(false),
    retraction_region_calculated(false)
{
}

bool InsetOrderOptimizer::addToLayer()
{
    // Settings & configs:
    const bool pack_by_inset = ! settings.get<bool>("optimize_wall_printing_order");
    const InsetDirection inset_direction = settings.get<InsetDirection>("inset_direction");
    const bool center_last = inset_direction == InsetDirection::CENTER_LAST;
    const bool alternate_walls = settings.get<bool>("material_alternate_walls");

    const bool outer_to_inner = inset_direction == InsetDirection::OUTSIDE_IN;

    size_t start_inset;
    size_t end_inset;
    int direction;
    //If the entire wall is printed with the current extruder, print all of it.
    if (wall_0_extruder_nr == wall_x_extruder_nr && wall_x_extruder_nr == extruder_nr)
    {
        //If printing the outer inset first, start with the lowest inset.
        //Otherwise start with the highest inset and iterate backwards.
        if(inset_direction == InsetDirection::OUTSIDE_IN)
        {
            start_inset = 0;
            end_inset = paths.size();
            direction = 1;
        }
        else //INSIDE_OUT or CENTER_LAST.
        {
            start_inset = paths.size() - 1;
            end_inset = -1;
            direction = -1;
        }
    }
    //If the wall is partially printed with the current extruder, print the correct part.
    else if (wall_0_extruder_nr != wall_x_extruder_nr)
    {
        //If the wall_0 and wall_x extruders are different, then only include the insets that should be printed by the
        //current extruder_nr.
        if(extruder_nr == wall_0_extruder_nr)
        {
            start_inset = 0;
            end_inset = 1; // Ignore inner walls
            direction = 1;
        }
        else if(extruder_nr == wall_x_extruder_nr)
        {
            start_inset = paths.size() - 1;
            end_inset = 0; // Ignore outer wall
            direction = -1;
        }
        else
        {
            return added_something;
        }
    }
    else //The wall is not printed with this extruder, not even in part. Don't print anything then.
    {
        return added_something;
    }

    std::vector<const ExtrusionLine*> walls_to_be_added;
    //Add all of the insets one by one.
    for(size_t inset_idx = start_inset; inset_idx != end_inset; inset_idx += direction)
    {
        if (paths[inset_idx].empty())
        {
            continue; //Don't switch extruders either, etc.
        }
        const VariableWidthLines& inset = paths[inset_idx];
        for (const ExtrusionLine& wall : inset)
        {
            walls_to_be_added.emplace_back(&wall);
        }
    }

    std::unordered_set<std::pair<const ExtrusionLine*, const ExtrusionLine*>> order =
        pack_by_inset?
        getInsetOrder(walls_to_be_added, outer_to_inner)
        : getRegionOrder(walls_to_be_added, outer_to_inner);

    if (center_last)
    {
        for (const ExtrusionLine* line : walls_to_be_added)
            if (line->is_odd)
                for (const ExtrusionLine* other_line : walls_to_be_added)
                    if ( ! other_line->is_odd)
                        order.emplace(std::make_pair(other_line, line));
    }

    constexpr Ratio flow = 1.0_r;

    bool added_something = false;

    constexpr bool detect_loops = false;
    constexpr Polygons* combing_boundary = nullptr;
    //When we alternate walls, also alternate the direction at which the first wall starts in.
    //On even layers we start with normal direction, on odd layers with inverted direction.
    constexpr bool reverse_all_paths = false;
    PathOrderOptimizer<const ExtrusionLine*> order_optimizer(gcode_layer.getLastPlannedPositionOrStartingPosition(), z_seam_config, detect_loops, combing_boundary, reverse_all_paths, order);

    for (const ExtrusionLine* line : walls_to_be_added)
    {
        if (line->is_closed)
        {
            order_optimizer.addPolygon(line);
        }
        else
        {
            order_optimizer.addPolyline(line);
        }
    }

    order_optimizer.optimize();

    cura::Point p_end {0, 0};
    for(const PathOrderOptimizer<const ExtrusionLine*>::Path& path : order_optimizer.paths)
    {
        if (path.vertices->empty()) continue;

        const bool is_outer_wall = path.vertices->inset_idx == 0; // or thin wall 'gap filler'
        const bool is_gap_filler = path.vertices->is_odd;
        const GCodePathConfig& non_bridge_config = is_outer_wall ? inset_0_non_bridge_config : inset_X_non_bridge_config;
        const GCodePathConfig& bridge_config = is_outer_wall? inset_0_bridge_config : inset_X_bridge_config;
        const coord_t wipe_dist = is_outer_wall && ! is_gap_filler ? wall_0_wipe_dist : wall_x_wipe_dist;
        const bool retract_before = is_outer_wall ? retract_before_outer_wall : false;

        const bool alternate_direction_modifier = alternate_walls && (path.vertices->inset_idx % 2 == layer_nr % 2);
        const bool backwards = path.backwards != alternate_direction_modifier;

        p_end = path.backwards ? path.vertices->back().p : path.vertices->front().p;
        const cura::Point p_start = path.backwards ? path.vertices->front().p : path.vertices->back().p;
        const bool linked_path = p_start != p_end;

        added_something = true;
        gcode_writer.setExtruder_addPrime(storage, gcode_layer, extruder_nr);
        gcode_layer.setIsInside(true); //Going to print walls, which are always inside.
        gcode_layer.addWall(*path.vertices, path.start_vertex, settings, non_bridge_config, bridge_config, wipe_dist, flow, retract_before, path.is_closed, backwards, linked_path);
        added_something = true;
    }
    return added_something;
}

std::unordered_set<std::pair<const ExtrusionLine*, const ExtrusionLine*>> InsetOrderOptimizer::getRegionOrder(const std::vector<const ExtrusionLine*>& input, const bool outer_to_inner)
{
    std::unordered_set<std::pair<const ExtrusionLine*, const ExtrusionLine*>> order_requirements;

    coord_t max_line_w = 0u;
    { // compute max_line_w
        for (const ExtrusionLine* line : input)
        {
            for (const ExtrusionJunction& junction : *line)
            {
                max_line_w = std::max(max_line_w, junction.w);
            }
        }
    }
    if (max_line_w == 0u) return order_requirements;

    struct LineLoc
    {
        ExtrusionJunction j;
        const ExtrusionLine* line;
    };
    struct Locator
    {
        Point operator()(const LineLoc& elem)
        {
            return elem.j.p;
        }
    };

    constexpr float diagonal_extension = 1.7; // how much farther two verts may be apart due to corners
    const coord_t searching_radius = max_line_w * diagonal_extension;
    using GridT = SparsePointGrid<LineLoc, Locator>;
    GridT grid(searching_radius);

    for (const ExtrusionLine* line : input)
    {
        for (const ExtrusionJunction& junction : *line)
        {
            grid.insert(LineLoc{junction, line});
        }
    }
    for (const std::pair<SquareGrid::GridPoint, LineLoc>& pair : grid)
    {
        const LineLoc& lineloc_here = pair.second;
        const ExtrusionLine* here = lineloc_here.line;
        Point loc_here = pair.second.j.p;
        std::vector<LineLoc> nearby_verts = grid.getNearby(loc_here, searching_radius);
        for (const LineLoc& lineloc_nearby : nearby_verts)
        {
            const ExtrusionLine* nearby = lineloc_nearby.line;
            if (nearby == here) continue;
            if (nearby->inset_idx == here->inset_idx) continue;
            if (nearby->inset_idx > here->inset_idx + 1) continue; // not directly adjacent
            if (here->inset_idx > nearby->inset_idx + 1) continue; // not directly adjacent
            if ( ! shorterThan(loc_here - lineloc_nearby.j.p, (lineloc_here.j.w + lineloc_nearby.j.w) / 2 * diagonal_extension)) continue; // points are too far away from each other
            if (here->is_odd || nearby->is_odd)
            {
                if (here->is_odd && ! nearby->is_odd && nearby->inset_idx < here->inset_idx)
                {
                    order_requirements.emplace(std::make_pair(nearby, here));
                }
                if (nearby->is_odd && ! here->is_odd && here->inset_idx < nearby->inset_idx)
                {
                    order_requirements.emplace(std::make_pair(here, nearby));
                }
            }
            else if (nearby->inset_idx < here->inset_idx == outer_to_inner)
            {
                order_requirements.emplace(std::make_pair(nearby, here));
            }
            else
            {
                assert(nearby->inset_idx > here->inset_idx == outer_to_inner);
                order_requirements.emplace(std::make_pair(here, nearby));
            }
        }
    }
    return order_requirements;
}

std::unordered_set<std::pair<const ExtrusionLine*, const ExtrusionLine*>> InsetOrderOptimizer::getInsetOrder(const std::vector<const ExtrusionLine*>& input, const bool outer_to_inner)
{
    std::unordered_set<std::pair<const ExtrusionLine*, const ExtrusionLine*>> order;

    std::vector<std::vector<const ExtrusionLine*>> walls_by_inset;
    std::vector<std::vector<const ExtrusionLine*>> fillers_by_inset;

    for (const ExtrusionLine* line : input)
    {
        if (line->is_odd)
        {
            if (line->inset_idx >= fillers_by_inset.size())
            {
                fillers_by_inset.resize(line->inset_idx + 1);
            }
            fillers_by_inset[line->inset_idx].emplace_back(line);
        }
        else
        {
            if (line->inset_idx >= walls_by_inset.size())
            {
                walls_by_inset.resize(line->inset_idx + 1);
            }
            walls_by_inset[line->inset_idx].emplace_back(line);
        }
    }
    for (size_t inset_idx = 0; inset_idx + 1 < walls_by_inset.size(); inset_idx++)
    {
        for (const ExtrusionLine* line : walls_by_inset[inset_idx])
        {
            for (const ExtrusionLine* inner_line : walls_by_inset[inset_idx + 1])
            {
                const ExtrusionLine* before = inner_line;
                const ExtrusionLine* after = line;
                if (outer_to_inner)
                {
                    std::swap(before, after);
                }
                order.emplace(before, after);
            }
        }
    }
    for (size_t inset_idx = 1; inset_idx < fillers_by_inset.size(); inset_idx++)
    {
        for (const ExtrusionLine* line : fillers_by_inset[inset_idx])
        {
            assert(inset_idx - 1 < walls_by_inset.size());
            if (inset_idx - 1 >= walls_by_inset.size()) continue;
            for (const ExtrusionLine* enclosing_wall : walls_by_inset[inset_idx - 1])
            {
                order.emplace(enclosing_wall, line);
            }
        }

    }

    return order;
}

}//namespace cura
<|endoftext|>


<commit_before>
// ScopeTimer not supported on windows simulator or non-Debug
#if DEBUG

#include "../platform.h"
#include "scope_timer.h"

#include "calctype/calctype.h"
#include "calctype/fonts/arial_small/arial_small.h"

#if TARGET_WINSIM
unsigned int ScopeTimer_FrameCycles = 0;
LONGLONG ScopeTimer_Start = 0;
#else
#include "../ptune2_simple/Ptune2_direct.h"
#endif

ScopeTimer* ScopeTimer::firstTimer = NULL;
char ScopeTimer::debugString[128] = { 0 };
int ScopeTimer::fpsValue = 0;

static unsigned int lastFrame = 0;

unsigned int GetCycleFrameTime() {
#if TARGET_PRIZM
	return Ptune2_GetPLLFreq() * 242 * 256 >> Ptune2_GetPFCDiv();
#else
	return ScopeTimer_FrameCycles;
#endif
}

ScopeTimer::ScopeTimer(const char* withFunctionName, int withLine) : cycleCount(0), numCounts(0), funcName(withFunctionName), line(withLine) {
	// put me in the linked list
	nextTimer = firstTimer;
	firstTimer = this;
}

void ScopeTimer::Register(const char* withFunctionName) {
	// enforce registration if we have an init order problem
	ScopeTimer* curTimer = firstTimer;
	while (curTimer) {
		if (curTimer == this) {
			return;
		}
		curTimer = curTimer->nextTimer;
	}

	// put me back in the linked list
	nextTimer = firstTimer;
	firstTimer = this;
	cycleCount = 0;
	numCounts = 0;
	funcName = withFunctionName;
}

void ScopeTimer::InitSystem() {
	// initialize TMU2 at the fastest clock rate possible

#if TARGET_PRIZM
	// disable so we can set up
	REG_TMU_TSTR &= ~(1 << 2);

	REG_TMU_TCOR_2 = 0x7FFFFFFF; // max int
	REG_TMU_TCNT_2 = 0x7FFFFFFF;
	REG_TMU_TCR_2 = 0x0000; // no interrupt needed

	// enable TMU2
	REG_TMU_TSTR |= (1 << 2);
#endif

#if TARGET_WINSIM
	//
	LARGE_INTEGER result;
	QueryPerformanceFrequency(&result);
	ScopeTimer_FrameCycles = (result.QuadPart / 60);
	QueryPerformanceCounter(&result);
	ScopeTimer_Start = result.QuadPart;
#endif

	memset(debugString, 0, sizeof(debugString));

	// clear timers
	ScopeTimer* curTimer = firstTimer;
	while (curTimer) {
		curTimer->cycleCount = 0;
		curTimer->numCounts = 0;
		curTimer = curTimer->nextTimer;
	}
}

void ScopeTimer::ReportFrame() {
	unsigned int curFrame = GetCycles();
	if (lastFrame > curFrame && lastFrame != 0) {

		static int cycleRoll[10] = { 0 };
		static int curCycle = 0;
		cycleRoll[curCycle++] = lastFrame - curFrame;
		if (curCycle >= 10) curCycle = 0;

		int totalCycles = 0;
		for (int i = 0; i < 10; i++) {
			totalCycles += cycleRoll[i];
		}

		fpsValue = GetCycleFrameTime() * 10 / (totalCycles / 600);
	}

	lastFrame = curFrame;
}

void ScopeTimer::Shutdown() {
#if TARGET_PRIZM
	// disable TMU 2
	REG_TMU_TSTR &= ~(1 << 2);
#endif
}

static void PrintInfo(int row, const char* label, const char* info, unsigned short color) {
	// location of columns
	const int col1 = 0;
	const int col2 = 200;

	int x = col1;
	int y = row * 15 + 2;
	CalcType_Draw(&arial_small, label, x, y, color, 0, 0);

	x = col2;
	y = row * 15 + 2;
	CalcType_Draw(&arial_small, info, x, y, color, 0, 0);
}

void ScopeTimer::DisplayTimes() {
	// display debug view with get key
	int toKey;
	unsigned int maxCycles = 0;
	unsigned int maxCount = 0;

#if TARGET_PRIZM
	// disable TMU 2
	REG_TMU_TSTR &= ~(1 << 2);
#endif

	// mode descriptions
	const int numModes = 4;
	const char* modeNames[numModes] = {
		"% Max",
		"Num Cycles",
		"Num Hits",
		"Cycles/Hit"
	};

	// reset display area just in case
	Bdisp_PutDisp_DD();

	// allocate number of timers in use, support up to 256
	int numTimers = 0;
	ScopeTimer* timers[256];
	{
		ScopeTimer* curTimer = firstTimer;
		// determine maxCycles and maxCount first
		while (curTimer && numTimers < 256) {
			if (curTimer->cycleCount > maxCycles) {
				maxCycles = curTimer->cycleCount;
			}
			if (curTimer->numCounts > maxCount) {
				maxCount = curTimer->numCounts;
			}

			curTimer = curTimer->nextTimer;
		}

		// exclude timers with less than 0.1% of the total cycles
		curTimer = firstTimer;
		while (curTimer && numTimers < 256) {
			if (curTimer->cycleCount > maxCycles / 1024) {
				timers[numTimers] = curTimer;
				numTimers++;
			}
			curTimer = curTimer->nextTimer;
		}
	}

	int startTimer = 0;
	int mode = 0;

	do {
		// create stat display
		Bdisp_Fill_VRAM(0x0000, 3);

		// header
		PrintInfo(0, "Function(line)", modeNames[mode], COLOR_WHITE);

		// notify if there are no timers
		if (numTimers == 0) {
			PrintInfo(1, "No timers found!", "ERROR", COLOR_RED);
		}

		for (int timer = startTimer, row = 1; timer < numTimers && row < 12; timer++, row++) {
			ScopeTimer* curTimer = timers[timer];

			char name[256];
			memset(name, 0, sizeof(name));
			sprintf(name, "%s(%d)", curTimer->funcName, curTimer->line);

			bool isMax;
			char info[256];
			memset(info, 0, sizeof(info));
			switch (mode) {
				case 0:
				{
					int percent = curTimer->cycleCount / (maxCycles / 1000);
					sprintf(info, "%d.%d", percent / 10, percent % 10);
					isMax = curTimer->cycleCount == maxCycles;
					break;
				}
				case 1:
					sprintf(info, "%dk", curTimer->cycleCount / 1024);
					isMax = curTimer->cycleCount == maxCycles;
					break;
				case 2:
					sprintf(info, "%dk", curTimer->numCounts / 1024);
					isMax = curTimer->numCounts == maxCount;
					break;
				case 3:
					if (curTimer->numCounts)
						sprintf(info, "%d", curTimer->cycleCount / curTimer->numCounts);
					else
						strcpy(info, "N/A");
					isMax = false;
					break;
			}
			PrintInfo(row, name, info, isMax ? COLOR_SALMON : COLOR_LIGHTGREEN);
		}

		if (debugString[0]) {
			PrintInfo(12, debugString, "Nav: Arrows, Leave: EXIT", COLOR_WHITE);
		} else {
			PrintInfo(12, "Nav: Arrows", "Leave: EXIT", COLOR_WHITE);
		}

		char fpsBuffer[50] = { 0 };
		sprintf(fpsBuffer, "FPS: %d.%d  ", fpsValue / 10, fpsValue % 10);
		PrintInfo(13, fpsBuffer, "F3: Clear values", COLOR_LIGHTBLUE);

		GetKey(&toKey);

		switch (toKey) {
			case KEY_CTRL_UP:
				if (startTimer > 0) {
					startTimer--;
				}
				break;
			case KEY_CTRL_DOWN:
				if (startTimer < numTimers - 1) {
					startTimer++;
				}
				break;
			case KEY_CTRL_RIGHT:
				mode = (mode + 1) % numModes;
				break;
			case KEY_CTRL_LEFT:
				mode = (mode + numModes - 1) % numModes;
				break;
			case KEY_CTRL_F3:
			{
				ScopeTimer* curTimer = firstTimer;
				while (curTimer) {
					curTimer->cycleCount = 0;
					curTimer->numCounts = 0;
					curTimer = curTimer->nextTimer;
				}
				break;
			}
		}
	// wait for exit key
	} while (toKey != KEY_CTRL_EXIT);

#if TARGET_PRIZM
	// enable TMU2
	REG_TMU_TSTR |= (1 << 2);
#endif
}

#endif<commit_msg>Added timer sort<commit_after>
// ScopeTimer not supported on windows simulator or non-Debug
#if DEBUG

#include "../platform.h"
#include "scope_timer.h"

#include "calctype/calctype.h"
#include "calctype/fonts/arial_small/arial_small.h"

#if TARGET_WINSIM
unsigned int ScopeTimer_FrameCycles = 0;
LONGLONG ScopeTimer_Start = 0;
#else
#include "../ptune2_simple/Ptune2_direct.h"
#endif

ScopeTimer* ScopeTimer::firstTimer = NULL;
char ScopeTimer::debugString[128] = { 0 };
int ScopeTimer::fpsValue = 0;

static unsigned int lastFrame = 0;

unsigned int GetCycleFrameTime() {
#if TARGET_PRIZM
	return Ptune2_GetPLLFreq() * 242 * 256 >> Ptune2_GetPFCDiv();
#else
	return ScopeTimer_FrameCycles;
#endif
}

ScopeTimer::ScopeTimer(const char* withFunctionName, int withLine) : cycleCount(0), numCounts(0), funcName(withFunctionName), line(withLine) {
	// put me in the linked list
	nextTimer = firstTimer;
	firstTimer = this;
}

void ScopeTimer::Register(const char* withFunctionName) {
	// enforce registration if we have an init order problem
	ScopeTimer* curTimer = firstTimer;
	while (curTimer) {
		if (curTimer == this) {
			return;
		}
		curTimer = curTimer->nextTimer;
	}

	// put me back in the linked list
	nextTimer = firstTimer;
	firstTimer = this;
	cycleCount = 0;
	numCounts = 0;
	funcName = withFunctionName;
}

void ScopeTimer::InitSystem() {
	// initialize TMU2 at the fastest clock rate possible

#if TARGET_PRIZM
	// disable so we can set up
	REG_TMU_TSTR &= ~(1 << 2);

	REG_TMU_TCOR_2 = 0x7FFFFFFF; // max int
	REG_TMU_TCNT_2 = 0x7FFFFFFF;
	REG_TMU_TCR_2 = 0x0000; // no interrupt needed

	// enable TMU2
	REG_TMU_TSTR |= (1 << 2);
#endif

#if TARGET_WINSIM
	//
	LARGE_INTEGER result;
	QueryPerformanceFrequency(&result);
	ScopeTimer_FrameCycles = (result.QuadPart / 60);
	QueryPerformanceCounter(&result);
	ScopeTimer_Start = result.QuadPart;
#endif

	memset(debugString, 0, sizeof(debugString));

	// clear timers
	ScopeTimer* curTimer = firstTimer;
	while (curTimer) {
		curTimer->cycleCount = 0;
		curTimer->numCounts = 0;
		curTimer = curTimer->nextTimer;
	}
}

void ScopeTimer::ReportFrame() {
	unsigned int curFrame = GetCycles();
	if (lastFrame > curFrame && lastFrame != 0) {

		static int cycleRoll[10] = { 0 };
		static int curCycle = 0;
		cycleRoll[curCycle++] = lastFrame - curFrame;
		if (curCycle >= 10) curCycle = 0;

		int totalCycles = 0;
		for (int i = 0; i < 10; i++) {
			totalCycles += cycleRoll[i];
		}

		fpsValue = GetCycleFrameTime() * 10 / (totalCycles / 600);
	}

	lastFrame = curFrame;
}

void ScopeTimer::Shutdown() {
#if TARGET_PRIZM
	// disable TMU 2
	REG_TMU_TSTR &= ~(1 << 2);
#endif
}

static void PrintInfo(int row, const char* label, const char* info, unsigned short color) {
	// location of columns
	const int col1 = 0;
	const int col2 = 200;

	int x = col1;
	int y = row * 15 + 2;
	CalcType_Draw(&arial_small, label, x, y, color, 0, 0);

	x = col2;
	y = row * 15 + 2;
	CalcType_Draw(&arial_small, info, x, y, color, 0, 0);
}

void ScopeTimer::DisplayTimes() {
	// display debug view with get key
	int toKey;
	unsigned int maxCycles = 0;
	unsigned int maxCount = 0;

#if TARGET_PRIZM
	// disable TMU 2
	REG_TMU_TSTR &= ~(1 << 2);
#endif

	// mode descriptions
	const int numModes = 4;
	const char* modeNames[numModes] = {
		"% Max",
		"Num Cycles",
		"Num Hits",
		"Cycles/Hit"
	};

	// reset display area just in case
	Bdisp_PutDisp_DD();

	// allocate number of timers in use, support up to 256
	int numTimers = 0;
	ScopeTimer* timers[256];
	{
		ScopeTimer* curTimer = firstTimer;
		// determine maxCycles and maxCount first
		while (curTimer && numTimers < 256) {
			if (curTimer->cycleCount > maxCycles) {
				maxCycles = curTimer->cycleCount;
			}
			if (curTimer->numCounts > maxCount) {
				maxCount = curTimer->numCounts;
			}

			curTimer = curTimer->nextTimer;
		}

		// exclude timers with less than 0.1% of the total cycles
		curTimer = firstTimer;
		while (curTimer && numTimers < 256) {
			if (curTimer->cycleCount > maxCycles / 1024) {
				timers[numTimers] = curTimer;
				numTimers++;
			}
			curTimer = curTimer->nextTimer;
		}

		// then add the timers that are left that fit
		curTimer = firstTimer;
		while (curTimer && numTimers < 256) {
			if (curTimer->cycleCount <= maxCycles / 1024) {
				timers[numTimers] = curTimer;
				numTimers++;
			}
			curTimer = curTimer->nextTimer;
		}

		// now bubble sort by cycle count
		for (int i = 0; i < numTimers - 1; i++) {
			for (int j = i + 1; j < numTimers; j++) {
				if (timers[i]->cycleCount < timers[j]->cycleCount) {
					ScopeTimer* swapTimer = timers[i];
					timers[i] = timers[j];
					timers[j] = swapTimer;
				}
			}
		}
	}

	int startTimer = 0;
	int mode = 0;

	do {
		// create stat display
		Bdisp_Fill_VRAM(0x0000, 3);

		// header
		PrintInfo(0, "Function(line)", modeNames[mode], COLOR_WHITE);

		// notify if there are no timers
		if (numTimers == 0) {
			PrintInfo(1, "No timers found!", "ERROR", COLOR_RED);
		}

		for (int timer = startTimer, row = 1; timer < numTimers && row < 12; timer++, row++) {
			ScopeTimer* curTimer = timers[timer];

			char name[256];
			memset(name, 0, sizeof(name));
			sprintf(name, "%s(%d)", curTimer->funcName, curTimer->line);

			bool isMax;
			char info[256];
			memset(info, 0, sizeof(info));
			switch (mode) {
				case 0:
				{
					int percent = curTimer->cycleCount / (maxCycles / 1000);
					sprintf(info, "%d.%d", percent / 10, percent % 10);
					isMax = curTimer->cycleCount == maxCycles;
					break;
				}
				case 1:
					sprintf(info, "%dk", curTimer->cycleCount / 1024);
					isMax = curTimer->cycleCount == maxCycles;
					break;
				case 2:
					sprintf(info, "%dk", curTimer->numCounts / 1024);
					isMax = curTimer->numCounts == maxCount;
					break;
				case 3:
					if (curTimer->numCounts)
						sprintf(info, "%d", curTimer->cycleCount / curTimer->numCounts);
					else
						strcpy(info, "N/A");
					isMax = false;
					break;
			}
			PrintInfo(row, name, info, isMax ? COLOR_SALMON : COLOR_LIGHTGREEN);
		}

		if (debugString[0]) {
			PrintInfo(12, debugString, "Nav: Arrows, Leave: EXIT", COLOR_WHITE);
		} else {
			PrintInfo(12, "Nav: Arrows", "Leave: EXIT", COLOR_WHITE);
		}

		char fpsBuffer[50] = { 0 };
		sprintf(fpsBuffer, "FPS: %d.%d  ", fpsValue / 10, fpsValue % 10);
		PrintInfo(13, fpsBuffer, "F3: Clear values", COLOR_LIGHTBLUE);

		GetKey(&toKey);

		switch (toKey) {
			case KEY_CTRL_UP:
				if (startTimer > 0) {
					startTimer--;
				}
				break;
			case KEY_CTRL_DOWN:
				if (startTimer < numTimers - 1) {
					startTimer++;
				}
				break;
			case KEY_CTRL_RIGHT:
				mode = (mode + 1) % numModes;
				break;
			case KEY_CTRL_LEFT:
				mode = (mode + numModes - 1) % numModes;
				break;
			case KEY_CTRL_F3:
			{
				ScopeTimer* curTimer = firstTimer;
				while (curTimer) {
					curTimer->cycleCount = 0;
					curTimer->numCounts = 0;
					curTimer = curTimer->nextTimer;
				}
				break;
			}
		}
	// wait for exit key
	} while (toKey != KEY_CTRL_EXIT);

#if TARGET_PRIZM
	// enable TMU2
	REG_TMU_TSTR |= (1 << 2);
#endif
}

#endif<|endoftext|>