junkdog / artemis-odb

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

[Archetypes] drafting EntityFactory for parameterizing entity creation #160

Closed junkdog closed 10 years ago

junkdog commented 10 years ago

Rev 2:

New core interface:

Remains pretty much the same, except for overloaded group:s.

public interface ParameterizedArchetype<T> {
    ParameterizedArchetype<T> copy();
    ParameterizedArchetype<T> tag(String tag);
    ParameterizedArchetype<T> group(String group);
    ParameterizedArchetype<T> group(String groupA, String... groups);
    ParameterizedArchetype<T> group(String groupA, String groupB, String... groups);
    Entity create();
}
Declare Ship interface

Adds class-level @Cref to avoid using the archetype builder at all.

@Cref({FlagCompoentA.class, SomeOtherFlagTypeComponent.class})
public interface Ship extends ParameterizedArchetype<Ship> {
    @CRef(Position.class) Ship pos(float x, float y);
    @CRef(Velocity.class) Ship velocity(float x, float y);
    @CRef(Asset.class) @Sticky Ship asset(String path);
    @CRef(Size.class) @Sticky Ship size(float width, float height);
    @CRef(HitPoints.class) Ship hitPoints(int current);
}
Create ship template and factory
public class EntityFactory extends Manager {
    public Ship shipA;
    public Ship shipB;
    public Ship shipC;

    @Override
    protected void initialize() {
        shipA = Archetype.of(Ship.class)
                .asset("ship42")
                .size(20, 15)
                .build(world);
        shipB = shipA.copy().asset("ship21");
                // .size(20, 15) - same size as shipA
                // .build(world) - no need to call build

        // reconstruct from start
        shipC = Archetype.of(Ship.class)
                .asset("ship3")
                .size(30, 12)
                .build(world);
    }

Original desription

Ok, so this is what I suggest: Class, annotation and interface names are very much placeholders, feel free to chime in.

New core interface:

public interface ParameterizedArchetype<T> {
    ParameterizedArchetype<T> copy();
    ParameterizedArchetype<T> tag(String tag);
    ParameterizedArchetype<T> group(String group);
    Entity create();
}

Edit #3: Added CRef annotation; makes life simpler for the annotation processor.

public interface Ship extends ParameterizedArchetype<Ship> {
    @CRef(Position.class) Ship pos(float x, float y);
    @CRef(Velocity.class) Ship velocity(float x, float y);
    @CRef(Asset.class) @Sticky Ship asset(String path);
    @CRef(Size.class) @Sticky Ship size(float width, float height);
    @CRef(HitPoints.class) Ship hitPoints(int current);
}

There's a bit of magic going on here, unfortunately, but it's done in the interest of keeping boilerplate to a minimum:

public class EntityFactory extends Manager {
    public Ship ship;
    public Ship shipSlow;
    public Ship shipFast;

    @Override
    protected void initialize() {
        ship = new ArchetypeBuilder()
            .add(Position.class)
            .add(Velocity.class)
            .add(Asset.class)
            .add(Size.class)
            .add(HitPoints.class)
            .buildTemplate(world, Ship.class);

        ship.position(120, 200)
            .asset("ship2")
            .velocity(20, 0)
            .group("allies");

        // or more concise
        shipSlow = new ArchetypeBuilder()
            .add(Position.class)
            .add(Velocity.class)
            .add(Asset.class)
            .add(Size.class)
            .add(HitPoints.class)
            .buildTemplate(world, Ship.class)
                .position(120, 200)
                .asset("ship14")
                .velocity(5, 0);

        // alternatively infer additional components
        // from the ParameterizedArchetype
        shipFast = new ArchetypeBuilder()
            .buildTemplate(world, Ship.class)
                .position(120, 200)
                .asset("ship42")
                .velocity(230, 0);

    }

Creating entities

EntityFactory factory = world.getManager(EntityFactory.class);
Entity player = factory.ship.position(0, 0).group("human").create();

edit: added @Sticky

junkdog commented 10 years ago

More granular tuning is planned for a later issue, this is mostly about getting something useful in there asap.

edit: in part covered by @Sticky, but user-supplied strategies are not included.

DaanVanYperen commented 10 years ago

Very smart design! I Like this a lot.

Just a small comment about the staging that might benefit from some tweaking: Stage 1: Define archetype composition, not state. Stage 2: Define archetype state + instance.

That means that any state changes during entity creation carry over to the next instancing, which may be undesirable. It also creates a bit of duplicity. (When I call position(1,1), The need for Position.class is implied.)

Maybe you can see a way to change it to: Stage 1: Define archetype composition + state Stage 2: Define entity divergent state + instance

Make it so the setters implicitly add components while assembling the archetype. It would increase utility immensely by all the boilerplate we would get rid of. so instead of :

