diff --git a/systems/framework/diagram.cc b/systems/framework/diagram.cc index 26ce40f4c28a..9b92df762704 100644 --- a/systems/framework/diagram.cc +++ b/systems/framework/diagram.cc @@ -1431,6 +1431,10 @@ Diagram::ConvertScalarType() const { // Move the new systems into the blueprint. blueprint->systems = std::move(new_systems); + // Do nothing about life_support. Since scalar conversion is effectively a + // deep copy, the lifetime extensions provided by life_support are not needed + // here. + return blueprint; } @@ -1551,6 +1555,7 @@ void Diagram::Initialize(std::unique_ptr blueprint) { connection_map_ = std::move(blueprint->connection_map); output_port_ids_ = std::move(blueprint->output_port_ids); registered_systems_ = std::move(blueprint->systems); + life_support_ = std::move(blueprint->life_support); // This cache entry just maintains temporary storage. It is only ever used // by DoCalcNextUpdateTime(). Since this declaration of the cache entry diff --git a/systems/framework/diagram.h b/systems/framework/diagram.h index 4ffaaa985d80..c802805d3358 100644 --- a/systems/framework/diagram.h +++ b/systems/framework/diagram.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -12,6 +13,7 @@ #include "drake/common/default_scalars.h" #include "drake/common/drake_copyable.h" #include "drake/common/pointer_cast.h" +#include "drake/common/string_map.h" #include "drake/systems/framework/diagram_context.h" #include "drake/systems/framework/diagram_continuous_state.h" #include "drake/systems/framework/diagram_discrete_values.h" @@ -27,9 +29,9 @@ namespace systems { namespace internal { -/// Destroys owned systems in the reverse order they were added; this enables -/// Systems to refer to each other during destruction, in the usual "undo" -/// resource order one would expect for C++. +// Destroys owned systems in the reverse order they were added; this enables +// Systems to refer to each other during destruction, in the usual "undo" +// resource order one would expect for C++. template class OwnedSystems { public: @@ -60,6 +62,17 @@ class OwnedSystems { std::vector>> vec_; }; +// External life support data for the diagram. The data will be moved to the +// diagram at Build() time. Data stored here will have a life-cycle that is the +// union of the builder and the diagram. +// +// This mechanism is particularly useful for the Python FFI. It can be used to +// extend the lifetime of Python-wrapped systems, when the Build() call occurs +// in C++ that is not exposed to the Python bindings. +struct DiagramLifeSupport { + string_map attributes; +}; + } // namespace internal template @@ -528,6 +541,8 @@ class Diagram : public System, internal::SystemParentServiceInterface { std::map connection_map; // All of the systems to be included in the diagram. internal::OwnedSystems systems; + + internal::DiagramLifeSupport life_support; }; // Constructs a Diagram from the Blueprint that a DiagramBuilder produces. @@ -603,6 +618,8 @@ class Diagram : public System, internal::SystemParentServiceInterface { // allocated as a cache entry to avoid heap operations during simulation. CacheIndex event_times_buffer_cache_index_{}; + internal::DiagramLifeSupport life_support_; + // For all T, Diagram considers DiagramBuilder a friend, so that the // builder can set the internal state correctly. friend class DiagramBuilder; diff --git a/systems/framework/diagram_builder.cc b/systems/framework/diagram_builder.cc index fa7d209a80ff..52c5172a974b 100644 --- a/systems/framework/diagram_builder.cc +++ b/systems/framework/diagram_builder.cc @@ -681,6 +681,7 @@ std::unique_ptr::Blueprint> DiagramBuilder::Compile() { blueprint->output_port_names = output_port_names_; blueprint->connection_map = connection_map_; blueprint->systems = std::move(registered_systems_); + blueprint->life_support = std::move(life_support_); already_built_ = true; diff --git a/systems/framework/diagram_builder.h b/systems/framework/diagram_builder.h index e1acfa825414..ba9cb0390614 100644 --- a/systems/framework/diagram_builder.h +++ b/systems/framework/diagram_builder.h @@ -467,6 +467,14 @@ class DiagramBuilder { /// as more ports are exported. int num_output_ports() const; + /// (Internal use only). Returns a mutable reference to life support data for + /// the diagram. The data will be moved to the diagram at Build() time. Data + /// stored here will have a life-cycle that is the union of the builder and + /// the diagram. + internal::DiagramLifeSupport& get_mutable_life_support() { + return life_support_; + } + private: // Declares a new input to the entire Diagram, using @p model_input to // supply the data type. @p name is an optional name for the input port; if @@ -533,6 +541,8 @@ class DiagramBuilder { std::unordered_set*> systems_; // The Systems in this DiagramBuilder, in the order they were registered. internal::OwnedSystems registered_systems_; + + internal::DiagramLifeSupport life_support_; }; } // namespace systems diff --git a/systems/framework/test/diagram_test.cc b/systems/framework/test/diagram_test.cc index 82493be5269c..765aa47f051f 100644 --- a/systems/framework/test/diagram_test.cc +++ b/systems/framework/test/diagram_test.cc @@ -4084,6 +4084,35 @@ GTEST_TEST(ImplicitTimeDerivatives, DiagramProcessing) { EXPECT_EQ(residual, expected_result); } +// Life support data has a lifetime that is the union of the builder and the +// resulting diagram. +GTEST_TEST(LifeSupport, Lifetime) { + std::weak_ptr spy; + { + auto do_build = [&]() { + auto thing = std::make_shared(42); + spy = thing; + DiagramBuilder builder; + builder.get_mutable_life_support().attributes.emplace("thing", + std::move(thing)); + // Thing is living inside builder. + EXPECT_FALSE(builder.get_mutable_life_support().attributes.empty()); + EXPECT_FALSE(spy.expired()); + builder.AddSystem>(); + auto result = builder.Build(); + // Thing has been transferred to diagram. + EXPECT_TRUE(builder.get_mutable_life_support().attributes.empty()); + EXPECT_FALSE(spy.expired()); + return result; + }; + auto diagram = do_build(); + // Builder is gone; thing remains. + EXPECT_FALSE(spy.expired()); + } + // Diagram is gone; thing is now also gone. + EXPECT_TRUE(spy.expired()); +} + } // namespace } // namespace systems } // namespace drake