Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: mockingbirdnest/Principia
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: cb1c329c7e5a
Choose a base ref
...
head repository: mockingbirdnest/Principia
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 8b337e791b72
Choose a head ref
  • 3 commits
  • 5 files changed
  • 1 contributor

Commits on Aug 28, 2021

  1. Copy the full SHA
    2e45993 View commit details
  2. Compilation error.

    pleroy committed Aug 28, 2021
    Copy the full SHA
    41e9229 View commit details
  3. Merge pull request #3118 from pleroy/ReanimateMore

    Add method Reanimate and add thread safety to the Vessel history
    pleroy authored Aug 28, 2021
    Copy the full SHA
    8b337e7 View commit details
Showing with 91 additions and 19 deletions.
  1. +60 −6 ksp_plugin/vessel.cpp
  2. +23 −8 ksp_plugin/vessel.hpp
  3. +2 −1 physics/discrete_trajectory_test.cpp
  4. +4 −3 physics/ephemeris.hpp
  5. +2 −1 physics/ephemeris_body.hpp
66 changes: 60 additions & 6 deletions ksp_plugin/vessel.cpp
Original file line number Diff line number Diff line change
@@ -166,6 +166,8 @@ void Vessel::ClearAllIntrinsicForcesAndTorques() {
void Vessel::DetectCollapsibilityChange() {
bool const becomes_collapsible = IsCollapsible();
if (is_collapsible_ != becomes_collapsible) {
absl::ReaderMutexLock l(&lock_);

// If collapsibility changes, we create a new history segment. This ensures
// that downsampling does not change collapsibility boundaries.
// NOTE(phl): It is always correct to mark as non-collapsible a collapsible
@@ -191,6 +193,7 @@ void Vessel::DetectCollapsibilityChange() {
}

void Vessel::CreateHistoryIfNeeded(Instant const& t) {
absl::ReaderMutexLock l(&lock_);
CHECK(!parts_.empty());
if (history_->Empty()) {
LOG(INFO) << "Preparing history of vessel " << ShortDebugString()
@@ -211,6 +214,7 @@ void Vessel::CreateHistoryIfNeeded(Instant const& t) {
}

void Vessel::DisableDownsampling() {
absl::ReaderMutexLock l(&lock_);
history_->ClearDownsampling();
}

@@ -311,6 +315,7 @@ void Vessel::CreateFlightPlan(
flight_plan_adaptive_step_parameters,
Ephemeris<Barycentric>::GeneralizedAdaptiveStepParameters const&
flight_plan_generalized_adaptive_step_parameters) {
absl::ReaderMutexLock l(&lock_);
auto const history_back = history_->back();
flight_plan_ = std::make_unique<FlightPlan>(
initial_mass,
@@ -327,6 +332,7 @@ void Vessel::DeleteFlightPlan() {
}

absl::Status Vessel::RebaseFlightPlan(Mass const& initial_mass) {
absl::ReaderMutexLock l(&lock_);
CHECK(has_flight_plan());
Instant const new_initial_time = history_->back().time;
int first_manœuvre_kept = 0;
@@ -446,6 +452,7 @@ std::string Vessel::ShortDebugString() const {
void Vessel::WriteToMessage(not_null<serialization::Vessel*> const message,
PileUp::SerializationIndexForPileUp const&
serialization_index_for_pile_up) const {
absl::ReaderMutexLock l(&lock_);
message->set_guid(guid_);
message->set_name(name_);
body_.WriteToMessage(message->mutable_body());
@@ -510,6 +517,7 @@ not_null<std::unique_ptr<Vessel>> Vessel::ReadFromMessage(
vessel->kept_parts_.insert(part_id);
}

absl::MutexLock l(&vessel->lock_);
if (is_pre_cesàro) {
auto const psychohistory =
DiscreteTrajectory<Barycentric>::ReadFromMessage(message.history(),
@@ -602,6 +610,7 @@ Vessel::Vessel()

Checkpointer<serialization::Vessel>::Writer Vessel::MakeCheckpointerWriter() {
return [this](not_null<serialization::Vessel::Checkpoint*> const message) {
lock_.AssertReaderHeld();
if (backstory_ == history_.get()) {
history_->WriteToMessage(message->mutable_non_collapsible_segment(),
/*excluded=*/{psychohistory_},
@@ -630,8 +639,40 @@ Checkpointer<serialization::Vessel>::Reader Vessel::MakeCheckpointerReader() {
};
}

absl::Status Vessel::Reanimate(Instant const desired_t_min) {
// This method is very similar to Ephemeris::Reanimate. See the comments
// there for some of the subtle points.
static_assert(base::is_serializable_v<Barycentric>);
std::set<Instant> checkpoints;

{
absl::ReaderMutexLock l(&lock_);

Instant const oldest_checkpoint_to_reanimate =
checkpointer_->checkpoint_at_or_before(desired_t_min);
checkpoints = checkpointer_->all_checkpoints_between(
oldest_checkpoint_to_reanimate, oldest_reanimated_checkpoint_);

// The |oldest_reanimated_checkpoint_| has already been reanimated before,
// we don't need it below.
checkpoints.erase(oldest_reanimated_checkpoint_);
}

for (auto it = checkpoints.crbegin(); it != checkpoints.crend(); ++it) {
Instant const& checkpoint = *it;
RETURN_IF_ERROR(checkpointer_->ReadFromCheckpointAt(
checkpoint,
[this, t_initial = checkpoint](
serialization::Vessel::Checkpoint const& message) {
return ReanimateOneCheckpoint(message, t_initial);
}));
}
return absl::OkStatus();
}

absl::Status Vessel::ReanimateOneCheckpoint(
serialization::Vessel::Checkpoint const& message) {
serialization::Vessel::Checkpoint const& message,
Instant const& t_initial) {
// Restore the non-collapsible segment that was fully saved.
auto non_collapsible_segment =
DiscreteTrajectory<Barycentric>::ReadFromMessage(
@@ -643,9 +684,17 @@ absl::Status Vessel::ReanimateOneCheckpoint(
CHECK(!non_collapsible_segment->Empty());

// Construct a new collapsible segment at the end of the non-collapsible
// segment and integrate it until the start time of the history.
Instant const& t_initial = non_collapsible_segment->back().time;
Instant const& t_final = history_->begin()->time;
// segment and integrate it until the start time of the history. We cannot
// hold the lock during the integration, so this code would race if there were
// two threads changing the start of the |history_| concurrently. That
// doesn't happen because there is a single reanimator.
CHECK_EQ(t_initial, non_collapsible_segment->back().time);
Instant t_final;
{
absl::ReaderMutexLock l(&lock_);
t_final = history_->begin()->time;
}

auto const collapsible_segment = non_collapsible_segment->NewForkAtLast();
auto fixed_instance =
ephemeris_->NewInstance({collapsible_segment},
@@ -658,8 +707,13 @@ absl::Status Vessel::ReanimateOneCheckpoint(
// make the whole enchilada the new |history_|. If integration reached the
// start of the existing |history_|, attaching will drop the duplicated point.
// If not, we'll have very close points near the stich.
non_collapsible_segment->AttachFork(std::move(history_));
history_ = std::move(non_collapsible_segment);
{
absl::MutexLock l(&lock_);
CHECK_EQ(t_final, history_->begin()->time);
non_collapsible_segment->AttachFork(std::move(history_));
history_ = std::move(non_collapsible_segment);
oldest_reanimated_checkpoint_ = t_initial;
}

return absl::OkStatus();
}
31 changes: 23 additions & 8 deletions ksp_plugin/vessel.hpp
Original file line number Diff line number Diff line change
@@ -10,6 +10,7 @@

#include "absl/status/status.h"
#include "absl/synchronization/mutex.h"
#include "astronomy/epoch.hpp"
#include "base/jthread.hpp"
#include "base/recurring_thread.hpp"
#include "ksp_plugin/celestial.hpp"
@@ -28,6 +29,7 @@ namespace principia {
namespace ksp_plugin {
namespace internal_vessel {

using astronomy::InfinitePast;
using base::not_null;
using base::RecurringThread;
using geometry::Instant;
@@ -104,18 +106,18 @@ class Vessel {

// Detects a change in the collapsibility of the vessel and creates a new fork
// if needed. Must be called after the pile-ups have been collected.
virtual void DetectCollapsibilityChange();
virtual void DetectCollapsibilityChange() EXCLUDES(lock_);

// If the history is empty, appends a single point to it, computed as the
// barycentre of all parts. |parts_| must not be empty. After this call,
// |history_| is never empty again and the psychohistory is usable. Must be
// called (at least) after the creation of the vessel.
virtual void CreateHistoryIfNeeded(Instant const& t);
virtual void CreateHistoryIfNeeded(Instant const& t) EXCLUDES(lock_);

// Disables downsampling for the history of this vessel. This is useful when
// the vessel collided with a celestial, as downsampling might run into
// trouble.
virtual void DisableDownsampling();
virtual void DisableDownsampling() EXCLUDES(lock_);

// Returns the part with the given ID. Such a part must have been added using
// |AddPart|.
@@ -151,7 +153,7 @@ class Vessel {
Ephemeris<Barycentric>::AdaptiveStepParameters const&
flight_plan_adaptive_step_parameters,
Ephemeris<Barycentric>::GeneralizedAdaptiveStepParameters const&
flight_plan_generalized_adaptive_step_parameters);
flight_plan_generalized_adaptive_step_parameters) EXCLUDES(lock_);

// Deletes the |flight_plan_|. Performs no action unless |has_flight_plan()|.
virtual void DeleteFlightPlan();
@@ -165,7 +167,7 @@ class Vessel {
// performed.
// If |history_->back().time| is greater than the current desired final time,
// the flight plan length is kept; otherwise, the desired final time is kept.
absl::Status RebaseFlightPlan(Mass const& initial_mass);
absl::Status RebaseFlightPlan(Mass const& initial_mass) EXCLUDES(lock_);

// Tries to replace the current prediction with a more recently computed one.
// No guarantees that this happens. No guarantees regarding the end time of
@@ -204,7 +206,8 @@ class Vessel {
// The vessel must satisfy |is_initialized()|.
virtual void WriteToMessage(not_null<serialization::Vessel*> message,
PileUp::SerializationIndexForPileUp const&
serialization_index_for_pile_up) const;
serialization_index_for_pile_up) const
EXCLUDES(lock_);
static not_null<std::unique_ptr<Vessel>> ReadFromMessage(
serialization::Vessel const& message,
not_null<Celestial const*> parent,
@@ -241,8 +244,13 @@ class Vessel {
Checkpointer<serialization::Vessel>::Reader
MakeCheckpointerReader();

absl::Status Reanimate(Instant const desired_t_min) EXCLUDES(lock_);

// |t_initial| is the time of the checkpoint, which is the end of the non-
// collapsible segment.
absl::Status ReanimateOneCheckpoint(
serialization::Vessel::Checkpoint const& message);
serialization::Vessel::Checkpoint const& message,
Instant const& t_initial) EXCLUDES(lock_);

// Runs the integrator to compute the |prognostication_| based on the given
// parameters.
@@ -275,6 +283,8 @@ class Vessel {
not_null<Celestial const*> parent_;
not_null<Ephemeris<Barycentric>*> const ephemeris_;

mutable absl::Mutex lock_;

// When reading a pre-Zermelo save, the existing history must be
// non-collapsible as we don't know anything about it.
bool is_collapsible_ = false;
@@ -284,12 +294,17 @@ class Vessel {

not_null<std::unique_ptr<Checkpointer<serialization::Vessel>>> checkpointer_;

// This member must only be accessed by the |reanimator_| thread, or before
// the |reanimator_| thread is started.
Instant oldest_reanimated_checkpoint_ = InfinitePast;

// See the comments in pile_up.hpp for an explanation of the terminology.

// The |history_| is empty until the first call to CreateHistoryIfNeeded.
// It is made of a series of forks, alternatively non-collapsible and
// collapsible.
not_null<std::unique_ptr<DiscreteTrajectory<Barycentric>>> history_;
not_null<std::unique_ptr<DiscreteTrajectory<Barycentric>>> history_
GUARDED_BY(lock_);

// The last (most recent) segment of the |history_| prior to the
// |psychohistory_|. May be identical to |history_|, therefore not always a
3 changes: 2 additions & 1 deletion physics/discrete_trajectory_test.cpp
Original file line number Diff line number Diff line change
@@ -932,7 +932,8 @@ TEST_F(DiscreteTrajectoryTest, DownsamplingSerialization) {

serialization::DiscreteTrajectory message;
circle.WriteToMessage(&message,
/*forks=*/{},
/*excluded=*/{},
/*tracked=*/{},
/*exact=*/{circle.LowerBound(t0_ + 2 * Second),
circle.LowerBound(t0_ + 3 * Second)});
auto deserialized_circle =
7 changes: 4 additions & 3 deletions physics/ephemeris.hpp
Original file line number Diff line number Diff line change
@@ -317,14 +317,14 @@ class Ephemeris {
// and its trajectories starting in such a way that |t_min()| is at or before
// |desired_t_min|. The member variable |oldest_reanimated_checkpoint_| tells
// the reanimator where to stop.
absl::Status Reanimate(Instant const desired_t_min);
absl::Status Reanimate(Instant const desired_t_min) EXCLUDES(lock_);

// Reconstructs the past state of the ephemeris between |t_initial| and
// |t_final| using the given checkpoint |message|.
absl::Status ReanimateOneCheckpoint(
serialization::Ephemeris::Checkpoint const& message,
Instant const& t_initial,
Instant const& t_final);
Instant const& t_final) EXCLUDES(lock_);

// Callbacks for the integrators.
void AppendMassiveBodiesState(
@@ -445,7 +445,8 @@ class Ephemeris {
not_null<
std::unique_ptr<Checkpointer<serialization::Ephemeris>>> checkpointer_;

// This member must only be accessed by the |reanimator_| thread.
// This member must only be accessed by the |reanimator_| thread, or before
// the |reanimator_| thread is started.
Instant oldest_reanimated_checkpoint_ = InfinitePast;

// The techniques and terminology follow [Lov22].
3 changes: 2 additions & 1 deletion physics/ephemeris_body.hpp
Original file line number Diff line number Diff line change
@@ -251,7 +251,6 @@ Ephemeris<Frame>::Ephemeris(

IntegrationProblem<NewtonianMotionEquation> problem;
problem.equation = MakeMassiveBodiesNewtonianMotionEquation();
reanimator_.Start();

typename NewtonianMotionEquation::SystemState& state = problem.initial_state;
state.time = DoublePrecision<Instant>(initial_time);
@@ -360,6 +359,8 @@ absl::Status Ephemeris<Frame>::last_severe_integration_status() const {

template<typename Frame>
void Ephemeris<Frame>::RequestReanimation(Instant const& desired_t_min) {
reanimator_.Start();

bool must_restart;
{
absl::MutexLock l(&lock_);