   new ArchetypeBuilder().add(...).add(...).add(...).buildTemplate(world, Ship.class)

do:

        Ship shipSlow = Archetype.of(world, Ship.class)  // no constructor, so we can return Ship
                .with(Size.class, Hitpoints.class)
                .position(120, 200)
                .asset("ship14")
                .velocity(5, 0);

Not sure how to solve setting properties for a specific instance pollute the archetype. First thought was to create two interface implementations, but it just ends one big inappropriate mess.

Some other small suggestions.

public interface Archetype<T> {
    T copy();
    Archetype<T> with(.. components);
    Archetype<T> tag(String tag);
    Archetype<T> group(String[] group);
    Entity create();
}
DaanVanYperen commented 10 years ago

Another comment on usability, having one interface per archetype is not something I'd use in practice, especially since it doesn't define structure at all. It's just extra work.

I'd probably have more use for the same concept as you wrote up as a builder to assemble archetypes with, not as an actual concrete archetype.

DaanVanYperen commented 10 years ago

Just to clarify what I mean,

If your goal is a builder, maybe it makes more sense to allow the user to create a single implementation of the auto completed interface with setters for all my components to bake archetypes with. And provide a different solution for post creation. (since post creation the usecases are a little different anyway. like set UUID, removing components, set up hierarchies, etc).

Or alternatively, if you /do/ want the user to define concrete types, why not in the form of Pojo's so they get to save time defining types and just hook up whatever serialization they desire. Edit: PERFORMANCE OFC SILLY DAAN!

    interface ConcreteArchetype<T> { Entity getEntity(); }
    class Ship implements ConcreteArchetype<Ship> {
        public Pos pos = new Pos(5,5);
    }
class World {
    public <T> T create(ConcreteArchetype<T> archetype) { .. }
}
    public void test() 
    {
        Ship ship = world.create(myExampleShip);
        ship.pos.x = 1;
        ship.pos.y = 1;
    }
junkdog commented 10 years ago

That means that any state changes during entity creation carry over to the next instancing, which may be undesirable. It also creates a bit of duplicity. (When I call position(1,1), The need for Position.class is implied.)

That's what @Sticky is for; it's effectively a default value; could even forbid changing it; as it carries over to the next created instance, if the ParameterizedArchetype is changed (just using the name now to avoid confusion btw), but it makes it harder to create a couple of different templates per entity.

This could probably be worked around though.

Make it so the setters implicitly add components while assembling the archetype. It would increase utility immensely by all the boilerplate we would get rid of. so instead of

Yup, much cleaner implicitly adding them. @CRef is pretty much a required annotation - to avoid problems with incremental compilation - not even JMH handles it properly. I've used the reflections library in the past to overcome this, but it doesn't like reading from anything which comes from external libraries and projects.

It would then become:

