To entity or not to entity?

Here is how I am doing physic in server side :

Pretty mush similar to SimplePhysic system in sim-eth-es example.
Here I am having one PhysicSpace, you can have multiple physic spaces. (They can be run multi-threaded)

/**
 * Bullet physics system.
 *
 * @author Ali-RS <ali_codmw@yahoo.com>
 */
public class BulletPhysicsState extends AbstractGameSystem implements PhysicsSystem<PhysicsControlDriver> {

    static Logger log = LoggerFactory.getLogger(BulletPhysicsState.class);
    
    private PhysicsSpace physicSpace;

    private PhysicsDriverFactory driverFactory;
    
    private EntityData ed;
    private BulletPhysicsState.MobPhysicsBodyContainer mobBodies;
    private BulletPhysicsState.StaticPhysicsBodyContainer staticBodies;

    // Single threaded.... we'll have to take care when adding/removing
    // items.
    //private SafeArrayList<Body> mobBodies = new SafeArrayList<>(Body.class);
    private Map<EntityId, Body> index = new ConcurrentHashMap<>();
    private Map<EntityId, PhysicsControlDriver> driverIndex = new ConcurrentHashMap<>();

    // Still need these to manage physics listener notifications in a 
    // thread-consistent way   
    private ConcurrentLinkedQueue<Body> toAdd = new ConcurrentLinkedQueue<>();
    private ConcurrentLinkedQueue<Body> toRemove = new ConcurrentLinkedQueue<>();

    private SafeArrayList<PhysicsListener> listeners = new SafeArrayList<>(PhysicsListener.class);

    public BulletPhysicsState() {
    }

    protected void initialize() {
        log.info("initialize()");
        //The core of Bullet physic simulation.
        physicSpace = new PhysicsSpace();
        
        //Get PhysicsDriverFactory in case there is one registered
        //so it will auto create physic driver when new entity comes.
        driverFactory = getSystem(PhysicsDriverFactory.class);
        if(driverFactory != null){
            log.info("Found PhysicsDriverFactory.");
        }

        this.ed = getSystem(EntityData.class);
        if (ed == null) {
            throw new RuntimeException("Physics system requires an EntityData object.");
        }
        
    }

    protected void terminate() {
        physicSpace.destroy();
    }

    /**
     * Adds a listener that will be notified about physics related updates. This
     * is not a thread safe method call so must be called during setup or from
     * the physics/simulation thread.
     */
    @Override
    public void addPhysicsListener(PhysicsListener l) {
        listeners.add(l);
    }

    @Override
    public void removePhysicsListener(PhysicsListener l) {
        listeners.remove(l);
    }

    @Override
    public Body getBody(EntityId entityId) {
        return index.get(entityId);
    }

    @Override
    public void setControlDriver(EntityId entityId, PhysicsControlDriver driver) {
        synchronized (this) {
            driverIndex.put(entityId, driver);
            Body current = getBody(entityId);
            if (current != null) {
                current.driver = driver;
            }
        }
    }

    @Override
    public PhysicsControlDriver getControlDriver(EntityId entityId) {
        return driverIndex.get(entityId);
    }

    private void fireBodyListListeners() {
        if (!toAdd.isEmpty()) {
            Body body = null;
            while ((body = toAdd.poll()) != null) {
                //bodies.add(body);
                for (PhysicsListener l : listeners.getArray()) {
                    l.addBody(body);
                }
            }
        }
        if (!toRemove.isEmpty()) {
            Body body = null;
            while ((body = toRemove.poll()) != null) {
                //bodies.remove(body);
                for (PhysicsListener l : listeners.getArray()) {
                    l.removeBody(body);
                }
            }
        }
    }

    @Override
    public void start() {
        physicSpace.setGravity(new Vector3f(0, -5f, 0));
        mobBodies = new BulletPhysicsState.MobPhysicsBodyContainer(ed);
        mobBodies.start();

        staticBodies = new BulletPhysicsState.StaticPhysicsBodyContainer(ed);
        staticBodies.start();
    }

    @Override
    public void stop() {
        mobBodies.stop();
        mobBodies = null;
        staticBodies.stop();
        staticBodies = null;
    }

    @Override
    public void update(SimTime time) {
        physicSpace.distributeEvents();
        physicSpace.update((float) time.getTpf());

        for (PhysicsListener l : listeners.getArray()) {
            l.beginFrame(time);
        }

        // Update the entity list       
        mobBodies.update();
        staticBodies.update();

        // Fire off any pending add/remove events 
        fireBodyListListeners();

        double tpf = time.getTpf();

        // Apply control driver changes
        for (Body b : mobBodies.getArray()) {
            if (b.driver != null) {
                b.driver.update(tpf, b);
            }
        }

        // Integrate
        for( Body b : mobBodies.getArray() ) {
            b.integrate(tpf);
        }
        // Publish the results
        for (PhysicsListener l : listeners.getArray()) {
            for (Body b : mobBodies.getArray()) {
                l.updateBody(b);
            }
        }

        for (PhysicsListener l : listeners.getArray()) {
            l.endFrame(time);
        }
    }

