Zay-ES client/server entity inspection question


#1

I am switching to the Zay-ES library for entity management for my networked game. The server will contain and maintain the EntityData. I use Ethereal for client/server communication. When a message comes from Ethereal for an entity ID that the client does not recognize a message is sent to the server that returns the Entity and ONLY the location, rotation, and model components.

All is well in the world … ships can fly around etc … now, When you click on a ship you should be able to bring up it’s stats. Since the client side ship entity only contains visual specific components, I am thinking of creating a message that gets sent to the server that will ask for a set of components for that entity so that I can display them. Is this the best pattern for requesting extra information for an entity? Is there something in Zay-ES that can provide this functionality?

Thoughts?


#2

Zay-ES includes its own networking support that can be easily plugged right into SpiderMonkey. In that case, the client will get its own read-only view of the EntityData, can watch entities, entity sets, etc…

There is no need to create custom messages for this. If you want to access the components of a particular entity then you can either ask the client-side (remote) EntityData directly for the components or you can ask for a WatchedEntity or EntitySet. Zay-es-net will take care of sending you updates while you have these entities watched and not released.


#3

Any jump start tutorial on Zay-es-net available?


#4

Best way is probably to look at the sim-eth-es example:

It’s pretty straight forward, though. Register the host service on the server. Register the client service on the client. Grab the EntityData from the client. Use it just like you would any other EntityData except that it’s read-only.


#5

See here on the server:

And here on the client:

You can even see on line 96 where I grab the remote EntityData.


#6

Nice! Two final questions.

When I get an EntitySet on the client for specific components only those components get updated from the server correct?

Are the client side entities always read only?


#7

Yes.

Yes.

The client doesn’t have any game logic that should be able to directly set stuff on entities anyway… it would be a huge security issue anyway.


#8

True … so using the library will keep me honest in terms of a server authoritative design :wink:


#9

@pspeed ok net version looks easy. Quick question. My use case is the following:

Client requests an EntitySet of all entities with position and model components. I don’t want position component data update to be sent over the wire since I am using Ethereal for that but I DO want the position component itself sent to the client. All I need to do is ensure that the position component implements TransientComponent correct?


#10

Transient components are not sent at all… ever. (if it even works then you end up with null for those components… I don’t remember if it works as I never ended up using it.)

If you look at the sim-eth examples you can see I use a separate network-synched BodyPosition for the stuff that moves. Physics objects can still occasionally update the regular Position if they want to but it might just be good to do when the stop being management by the physics engine (if). Or like in some of my other stuff, whenever they go to ‘sleep’.

The client asks for BodyPosition which is not transient but its fields are transient. A SimEthereal listener on the client then back-fills the BodyPosition’s transition buffer so that the client can properly interpolate position. It’s a little bit of slight-of-hand style trickery but it works and keeps things clean.


#11

As soon as I read that, I knew I’d run into problems with my implementation. Without going thru screeds of example code and documentation, I’m picking I’ll have to instantiate a DefaultEntityData object in the Client app and copy entities from the read-only ED to a local ED instance.

Is this what I’m supposed to do?

What I am wanting to do is set a Model component to an incoming entity to move it around and rotate it etc … once it has been instantiated on the client. The ModelCt class only keeps a pointer to the model instance.

public class ModelCt implements EntityComponent {

    private Spatial spatial;

    // NOT SERIALIZABLE SO NO NEED FOR PROTECTED EMPTY CONSTRUCTOR.
    public ModelCt(Spatial s){
        this.spatial = s;
    }

    public Spatial getSpatial(){
        return spatial;
    }
}

I am setting the ModelCt like this:
Note: The SquareMarble class just creates a typical jME3 blue-box and returns it when asked for. Nothing special there.

private void addSpatialToRootNode(Entity e) {
    PositionCt p = e.get(PositionCt.class);
    SquareMarble marble = new SquareMarble(this);
    Geometry marbleGeom = marble.getMarble();
    // ADD GEOMETRY OBJECT TO ENTITY DATA.
    ModelCt model = new ModelCt(marbleGeom);
    
    // Exception thrown here:  Not permitted to write to Read-only EntityData object ...   
    // : (
    e.set(model);

    rootNode.attachChild(marbleGeom);
    //SET POSITION:
    Vector3f v3f = new Vector3f(p.getLocation());
    marbleGeom.move(v3f);
    //SET ROTATION:
    Quaternion quat = new Quaternion(p.getFacing());
    marbleGeom.rotate(quat);
}

