Skip to content

Commit 28cc4f2

Browse files
committed
fix an occasional SLiMgui crash involving removed subpops
1 parent 7bac7e9 commit 28cc4f2

3 files changed

Lines changed: 41 additions & 15 deletions

File tree

QtSLiM/QtSLiMPopulationTable.cpp

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -68,13 +68,24 @@ QVariant QtSLiMPopulationTableModel::data(const QModelIndex &p_index, int role)
6868

6969
std::advance(popIter, p_index.row());
7070
Subpopulation *subpop = *popIter;
71+
Species *species = &(subpop->species_);
72+
73+
// BCH 3/21/2024: This check is a debugging leftover that should stay permanently. The display list
74+
// for the population table was out of date and contained subpopulations that had been deallocated,
75+
// leading to a crash. This was a very hard bug to find, so it's worth keeping this code here. The
76+
// bug was fixed with needsUpdateForDisplaySubpops() in QtSLiMPopulationTableModel.
77+
if (species == nullptr)
78+
{
79+
qDebug() << "INVALID SUBPOPULATION in QtSLiMPopulationTableModel::data()!";
80+
qApp->beep();
81+
}
7182

7283
if (p_index.column() == 0)
7384
{
7485
QString idString = QString("p%1").arg(subpop->subpopulation_id_);
7586

7687
if (community->all_species_.size() > 1)
77-
idString.append(" ").append(QString::fromStdString(subpop->species_.avatar_));
88+
idString.append(" ").append(QString::fromStdString(species->avatar_));
7889

7990
return QVariant(idString);
8091
}
@@ -210,17 +221,19 @@ QVariant QtSLiMPopulationTableModel::headerData(int section,
210221
return QVariant();
211222
}
212223

213-
void QtSLiMPopulationTableModel::reloadTable(void)
224+
bool QtSLiMPopulationTableModel::needsUpdateForDisplaySubpops(std::vector<Subpopulation *> &newDisplayList)
225+
{
226+
// Checks whether our cached display list is out of date; if it is, a reload needs to be forced.
227+
return (displaySubpops != newDisplayList);
228+
}
229+
230+
void QtSLiMPopulationTableModel::reloadTable(std::vector<Subpopulation *> &newDisplayList)
214231
{
215232
beginResetModel();
216233

217234
// recache the list of subpopulations we display
218-
displaySubpops.clear();
219-
220-
QtSLiMWindow *controller = static_cast<QtSLiMWindow *>(parent());
221-
222-
if (controller)
223-
displaySubpops = controller->listedSubpopulations();
235+
std::swap(displaySubpops, newDisplayList);
236+
newDisplayList.clear();
224237

225238
endResetModel();
226239
}

QtSLiM/QtSLiMPopulationTable.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ class QtSLiMPopulationTableModel : public QAbstractTableModel
4242
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
4343
virtual QVariant headerData(int section, Qt::Orientation p_orientation, int role = Qt::DisplayRole) const override;
4444

45-
void reloadTable(void);
45+
bool needsUpdateForDisplaySubpops(std::vector<Subpopulation *> &newDisplayList);
46+
void reloadTable(std::vector<Subpopulation *> &newDisplayList);
4647

4748
Subpopulation *subpopAtIndex(int i) const { return displaySubpops[i]; }
4849

QtSLiM/QtSLiMWindow.cpp

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2612,17 +2612,25 @@ void QtSLiMWindow::updateAfterTickFull(bool fullUpdate)
26122612
// The rest of the code here needs to be careful about the invalid state; we do want to update our controls when invalid, but sim is nil.
26132613
bool inInvalidState = (!community || !community->simulation_valid_ || invalidSimulation());
26142614

2615-
if (fullUpdate)
2615+
if (fullUpdate)
2616+
updateOutputViews();
2617+
2618+
// Minimal population table updating. When the list of subpops changes, we always need to redisplay, otherwise the display
2619+
// list is outdated and might contain deallocated Subpopulations. Other than that, though, we only want to redisplay
2620+
// on full updates, because reloading and redisplaying the tableview can be quite expensive. The QtSLiMPopulationTableModel
2621+
// keeps a cache of its display list, and we can use that to see whether a redisplay is needed or not.
2622+
std::vector<Subpopulation *> newDisplaySubpops = listedSubpopulations();
2623+
2624+
if (fullUpdate || populationTableModel_->needsUpdateForDisplaySubpops(newDisplaySubpops))
26162625
{
2617-
// FIXME it would be good for this updating to be minimal; reloading the tableview every time, etc., is quite wasteful...
2618-
updateOutputViews();
2619-
2626+
//qDebug() << "UPDATING TABLE";
2627+
26202628
// Reloading the subpop tableview is tricky, because we need to preserve the selection across the reload, while also noting that the selection is forced
26212629
// to change when a subpop goes extinct. The current selection is noted in the gui_selected_ ivar of each subpop. So what we do here is reload the tableview
26222630
// while suppressing our usual update of our selection state, and then we try to re-impose our selection state on the new tableview content. If a subpop
26232631
// went extinct, we will fail to notice the selection change; but that is OK, since we force an update of populationView and chromosomeZoomed below anyway.
2624-
reloadingSubpopTableview = true;
2625-
populationTableModel_->reloadTable();
2632+
reloadingSubpopTableview = true; // suppresses QtSLiMWindow::subpopSelectionDidChange()
2633+
populationTableModel_->reloadTable(newDisplaySubpops); // invalidates newDisplaySubpops with std::swap()
26262634

26272635
int subpopCount = populationTableModel_->rowCount();
26282636

@@ -2654,6 +2662,10 @@ void QtSLiMWindow::updateAfterTickFull(bool fullUpdate)
26542662
if ((ui->subpopTableView->selectionModel()->selectedRows().size() == 0) && subpopCount)
26552663
ui->subpopTableView->selectAll();
26562664
}
2665+
else
2666+
{
2667+
//qDebug() << "skipping unnecessary table update";
2668+
}
26572669

26582670
// Now update our other UI, some of which depends upon the state of subpopTableView
26592671
ui->individualsWidget->update();

0 commit comments

Comments
 (0)