        Ship shipSlow = Archetype.of(Ship.class)  // no constructor, so we can return Ship
//                .with(Size.class, Hitpoints.class)
                .position(120, 200)
                .asset("ship14")
                .velocity(5, 0)
                .build(world);

The build(world) call is necessary for determining compositionId and resolving component classes to CompontentTypes. Also need a way to serialize these to json and other external formats, existing independently from a world instance.

We're going to type it constantly, Just name the interface Archetype.

But that breaks stuff :( - how about Template? Besides, apart from defining interfaces, it won't ever be typed in code.

Make group an array.

:+1:

Another comment on usability, having one interface per archetype is not something I'd use in practice, especially since it doesn't define structure at all. It's just extra work.

With @CRef we'd get rid of the first step completely. In practice, most games would have less than (10 templates) + variations.

We need to define the interface somewhere, so that the IDE can intellisense it.

In practice, this would demote Archetype to an internal class, which is a good thing.

...

Posting what I have while I answer the next comment..

junkdog commented 10 years ago

Edit: PERFORMANCE OFC SILLY DAAN!

That and we can't propagate component values in a meaningful way to the created entities.

DaanVanYperen commented 10 years ago

That and we can't propagate component values in a meaningful way to the created entities.

Can you elaborate? Generating a clone(C target) method shouldn't be much harder than populating an interface right?

junkdog commented 10 years ago

The approach I'm going for is to use an annotation processor to generate the code: meaning, no maven/gradle plugin + works with GWT out of the box. Using an AP also limits us to working on interfaces* - AP:s can't modify existing sources - only generate sources/classes as part of the compilation process (this leads to smoother IDE integration).

Clone wouldn't work with packed or pooled components as these require the World for instantiation. It could be overcome - especially in the case of pooled components - but it'd open up a can of worms (requiring changes to the weaver + making sure it actually works for all cases; multi-module projects encountering out-of-sync states may be another problem).

edit: Another issue is dealing with clone for fields which aren't primitives. It's possible these wouldn't work as expected.

*) It doesn't limit us to working on interfaces, but if we extract the actual implementation from classes, we might give the end-user the idea that they are actually working on the pojo, not a behind-the-scenes implementation exposed via an interface. If we choose to actually use the pojo, we need to take care multiple instances of the same pojo and communicate that to the end-users (another brittle set of edge-cases).

junkdog commented 10 years ago

Updated issue description with rev 2.

DaanVanYperen commented 10 years ago

The approach I'm going for is WALL OF TEXT SNIPPED

Pretty cool, I should read up on annotation processors! Nb. I've seen IntellIJ intellisense generated classes without predefined interface. No clue how it works though. Might be making it up.

I agree the Pojos are a bad idea, given the impression of concrete types. Would make more sense in the form of Aspects as Pojos, and allow entity instancing and accessing using these. A discussion better held elsewhere though. After reflection I like your plan better.

That's what @Sticky is for; it's effectively a default value; could even forbid changing it; as it carries over to the next created instance, if the ParameterizedArchetype is changed (just using the name now to avoid confusion btw), but it makes it harder to create a couple of different templates per entity.

Hey no fair, you ninja-ed in some changes! XD I'm liking this evolution though, especially getting rid of the boilerplate.

Still not sure about sticky though. It doesn't feel very intuitive. With templates, I'd expect the setters to 'stick' by default. maybe it makes more sense having an annotation for properties that are considered arguments for construction? That way there's no ambiguousness going on. The value doesn't persist, and they get an exception when they forget to set it.

Most of the effort is pre-creation, making it limited but crystal clear would be more beneficial than trying to cover all post creation cases as well. Composition divergence after creation is something that quickly grows far beyond this system. Think effects of Diablo specials, uuid, entity hierarchy.

Nb. Can you explain the process behind archetype composition, especially with Sticky? I assume the CRefs are always added?

Nb: support for parameter components please! Nb 2: interface inheritance pretty please!

But that breaks stuff :( - how about Template? Besides, apart from defining interfaces, it won't ever be typed in code.

Template can work, with Archetype as an internalized class that makes sense.

Nb. If I make a space shooter and have 50 different ship types that share composition but not state, what's the plan there? User needs to set it pre creation? Edit: I assume we do Archetype.of(Ship.class) x 50, and each instance is distinct.

The build(world) call is necessary for determining compositionId and resolving component classes to CompontentTypes. Also need a way to serialize these to json and other external formats, existing independently from a world instance.

If you put world in the Archetype.of method we can save the user some boilerplate when they make their own convenience methods. ;) createShip().bla.build();

DaanVanYperen commented 10 years ago

Continuation on inverting @Sticky, assuming all components are added to the template by default, we could broaden the use for instancing by providing an @Optional parameter.

public interface Chicken extends Template<Chicken> {
     // optional components are always instance parameters, and not added if not called pre create.
    @CRef(Flaming.class, optional=true) Chicken flaming();
    // not optional instance parameters throw exception when missing.
    @CRef(Position.class, instanceParameter=true) Chicken pos(float x, float y);
    // by default, component is sticky, and always added. 
    @CRef(Squawk.class) Chicken squawks(int volume);
}

or perhaps:

public interface Chicken extends Template<Chicken> {
    @CRef(Flaming.class) @Optional Chicken flaming(); 
    @CRef(Position.class) @Manual Chicken pos(float x, float y);
    @CRef(Squawk.class) Chicken squawks(int volume);
}

or perhaps:

public interface Chicken extends Template<Chicken> {
    @OptionalArgument(Flaming.class) Chicken flaming();
    @RequiredArgument(Position.class) Chicken pos(float x, float y);
    @CRef(Squawk.class) Chicken squawks(int volume);
}

bleh can't come up with sane names, but you get the idea.

DaanVanYperen commented 10 years ago

Just to clarify why inverting sticky, @Sticky methods will be more common than nonsticky. Plus feels a bit like the equivalent of flagging fields on a persistence Entity @NotTransient.

Still, I hope you can come up with better names for @NotSticky XD.

DaanVanYperen commented 10 years ago

Just for the sake of DRY and boilerplate:

public interface MobileActor<T> extends Template<T> {
    @CRef(Position.class) @Manual Chicken pos(float x, float y);
    @CRef(Anim.class) @Manual Anim anim(String id);
}
public interface Chicken extends MobileActor<Chicken> {
    @CRef(Flaming.class) @Optional Chicken flaming(); 
    @CRef(Squawk.class) Chicken squawks(int volume);
}
DaanVanYperen commented 10 years ago

Here's an edge case to keep in mind when dealing with (un)sticky. (Support for this would be desirable, though you could argue people need to break up their components when this happens or apply the same (un)sticky and optional parameters on each).

public interface Cookie extends Template<Cookie > {
    @CRef(Color.class) Chicken color(float r,float g, floatb); 
    @CRef(Color.class) @Manual Chicken alpha(float alpha);
}
DaanVanYperen commented 10 years ago

Here's another one. Maybe we can come up with an annotation/contract to allow hooking up managers?

public interface MyTemplate extends Template<MyTemplate> {

