Zay-es Bug in multiplayer? Entities are in the wrong sets

Me too!

So it sounds like you need to start adding stuff to your test case until it breaks in a similar way or something… then we’ll spot the culprit red-handed.

Well, I hope I got time tomorrow and we will see what we get… . This takes so much time, it’s unbelievable. :disappointed:

In my main project where I get the errors, I from time to time get the following exception when the client starts. Could this be relevant to our problem? It’s weird however that it occurs only sometimes…

SEVERE: Termining connection due to unhandled error
java.lang.RuntimeException: Error deserializing object, class ID:-116
	at com.jme3.network.base.MessageProtocol.createMessage(MessageProtocol.java:184)
	at com.jme3.network.base.MessageProtocol.addBuffer(MessageProtocol.java:160)
	at com.jme3.network.base.ConnectorAdapter.run(ConnectorAdapter.java:169)
Caused by: com.jme3.network.serializing.SerializerException: Class not found for buffer data.
	at com.jme3.network.serializing.Serializer.readClassAndObject(Serializer.java:405)
	at com.jme3.network.base.MessageProtocol.createMessage(MessageProtocol.java:180)
	... 2 more

Hey @pspeed I found the reason for this - finally!

It seems like the order matters the way your components are added in the time of creation.

I extended my test case to show the difference. Basically, if you add the component you are going to apply the filter to before the other components required by the entity set then it will work fine. However, if you add the other component(s) first (the one which is not filtered) then this weird behavior takes place.

I uploaded the test case on Google Drive since it contains several classes. Here’s the link:

https://drive.google.com/drive/folders/1K1wsZYK-m8CNJfcgulbYTDqzkqocLCig?usp=sharing

In the connectionAdded() method of the GameServer class you can switch between the working and not working case. I hope this helps.

It seems that there’s actually a bug in the system somewhere. However, you will have to start 2 clients to see the effect. If there is just one entitiy (player) it won’t show you the relevant output. Ah, and the “wrong” output comes from the first client connected.

I would really appreciate a short answer.

Best regards
Domenic :slight_smile:

3 Likes

I’m not sure why this happens. I will try to look into it soon if you don’t happen to find the issue first for some reason.

It seems to me like it should be possible to create a simpler one-client test just by creating both example entities. But maybe there needs to be a time delay?

Did you have time to look deeper into this? :slightly_smiling_face:

Not yet. Unfortunately, I have to work a day job and take care of two kids during the week. :frowning:

1 Like

Sorry to bumb this again but I just wanted to know if this issue will be fixed sometime? If not, it might be worth writing some hints or documentation so users will not fall into the same trap as I did.

What do you think?

I have not had a chance to look into it. I guess you also haven’t found the problem in your own tinkering.

The problem is that I still need to put together a test case and that takes a while.

What kind of testcase would you need to be able to dig further into this problem?

A simpler one that doesn’t require 50 jumping jacks every time, basically.

Hi, I am sorry to bumb this thread again. I just wanted to know if you could find the issue yet?

No, I still haven’t had the 4-5 hours to write a proper test case from scratch and test the issue.

Sorry… I will get to it eventually. Probably when I have the same problem in one of my own games.

Edit: see the biggest issue for me is that I’ve looked through the code a dozen times now and I can’t see any possible way that this could ever happen. So I have a strong indication that if I write a test case from scratch that it might “just work”… and that’s kind of a waste of time. It may not be true but until there is a proper test case then I won’t know for sure.

Hey Paul, what if I make a test case for you? So, this could be solved eventually…

You know, I think Zay-Es is a fantastic library which should contain as few bugs as possible.

That would be tremendously helpful.

I agree. I think it’s great that so few have been found so far and all of them have pretty much been solved.

It just means that the ones left are going to be especially tricky to find.

Alright, here it is :blush:

This is the main test class.

import com.jme3.app.Application;
import com.jme3.app.SimpleApplication;
import com.jme3.app.state.AbstractAppState;
import com.jme3.app.state.AppStateManager;
import com.jme3.network.*;
import com.jme3.network.serializing.Serializer;
import com.jme3.system.JmeContext;
import com.simsilica.es.Entity;
import com.simsilica.es.EntityData;
import com.simsilica.es.EntitySet;
import com.simsilica.es.base.DefaultEntityData;
import com.simsilica.es.client.EntityDataClientService;
import com.simsilica.es.filter.FieldFilter;
import com.simsilica.es.server.EntityDataHostedService;

import java.io.IOException;

public class SingleTestCase extends SimpleApplication {

    private static final int PORT = 5566;
    private DefaultEntityData entityData;
    private Server server;

