Client/Server questions for Zay-ES

Hi @pspeed !

I have some questions how to setup your cool ES into a server and use it on a client side. As Zay-ES should be read-only on the client side.

Questions:

  • I create EntityData on the server, then how to get the EntityData properly on the client side to manipulate entities in read-only mode? (The most important question)

  • Where should be used EntitySet class? On the client or on the server?

  • Should a server use update() loop like in SimpleApplication to do some checks for the ES? Is your server(in Mythruna) extended with Application class (as MonkeyZone-Server is extended)?

This is my first attempt to make a server/client things, so i’ll have many questions. :slight_smile:

Thanks.

It’s kind of complicated to do right… but here is a high level description of how it works…

On the client there is a RemoteEntityData class. This is responsible for turning the EntityData method calls into SpiderMonkey messages and sending them to the server. For any of the write methods, it throws an exception.

When a call requests an EntitySet, it is actually getting a RemoteEntitySet subclass that is asynchronously pulling over the data on a background thread. Code kind of needs to be a little aware of this, for example:

In single player:
EntitySet myEntities = ed.getEntities(…);
…myEntities will have data right away.

In multi-player:
EntitySet myEntities = ed.getEntities(…);
…call returns pretty much right away but the entity set may not have data.
if( myEntities.applyChanges() ) {
…asynchronously delivered data will be delivered here.
}

Normally that’s ok because code is written to detect adds anyway… but code expecting values right away will need to change its strategy.

On the server, for each client the messages are received and used to update some local book-keeping. When the client requests a new EntitySet, on the server an EntitySet is grabbed and kept as part of that user’s session data. I call this stuff HostedEntityData but it is not actually an EntityData implementation. It keeps track of the active EntitySets by set ID and is responsible for creating new IDs, and so on. Periodically, the server goes through all of the HostedConnections and has these HostedEntityData objects send any updates to the client, ie: they run through all of the entity sets, call applyChanges() and stream the results to the client in the background.

Note that I will eventually be adding this support to Zay-ES. I will need it for my Mythruna rewrite that I’m working on but I haven’t gotten to the actual networking yet in this version.

3 Likes
@pspeed said: It's kind of complicated to do right... but here is a high level description of how it works...

On the client there is a RemoteEntityData class. This is responsible for turning the EntityData method calls into SpiderMonkey messages and sending them to the server. For any of the write methods, it throws an exception.

Thanks a lot for explanation!
But i did not find RemoteEntityData class in JME-network and Zay-ES projects. Where i can find RemoteEntityData and should i initialize it?

Or i simply should use EntitySets for the client through messages?

Also, what FPS do you use for you server? Just interesting, how often should i update the server and the ES.

Thanks.

It doesn’t exist. You would have to create it. You asked me how you would write your own remote support so I told you.

@pspeed said: It doesn't exist. You would have to create it. You asked me how you would write your own remote support so I told you.

Aha, ok. I get you. Thanks.
Just one more question:
EntitySet class is not Serializable. How to make the EntitySet to be Serializable so that to transfer it through JME-networking?
Or this is bad design of data transfering?

You don’t transfer it. You can’t transfer it. You have a RemoteEntitySet on the client and regular EntitySet on the server… like I wrote originally.

@pspeed said: You don't transfer it. You can't transfer it. You have a RemoteEntitySet on the client and regular EntitySet on the server... like I wrote originally.

Thank you! Sorry for my misunderstanding. I get you, i’ll try to do like you said.
I’ll try creating RemoteEntityData, RemoteEntitySet.

Note that I will eventually be adding this support to Zay-ES. I will need it for my Mythruna rewrite that I'm working on but I haven't gotten to the actual networking yet in this version.

I look forward to this update. It’s a long way yet, but we’ll need it as well for our game :slight_smile:

I’ve already working on using Zay-ES for client/server.

Here how it’s done.

On client side :

[java]public class RemoteEntity
{

private EntityId id;
private List<EntityComponent> components;

public RemoteEntity()
{

}

public EntityId getId()
{
    return id;
}

public List<EntityComponent> getComponents()
{
    return components;
}

}[/java]

