Serializing a JBox2D Body

30 views
Skip to first unread message

Joshua Lee

unread,
Aug 22, 2014, 6:46:58 PM8/22/14
to snakeya...@googlegroups.com
Hello. So I'm completely new to both yaml and serialization. I think I'm jumping into the deep end with a lot of this and I haven't found very many resources explaining things, so I'm a bit lost. Anyway, I ran into trouble with serializing an instance of JBox2D's Body. This error happens when I attempt to serialize:

Exception in thread "main" org.yaml.snakeyaml.error.YAMLException: No JavaBean properties found in org.jbox2d.dynamics.FixtureProxy
    at org
.yaml.snakeyaml.introspector.PropertyUtils.getPropertiesMap(PropertyUtils.java:87)
    at org
.yaml.snakeyaml.introspector.PropertyUtils.createPropertySet(PropertyUtils.java:110)
    at org
.yaml.snakeyaml.introspector.PropertyUtils.getProperties(PropertyUtils.java:102)
    at org
.yaml.snakeyaml.introspector.PropertyUtils.getProperties(PropertyUtils.java:94)
    at org
.yaml.snakeyaml.representer.Representer.getProperties(Representer.java:238)
    at org
.yaml.snakeyaml.representer.Representer$RepresentJavaBean.representData(Representer.java:49)
    at org
.yaml.snakeyaml.representer.BaseRepresenter.representData(BaseRepresenter.java:109)
    at org
.yaml.snakeyaml.representer.BaseRepresenter.representSequence(BaseRepresenter.java:137)
    at org
.yaml.snakeyaml.representer.SafeRepresenter$RepresentArray.representData(SafeRepresenter.java:206)
    at org
.yaml.snakeyaml.representer.BaseRepresenter.representData(BaseRepresenter.java:95)
    at org
.yaml.snakeyaml.representer.Representer.representJavaBeanProperty(Representer.java:125)
    at org
.yaml.snakeyaml.representer.Representer.representJavaBean(Representer.java:83)
    at org
.yaml.snakeyaml.representer.Representer$RepresentJavaBean.representData(Representer.java:49)
    at org
.yaml.snakeyaml.representer.BaseRepresenter.representData(BaseRepresenter.java:109)
    at org
.yaml.snakeyaml.representer.Representer.representJavaBeanProperty(Representer.java:125)
    at org
.yaml.snakeyaml.representer.Representer.representJavaBean(Representer.java:83)
    at org
.yaml.snakeyaml.representer.Representer$RepresentJavaBean.representData(Representer.java:49)
    at org
.yaml.snakeyaml.representer.BaseRepresenter.representData(BaseRepresenter.java:109)
    at org
.yaml.snakeyaml.representer.BaseRepresenter.represent(BaseRepresenter.java:65)
    at org
.yaml.snakeyaml.Yaml.dumpAll(Yaml.java:271)
    at org
.yaml.snakeyaml.Yaml.dumpAll(Yaml.java:262)
    at org
.yaml.snakeyaml.Yaml.dumpAll(Yaml.java:234)
    at org
.yaml.snakeyaml.Yaml.dump(Yaml.java:209)

Here's the source for Body: https://github.com/jbox2d/jbox2d/blob/master/jbox2d-library/src/main/java/org/jbox2d/dynamics/Body.java
Looking at the source for Body I assumed it would be impossible to serialize some of its fields. m_sweep, m_world, m_prev, m_next, and m_userData can all be decided at runtime and so don't need to be serialized. The World and Body fields in particular, I assume, are problematic because of circular referencing. World contains a reference to the Body we're trying to serialize which contains the World, etc. I went ahead and tried to make a custom representer:

public class BodyRepresenter extends Representer
{
   
public BodyRepresenter()
   
{
       
this.representers.put(Body.class, new RepresentBody());
   
}

   
private class RepresentBody implements Represent
   
{
       
@Override
       
public Node representData(Object o)
       
{
           
Body body = (Body)o;
           
String newline = " ";
           
String value = "";
           
DumperOptions options = new DumperOptions();
            options
.setDefaultFlowStyle(DumperOptions.FlowStyle.FLOW);
            options
.setDefaultScalarStyle(DumperOptions.ScalarStyle.PLAIN);
            options
.setWidth(200);
           
Yaml yaml = new Yaml(new FixtureRepresenter(), options);
            value
+= "m_type: " + body.m_type;
            value
+= newline + "m_flags: " + body.m_flags;
            value
+= newline + "m_islandIndex: " + body.m_islandIndex;
            value
+= newline + "m_xf: " + yaml.dump(body.m_xf);
            value
+= "m_linearVelocity: " + yaml.dump(body.m_linearVelocity);
            value
+= "m_angularVelocity: " + body.m_angularVelocity;
            value
+= newline + "m_force: " + yaml.dump(body.m_force);
            value
+= "m_torque: " + body.m_torque;
            value
+= newline + "m_linearDamping: " + body.m_linearDamping;
            value
+= newline + "m_angularDamping: " + body.m_angularDamping;
            value
+= newline + "m_gravityScale: " + body.m_gravityScale;
            value
+= newline + "m_sleepTime: " + body.m_sleepTime;
            value
+= newline + "m_fixtureList: " + yaml.dump(body.m_fixtureList);
           
System.out.println(value);

           
return representScalar(new Tag("!!org.jbox2d.dynamics.Body"), value);

       
}
   
}
}

And also for Fixture.
Source is here: https://github.com/jbox2d/jbox2d/blob/master/jbox2d-library/src/main/java/org/jbox2d/dynamics/Fixture.java

