apotapov / gdx-artemis

Fork of artemis entity-system (http://gamadu.com/artemis/) refactored to integrate with Libgdx (http://libgdx.badlogicgames.com/)
Other
53 stars 5 forks source link

FiniteStateMachine for Components #42

Open vkvam opened 10 years ago

vkvam commented 10 years ago

So, I've been working a while on an FiniteStateMachine solution for components. My brain hurts pretty bad, but I think I've found a way that requires minimal change to existing code, just a few additions, while still being quite efficient and flexible.

The solution is inspired by http://www.richardlord.net/blog/finite-state-machines-with-ash. Have a look when you find some time, and see if it is something we could use.

Quite a few tests are in place to check both functionality and pooling. I've also profiled to check performance, which seems pretty decent.

Component are provided by componentproviders that are added to the EntityStateMachine through EntityState.

Simple example (tests: com.artemis.fsm.Simple):

package com.artemis.fsm;

import com.artemis.Component;
import com.artemis.Entity;
import com.artemis.World;
import junit.framework.Assert;
import org.junit.Test;

/**
 * Created by Vemund Kvam on 25/06/14.
 */
public class Simple {

    public static class ComponentA implements Component {
        public int valueA;
        @Override
        public void reset() {
        }
    }

    public static class ComponentB implements Component {
        public int valueA;
        @Override
        public void reset() {
        }
    }

    public static class ComponentProviderA extends ComponentProvider<ComponentA> {
        public int value, initialValue;
        @Override
        protected void onProviderInit() {
            value = initialValue;
        }
        @Override
        protected void onRemove(ComponentA component) {
            value = component.valueA;
        }
        @Override
        protected void onAdd(ComponentA component) {
            component.valueA = value;
        }
    }

    public static class ComponentProviderB extends ComponentProvider<ComponentB> {
        public int value, initialValue;
        @Override
        protected void onProviderInit() {
            value = initialValue;
        }
        @Override
        protected void onRemove(ComponentB component) {
            value = component.valueA;
        }
        @Override
        protected void onAdd(ComponentB component) {
            component.valueA = value;
        }
    }

    @Test
    public void testAPISimple() {
        World world = new World();
        Entity entity = world.createEntity();

        EntityStateMachine machine = entity.getEntityStateMachine();
        ComponentProviderA providerA = machine.createComponentProvider(ComponentProviderA.class);
        providerA.initialValue = 1;

        ComponentProviderB providerB = machine.createComponentProvider(ComponentProviderB.class);
        providerB.initialValue = 2;

        machine.createState("State A").add(providerA);
        machine.createState("State B").add(providerA).add(providerB);

        entity.activateFiniteState("State B");
        world.process();
        ComponentA componentAFromStateB = entity.getComponent(ComponentA.class);
        // Assert initialValue was set.
        Assert.assertEquals(1,componentAFromStateB.valueA);
        // Set a value to test restore functionality given by providerB.onRemove(component) and providerB.onAdd(component);
        entity.getComponent(ComponentB.class).valueA=-1;

        entity.activateFiniteState("State A");
        world.process();
        // Assert ComponentB was removed
        Assert.assertNull(entity.getComponent(ComponentB.class));
        // Assert ComponentA was untouched as State A and State B uses the same provider instance for ComponentA
        Assert.assertEquals(componentAFromStateB,entity.getComponent(ComponentA.class));

        entity.activateFiniteState("State B");
        world.process();
        // Assert value was transferred back to component from provider.
        Assert.assertEquals(-1,entity.getComponent(ComponentB.class).valueA);
    }
}
apotapov commented 10 years ago

Sorry been a busy couple of weeks. Thank you very much for putting it together. I'll take a look at this over the weekend.