 // resolve manager + maps to TagManager#tag(entity, tag);
 @MRef(TagManager.class)
MyTemplate tag(String tag); 

 // resolve manager + maps to GroupManager#group(entity, group);
 @MRef(GroupManager.class)
 MyTemplate group(String group);

 // resolve manager + maps to GroupManager#fruitit(entity, apple, pear, midgets);
 @MRef(MyCustomManager.class)
 MyTemplate fruitIt( Apple apple, Pear pear, String []midgets );

 @MRef(UuidManager.class) @Optional // optional so creation only!
 MyTemplate uuid( UUID uuid );
}
DaanVanYperen commented 10 years ago

Ok why didn't I know about annotation processors, those are seriously cool :P

junkdog commented 10 years ago

maybe it makes more sense having an annotation for properties that are considered arguments for construction? That way there's no ambiguousness going on. The value doesn't persist, and they get an exception when they forget to set it.

It's mostly about minimizing boilerplate; default valued components are, I think, much less frequently occurring than per-Entity values. reads newly added comments But you disagree. Hmm, I'll look into how it looks in my games - it might be more evenly distributed.

I agree that @Sticky is a crappy name; dropping a few alternatives:

Nb. If I make a space shooter and have 50 different ship types that share composition but not state, what's the plan there? User needs to set it pre creation?

Common conceptual groups of enemties could copy() a more base Template (baseShip) and make changes to it:

// hitpoints, yadi & yada are @Sticky
bossShip = ship.copy().hitpoints(20234324).yadi("something").yada(32).rebuild();

If so, it might actually be best to force a subsequent build(world) (or just rebuild()) to seal the default values.

Then when creating:

factory.bossShip.pos(x, y).vel(20, 30).create();

Continuation on inverting @Sticky, assuming all components are added to the template by default, we could broaden the use for instancing by providing an @Optional parameter.

Optional parameters would be nice, but adds a lot of logic (need to track all optional-imposed permutations, as these affect compositionId). I'd rather have extended Templates/template inheritance (in the future we could weave away the inheritance, until then one can do it by hand).

Here's an edge case to keep in mind when dealing with (un)sticky.

Already implied by the design (that's why interface param names are enforced even if there's only one field in the referenced component)!

The @MRef ideas I really like - will only have group and tag from the start; need to come up with a good way of associating generic managers with the same/similar approach.

Just for the sake of DRY and boilerplate:

:+1:

Ok why didn't I know about annotation processors, those are seriously cool :P

Because when you run into the limitations they really start bothering you! Also, there used to be a real lack of tutorials, especially those which did something useful.

Namek commented 10 years ago

I like the whole idea at the current point (edit #3). + Daan's managers + Sticky->Default.

I first thought that @Default (@Sticky) should be the default and it would be better to have @NonPersisted (@NonSticky) but now I'm rather confused. You're both posting too much, I can't keep up with all of this!

junkdog commented 10 years ago

I checked out my entity compositions in shaman's:

In another game it's mostly 50/50 - in part because many transient components are bootstrapped by managers.

junkdog commented 10 years ago

You're both posting too much, I can't keep up with all of this!

And the details are changing so much! So, if I overlooked responding to something, poke me.

Namek commented 10 years ago

Implementation for interface Ship extends ParameterizedArchetype would be provided automatically? How it works? Is it based on on Components' constructors? Like, Position component have constructor Position(float x, float y) then I can put pos(float x, float y) into archetype?

Coming back to Sticky topic. Would it be possible to create some @RequiredArgument (but I like @CRef+@Manual more) to throw some exception (which Daan posted about)?

junkdog commented 10 years ago

Implementation for interface Ship extends ParameterizedArchetype would be provided automatically? How it works? Is it based on on Components' constructors? Like, Position component have constructor Position(float x, float y) then I can put pos(float x, float y) into archetype?

It would use an Annotation Processor to generate the required implementation. Internally it would use Archetype + ComponentMappers to modify the component values upon creating the entity, but before returning it to the end-user, as this is common denominator for working with all component types while also being the fastest/most efficient).

Coming back to Sticky topic. Would it be possible to create some @RequiredArgument (but I like @CRef+@Manual more) to throw some exception (which Daan posted about)?

Yup, it will, but when trying to set a shared/sticky/persisted value on an already built template.

DaanVanYperen commented 10 years ago

It's mostly about minimizing boilerplate; default valued components are, I think, much less frequently occurring than per-Entity values. reads newly added comments But you disagree. Hmm, I'll look into how it looks in my games - it might be more evenly distributed.

Ok last thing I'll post, then I'll shut up about it since it doesn't matter /that/ much ;)

I think all of this can be solved by deciding if this is a Factory or a Template. The whole point of Templating from the users perspective is a blueprint for entities that detangles entity definition from instancing. If it's mainly about instancing that happens to support sticky some previous setters then it isn't primarily a Template but a factory. If we add the optional components it would be closer to a builder. Bit semantic perhaps, forgive the nitpicking.

If I need to do anything more than position and perhaps apply a physics force onto my templates, I'm not sure if I'm actually templating. (Though some things, like UI, will probably be mainly far less concrete templates than in game entities).

Persisted Shared Default

I'd name Template differently if you prefer to keep Sticky. thinking of it like a factory instead of a template might make @Sticky more intuitive.

Common conceptual groups of enemties could copy() a more base Template (baseShip) and make changes to it:

:+1:

(or just rebuild()) to seal the default values.

I'd prefer annotations over sealing default values, it's more granular and less of a case of function defining form.

Optional parameters would be nice, but adds a lot of logic (need to track all optional-imposed permutations, as these affect compositionId). I'd rather have extended Templates/template inheritance (in the future we could weave away the inheritance, until then one can do it by hand).

Optional setters are all instance time though. If I want to add some random flag components on my spaceships I'm going to have to make either hundreds of templates, or do it myself after instancing anyway. Being able to do it with template as an optional setter would be preferable.

Already implied by the design (that's why interface param names are enforced even if there's only one field in the referenced component)!

I checked out my entity compositions in shaman's: 13 sticky vs 3 optional

Just so we're talking about the same thing, @optional would mean a instance only setter, if when not called the component is not added either. I think here you're talking about nonsticky?

Just did a quick scan, for me it's mainly Pos and Physics and callbacks being an instance-time parameter. For UI concrete templates start to make far less sense, more about generic templates,

DaanVanYperen commented 10 years ago

The build(world) call is necessary for determining compositionId and resolving component classes to CompontentTypes. Also need a way to serialize these to json and other external formats, existing independently from a world instance.

Is there are reason not to do .build() upon .create() via dirty flag? I assume the once-off delay will be negligible and it saves the user some complexity.

Nb. Maybe just park @Optional for now, get the basics in place first.

junkdog commented 10 years ago

Ok last thing I'll post, then I'll shut up about it since it doesn't matter /that/ much ;)

But it does! Otherwise we might corner ourselves in a mess in the future.

If it's mainly about instancing that happens to support sticky some previous setters then it isn't primarily a Template but a factory.

Factory it is.

I'd prefer annotations over sealing default values, it's more granular and less of a case of function defining form.

How do you mean - via extended interfaces?

Optional setters are all instance time though. If I want to add some random flag components on my spaceships I'm going to have to make either hundreds of templates, or do it myself after instancing anyway. Being able to do it with template as an optional setter would be preferable.

I'd be tricky to do this efficiently - meaning without invoking an edit().add() internally each time. It'd be potentially OptionalComponent x OptionalComponent permutations.

junkdog commented 10 years ago

Is there are reason not to do .build() upon .create() via dirty flag? I assume the once-off delay will be negligible and it saves the user some complexity.

Fair enough.

DaanVanYperen commented 10 years ago

Implementation for interface Ship extends ParameterizedArchetype would be provided automatically? How it works? Is it based on on Components' constructors? Like, Position component have constructor Position(float x, float y) then I can put pos(float x, float y) into archetype?

I assume @junkdog plans to match fields by name, since using constructors cause a lot of precedence issues, and are fubar with packed/pooled components anyway.

junkdog commented 10 years ago

Exaclty.

But, so wait, if we rename Template->Factory, they almost need some more descriptive prefix - factory feels very generic.

junkdog commented 10 years ago

Just so we're talking about the same thing, @optional would mean a instance only setter, if when not called the component is not added either. I think here you're talking about nonsticky?

Oops, I meant to write per-instance.

DaanVanYperen commented 10 years ago

Factory it is.

I realize this came about of me moaning about ParameterizedArchetype being too long, since @Sticky sorta works with it. ;) Like (Entity)Factory better though.

How do you mean - via extended interfaces?

Got to retrace the conversation now! XD You mentioned sealing default values via a method call. I thought this implied no @Sticky, but instead having the method freeze all the current values as sticky?

I'd be tricky to do this efficiently - meaning without invoking an edit().add() internally each time. It'd be potentially OptionalComponent x OptionalComponent permutations.

Figured it would just be a Bag with components that you flush before create.;) Curses!

