Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEATURE] Iterate over many aspects with less boilerplate code ? #614

Open
genaray opened this issue Sep 27, 2020 · 1 comment
Open

[FEATURE] Iterate over many aspects with less boilerplate code ? #614

genaray opened this issue Sep 27, 2020 · 1 comment

Comments

@genaray
Copy link

genaray commented Sep 27, 2020

Currently i strictly divide my code down into seperate systems.
At this moment i have nearly 40 Systems in my hierarchy, each one doing one task.
Of course not all of them are running each frame, out of 40 Systems, probably 3-4 are running each frame.
This targets mostly stuff like movement, network and ai.

Screenshot_2

This is a little screenshot from some of my systems. If you take a look at the spawn mechanics ( about 10 systems ) you may notice that this gets really, really messy at some point.

Why did i divide the spawn mechanic into so many systems ? Flexibility, the main reason to use an ECS. That spawning mechanic is not running every frame, but if it does... most of the systems are used to transfer data from one component into the other one. I wanted to have it as dynamic as possible, but thats another topic.

Nevertheless this caused a lot of boilerplate code for those systems. Most of them only contain a few lines of code to copy one object into the next step of the spawn mechanic. Instead of one System for each "loop" i could also write one subscribtion... but this is also a lot of boilerplate code.

It would be great to have a feature that allows us to iterate over different aspects in the same system with less boilerplate code.
Something like :

public class MultipleIterations extends MultipleSystem{

     public void process(){

             Entities.ForEach((Player player, Location location, Movement movement) -> {
                // Logic
            })
            
            Entities.ForEach((LocationHistory lch, Location lc) -> {
                // Logic
            })

            Entities.ForEach((Countdown cd) -> {
                // Logic
            })


            Entities.<Player, Location>((Player player, Location lc) -> {
                // Logic :)
            })

            // Or some other way of iterating in an easy way over those aspects.
     }
}

instead of soo much boilerplate code

@All(Spawn.class)
public class SpawnSystem extends IteratingSystem{

     ComponentMapper<Spawn> spawn;
     ....

     public void process(int i){
        // Logic  
    }
} 

public class OtherSystem extends BaseSystem{

     EntitySubscribtion first;
     EntitySubscribtion second;
     ....

     public void process(){
        
        IntBag firstEntities = first.getEntities();
        for(var index = 0; index < firstEntities.size(); index++){
            // Logic :c
        }

        IntBag secondEntities = second.getEntities();
        for(var index = 0; index < secondEntities.size(); index++){
            // Logic :c
        }
    }
} 
@DaanVanYperen
Copy link
Collaborator

DaanVanYperen commented Jul 11, 2021

The way we iterate definitely is verbose, but it is fast and avoids creating iterators or other objects.

I personally favor less boiler plate myself and wouldn't mind a plugin or extension that does what you suggest. I'm not too familiar with the performance impact of lambda's on the GC, beyond them being singletons as long as you don't pull in state from the side.

A way to add this functionality would be to BaseSystem and inject wrapped subscriptions with your own API. The example below doesn't provide component mappers, that would have to be solved separately. I use https://github.com/junkdog/artemis-odb/wiki/Fluid-Entities to avoid those.

public class MySystem extends BaseSystem {

      @All(Countdown.class)
      MySubscription countdowns;

     protected abstract void processSystem() {
           for(Entity e : countdowns) {
           }
     }
}
public class MySubscriptionAspectResolver implements FieldResolver {

    // we need to delegate to the aspect field resolver.
    private AspectFieldResolver aspectFieldResolver = new AspectFieldResolver();

    @Override
    public void initialize(World world) {
        aspectFieldResolver.initialize(world);
    }

    @Override
    public Object resolve(Object target, Class<?> fieldType, Field field) {
        if (MySubscription.class == fieldType) {
            return new MySubscription(
                     ((EntitySubscription) aspectFieldResolver.resolve(target, EntitySubscription.class, field)));
        }
        return null;
    }
}
public class MySubscription<A,B> implements Iterable<Entity>{
    private final EntitySubscription wrappedSubscription;

    public ESubscription(EntitySubscription wrappedSubscription) {
        this.wrappedSubscription = wrappedSubscription;
    }

    @Override
    public Iterator<Entity> iterator() {
        // custom iterator implementation
    }

   // lambda API
}

Beyond that even for small jam games it's fairly easy to lose the big picture when things start to grow. Combining multiple systems is a solution but it feels like a bit of a workaround to the current architecture. A more flexible way to structure and (re)organize game logic would be nice to have, perhaps combined with a less rigid more lightweight alternative to systems.

We could do configurable method callsites on systems with components as parameters (assuming we could solve performance issues with some bytecode weaving or proxies). something like:

public class Spawning {

     @Process
     public void doAThing(Player player, Location location, Movement movement) {..}
            
     @Process
     public void doAThing2(LocationHistory lch, Location lc) {..}
        
     @Process
     public void doAThing3(Countdown cd) {..}

     @Process
     // other annotations that control iteration and timing behavior.
     public void doAThing4(Player player, Location lc) {..}
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants