diff --git a/ksp_plugin/plugin.cpp b/ksp_plugin/plugin.cpp index f3722ac340..92a5c029d3 100644 --- a/ksp_plugin/plugin.cpp +++ b/ksp_plugin/plugin.cpp @@ -699,7 +699,7 @@ void Plugin::SetPredictionAdaptiveStepParameters( void Plugin::UpdatePrediction(GUID const& vessel_guid) const { CHECK(!initializing_); - FindOrDie(vessels_, vessel_guid)->UpdatePrediction(InfiniteFuture); + FindOrDie(vessels_, vessel_guid)->FlowPrediction(InfiniteFuture); } void Plugin::CreateFlightPlan(GUID const& vessel_guid, diff --git a/ksp_plugin/renderer.cpp b/ksp_plugin/renderer.cpp index db04016e26..743c7b5d67 100644 --- a/ksp_plugin/renderer.cpp +++ b/ksp_plugin/renderer.cpp @@ -76,7 +76,7 @@ Vessel const& Renderer::GetTargetVessel() const { DiscreteTrajectory const& Renderer::GetTargetVesselPrediction( Instant const& time) const { CHECK(target_); - target_->vessel->UpdatePrediction(time); + target_->vessel->FlowPrediction(time); // The prediction may not have been prolonged to |time| if we are near a // singularity. CHECK_LE(time, target_->vessel->prediction().last().time()); @@ -109,7 +109,7 @@ Renderer::RenderBarycentricTrajectoryInPlotting( if (target_ && begin != end) { auto last = end; --last; - target_->vessel->UpdatePrediction(last.time()); + target_->vessel->FlowPrediction(last.time()); } for (auto it = begin; it != end; ++it) { Instant const& t = it.time(); diff --git a/ksp_plugin/vessel.cpp b/ksp_plugin/vessel.cpp index 79bb1858e8..b6a62ebdbe 100644 --- a/ksp_plugin/vessel.cpp +++ b/ksp_plugin/vessel.cpp @@ -7,6 +7,7 @@ #include #include +#include "astronomy/epoch.hpp" #include "ksp_plugin/integrators.hpp" #include "ksp_plugin/pile_up.hpp" #include "quantities/si.hpp" @@ -16,6 +17,7 @@ namespace principia { namespace ksp_plugin { namespace internal_vessel { +using astronomy::InfiniteFuture; using base::Contains; using base::FindOrDie; using base::make_not_null_unique; @@ -36,9 +38,9 @@ Vessel::Vessel(GUID const& guid, prediction_adaptive_step_parameters_(prediction_adaptive_step_parameters), parent_(parent), ephemeris_(ephemeris), - history_(make_not_null_unique>()), - prediction_(make_not_null_unique>()) { - // Can't create the |psychohistory_| here because |history_| is empty; + history_(make_not_null_unique>()) { + // Can't create the |psychohistory_| and |prediction_| here because |history_| + // is empty; } Vessel::~Vessel() { @@ -141,6 +143,7 @@ void Vessel::PrepareHistory(Instant const& t) { CHECK(psychohistory_ == nullptr); history_->Append(t, calculator.Get()); psychohistory_ = history_->NewForkAtLast(); + prediction_ = psychohistory_->NewForkAtLast(); } } @@ -192,6 +195,8 @@ void Vessel::AdvanceTime() { AppendToVesselTrajectory(&Part::psychohistory_begin, &Part::psychohistory_end, *psychohistory_); + prediction_ = psychohistory_->NewForkAtLast(); + for (auto const& pair : parts_) { Part& part = *pair.second; part.ClearHistory(); @@ -200,10 +205,9 @@ void Vessel::AdvanceTime() { void Vessel::ForgetBefore(Instant const& time) { // Make sure that the history keeps at least one (authoritative) point and - // don't change the psychohistory. We cannot use the parts because they may - // have been moved to the future already. + // don't change the psychohistory or prediction. We cannot use the parts + // because they may have been moved to the future already. history_->ForgetBefore(std::min(time, history_->last().time())); - prediction_->ForgetBefore(time); if (flight_plan_ != nullptr) { flight_plan_->ForgetBefore(time, [this]() { flight_plan_.reset(); }); } @@ -228,19 +232,30 @@ void Vessel::DeleteFlightPlan() { flight_plan_.reset(); } -void Vessel::UpdatePrediction(Instant const& last_time) { - // TODO(phl): The prediction should probably be a fork of the psychohistory. - auto const psychohistory_last = psychohistory_->last(); - auto const prediction_begin = prediction_->Begin(); - if (prediction_->Empty() || - prediction_begin.time() != psychohistory_last.time() || - prediction_begin.degrees_of_freedom() != - psychohistory_last.degrees_of_freedom()) { - prediction_ = make_not_null_unique>(); - prediction_->Append(psychohistory_last.time(), - psychohistory_last.degrees_of_freedom()); +void Vessel::FlowPrediction(Instant const& time) { + if (time > prediction_->last().time()) { + bool const finite_time = IsFinite(time - prediction_->last().time()); + Instant const t = finite_time ? time : ephemeris_->t_max(); + // This will not prolong the ephemeris if |time| is infinite (but it may do + // so if it is finite). + bool const reached_t = ephemeris_->FlowWithAdaptiveStep( + prediction_, + Ephemeris::NoIntrinsicAcceleration, + t, + prediction_adaptive_step_parameters_, + FlightPlan::max_ephemeris_steps_per_frame, + /*last_point_only=*/false); + if (!finite_time && reached_t) { + // This will prolong the ephemeris by |max_ephemeris_steps_per_frame|. + ephemeris_->FlowWithAdaptiveStep( + prediction_, + Ephemeris::NoIntrinsicAcceleration, + time, + prediction_adaptive_step_parameters_, + FlightPlan::max_ephemeris_steps_per_frame, + /*last_point_only=*/false); + } } - FlowPrediction(last_time); } DiscreteTrajectory const& Vessel::psychohistory() const { @@ -263,9 +278,7 @@ void Vessel::WriteToMessage( message->add_kept_parts(part_id); } history_->WriteToMessage(message->mutable_history(), - /*forks=*/{psychohistory_}); - prediction_->WriteToMessage(message->mutable_prediction(), - /*forks=*/{}); + /*forks=*/{psychohistory_, prediction_}); if (flight_plan_ != nullptr) { flight_plan_->WriteToMessage(message->mutable_flight_plan()); } @@ -277,6 +290,8 @@ not_null> Vessel::ReadFromMessage( not_null*> const ephemeris, std::function const& deletion_callback) { bool const is_pre_cesàro = message.has_psychohistory_is_authoritative(); + bool const is_pre_chasles = message.has_prediction(); + // NOTE(egg): for now we do not read the |MasslessBody| as it can contain no // information. auto vessel = make_not_null_unique( @@ -319,15 +334,20 @@ not_null> Vessel::ReadFromMessage( if (message.psychohistory_is_authoritative()) { vessel->psychohistory_ = vessel->history_->NewForkAtLast(); } - } else { + vessel->prediction_ = vessel->psychohistory_->NewForkAtLast(); + vessel->FlowPrediction(InfiniteFuture); + } else if (is_pre_chasles) { vessel->history_ = DiscreteTrajectory::ReadFromMessage( message.history(), /*forks=*/{&vessel->psychohistory_}); + vessel->prediction_ = vessel->psychohistory_->NewForkAtLast(); + vessel->FlowPrediction(InfiniteFuture); + } else { + vessel->history_ = DiscreteTrajectory::ReadFromMessage( + message.history(), + /*forks=*/{&vessel->psychohistory_, &vessel->prediction_}); } - vessel->prediction_ = - DiscreteTrajectory::ReadFromMessage(message.prediction(), - /*forks=*/{}); if (message.has_flight_plan()) { vessel->flight_plan_ = FlightPlan::ReadFromMessage(message.flight_plan(), ephemeris); @@ -353,8 +373,7 @@ Vessel::Vessel() prediction_adaptive_step_parameters_(DefaultPredictionParameters()), parent_(testing_utilities::make_not_null()), ephemeris_(testing_utilities::make_not_null*>()), - history_(make_not_null_unique>()), - prediction_(make_not_null_unique>()) {} + history_(make_not_null_unique>()) {} void Vessel::AppendToVesselTrajectory( TrajectoryIterator const part_trajectory_begin, @@ -404,32 +423,6 @@ void Vessel::AppendToVesselTrajectory( } } -void Vessel::FlowPrediction(Instant const& time) { - if (time > prediction_->last().time()) { - bool const finite_time = IsFinite(time - prediction_->last().time()); - Instant const t = finite_time ? time : ephemeris_->t_max(); - // This will not prolong the ephemeris if |time| is infinite (but it may do - // so if it is finite). - bool const reached_t = ephemeris_->FlowWithAdaptiveStep( - prediction_.get(), - Ephemeris::NoIntrinsicAcceleration, - t, - prediction_adaptive_step_parameters_, - FlightPlan::max_ephemeris_steps_per_frame, - /*last_point_only=*/false); - if (!finite_time && reached_t) { - // This will prolong the ephemeris by |max_ephemeris_steps_per_frame|. - ephemeris_->FlowWithAdaptiveStep( - prediction_.get(), - Ephemeris::NoIntrinsicAcceleration, - time, - prediction_adaptive_step_parameters_, - FlightPlan::max_ephemeris_steps_per_frame, - /*last_point_only=*/false); - } - } -} - } // namespace internal_vessel } // namespace ksp_plugin } // namespace principia diff --git a/ksp_plugin/vessel.hpp b/ksp_plugin/vessel.hpp index 3026b2e091..27105c7a96 100644 --- a/ksp_plugin/vessel.hpp +++ b/ksp_plugin/vessel.hpp @@ -138,7 +138,9 @@ class Vessel { // Deletes the |flight_plan_|. Performs no action unless |has_flight_plan()|. virtual void DeleteFlightPlan(); - virtual void UpdatePrediction(Instant const& last_time); + // Tries to extend the prediction up to and including |last_time|. May not be + // able to do it next to a singularity. + virtual void FlowPrediction(Instant const& last_time); virtual DiscreteTrajectory const& psychohistory() const; @@ -168,8 +170,6 @@ class Vessel { TrajectoryIterator part_trajectory_end, DiscreteTrajectory& trajectory); - void FlowPrediction(Instant const& time); - GUID const guid_; std::string name_; @@ -187,7 +187,8 @@ class Vessel { not_null>> history_; DiscreteTrajectory* psychohistory_ = nullptr; - not_null>> prediction_; + // The |prediction_| is forked off the end of the |psychohistory_|. + DiscreteTrajectory* prediction_ = nullptr; std::unique_ptr flight_plan_; }; diff --git a/ksp_plugin_test/mock_vessel.hpp b/ksp_plugin_test/mock_vessel.hpp index 5d571c7d89..eb211de175 100644 --- a/ksp_plugin_test/mock_vessel.hpp +++ b/ksp_plugin_test/mock_vessel.hpp @@ -34,7 +34,7 @@ class MockVessel : public Vessel { MOCK_METHOD0(DeleteFlightPlan, void()); - MOCK_METHOD1(UpdatePrediction, void(Instant const& last_time)); + MOCK_METHOD1(FlowPrediction, void(Instant const& last_time)); MOCK_CONST_METHOD0(psychohistory, DiscreteTrajectory const&()); MOCK_CONST_METHOD0(psychohistory_is_authoritative, bool()); diff --git a/ksp_plugin_test/vessel_test.cpp b/ksp_plugin_test/vessel_test.cpp index a91dce4fdb..f4106d08b0 100644 --- a/ksp_plugin_test/vessel_test.cpp +++ b/ksp_plugin_test/vessel_test.cpp @@ -227,7 +227,7 @@ TEST_F(VesselTest, Prediction) { 50.0 * Metre / Second, 40.0 * Metre / Second}))), Return(true))); - vessel_.UpdatePrediction(astronomy::J2000 + 1 * Second); + vessel_.FlowPrediction(astronomy::J2000 + 1 * Second); EXPECT_EQ(2, vessel_.prediction().Size()); auto it = vessel_.prediction().Begin(); @@ -293,7 +293,7 @@ TEST_F(VesselTest, PredictBeyondTheInfinite) { 60.0 * Metre / Second, 50.0 * Metre / Second}))), Return(true))); - vessel_.UpdatePrediction(astronomy::InfiniteFuture); + vessel_.FlowPrediction(astronomy::InfiniteFuture); EXPECT_EQ(3, vessel_.prediction().Size()); auto it = vessel_.prediction().Begin(); diff --git a/physics/forkable_body.hpp b/physics/forkable_body.hpp index 8bbea9f783..3e3b41c91e 100644 --- a/physics/forkable_body.hpp +++ b/physics/forkable_body.hpp @@ -296,8 +296,10 @@ std::int64_t Forkable::Size() const { // Go up the ancestry chain adding the sizes. Tr4jectory const* parent = ancestor->parent_; while (parent != nullptr) { - size += std::distance(parent->timeline_begin(), - *ancestor->position_in_parent_timeline_) + 1; + if (!parent->timeline_empty()) { + size += std::distance(parent->timeline_begin(), + *ancestor->position_in_parent_timeline_) + 1; + } ancestor = parent; parent = ancestor->parent_; } @@ -307,7 +309,7 @@ std::int64_t Forkable::Size() const { template bool Forkable::Empty() const { - // If this object has an ancestor surely it is hook off of a point in some + // If this object has an ancestor surely it is hooked off of a point in some // timeline, so this object is not empty. return timeline_empty() && parent_ == nullptr; } diff --git a/physics/forkable_test.cpp b/physics/forkable_test.cpp index e4dbfe302a..f2cd820bd2 100644 --- a/physics/forkable_test.cpp +++ b/physics/forkable_test.cpp @@ -229,6 +229,18 @@ TEST_F(ForkableTest, ForkSuccess) { EXPECT_THAT(times, ElementsAre(t1_, t2_, t4_)); } +TEST_F(ForkableTest, Size) { + EXPECT_TRUE(trajectory_.Empty()); + trajectory_.push_back(t1_); + EXPECT_EQ(1, trajectory_.Size()); + not_null const fork1 = + trajectory_.NewFork(trajectory_.timeline_find(t1_)); + EXPECT_EQ(1, fork1->Size()); + not_null const fork2 = fork1->NewFork(fork1->timeline_end()); + fork2->push_back(t2_); + EXPECT_EQ(2, fork2->Size()); +} + TEST_F(ForkableTest, ForkAtLast) { trajectory_.push_back(t1_); trajectory_.push_back(t2_); diff --git a/serialization/ksp_plugin.proto b/serialization/ksp_plugin.proto index 45aa33a5f9..9238119db2 100644 --- a/serialization/ksp_plugin.proto +++ b/serialization/ksp_plugin.proto @@ -134,7 +134,7 @@ message Vessel { repeated fixed32 kept_parts = 15; required DiscreteTrajectory history = 16; optional bool psychohistory_is_authoritative = 17; // Pre-Cesàro. - required DiscreteTrajectory prediction = 18; + optional DiscreteTrajectory prediction = 18; // Pre-Chasles. optional FlightPlan flight_plan = 4; // Pre-Буняковский.