In the end it's about making things convenient while luring users away from bad decisions. Maybe forgetting about this and force users to post factory dynamic components is the most sensible option. People can make their own post-factory parametrizers.

Nb. Same with Tag and Group though, are those sticky or non sticky by default?

DaanVanYperen commented 10 years ago

So how about renaming this ticket 'First draft of plan for X' after we're done and compacting all this into a new ticket so people who don't have ten hours to read through our brainfarting can weigh in. ;)

But it does! Otherwise we might corner ourselves in a mess in the future.

Would expect that more in the serialization department, not sure how it connects with factories. I'd want the option to define composition externally, factories wouldn't help here.

DaanVanYperen commented 10 years ago

But, so wait, if we rename Template->Factory, they almost need some more descriptive prefix - factory feels very generic.

Sweatshop ComponentMines InstanceMill AssemblyPlant EntityFactory

;)

junkdog commented 10 years ago

Got to retrace the conversation now! XD You mentioned sealing default values via a method call. I thought this implied no @Sticky, but instead having the method freeze all the current values as sticky?

No, only finalizes the @Sticky values - applicable in the copy() case, when you want to make changes to a default values. Once a factory is setup, you don't want to accidentally change a per-Factory value, as it'd affect all future entities, and probably an annoyance to debug until you know what to look for.