[java]public class RemoteEntityData extends DefaultEntityData
{

private static RemoteEntityData instance;

public RemoteEntityData()
{
    super();
}

public static RemoteEntityData getInstance()
{
    if(instance == null)
        instance = new RemoteEntityData();
    
    return instance;
}

public void loadRemoteEntity(RemoteEntity entity)
{
    for(EntityComponent _c : entity.getComponents())
        setComponent(entity.getId(), _c);
}

}
[/java]

And on server side :

[java]public class RemoteEntity
{

private EntityId id;
private List<EntityComponent> components = new ArrayList<EntityComponent>();

public RemoteEntity()
{

}

public RemoteEntity(EntityId id, EntityComponent... components)
{
    this.id = id;
    	
    for(EntityComponent _c : components)
        this.components.add(_c);
}

public RemoteEntity(EntityId id, Class... types)
{
    this.id = id;
    
    for(Class _class : types)
    {
        EntityComponent _c = MasterEntityData.getInstance().getComponent(id, _class);
        if(_c == null)
        {
    	LogHandler.getInstance().warning("Cannot get component type " + _class.getSimpleName());
    	
    	continue;
        }
        
        components.add(_c);
    }
}

public RemoteEntity(EntityId id, EntityComponent[] components, EntityComponent... toAdd)
{
    this.id = id;
    	
    for(EntityComponent _c : components)
        this.components.add(_c);
    
    for(EntityComponent _cAdd : toAdd)
        this.components.add(_cAdd);
}

public void setComponents(EntityComponent ... components)
{
	for(EntityComponent _c : components)
        this.components.add(_c);
}

public List<EntityComponent> getComponents()
{
	return components;
}

}[/java]

[java]public class MasterEntityData extends DefaultEntityData
{

private static MasterEntityData instance;

public MasterEntityData()
{
	this.setIdGenerator(DefaultIdGenerator.getInstance());
}

public static MasterEntityData getInstance()
{
	if(instance == null)
		instance = new MasterEntityData();

	return instance;
}

public RemoteEntity getRemoteEntity(EntityId entityId, Class<? extends EntityComponent>... types)
{
    EntityComponent[] values = new EntityComponent[types.length]; 
    for( int i = 0; i < values.length; i++ )
        values[i] = getComponent( entityId, types[i] );
    
    return new RemoteEntity(entityId, values);
}

}[/java]

[java]public class DefaultIdGenerator implements EntityIdGenerator
{

private long nextId;

private static DefaultIdGenerator instance;

public DefaultIdGenerator()
{

}

public static DefaultIdGenerator getInstance()
{
	if(instance == null)
		instance = new DefaultIdGenerator();
	
	return instance;
}

@Override
public long nextEntityId()
{
	return nextId++;
}

}[/java]

How it’s working
&nbsp
To get entities from server to client, you need to create a new RemoteEntity object with for parameters, the EntityId of the entity you want to send and its components you want to send.

Example :
[java]RemoteEntity _entity = new RemoteEntity(_entitty.getId(), NameComponent.class, PositionComponent.class);[/java]

You send this object over the network to the client and handle it like that :
[java]RemoteEntityData.getInstance().loadRemoteEntity(_entity);[/java]

I don’t know if it’s clear but if you have some questions, ask me.
And I can’t tell if it’s the best way to do this.

1 Like

@jonesadev you save my life! :slight_smile:

i get how client part works. But what server part does?
How to use server part? I should create EntityData and MasterEntityData on the server?

Also, why do you use singletons (instances)? What if i want to create many EntityData objects on the server and on the client? Is it ok if i remove singletons?

His implementation is simple (and probably effective) but it has several drawbacks:

  1. it means that your client has to behave differently for a remote ES versus a local ES.
  2. it does not take advantage of the architecture to do things asynchronously and automatically transfer changes back to the client.

There is a better way but you may have to wait.

@mifth said: Also, why do you use singletons (instances)? What if i want to create many EntityData objects on the server and on the client? Is it ok if i remove singletons?