    protected Body createMobBody(EntityId entityId, double invMass, double radius, boolean create) {
        Body result = index.get(entityId);
        if (result == null && create) {
            synchronized (this) {
                result = index.get(entityId);
                if (result != null) {
                    return result;
                }
                result = new Body(entityId);
                result.radius = radius;
                result.invMass = invMass;

                // Hookup the driver if it has one waiting
                PhysicsControlDriver driver = driverIndex.get(entityId);
                //else ask driver factory(if there is one already registered) to create it
                if(driver == null && driverFactory != null){
                    driver = driverFactory.createDriver(ed, entityId);
                    setControlDriver(entityId, driver);
                }
                result.driver = driver;

                // Set it up to be managed by physics
                toAdd.add(result);
                index.put(entityId, result);
            }
        }
        return result;
    }

    protected boolean removeMobBody(EntityId entityId) {
        Body result = index.remove(entityId);
        if (result != null) {
            toRemove.add(result);
            PhysicsControlDriver driver = ((PhysicsControlDriver) result.driver);
            driver.removeFromPhysicSpace(physicSpace);
            driverIndex.remove(entityId, driver);
            return true;
        }
        return false;
    }

    protected Body createStaticBody(EntityId entityId, double invMass, boolean create) {
        Body result = index.get(entityId);
        if (result == null && create) {
            synchronized (this) {
                result = index.get(entityId);
                if (result != null) {
                    return result;
                }
                result = new Body(entityId);
                result.invMass = invMass;

                // Hookup the driver if it has one waiting
                PhysicsControlDriver driver = driverIndex.get(entityId);
                //else ask driver factory(if there is one already registered) to create it
                if(driver == null && driverFactory != null){
                    driver = driverFactory.createDriver(ed, entityId);
                    setControlDriver(entityId, driver);
                }
                result.driver = driver;
                
                index.put(entityId, result);
            }
        }
        return result;
    }

    protected boolean removeStaticBody(EntityId entityId) {
        Body result = index.remove(entityId);
        if (result != null) {
            PhysicsControlDriver driver = ((PhysicsControlDriver) result.driver);
            driver.removeFromPhysicSpace(physicSpace);
            driverIndex.remove(entityId, driver);
            return true;
        }
        return false;
    }

    /**
     * Maps the appropriate entities to physics mob bodies.
     */
    private class MobPhysicsBodyContainer extends EntityContainer<Body> {

        public MobPhysicsBodyContainer(EntityData ed) {
            super(ed, OrFilter.create(PhysicsType.class,
                    Filters.fieldEquals(PhysicsType.class, "type", PhysicsType.DYNAMIC),
                    Filters.fieldEquals(PhysicsType.class, "type", PhysicsType.KINEMATIC)),
                    SphereShape.class,
                    PhysicsDriverInfo.class,
                    Position.class, 
                    MassProperties.class, 
                    PhysicsType.class
            );
        }

        @Override
        protected Body[] getArray() {
            return super.getArray();
        }

        @Override
        protected Body addObject(Entity e) {
            log.info("Add Mob physic body");
            MassProperties mass = e.get(MassProperties.class);
            SphereShape shape = e.get(SphereShape.class);
      
            // Right now only works for CoG-centered shapes                   
            Body newBody = createMobBody(e.getId(), mass.getInverseMass(), shape.getRadius(), true);

            Position pos = e.get(Position.class);
            newBody.setPosition(pos.getLocation());
            newBody.setRotation(pos.getFacing());
            if(newBody.driver != null){
                PhysicsControlDriver driver = ((PhysicsControlDriver) newBody.driver);
                driver.updatePhysicalProperties(ed, e.getId());
                driver.addToPhysicSpace(physicSpace);
            }
            
            return newBody;
        }

        @Override
        protected void updateObject(Body object, Entity e) {
            // We don't support live-updating mass or shape right now
        }

        @Override
        protected void removeObject(Body object, Entity e) {
            removeMobBody(e.getId());
        }

    }

    /**
     * Maps the appropriate entities to physics static bodies.
     */
    private class StaticPhysicsBodyContainer extends EntityContainer<Body> {

        public StaticPhysicsBodyContainer(EntityData ed) {

            super(ed, 
                    Filters.fieldEquals(PhysicsType.class, "type", PhysicsType.STATIC),
                    PhysicsDriverInfo.class,
                    Position.class, 
                    MassProperties.class, 
                    PhysicsType.class                  
            );

        }

        @Override
        protected Body[] getArray() {
            return super.getArray();
        }