private void moveSpatial(Entity e) {
    // GET POSITION INSTANCE
    PositionCt p = e.get(PositionCt.class);
    // GET SPATIAL INSTANCE
    ModelCt m = e.get(ModelCt.class);
    Geometry marbleGeom = (Geometry)m.getSpatial();  // getSpatial() returns a Spatial.  But we need a Geometry instance.                                                                                                    
    // MOVE IT
    //SET POSITION:
    Vector3f v3f = new Vector3f(p.getLocation());
    marbleGeom.move(v3f);
    //SET ROTATION:
    Quaternion quat = new Quaternion(p.getFacing());
    marbleGeom.rotate(quat);
    
}

private void removeSpatialFromRootNode(Entity e) {
    // GET SPATIAL INSTANCE:
    ModelCt m = e.get(ModelCt.class);
    Geometry g = (Geometry)m.getSpatial();  // getSpatial() returns a Spatial.  But we need a Geometry instance.                                                                                                    
    // REMOVE IT.
    rootNode.detachChild(g);
}

As an aside: It is evident that the Client will create Command Components from user input to be sent back to the server. I’m also picking these command components will be created in a Client-side DefaultEntityData object to be later whisked off to the server over the wire and received on a Read-Only Entity Data object on the server. But that’s just a guess.


#12

No. The client doesn’t need to set components directly because setting components directly is game logic that should be run on the server.

Your ModelCt is already wrong because it has a Spatial in it. That’s immediately not an ES anymore and you are just using components as a general bag for visualization stuff. It’s exactly like storing a Swing JTextField in the database.

You can see how to associate spatials with an entity in every example I’ve ever written and posted. So you can just pick one.

No. If you want to call game logic then you call game logic. Don’t sneak events into the entity system this way. A) Zay-ES-Net doesn’t do that so you’d have to write your own anyway, and B) it’s useless indirection when you can just make an RMI call directly or define your own spider monkey message or whatever. You can see exactly how to do this in how the player thrust control information is sent to the server in the sim-eth-es example.

When you let the client set entity components directly, you are basically letting hackers do whatever they want.

Here is the sim-eth-es example again since I’m not sure you’ve seen it:


#13

Search for Spatial in the SimEtherial Example project led to the following files:

HudLabelState.java
Main.java
ModelViewState.java
PlayerMovementState.java
SiliconDioxideState.java
SkyState.java

##Tips in ModelViewState.java (code snippets) :

1. 	private Map<EntityId, Spatial> modelIndex = new HashMap<>();

2. 	public Spatial getModel( EntityId id ) {
		return modelIndex.get(id);
	}

3.      @Override
	protected void onEnable() {
		
		mobs = new MobContainer(ed);
		models = new ModelContainer(ed);
		mobs.start(); 
		models.start();
	
		((Main)getApplication()).getRootNode().attachChild(modelRoot);
	}



4.      @Override
	protected void onDisable() {
		modelRoot.removeFromParent();

		models.stop();
		mobs.stop();        
		mobs = null;
		models = null;        
	}
	
5.       @Override
	public void update( float tpf ) {
 
		// Grab a consistent time for this frame
		long time = timeState.getTime();

		// Update all of the models
		models.update();
		mobs.update();
		for( Mob mob : mobs.getArray() ) {
			mob.updateSpatial(time);
		} 
	}

6.  protected Spatial createShip( Entity entity ) {
	
		AssetManager assetManager = getApplication().getAssetManager();
		
		Spatial ship = assetManager.loadModel("Models/fighter.j3o");
		
		// THIS ONE IS NEW TO ME.  Spatial.center() -> Centers the spatial in the origin of the world bound.
		ship.center();
		
		Texture texture = assetManager.loadTexture("Textures/ship1.png");
		//Material mat = GuiGlobals.getInstance().createMaterial(texture, false).getMaterial();
		Material mat = new Material(getApplication().getAssetManager(), "MatDefs/FogUnshaded.j3md");
		mat.setTexture("ColorMap", texture);
		mat.setColor("FogColor", new ColorRGBA(0, 0, 0.1f, 1));        
		mat.setFloat("FogDepth", 64);        
		ship.setMaterial(mat);
 
		
		// HERE THE NODE NAME INCLUDES THE ENTITY ID NUMBER.
		Node result = new Node("ship:" + entity.getId());
		result.attachChild(ship);        
 
		result.setUserData("entityId", entity.getId().getId());
		
		return result;
	}