Saw this part after… if you want to create many EntityData objects on the server then you have probably misunderstood something pretty significant.

@pspeed said: Saw this part after... if you want to create many EntityData objects on the server then you have probably misunderstood something pretty significant.

I suppose to use EntityData per Scene. I suppose to use some small mini-games in a big game(scene). It could be useful in some cases.

EDITED:
Or I can use EntitySet to separete some parts of a scene?

@mifth said: I suppose to use EntityData per Scene. I suppose to use some small mini-games in a big game(scene). It could be useful in some cases.

EDITED:
Or I can use EntitySet to separete some parts of a scene?

Yeah… this is all a sign that you still might not understand. How many file systems do you have on your computer? (hint: just one) Think of it the same way.

@pspeed said: Yeah... this is all a sign that you still might not understand. How many file systems do you have on your computer? (hint: just one) Think of it the same way.

Thank you for the right way! :slight_smile:

@pspeed can you help how to get all Components?
I have EndtityID and EntityData. But i cannot find how to get all Components of the EntityID.

Thanks.

You shouldn’t need this.

@pspeed said: You shouldn't need this.

Aha, then my way should be like this?

  • I create EntitySet with ALL available components in my game (Render, Physics, Transform, Velocyty, Player, NPC, Station, Health, etc).

  • Then I get an EntitySet.getEntity(EntityID) object with ALL these components. Some components will be NULL as some entities will not have them.

  • Then I transfer EntityID and Entity.getComponents() from server to the client using a message.

Is this right way?

@mifth said: Aha, then my way should be like this? - I create EntitySet with ALL available components in my game (Render, Physics, Transform, Velocyty, Player, NPC, Station, Health, etc).
  • Then I get an EntitySet.getEntity(EntityID) object with ALL these components. Some components will be NULL as some entities will not have them.

  • Then I transfer EntityID and Entity.getComponents() from server to the client using a message.

Is this right way?

No, absolutely not. But I’m running out of ways to be clearer so I will break it down into painful detail.

Client code calls Entity.getEntities( Foo.class, Bar.class );
RemoteEntityData makes a SpiderMonkey message that says, “Hey, I want a new entity set for Foo.class and Bar.class entities”
Server receives this message for that client.
Server creates a new EntitySet by calling getEntities(Foo.class, Bar.class) on the server-side EntityData.
Server assigns this an ID for that client.
Server sends back the entity set ID to the client.
Client gets the entity set ID and returns a RemoteEntitySet to the client code.
Client code initially sees no entities because they are streaming over in the background.
Server is streaming the matching entities (of just the Foo.class and Bar.class components) to the client.
Client sees these as adds when it calls myEntitySet.applyChanges()

If the client wants a different entity set, it does this same thing and gets a completely different RemoteEntitySet instance.

1 Like
@pspeed said: No, absolutely not. But I'm running out of ways to be clearer so I will break it down into painful detail.

Client code calls Entity.getEntities( Foo.class, Bar.class );
RemoteEntityData makes a SpiderMonkey message that says, “Hey, I want a new entity set for Foo.class and Bar.class entities”
Server receives this message for that client.
Server creates a new EntitySet by calling getEntities(Foo.class, Bar.class) on the server-side EntityData.
Server assigns this an ID for that client.
Server sends back the entity set ID to the client.
Client gets the entity set ID and returns a RemoteEntitySet to the client code.
Client code initially sees no entities because they are streaming over in the background.
Server is streaming the matching entities (of just the Foo.class and Bar.class components) to the client.
Client sees these as adds when it calls myEntitySet.applyChanges()

If the client wants a different entity set, it does this same thing and gets a completely different RemoteEntitySet instance.

Thanks a lot! I get it more clear.

But i have some issues with getting components:
For example, I have 2 Entities:
Ent1(Transform, Velocity, Health, CoolItem1, CoolItem2)
Ent2(Transform, Velocity, Health, CoolItem3)

If i make EntityData,getEntities(Transform, Velocity, Health) then i get all these entities, but how can i know if these entities have CoolItemX components? I should make checkes for all such components like EntityData.getComponent(EntityId, CoolItemX)?