        @Override
        protected Body addObject(Entity e) {
            log.info("Add static physic body");
            MassProperties mass = e.get(MassProperties.class);
            //SphereShape shape = e.get(SphereShape.class);

            // Right now only works for CoG-centered shapes                   
            Body newBody = createStaticBody(e.getId(), mass.getInverseMass(), true);

            Position pos = e.get(Position.class);
            newBody.setPosition(pos.getLocation());
            newBody.setRotation(pos.getFacing());

            if(newBody.driver != null){
                PhysicsControlDriver driver = ((PhysicsControlDriver) newBody.driver);
                driver.updatePhysicalProperties(ed, e.getId());
                driver.addToPhysicSpace(physicSpace);
            }
         
            return newBody;
        }

        @Override
        protected void updateObject(Body object, Entity e) {
            // We do support live-updating mass or shape .
            ((PhysicsControlDriver) object.driver).updatePhysicalProperties(ed, e.getId());
        }

        @Override
        protected void removeObject(Body object, Entity e) {
            removeStaticBody(e.getId());
        }

    }
}

I added a PhysicsControlDriver over ControlDriver in sim-eth-es to support physic controls.

package com.overthemoon.core.physics.bullet;

import com.jme3.bullet.PhysicsSpace;
import com.overthemoon.core.api.physics.ControlDriver;
import com.simsilica.es.EntityData;
import com.simsilica.es.EntityId;

/**
 *
 * @author Ali-RS <ali_codmw@yahoo.com>
 */
public interface PhysicsControlDriver extends ControlDriver {
    /**
     * Add your physic objects to physicSpace provided by this method.
     * @param physicsSpace 
     */
    public void addToPhysicSpace(PhysicsSpace physicsSpace);
    
    /**
     * Remove physic objects from physicSpace.
     * @param physicsSpace 
     */
    public void removeFromPhysicSpace(PhysicsSpace physicsSpace);
    
    /**
     * Update physics state with your physic components.
     * Note, this method is supposed to work with EntitySystem 
     * structure using Zay-ES.
     * @param entity 
     */
    public void updatePhysicalProperties (EntityData ed, EntityId entityId);
}

and as an example here is BetterCharacterDriver

/**
 * Basic driver for controlling characters
 *
 * @author Ali-RS <ali_codmw@yahoo.com>
 */
public class BetterCharacterDriver implements PhysicsControlDriver {

    private PhysicsCharacterControl control; // extends BetterCharacterControl

    private volatile Vector3f walkDirection = new Vector3f();
    private volatile Vector3f viewDirection = new Vector3f();
    private Quaternion tempRot = new Quaternion();
    private Vector3f tempPos = new Vector3f();

    public BetterCharacterDriver(PhysicsCharacterControl control) {
        this.control = control;

    }

    @Override
    public void addToPhysicSpace(PhysicsSpace physicsSpace) {
        if (control != null && physicsSpace != null) {
            control.setPhysicsSpace(physicsSpace);
            control.setEnabled(true);
        } else {
            throw new NullPointerException(" BetterCharacterControl not instantiated !");
        }
    }

    @Override
    public void removeFromPhysicSpace(PhysicsSpace physicsSpace) {
        if (control != null && physicsSpace != null) {
            physicsSpace.remove(control);
            control.setEnabled(false);
        }
    }

    @Override
    public void update(double tpf, Body body) {
        // Grab local versions of the player settings in case another
        // thread sets them while we are calculating.
        Vector3f walkDir = walkDirection;
        Vector3f viewDir = viewDirection;

        control.setViewDirection(viewDir);

        control.setWalkDirection(walkDir);

        // Setup the current world rotation of the body 
        tempRot.lookAt(control.getViewDirection(), Vector3f.UNIT_Y);
        body.orientation.set(tempRot.getX(), tempRot.getY(), tempRot.getZ(), tempRot.getW());

        // Apply the accelerated velocity oriented into world space       
        tempPos = control.getPhysicsRigidBody().getPhysicsLocation();
        body.pos.set(tempPos.x, tempPos.y, tempPos.z);

        Vector3f tempVel = control.getVelocity();
        body.velocity.set(tempVel.x, tempVel.y, tempVel.z);

    }

    @Override
    public void updatePhysicalProperties(EntityData ed, EntityId entityId) {
        Position pos = ed.getComponent(entityId, Position.class);
        control.warp(pos.getLocation().toVector3f());
        control.getPhysicsRigidBody().setDamping(0.2f, 0.2f);
        control.getPhysicsRigidBody().setLinearSleepingThreshold(0.1f);
    }

    public void applyMovementState(Vector3f walkDirection, Vector3f viewDirection) {
        this.walkDirection = walkDirection;
        this.viewDirection = viewDirection;
    }

    public PhysicsCharacterControl getControl() {
        return control;
    }

}

I share the driver with SteerSystem to apply steer force.

One problem I have is I do not know how should i integrate collision shapes which are controlled by animation bones, to server side.