Nb. Same with Tag and Group though, are those sticky or non sticky by default?

Think they'll need the Sticky annotation, otherwise per-instance, to adhere to the same semantics.

Figured it would just be a Bag with components that you flush before create.;) Curses!

It'd work, but it would remove all optimizations pertaining to Archetypes.

Would expect that more in the serialization department, not sure how you plan to connect the two. I'd want to define composition externally, factories wouldn't help here.

It could if the interface is linked to an external definition; it could even be generated (by a future extension of the plugin), but you're right - it's very speculative and not necessarily practical.

Ok, I'll update with what we've concluded and post it in a comment; if there are any omissions, update it and once it's done, create the new ticket matching/based on the most recent comment.

junkdog commented 10 years ago

New core interface:

Remains pretty much the same, except for overloaded group:s.

public interface EntityFactory<T> {
    EntityFactory<T> copy(); // create a new EntityFactor based on this
    EntityFactory<T> tag(String tag); // for now, per-Instance
    EntityFactory<T> group(String group); // per Factory (?)
    EntityFactory<T> group(String groupA, String... groups);
    EntityFactory<T> group(String groupA, String groupB, String... groups);
    Entity create();
}
Declare (example) Ship interface

Adds class-level @Cref to avoid using the archetype builder at all.

// declare components not covered by methods (flag-type components etc)
@Cref({FlagCompoentA.class, SomeOtherFlagTypeComponent.class})
public interface Ship extends EntityFactory<Ship> {
    @CRef(Position.class) Ship pos(float x, float y);
    @CRef(Velocity.class) Ship velocity(float x, float y);
    @CRef(Asset.class) @Sticky Ship asset(String path);
    @CRef(Size.class) @Sticky Ship size(float width, float height);
    @CRef(HitPoints.class) Ship hitPoints(int current);
}

