junkdog / artemis-odb

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

Identifying Entities Across Worlds (Networking) #267

Closed Wulf closed 9 years ago

Wulf commented 9 years ago

Background

Creating multiplayer games is tough with artemis-odb. It's hard to synchronize two systems. I've read an issue #103 of @DaanVanYperen who discussed creating a co-op multiplayer game example, but wasn't able to find said example.

In that thread, @junkdog mentioned 'slave-worlds' and 'nested-worlds' but I couldn't find any documentation with those keywords and didn't really know what he meant.

Global Entity Identification

I hope to identify entities globally (across worlds) using the following simple structure:

Server -> "master world" assigns entityIDs Client -> "slave world" uses IdEntityManager (similar to UuidEntityManager) This way, entities are identified using the master world's entityID assignment.

Can anyone foresee any complications in this setup? Would you agree that this is simpler than having a custom EntityManager & World?

DaanVanYperen commented 9 years ago

Just waiting for pixies to deliver me a wand of more-hours-in-a-day! https://github.com/DaanVanYperen/artemis-odb-contrib/issues/5

Solve your problem by adding custom systems, components and managers; it will be a lot easier to understand and maintain in the long run. Editing World is a bit of a smell, and should only be a last resort. Keeps it nice and decoupled.

Network identities separate from local identities sounds like a smart way to go. I can't speak to your question directly as it depends a lot on your needs and constraints.

Namek commented 9 years ago

Just create and add your own manager to manage all that networky entities (bind local standard IDs with master IDs). It's all about binding sets of components with some common (common between server and clients) IDs that server generates. You don't want to remove standard EntityManager because you probably will have things that don't need synchronization, like particles.

The real problem about synchronization is to synchronize at all and it's always different. For FPS games (like Quake 3) or racing games you need move prediction for good extrapolation. For RTS (like Age of Empires or Starcraft) you need step-lock algorithm with step synchronization. Some boring turn-based games (like https://github.com/Namek/lets-code-game) need very simple synchronization based on send-and-wait-for-reply strategy, so that's not really hard task.

In the topic here -> DaanVanYperen/artemis-odb-contrib#5 i linked some parts of my work towards FPS-like games to discover (or actually port to Java) some algorithms about synchronization: https://github.com/Namek/quakemonkey https://github.com/Namek/jEPIC

It would be very cool to put it somehow into some configurable NetworkSystem.

Wulf commented 9 years ago

lol DaanVanYperen, can't disagree about the more hours in a day!

I stumbled upon your quakemonkey port earlier, Namek, but (as of now) using delta compression is overkill for my project.

Thanks for your responses, they have definitely helped.

Namek commented 9 years ago

And what are you after, @Haris1112? You might share your thoughts on the topic after you develop some methods around network synchronization in ECS.

Wulf commented 9 years ago

Namek, I'm not really sure what I'm after perhaps due to my limited experience - but I'll give it my best and release details for future reference.

In the case that I forget to do so, please don't hold it against me.

Wulf commented 8 years ago

Okay so this was what I went with, and it turned out just fine:

On the server, there was a World object, which created entities by itself. On the client, there was a World object, which created entities by itself and also assigned the entity another ID - a "server id" using an IdEntityManager.

Here's the IdEntityManager that I used on the client which helped me attach another id to an entity. It's just a manager (which are now systems), so add it and use it as you would any other System!

import java.util.HashMap;
import java.util.Map;

import com.artemis.Entity;
import com.artemis.Manager;
import com.artemis.utils.Bag;

/**
 * Helps manage entities in a slave-world (i.e. a client-side world).
 */
public class IdEntityManager extends Manager {
    private final Map<Integer, Entity> idToEntity;
    private final Bag<Integer> entityToId;

    public IdEntityManager() {
        this.idToEntity = new HashMap<Integer, Entity>();
        this.entityToId = new Bag<Integer>();
    }

    @Override
    public void deleted(Entity e) {
        Integer id = entityToId.safeGet(e.getId());
        if (id == null) {
            return;
        }

        Entity oldEntity = idToEntity.get(id);
        if(oldEntity != null && oldEntity.equals(e)) {
            idToEntity.remove(id);
        }

        entityToId.set(e.getId(), null);
    }

    public Entity getEntity(int id) {
        return idToEntity.get(id);
    }

    /**
     * @param e Entity to get ID for
     * @return MAY RETURN NULL
     */
    public int getId(Entity e) {
        int id = entityToId.safeGet(e.getId());

        return id;
    }

    public void setId(Entity e, int newId) {
        Integer oldId = entityToId.safeGet(e.getId());
        if (oldId != null)
            idToEntity.remove(oldId);

        idToEntity.put(newId, e);
        entityToId.set(e.getId(), newId);
    }
}

Hope this helps.

nanonull commented 8 years ago

What about server and client communicate only using uuid (there is such system exists already) and internaly server/client could use own world ids?

Wulf commented 8 years ago

My reasoning for not using UuidEntityManager was that it was too expensive (in terms of bandwidth over a network) to send a whole Java UUID (something like d5628473-e293-4288-8db4-80fc1e5a349e).

This may be feasible for your situation, but my application required sending many packets per second, so I was very bitter about ever bit ;)