7.  protected Spatial createModel( Entity entity ) {
		// Check to see if one already exists
		Spatial result = modelIndex.get(entity.getId());
		if( result != null ) {
			return result;
		}
		
		// Else figure out what type to create... 
		ObjectType type = entity.get(ObjectType.class);
		String typeName = type.getTypeName(ed);
		switch( typeName ) {
			case ObjectTypes.SHIP:
				result = createShip(entity);
				break;
			case ObjectTypes.GRAV_SPHERE:
				result = createGravSphere(entity);
				break;
			default:
				throw new RuntimeException("Unknown spatial type:" + typeName); 
		}
		
		// Add it to the index
		modelIndex.put(entity.getId(), result);
 
		modelRoot.attachChild(result);       
		
		return result;        
	}

8.  protected void updateModel( Spatial spatial, Entity entity, boolean updatePosition ) {
		if( updatePosition ) {
			Position pos = entity.get(Position.class);
			
			// I like to move it... move it...
			spatial.setLocalTranslation(pos.getLocation().toVector3f());
			spatial.setLocalRotation(pos.getFacing().toQuaternion());
		}
	}

9.  protected void removeModel( Spatial spatial, Entity entity ) { 
		modelIndex.remove(entity.getId());
		spatial.removeFromParent();
	}

See also ModelViewState.Mob class. This does model position interpolation between packets received from the network.

Question: What does Mob stand for?

See also ModelViewState.MobContainer class. It is an EntityContainer of type Mob.
Seems like it keeps a collection of ObjectType, Positions and entities of the above
mentioned Mob class. This is probably to do object position interpolations.

See also inner class ModelContainer, which extends EntityContainer
Seems to be another collection of spatials, positions and entities for position interpolation.
Takes precidence over MobContainer.

##Tips from PlayerMovementState.java (code snippets):

  1. A client network session is setup from this line:
    This is probably a SpiderMonkey messageTransmission channel to the server.

    // Grab the game session
    session = getState(ConnectionState.class).getService(GameSessionClientService.class);
    .

  2. Player commands are issued to the server in the update() method with this line:

    session.move(rot, thrust);
    

.
3. PlayerMovementState extends BaseAppState implements AnalogFunctionListener, StateFunctionListener
There are obviously method calls in the code that refer to these. getState() is one.

Conclusions … or more correctly …

Gleenings from code studied:

  1. EntityData is a one-way trip over the wire. From server to client only.

  2. Spatials to EntityId hashmaps are set up on the client.

  3. Client commands are sent directly to the server via a

    MessageListener
    when appropriately setup for this purpose.


#14

MOB… common video game name for “mobile object”… usually a monster. I use it for physics controlled objects so it’s sort of just short for ‘mobile’ in this case. It’s for the non-static objects.

No. MessageListener is how you listen for messages from the server on the client.

If you want to easily send method calls to the server then you set up an interface of some kind, like Foo.java… then implement that on the server like FooImpl.java and ‘share’ it with the client through the RMI service. The client can then retrieve the Foo interface from the RMI service on the client and call methods on it.

session.move() is such a method on the GameSession interface. Which in my case for convenience, the GameSessionClientService also implements and then forwards to the RMI remote version of the GameSession interface that it retrieves internally. It could also have just returned that object and let the game use it instead.

Yes, it’s a convenient way to manage the mapping. The EntityContainer base class will pretty much do this for you and is a nice utility class to avoid some common boiler plate. Otherwise you can look at that Asteroid Panic game to see how it did it manually. Probably the key entry point here:

A networked game is more complicated because the position updates are coming over the wire so it’s better to use an efficient protocol for that. If it’s ok for your position updates to occasionally stall for a few seconds then you could bypass SimEthereal for that and just use regular component updates.

A remote EntityData is still going to be read-only, though. The client should never be running enough game logic to know what to set components to and if it did then you might as well add an “I Win” button to your game and make it a game to see who clicks it first.


#15

Also for better understanding of client to server contact, you may look some Java RMI tutorials on youtube.

and for better understanding how position interpolation is done you can take a look at Valve article

https://developer.valvesoftware.com/wiki/Source_Multiplayer_Networking


#16

Cool to see how RMI works. I’d read some book chapters about it, but it’s the first time I’ve taken it onboard. And I must admit … I would have never imagined to use it for the client to communicate with the game server. Definitely a great idea. Thanks for the tip.

Question: Having searched for NetBeans + RMI on youtube, I saw that everyone used port 1099 for their RMI coms. I guess you can use other ports if you set them on both the server and client, right? Or are we stuck with 1099?


#17

The RMI included in Spider-Monkey’s available services is a lightweight RMI that is not the same as Java’s RMI. It works similarly but with some limitations… (and some side benefits)… but communicates over one of your already established SpiderMonkey channels.