EntityFactories can be extended:

public interface BossShip extends Ship {
    @CRef(Position.class) BossShip superWeapon(String name);
}
Create ship template and factory
public class EntityInstanceMill extends Manager {
    public Ship shipA;
    public Ship shipB;

    @Override
    protected void initialize() {
        shipA = Archetype.of(world, Ship.class)
                .asset("ship42")
                .size(20, 15)

        // copy() allows updating the factory with new stickied values
        // but the component composition remains
        shipB = shipA.copy().asset("ship21");
    }

Creating entities

EntityInstanceMill factory = world.getManager(EntityInstanceMill.class);
Entity player = factory.shipA.position(0, 0).tag("player").create();
junkdog commented 10 years ago

The MRef thing might actually be required right away, even if we choose to only include support for group, tag and uuid managers: mainly, because of group (might be sticky) and tag (probably not sticky).

junkdog commented 10 years ago

I kept the class-level CRef, as we need somewhere to define components whch aren't part of parameterized creation.

junkdog commented 10 years ago

Updated the post one more time; have I missed anything? It's been discussed over "a few" pages now.

DaanVanYperen commented 10 years ago

No, only finalizes the @Sticky values - applicable in the copy() case, when you want to make changes to a default values. Once a factory is setup, you don't want to accidentally change a per-Factory value, as it'd affect all future entities, and probably an annoyance to debug until you know what to look for.

Altering immutable values would throw an exception? Some users might use @Sticky for batching post create, maybe make freezing explicit action? Edit: Might make sense to keep it in, the moment of freeze isn't completely intuitive though.

Think they'll need the Sticky annotation, otherwise per-instance, to adhere to the same semantics.

Sticky tag only makes sense for singleton entities.

It could if the interface is linked to an external definition; it could even be generated (by a future extension of the plugin), but you're right - it's very speculative and not necessarily practical.

I like your design now though. Factories are ideal for lightweight prototyping and simple json can be easily added to branch out. When people go full json crazy they'll more than likely go straight for lower level Archetypes, factories have no added value then.

junkdog commented 10 years ago

Edit: Might make sense to keep it in, the moment of freeze isn't completely intuitive though.

Yeah, otherwise I agree. copy() could overcome it though, but is not as versatile.

When people go full json crazy they'll more than likely go straight for lower level Archetypes, factories have no added value then.

:+1:

DaanVanYperen commented 10 years ago

Yeah, otherwise I agree. copy() could overcome it though, but is not as versatile.

Maybe make stickies immutable upon .create(), unfreeze copies made with .copy(), and wait for use cases to appear in ticketing.


Since interfaces allow pretty flexible inheritance, maybe it makes sense splitting off the group/tag so people can mixin their own replacement managers? The Artemis ones are not exactly the greatest

public interface MinimalEntityFactory<T> {
    MinimalEntityFactory<T> copy();
    MinimalEntityFactory create();
}
public interface EntityFactory<T> extends MinimalEntityFactory<T> {
    EntityFactory<T> tag(String tag); // for now, per-Instance
    EntityFactory<T> group(String group); // per Factory (?)
    EntityFactory<T> group(String groupA, String... groups);
    EntityFactory<T> group(String groupA, String groupB, String... groups);
} 
interface Ship extends EntityFactory<Ship> {
  ..
}

Updated the post one more time; have I missed anything? It's been discussed over "a few" pages now.

Sure! I'll keep you occupied!:

