junkdog / artemis-odb

A continuation of the popular Artemis ECS framework
BSD 2-Clause "Simplified" License
776 stars 111 forks source link

Serialization problems with Spine #469

Closed JorenJoestar closed 7 years ago

JorenJoestar commented 7 years ago

Hello there, I am trying to serialize an entity that contains a very simple use case of Spine animations to a json file, but the results are HUGE! The file is 48 mb and if I am storing animations in another component it goes in stack overflow!

I am using the JsonArtemisSerializer, FileOutputStream and this code :

skeletalAnimationComponent.atlas = new TextureAtlas(Gdx.files.local("..."));
        SkeletonJson json = new SkeletonJson(skeletalAnimationComponent.atlas); // This loads skeleton JSON data, which is stateless.
        SkeletonData skeletonData = json.readSkeletonData(Gdx.files.local("..."));

        skeletalAnimationComponent.skeleton = new Skeleton(skeletonData); // Skeleton holds skeleton state (bone positions, slot attachments, etc).

        OutputStream writer = null;
        try {
            writer = new FileOutputStream("test.json");
            IntBag toSave = new IntBag();
            toSave.add(animatedEntity);
            worldSerializationManager.save(writer, new SaveFileFormat(toSave));

        }
        catch (Throwable t) {

        }

anything I am doing wrong or any way I can customize the serialization ? I am not sure if it's a Spine problem - I wanted to ask here first. Thank you!

junkdog commented 7 years ago

You probably want to separate your reference type and asset type components. We have SpineReference and SpineRenderable. What you're seeing now - I'm guessing - is that spine skeleton gets re-serialized, duplicating a lot of referenced data in the process - it should be enough to only hold onto the reference for the spine type (path to resource).

@Transient
public class SpineRenderable extends Component {
    public AnimationState state;
    public Skeleton skeleton;
}

@PooledWeaver
public final class SpineReference extends Component {
    public String path; // <- trivially serializable
}

@Transient exempts the component from serialization.

Create a reactive system for each reference-asset pair. It has an empty processSystem, only listening for unpaired references.

public class SpineResolver extends BaseEntitySystem {
/// ... fields

    public SpineResolver() {
        super(all(Size.class, SpineReference.class).exclude(SpineRenderable.class));
    }

    @Override
    protected void processSystem() {}

    @Override
    protected void inserted(int id) {
        // once inserted, aspect.exclude(SpineRenderable.class) is longer satisfied.
        // removing the SpineRenderable would immediately recreate it; can be useful
        // when dealing with reloaded textures, editor tooling etc.
        assignSpine(id);
    }

So, to summarize: separating reference vs transient/asset components ensures that serialization always work, without too much overhead:

junkdog commented 7 years ago

It's a matter of taste, but I prefer naming components without the "-Component" suffix. They're going to be everywhere - and it doesn't really mean anything in the contex they occur in, and component names themselves are enough, imo.

DaanVanYperen commented 7 years ago

documented. https://github.com/junkdog/artemis-odb/wiki/Model-for-Serialization