Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions Libs/Project/Project.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,18 @@ void Project::set_subjects(const std::vector<std::shared_ptr<Subject>>& subjects
update_subjects();
}

//---------------------------------------------------------------------------
void Project::clear_particle_filenames() {
for (auto& subject : subjects_) {
if (subject->is_fixed()) {
continue; // fixed subjects' particles are pre-existing inputs, not optimize outputs
}
subject->set_local_particle_filenames({});
subject->set_world_particle_filenames({});
}
update_subjects();
}

//---------------------------------------------------------------------------
void Project::update_subjects() {
originals_present_ = false;
Expand Down
6 changes: 6 additions & 0 deletions Libs/Project/Project.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,12 @@ class Project {

void update_subjects();

//! Clear the local and world particle filenames for all non-fixed subjects.
/*! Used when an optimization fails or is aborted so that the project isn't left
* referencing particle files that were never written (#2455). Fixed subjects
* keep their filenames since those particles are pre-existing inputs. */
void clear_particle_filenames();

//! Return if originals are present
bool get_originals_present() const;

Expand Down
5 changes: 5 additions & 0 deletions Studio/Optimize/OptimizeTool.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,11 @@ void OptimizeTool::handle_optimize_failed() {
optimization_is_running_ = false;
Q_EMIT progress(100);

// The optimizer assigns output particle filenames to each subject during setup, but on
// failure those files are never written. Clear them so saving the project doesn't leave
// dangling particle references that make it unreadable on reload (#2455).
session_->get_project()->clear_particle_filenames();

std::string duration = QString::number(elapsed_timer_.elapsed() / 1000.0, 'f', 1).toStdString();
SW_LOG("Optimize Failed. Duration: " + duration + " seconds");
Q_EMIT optimize_complete();
Expand Down
41 changes: 41 additions & 0 deletions Testing/ProjectTests/ProjectTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,47 @@ TEST(ProjectTests, conversion_to_json_test) {
compare_projects(project_compare1, project_compare2);
}

//---------------------------------------------------------------------------
// #2455: after a failed/aborted optimization, the project should not be left referencing
// particle files that were never written. clear_particle_filenames() clears them for
// non-fixed subjects (fixed subjects keep their pre-existing input particles).
TEST(ProjectTests, clear_particle_filenames_test) {
TestUtils::Instance().prep_temp(std::string(TEST_DATA_DIR) + "/project", "project_clear_particle_filenames_test");

ProjectHandle project = std::make_shared<Project>();
project->load("json_read.swproj");

auto& subjects = project->get_subjects();
ASSERT_GE(subjects.size(), 2);

// mark the first subject fixed; its particles are pre-existing inputs and must be kept
subjects[0]->set_fixed(true);
ASSERT_FALSE(subjects[1]->get_world_particle_filenames().empty());

project->clear_particle_filenames();

// the fixed subject keeps its particle filenames
EXPECT_FALSE(subjects[0]->get_local_particle_filenames().empty());
EXPECT_FALSE(subjects[0]->get_world_particle_filenames().empty());

// non-fixed subjects are cleared
for (size_t i = 1; i < subjects.size(); i++) {
EXPECT_TRUE(subjects[i]->get_local_particle_filenames().empty());
EXPECT_TRUE(subjects[i]->get_world_particle_filenames().empty());
}

// and a saved+reloaded project no longer references the missing particle files
project->save("cleared.swproj");
ProjectHandle reloaded = std::make_shared<Project>();
reloaded->load("cleared.swproj");
auto reloaded_subjects = reloaded->get_subjects();
ASSERT_EQ(reloaded_subjects.size(), subjects.size());
for (size_t i = 1; i < reloaded_subjects.size(); i++) {
EXPECT_TRUE(reloaded_subjects[i]->get_local_particle_filenames().empty());
EXPECT_TRUE(reloaded_subjects[i]->get_world_particle_filenames().empty());
}
}

//---------------------------------------------------------------------------
TEST(ProjectTests, conversion_to_excel_test) {
TestUtils::Instance().prep_temp(std::string(TEST_DATA_DIR) + "/project", "project_convert_to_excel_test");
Expand Down
Loading