-
Notifications
You must be signed in to change notification settings - Fork 472
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[WIP] Add infrastructure for state serialization #488
base: master
Are you sure you want to change the base?
Conversation
Added test cases to TestXml. Begin to serialize State using those; no committed State tests yet.
…ure_state_serialization
e.setAttributeValue("version", String(version)); | ||
e.appendNode(toXmlElementHelper(operand, "operand", true)); | ||
e.appendNode(toXmlElementHelper(operandDot, "operandDot", true)); | ||
e.appendNode(toXmlElementHelper(derivIsGood, "derivIsGood", true)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't like that toXmlElementHelper
has to be used here. Would be friendlier for people writing these methods if it were just toXmlElement
and the mysterious third parameter were hidden somewhere below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To clarify, you don't like that toXmlElementHelper
has to be used anywhere (within toXmlElement()
methods), right? Can you educate me as to why the third parameter is necessary for SFINAE?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, the "Helper" method should just be needed in the internal implementation. Here it is appearing in user-written code for serializing.
The issue is that we normally want classes to have toXmlElement()
methods. But when serializing an existing class (std::vector
, say) you can't add a new method. So you have to write a free function toXmlElement(std::vector<T>)
to serialize it. What if there is both a member named MyClass::toXmlElement()
and a free function toXmlElement(MyClass)
? I wanted to ensure that the member gets called. By template resolution rules, that true
value is a better match for a bool
than an int
. So I arranged things so that the member function gets called from a Helper that takes a bool
while the free function gets called from a Helper that takes an int
. That one gets called if there's no bool
competing with it because true
gets promoted to int
.
All that is still necessary; I'm just thinking it doesn't have to appear in user code. Use code could call some nicely-named method, then that method could invoke the Helper which would then pick out the right member or free function. I think that should be possible ...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you; that was very helpful. I also described this situation to @klshrinidhi, who may be interested in helping.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any other idea you guys have for the API would be welcome. It should be as straightforward as possible for someone to write serialization/deserialization methods for any class they want to stick into an AbstractValue in a State. I assume people will do it by copying from existing examples so it should be very predictable. The current API might be OK but it doesn't seem optimal.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If a specialization exists for the class, it will be used. Or the primary template is chosen, which calls the member function.
Thanks, @klshrinidhi. I think this prioritizes the free function over the member though, which is opposite of what we want.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It chooses the specialization over member function.
I think it makes sense because if I put the effort to specialize the function to change the serialization behavior, I would like it to be called instead of member function. Choosing the member function even when a specialization exists defeats the purpose of specializing. The person who specialized the function will be surprised to see it not take effect.
Again, I am not too familiar with this development. So you may be right.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@klshrinidhi and I just had a whiteboard session and he brought up two other points:
- Maybe it is desirable to prefer a free function (that is, template specialization of
toXmlElement()
) over a member function) if both exist. Why would one define the free function if the class already hadtoXmlElement()
/fromXmlElement()
? Perhaps the only reason would be because they want to override the behavior of the member function to achieve something new. Probable answer: the control of serializing a type should be held by that type; it would be dangerous for others to subsume that control. The resulting Xml file could not be loaded correctly by others who did not have that free function but were using the same class. - Drawing a parallel to
std::begin()
andstd::end()
, @klshrinidhi was saying how you can callbegin()
orend()
(without any namespace qualification), and if the argument is in thestd
namespace thenstd::begin()
/std::end()
will be called. He raised the idea that it could be nice to never have to qualifytoXmlElement()
(ortoXmlElementHelper()
) withSimTK::
. This would require a<ns>::toXmlElement()
in each namespace that wanted to use it, though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
would anything we do here eventually change behavior if Unified Call Syntax becomes part of the standard?
Yikes! Who knows? Some of the proposals would make this easier.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moving discussion out of the code ...
Fantastic! I'll start looking at this soon. |
Unfortunately, |
Removed check for now. Should pass on Linux and OSX now; still one mysterious test failure on Windows. |
I investigated the Windows failure. It appears to be a problem with the Visual Studio linker not correctly generating a single copy of each |
Ah I know very little about that stuff, but would |
Can't be extern because |
No, as explained in the Unified Call Syntax doc you linked to, the member function is to be preferred because it was provided by the class designer. |
Coincidentally that is also discussed in the Unified Call Syntax doc, where it was mostly described as a hack for range-for; it is definitely not a general principle and it didn't seem that they were particularly happy with it. (That's Stroustrup and Sutter so I'm inclined to take them seriously!) |
One potential issue with preferring the member function over a free function: say today there is a type X that I want to serialize but I don't have control over it, so I write a free function. Then next year, the author of X decides to add a |
Hmmm ... any time someone changes the code of a class you are using the behavior can change! Hopefully the author took seriously the need to serialize the class and now you won't have to. Otherwise I recommend sending a nasty email! |
I suppose that's true. @klshrinidhi seemed to be able to get rid of the third argument! |
One way to get rid of the name |
I have started working on a TestStateXml test for Simbody, and will open a PR for that eventually. Separately, I gave my first shot at writing out an OpenSim Model's State to XML. I got the following error:
I think this means (1) SBModelVars does not have a |
|
I don't understand. |
Thanks for explaining. I like idea of piggybacking off
I mean the interface for the implementor of auto xmlDoc = SimTK::toXmlElement(SimTK::State()); // this would work and call SimTK::toXmlElementImpl
Xml::Element SimTK::State::toXmlElement() {
return this->toXmlElementImpl();
}
Xml::Element SimTK::State::toXmlElementImpl() {
...
elem.appendNode(toXmlElement(...));
elem.appendNode(toXmlElement(...));
} Let me know if I'm still not being clear. The gist is that if you want to obtain an |
Alternatively, we could just rename |
I still like the idea of handling the potentially error-prone version and element name code in some central way. Here's a sketch of what I'm thinking: class XmlSerialization {
public:
XmlSerialization(std::string type, int version, std::string attrName="");
template <typename T>
void appendThing(std::string name, T value);
Xml::Element getXmlElement() const;
};
...
Xml::Element Measure_Differentiate_Result::toXmlElement(const std::string& name) const {
static const int version = 1;
XmlSerialization xml("Measure_Differentiate_Result", version, name);
xml.appendThing("operand", operand);
xml.appendThing("operandDot", operandDot);
xml.appendThing("derivIsGood", derivIsGood);
return xml.getXmlElement(); I think there are a bunch of flaws with this proposal. For example, how would we require that people use the It just sorta feels like the valuable information is "type name", "version", and a list of the members to serialize. It would be good if that were all one needed to specify. |
Well, I'm more confused now! What I want to achieve is I want those functions to be easy enough to write that there is a high probability of it being done successfully. Since user types will have members of arbitrary type (including other user types) the best way to do this would be recursively. So I'd like to be able to write a Anyway I could live with writing the member function or free function |
Even if it were possible, I think it might be confusing to use the same method name for so many things.
I think either of these are fine. |
@sherm1 I am starting to add in the necessary class SBInstanceVars {
public:
Array_<MassProperties,MobilizedBodyIndex> bodyMassProperties;
Array_<Transform, MobilizedBodyIndex> outboardMobilizerFrames;
Array_<Transform, MobilizedBodyIndex> inboardMobilizerFrames;
Array_<Motion::Level, MobilizedBodyIndex> mobilizerLockLevel;
Vector lockedQs;
Vector lockedUs; // also used for udot
Array_<bool, MobilizedBodyIndex> prescribedMotionIsDisabled;
Vector particleMasses;
Array_<bool,ConstraintIndex> constraintIsDisabled; Could you tell me which of these members should be serialized? |
"Expected Measure_Differentiate_Result version=1 but got %d.", | ||
versionInXml); | ||
if (!requiredName.empty()) { | ||
const String& name = e.getElementTag(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be e.getOptionalAttributeValue("name");
? This occurs elsewhere too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm...after looking at some more of the code, it seems there are two scenarios, one where the name is the tag name, and one where it's the value of the name attribute.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I think that's right. That's because I made it optional to create a unique tag for serializing user objects. If the "to" method created a special tag, then the "from" method can look for it. Not sure that was a good idea, but you don't want to have to make up tags for everything (int, char, etc.). They really aren't needed because we always know what type we're expecting by the time we read it in, except for AbstractValue
objects where we have the whole registration infrastructure in order to figure out what type we've got.
It's a variable, so everything in it should be serialized. Body mass properties are instance variables, but there aren't currently any methods for setting them so they just keep their default values. |
I'm not sure that solution can work... sometimes, that macro is used inside a class definition, to create a nested type; the free function would end up in the body of the containing class. |
Nevermind! You can define the friend function in the class body! #define SimTK_DEFINE_AND_EXPORT_UNIQUE_LOCAL_INDEX_TYPE \
class EXPORT NAME { ... \
public: \
friend std::istream& operator>>(std::istream& input, NAME& obj) { input >> obj.ix; }
}; Another question: is there a particular reason why |
Yes -- it's an abstract class (has pure virtuals) so you can't create one. Only concrete |
@sherm1 For testing, I plan to add test cases to |
Good idea! TestStateXml can just test the basic functionality while TestSimbodyStateXml checks that we managed to write the (de)serialization methods for all of the junk Simbody puts in the State. |
Do you think we could solve this issue now? I'd like to start submitting PRs and it'd be good for the appveyor tests to pass. Let me know how I may be able to help here. |
Previously, calling `fromXmlElement()` multiple times on the same State caused subsystems to erroneously pile up.
…erator Add operator>> to index types
Add operator>> to Stage
In StateImpl::fromXmlElement(), clear subsystems.
…ody into toXmlElement_exception
Throw meaningful exception if T cannot be converted to Xml element
Fix bug in ContinuousVarInfo::toXmlElement.
I was trying to learn more about static variables inside functions and came across this blog post that said dynamic initialization of static variables is not threadsafe in some compilers (non-GCC). The blog post is somewhat old, though; this page indicates that statics are threadsafe in MSVC 2015 ("magic statics"). This info doesn't seem relevant to the current issue with MSVC; I just thought it was worth sharing. |
Thread safety of function-local statics was fixed in C++11 so all compliant compilers should behave properly. This stackoverflow answer points to the specification in the standard. |
I have a question about if the registration mechanism for Value is sufficient. Is it possible for users to have an executable that deserializes an xml element that contains We may not readily detect this issue in our tests, because the obvious way to write tests is to first serialize then deserialize. It seems like we need tests that just deserialize from an Xml file that is stored in the repository. |
That's a good point - didn't occur to me. There would have to be a registration phase where There has to be a State object prior to deserializing. If that has to be a State created by a System many of the |
This is something I'd like to chat with you about in person at some point. I know you've mentioned before that we might need a System to properly deserialize a State. Right now, if we deserialize a state, about all we can do with it (without a System) is reserialize it. We can't even ask it for its time (b/c it's not realized to Time).
I'm not sure I understand; how can we register upon deserialization? |
I still like the idea of being able to (de)serialize with only a State around, but I could be talked out of it!
That's not right -- time and state are independent of anything else and don't require realization. If they aren't available something didn't deserialize correctly.
Someone calls |
Bulk registration could also be handled with a function static variable similar to what I did for individual registration of |
Ah bulk registration. I don't know...my hunch is that we should treat all |
Yeah I like that idea too. |
Things to discuss:
|
This was from way up this thread, referring to the problem where different instantiations of I know this is paused at the moment but a similar problem came up in Drake so I thought I'd record that thought here. |
Ah...interesting. I will try that out whenever I get back to this project. Thank you for sharing. |
This is the first crack at State serialization, which is not yet complete. This contains the necessary infrastructure to provide
toXmlElement()
andfromXmlElement()
support for general types and (most important) forAbstractValue
andValue<T>
.See TestXml.cpp for test cases for
AbstractValue
andValue<T>
.State.toXmlElement()
andState.fromXmlElement()
exist and will work if all the contained AbstractValue types have those methods, or if they have stream extraction/insertion operators from which workable defaults an be created.No State serialization tests are committed here yet. Those need to get written and debugged. @chrisdembia, if you want to work on this have a look and then we can discuss.
This change is