    @CRef(Color.class) @Sticky Ship color(float r,float g, floatb); 
    @CRef(Color.class) @Sticky Ship alpha(float alpha);
Namek commented 10 years ago

Sooo, will current ArchetypeBuilder stay? I refer to rev5 on wiki

junkdog commented 10 years ago

Yes, for now at least. We'll see about the future.

junkdog commented 10 years ago

Since interfaces allow pretty flexible inheritance, maybe it makes sense splitting off the group/tag so people can mixin their own replacement managers? The Artemis ones are not exactly the greatest

We'll fix mixin-style inheritance in time for 0.8.0

DaanVanYperen commented 10 years ago

Looks good, can you add a small note on what happens when a setter lacks @Sticky, and is not called before a random .create()? Exception?

junkdog commented 10 years ago

Ah, it's just added - ie; calling the default constructor.

DaanVanYperen commented 10 years ago

is it coded yet? What about now.

junkdog commented 10 years ago

Dishes, lunch followed by #189 - might do a little coding during the gaps.

junkdog commented 10 years ago

ugh....

// breaks, need to put generic methods last
// no.... fac.hitPoints(20).tag("hello").size(20, 10);
fac.hitPoints(20).size(20, 10).tag("hello"); // works
DaanVanYperen commented 10 years ago

make it return T? instead of ParameterizedArchetype<T>

public interface ParameterizedArchetype<T extends ParameterizedArchetype> {
    T cookies();
}
junkdog commented 10 years ago

It is, the problems lie within the limitations of java's type inference. At least, I think there's no way around it - except if using java 8; think it improves TI. Saving the variable to a new local variable after invoking tag() or group() correctly deduces T to the right type - the problem only arises when chaining.