    public static void main(String[] args) {
        new SingleTestCase().start(JmeContext.Type.Headless);
    }

    @Override
    public void simpleInitApp() {
        // setup Entity Data
         this.entityData = new DefaultEntityData();

        try {
            // setup server
            Server server = Network.createServer(PORT);

            Serializer.registerClass(Comp1.class);
            Serializer.registerClass(Comp2.class);

            server.getServices().addService(new EntityDataHostedService(MessageConnection.CHANNEL_DEFAULT_RELIABLE, entityData));

            server.start();

            this.server = server;
        } catch (IOException e) {
            e.printStackTrace();
        }

        // setup client
        try {
            Client client = Network.connectToServer("localhost", PORT);
            client.getServices().addService(new EntityDataClientService(MessageConnection.CHANNEL_DEFAULT_RELIABLE));

            client.addClientStateListener(new ClientStateListener() {
                @Override
                public void clientConnected(Client client) {
                    // get client entity data
                    EntityData clientEntityData = client.getServices().getService(EntityDataClientService.class).getEntityData();

                    // the following app state will create an entity in initialize()
                    stateManager.attach(new EntityCreator());

                    // create two app states to illustrate the issue
                    // the first one filters for x=0 whereas the second one does the same for x=1 (regarding Comp1)
                    stateManager.attach(new AS1(clientEntityData));
                    stateManager.attach(new AS2(clientEntityData));
                }

                @Override
                public void clientDisconnected(Client client, DisconnectInfo disconnectInfo) {}
            });

            client.start();

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private float timer = 0;
    @Override
    public void simpleUpdate(float tpf) {
        if ((timer += tpf) >= 0.1f) { // 10 updates per second
            server.getServices().getService(EntityDataHostedService.class).sendUpdates();
            timer = 0;
        }
    }

    private class EntityCreator extends AbstractAppState {
        @Override
        public void initialize(AppStateManager stateManager, Application app) {
            // note that the order which the components are added matters
            // the bug only shows when Comp2 is added first
            entityData.setComponents(entityData.createEntity(), new Comp2(), new Comp1(0));
        }
    }


    // app state which filters for x = 0
    private class AS1 extends AbstractAppState {

        private EntityData entityData;
        private EntitySet entities;

        public AS1(EntityData entityData) {
            this.entityData = entityData;
        }

        @Override
        public void initialize(AppStateManager stateManager, Application app) {
            this.entities = entityData.getEntities(new FieldFilter<>(Comp1.class, "x", 0), Comp1.class, Comp2.class);
        }

        @Override
        public void update(float tpf) {
            if (entities.applyChanges()) {
                for (Entity entity : entities.getAddedEntities()) {
                    System.out.println("AS1: x = " + entity.get(Comp1.class).getX());
                }
            }
        }
    }


    // app state which filters for x=1
    private class AS2 extends AbstractAppState {

        private EntityData entityData;
        private EntitySet entities;

        public AS2(EntityData entityData) {
            this.entityData = entityData;
        }

        @Override
        public void initialize(AppStateManager stateManager, Application app) {
            this.entities = entityData.getEntities(new FieldFilter<>(Comp1.class, "x", 1), Comp1.class, Comp2.class);
        }

        @Override
        public void update(float tpf) {
            if (entities.applyChanges()) {
                for (Entity entity : entities.getAddedEntities()) {
                    System.out.println("AS2: x = " + entity.get(Comp1.class).getX());
                }
            }
        }
    }

    @Override
    public void destroy() {
        this.server.close();
        super.destroy();
    }
}

The following two component classes are needed for this test-case as well:

import com.jme3.network.serializing.Serializable;
import com.simsilica.es.EntityComponent;

@Serializable
public class Comp1 implements EntityComponent {

    private int x;

    public Comp1() {}

    public Comp1(int x) {this.x = x;}

    public int getX() {return this.x;}
}

Here is the seond one

import com.jme3.network.serializing.Serializable;
import com.simsilica.es.EntityComponent;

@Serializable
public class Comp2 implements EntityComponent {

    public Comp2() {}

}

Alright, I hope I could help you with that!

Best regards
Domenic

So, for when I get to look at this… I assume I just run it and it will break? What does it look like when it breaks?

The app won’t crash! It just prints some lines onto the console. The thing is that AS2 which filters for the x value “1” prints out an entity that has the x value “0”. The x value is contained in the Comp1 class. However, this entity should not even be in the entity set at all since it doesn’t match the filter rules!

Do you get what I mean?

Maybe add an assertion which breaks it :slight_smile:

It’s fine as is. I can work with this.