junkdog / artemis-odb

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

Question: Referencing entities in components #73

Closed DaanVanYperen closed 10 years ago

DaanVanYperen commented 10 years ago

Is there a suggested (safer?) way to refer to entities in components? Currently I'm just doing this:

public class Homing extends Component {
    public Entity target;
    ..
}
Namek commented 10 years ago

I assume that it isn't a problem of calling an entity which is parent of component, but rather save reference to other entity. Since entities are pooled it surely isn't safe. What you probably have to do is to set target=null.

You probably have some system which is responsible for that Homing component. You could implement

protected void removed(Entity e) {};

to remember this entity in some bag and check it every frame for every component... sucks.

Interesting problem. Anyone?

Namek commented 10 years ago

got an idea.

public class Homing extends Component {
    public Entity target;
    public UUID targetEntityId;
    ..
}

and compare those two in your system every frame:

if (!homing.target.getUUID().equals(homing.targetEntityId)) {
    // looks like our target was changed, homing.target is invalid
    homing.target = null;
    homing.targetEntityId = null;
}

if (homing.target != null) {
    // TODO your current logic
}
DaanVanYperen commented 10 years ago

Currently playing around with a generic solution.

public interface EntityReference {
    /** Is reference set and active? */
    public boolean isActive();
    /** get referenced entity */
    public Entity get();
}

Should be flexible enough to extend with UUID check.

/**
 * Reference by.. reference! :D
 *
 * @author Daan van Yperen
 */
public class SafeEntityReference implements EntityReference {

    private UUID uuid;
    private Entity entity;

    public SafeEntityReference(Entity entity) {
        this.entity = entity;
        this.uuid = entity.getUuid();
    }

    @Override
    public boolean isActive() {
        final boolean active = entity != null && entity.isActive() && entity.getUuid().equals(uuid);
        if ( !active ) { entity = null; uuid = null; }
        return active;
    }

    @Override
    public Entity get() {
        return isActive() ? entity : null;
    }
}
junkdog commented 10 years ago

What about creating an UuidEntityManager that tracks entities by UUIID? That way you'd only need to store the UUID as target and perform identity check in the system.

DaanVanYperen commented 10 years ago

That sounds like the ECS way! Would be great if there were some more advanced examples of managers to learn from, like the AssetManager you talked about.

Right now i'm using the above UUIDEntityReference, TagEntityReference etc. Pretty flexible, but having the Reference resolve things makes easy Serialization a no go.

junkdog commented 10 years ago

Yeah, I suck at writing documentation. Examples are something severely lacking, even more so in the case of agrotera (eg, there's some nifty profiling stuff that's hard to achieve w/o using agrotera).

Here's the code I've used in the past for resolving non-serializable components from their serializable counterparts:

@ArtemisManager( // need to replace with mapper and if-check if not using agrotera
    requires=AssetReference.class)
public final class RenderableResolverManager extends Manager {

    @Override
    protected void initialize() {}

    @Override
    public void added(Entity e) {
        AssetReference reference = assetReferenceMapper.get(e);

        Sprite sprite = (reference.frame != null)
            ? Assets.allocateSprites(reference.atlas, reference.frame).first();
            : Assets.allocateSprite(reference.atlas);

        SpriteFlip flip = reference.flip;
        if (flip == null)
            flip = SpriteFlip.NO_FLIPPING;

        sprite.flip(flip.flipX(), flip.flipY());

        if (reference.animated) {
            Array<Sprite> sprites = Assets.allocateSprites(reference.atlas, reference.frame);
            Animation animation = new Animation(sprites, reference.duration);

            for (int i = 0, s = sprites.size; s > i; i++)
                sprites.get(i).flip(flip.flipX(), flip.flipY());

            e.addComponent(animation);
        }

        e.addComponent(new Renderable(sprite));
        e.changedInWorld();
    }
}

@ToString
public final class AssetReference extends Component {
    public String atlas;
    public String frame;

    public boolean animated;
    public float duration;

    public SpriteFlip flip;
}

@EqualsAndHashCode(callSuper=false) @ExcludeFromJson // <- transient component
public final class Renderable extends Component {
    public Sprite sprite;

    public Renderable(Sprite sprite) {
        this.sprite = sprite;
    }

    @Override
    public String toString() {
        return String.format("%s(sprite=%s)",
            getClass().getSimpleName(), formatSprite(sprite));
    }

    private static String formatSprite(Sprite sprite) {
        if (sprite == null)
            return "null";

        String scale = sprite.getScaleX() == sprite.getScaleY()
            ? String.format("%.2f", sprite.getScaleX())
            : String.format("%.2fx%.2f", sprite.getScaleX(), sprite.getScaleY());

        return String.format("(pos=%.2fx%.2f, size=%.2fx%.2f, offset=%.2fx%.2f, scale=%s)",
                sprite.getX(), sprite.getY(),
                sprite.getWidth(), sprite.getHeight(),
                sprite.getOriginX(), sprite.getOriginY(),
                scale);
    }
}

The Assets class is just a convenience wrapper around libgdx's AssetManager.

DaanVanYperen commented 10 years ago

Just having some abandoned artemis games on github would help, regardless of quality. ;) Especially with more post-beginner topics like separating input from interactions, driving animations from other systems. It's hard conceptualizing these coming from a world of monolithic hierarchical monsters.