public class FixtureRepresenter extends Representer
{
   
public FixtureRepresenter()
   
{
       
this.representers.put(Fixture.class, new RepresentFixture());
   
}

   
private class RepresentFixture implements Represent
   
{
       
@Override
       
public Node representData(Object o)
       
{
           
Fixture fixture = (Fixture)o;
           
String newline = " ";
           
String value = "";
           
DumperOptions options = new DumperOptions();
            options
.setDefaultFlowStyle(DumperOptions.FlowStyle.FLOW);
            options
.setDefaultScalarStyle(DumperOptions.ScalarStyle.PLAIN);
            options
.setWidth(200);
           
Yaml yaml = new Yaml(options);
            value
+= "m_density: " + fixture.m_density;
            value
+= newline + "m_shape: " + yaml.dump(fixture.m_shape);
            value
+= "m_friction: " + fixture.m_friction;
            value
+= newline + "m_restitution: " + fixture.m_restitution;
            value
+= newline + "m_isSensor: " + fixture.m_isSensor;

           
return representScalar(new Tag("!!org.jbox2d.dynamics.Fixture"), value);
       
}
   
}
}

Now here's the problem. When I dump using these representers, I get this unreadable mess:

!!org.jbox2d.dynamics.Body "m_type: DYNAMIC m_flags: 38 m_islandIndex: 0 m_xf: !!org.jbox2d.common.Transform\
  \ {p: {x: 12.5, y: 12.5}, q: {c: 1.0, s: 0.0}}\nm_linearVelocity: !!org.jbox2d.common.Vec2\
  \ {x: 0.0, y: 0.0}\nm_angularVelocity: 0.0 m_force: !!org.jbox2d.common.Vec2 {x:\
  \ 0.0, y: 0.0}\nm_torque: 0.0 m_linearDamping: 3.0 m_angularDamping: 10.0 m_gravityScale:\
  \ 1.0 m_sleepTime: 0.0 m_fixtureList: !!org.jbox2d.dynamics.Fixture 'm_density:\
  \ 1.0 m_shape: !!org.jbox2d.collision.shapes.CircleShape {m_p: {x: 0.0, y: 0.0},\
  \ m_radius: 0.38, m_type: CIRCLE, radius: 0.38}\n\n  m_friction: 0.2 m_restitution:\
  \ 1.0 m_isSensor: false'\n"

If I replace newline with "\n" then I get pretty much the same thing with more \n's strewn around. I must be missing something obvious with how the representScalar() method works because I'm sure that's where everything goes wrong.

Jordan Angold

unread,
Aug 23, 2014, 8:23:31 PM8/23/14
to snakeya...@googlegroups.com
Hi Joshua,

It looks like you've run into a library that isn't written in the Java Bean style. That's okay, SnakeYaml has ways around that. If you look at https://code.google.com/p/snakeyaml/wiki/Documentation#JavaBeans , you'll see a part that mentions BeanAccess.FIELD, which is a setting that tells SnakeYaml how to look at your objects. There's an example here: https://code.google.com/p/snakeyaml/source/browse/src/test/java/org/yaml/snakeyaml/issues/issue55/FieldListTest.java .

Unfortunately, you cannot change that setting on a per-class basis.

Fortunately, SnakeYaml can handle arbitrary object graphs -- circular references are not a problem. SnakeYaml uses YAML's anchors and references mechanism for that, so if you want to read more, see here: http://yaml.org/spec/1.2/spec.html#id2760395

By default, SnakeYaml will serialize all fields, even ones you don't need to serialize. If you want to change that, you'll need to build your own Representer. A correct implementation of a Representer's methods maps from [Java object] to [MappingNode instance, with field names as keys and field values as values]; that MappingNode will then be turned into text later on in SnakeYaml's internal mechanisms. You shouldn't have to manipulate text yourself.

Try using BeanAccess.FIELD. If that's a satisfactory solution to your problem, great. If not, please post back.

Thanks,
/Jordan

Joshua Lee

unread,
Aug 27, 2014, 10:12:40 PM8/27/14
to snakeya...@googlegroups.com
Hey Jordan,
 
A correct implementation of a Representer's methods maps from [Java object] to [MappingNode instance, with field names as keys and field values as values]
Yeah that seems obvious now. I feel kind of dumb. In any case, I got everything working just fine, but not without a few hitches. First, using BeanAccess.FIELD yielded an exception where "arrays of primitives are not fully supported". This wasn't a big deal because I really wanted to use the custom Representers and Constructors. I got all of that working except when the yaml was generated the !body tag wasn't being added to the body field which resulted in the default Constructor being called instead of my custom one. This, of course, ends in an error. So, every time I dump an object I have to manually change this line:
body:
to this
body: !body
I don't understand why the Representer doesn't add that automatically. Again, not a big deal but it is an annoyance.

Thanks for the help!
Here is my Representer and Constructor if anyone is interested. I wasn't sure how to add multiple custom Representers and Constructors so I just shoved them all into single classes.
https://gist.github.com/JoshuaCLee/fb74e1a59751733cb570

Jordan Angold

unread,
Aug 28, 2014, 1:47:38 AM8/28/14
to snakeya...@googlegroups.com
Cool, I'm glad that's working now.

Support for arrays of primitives has been in the source code for over a year now, but it looks like we haven't made a new release in that time.

SnakeYaml uses some pretty complicated logic to figure out whether to emit tags or not (preferring not, to make the resulting yaml easier to read). Are you getting the !fixture and !entity tags? If you are, can you post your yaml output and any relevant code to generate it?

Thanks,
/Jordan
Reply all
Reply to author
Forward
0 new messages