Must be some gems hiding around the web somewhere!.

junkdog commented 10 years ago

I'll look into the open-sourcing the game from where the above code originated. Asset licensing is a bit tricky though, but I could probably replace them with placeholder graphics.

junkdog commented 10 years ago

https://github.com/junkdog/artemis-odb/blob/master/artemis/src/main/java/com/artemis/managers/UuidEntityManager.java

ghost commented 10 years ago

Has anyone deployed artermis-odb to RoboVM? I know I had problems dealing with UUIDs in the original Artemis. These problems cropped up with using libgdx and Monotouch/IKVM.

Just wondering if updates like this will work or not on an iOS platform.

Thanks for keeping this project alive, BTW! I'm already using artemis-odb in my current prototype game.

--tim

On Fri, Apr 25, 2014 at 8:46 AM, Adrian Papari notifications@github.comwrote:

https://github.com/junkdog/artemis-odb/blob/master/artemis/src/main/java/com/artemis/managers/UuidEntityManager.java

— Reply to this email directly or view it on GitHubhttps://github.com/junkdog/artemis-odb/issues/73#issuecomment-41393936 .

junkdog commented 10 years ago

I'm not sure, if I had to venture a guess, I'd say it hasn't been tested with RoboVM.

In case it doesn't work, I'd like to make sure it does. Not entirely sure about why UUID should be around - it does show up under profiling; I'm pulling numbers out of my ass (that's where I keep my long-term memory and trivia), but I seem to recall that UUID accounted for ~11% of artemis' CPU usage under heavy load (only profiling artemis' classes, spawning/destroying ~1000 entities/second with ~10 components each).

An alternative would be to just use a long for UUID, not sure about how to deal with collisions and explicit ids though (there are probably viable solutions if one looks around however).

Are you planning on compiling for RoboVM with artemis-odb anytime soon? If so we could make sure it works for RoboVM in time for the upcoming artemis-odb release. I unfortunately don't have access to a mac myself, but it should be fine regardless.

ghost commented 10 years ago

Depending on how the next steps of my prototyping go, I'll be looking at updating things to the new libgdx 1.0 release. So, "soon" may be as early as next weekend. I have a Mac and can help test things out.

--tim

DaanVanYperen commented 10 years ago

Hey, stop polluting my ticket!

Have you seen any multiplayer/multithreaded games on artemis?

timtips commented 10 years ago

@DaanVanYperen I implemented a rts style deterministic lock-step multiplayer with artemis for http://holowars.timtips.com/ using kryo-net. Not open source though (and also not very actively developed currently either) :) I actually switched to artemis-odb / agrotera with it a while ago (from vanilla artemis). But the already mentioned lack of documentation/examples makes it hard to actually utilize everything odb/agrotera has to offer without some not so minor time investement (at least for me). Looking forward to seeing your LD game! Will participate too :)

junkdog commented 10 years ago

Hehe. Nope, multithreading if meaning that different threads share components is difficult without making artemis much more complex; having systems spawn their own threads and then polling ofc works (say for AI systems etc).

Multiplayer should be ideal for an ECS framework - but I don't have any real experience with multiplayer.

@tescott Nice, the next release is at least a couple of weeks away.

Ah, @timtips posted while I was writing. About the documentation; if you (meaning you and whoever else) posts questions in the issue tracker I could try to turn them into wiki pages. It's certainly easier writing answers than writing up documentation from scratch.

timtips commented 10 years ago

@junkdog That's a good idea. I will try to come up with some precise questions that could be an entry point for wiki documentation (that will only be after the LD though). Just wanted to add that I am really thankful that you (and some others that I am reading on the issue tracker as a long time lurker) are maintaining this project.

junkdog commented 10 years ago

Many thanks, makes me happy :)

DaanVanYperen commented 10 years ago

With so many participants I'm amazed we always manage to bounce into each other. Can set my clock on a friendly run in with @timtips and @rezoner. XD

Same sentiment here @junkdog, appreciate all the work. Now this github just needs to dethrone ARTEMIS Spaceship Bridge Simulator from google first result. ;)

Have you ever reached out to http://gamadu.com/artemis/? A link to your repo could do a lot for artemis, their own repo is pretty much abandoned.

Braza commented 10 years ago

@timtips How did you arranged map with ECS? Is it tiled or only looks like?

Is it turn-based internally? Was there a complex effects like "damage reflected to the originator" or timed buff debuffs? I was thinking to use EventBus that would decouple related Systems but still allowing them to trigger each other synchronously, passing parameters in the temporary Components with intermediate data for the next System.

One more thing. I'm struggling to write is "Strength decreased for 3 turns". I can imagine it in various ways like additional component meaning debuff with counter in there or more complicated strength component but nothing seemss right enough.

@DaanVanYperen Sorry for polluting thread, i`ve got no idea how the hell can I send a PM to Github user... ready to be shouted down)

@junkdog I really appreciate your ongoing support of the Fork. GJ man) General question about Systems concept here. Is there any reason (even ideological) why wouldn`t I leave all overriden System.precess methods blank and just trigger each system on events? Or is this world.process is a must?

DaanVanYperen commented 10 years ago

Original question has been sufficiently answered. The UuidEntityManager works for me, will eventually tie EntityReference as described above hook into hit to avoid a bit of boilerplate.