FEOMedia / artemis-odb-orion

Operations mini-DSL specialized to deal with artemis-odb entities
Apache License 2.0
21 stars 5 forks source link

SingleUseOperation does not get executed within sequence operation #16

Open sonirico opened 8 years ago

sonirico commented 8 years ago

I have a piece of code like this:

sequence(
    linear_move(xy(100, 100)), // TemporalOperation
    rotate(xy(100, 100)) // SingleUseOperation here
);

RotateOperation never gets executed. But if I do this...

sequence(
    rotate(xy(100, 100)), // SingleUseOperation here,
    linear_move(xy(100, 100)) // TemporalOperation    
);

Then it works. Btw, it also works if sequence is replaced by parallel (in both cases). Is this behavior normal or should I make a test case?

junkdog commented 8 years ago

try setting time with OperationFactory::seconds - i think it's a case of the temporal operation having a duration of 0.

unique("touch",
    scaleTo(1f, seconds(.5f), Interpolation.bounceOut)
).register(e);

PS. pass interpolation as a parameter - makes tweaking it much easier.

edit: OperationFactory::seconds simply sets the value to the smallest possible float which is greater than zero.

public static float seconds(float value) {
        if (value == 0) {
            int raw = floatToRawIntBits(0);
            value = intBitsToFloat(raw + 1);
        }

        return value;
    }
sonirico commented 8 years ago

I have just noticed that linear_move extended Operation base class straightaway. That is because I needed an operation which just sets the velocity for a MotionComponent once. I should have used SingleUseOperation instead.

In regard to Interpolation, I can barely use it for movements due to it works by modifying a position vector instead of velocities. Do you remember the gif in here ? xD

junkdog commented 8 years ago

I can barely use it for movements

Ah, yeah, more useful for UI and the like.

sonirico commented 8 years ago

I am using OperationFactory.xy(float, float) because it is less verbose than instanciating a new vector2 everytime, but I run into problems. I have an OperationComponent like:


public class OperationComponent extends PooledComponent {

    public Operation operation = null;

    public boolean registered = false;

    public OperationComponent () {

    }

    public void setOperation (Operation operation) {
        this.operation = operation;
    }

    public void register (Entity entity) {
        operation.register(entity);
    }

    public void register (World world, int entityId) {
        operation.register(world, entityId);
    }

    @Override
    protected void reset() {
        operation = null;
        registered = false;
    }
}

And a the proper system:

public class CustomOperationSystem extends EntityProcessingSystem {

    private ComponentMapper<OperationComponent> mapper;

    public CustomOperationSystem() {
        super(Aspect.all(OperationComponent.class));
    }

    @Override
    protected void process(Entity e) {
        OperationComponent oc = mapper.get(e);

        if (!oc.registered) {
            oc.register(e);
            oc.registered = true;
        }
    }
}

Then in my stage builder:

...
Operation o2 = sequence(
                animate(AnimationValues.STATES.WALKING),
                linearMove(new Vector2(-100, 0), 2),
                forever(
                        sequence(
                                animate(AnimationValues.STATES.ATTACKING),
                                delay(1),
                                shot(Weapon.ELF_ARROW, sequence( linearByVel(new Vector2(100, 0)), rotateOnce(new Vector2(100, 0))), G.GROUPS.BULLET),
                                animate(AnimationValues.STATES.WALKING),
                                linearMove(new Vector2(0, -200), 3),
                                animate(AnimationValues.STATES.ATTACKING),
                                delay(1),
                                shot(Weapon.ELF_ARROW, sequence( linearByVel(new Vector2(100, 0)), rotateOnce(new Vector2(100, 0))), G.GROUPS.BULLET),
                                animate(AnimationValues.STATES.WALKING),
                                linearMove(new Vector2(0, 200), 3)
                        )
                )

        );

        OperationComponent oc2 = orc.edit().create(OperationComponent.class);
        oc2.setOperation(o2);
...

If by any chance I replaced new Vector2with xy the whole behavior changes -not expected rare things. I think that if the xy method returned a new vector instance there would not be issues. Why is it static final?

DaanVanYperen commented 8 years ago

Why is it static final?

To avoid garbage collection as much as possible on slower / limited platforms. Causes issues when it is passed to the same method twice.

junkdog commented 8 years ago

Hmm, I don't see any operations with two or more parameters taking vectors? That's the only point where it can be problematic. are you setting the vector in the operation or overwriting the reference? The xy() vector has to be written to the operation's own vector, if it's not already the case.

    public static MoveToOperation moveTo(Vector2 destination,
                                         float duration,
                                         Interpolation interpolation) {

        MoveToOperation op = operation(MoveToOperation.class);
        configure(op, duration, interpolation);
        op.dest.set(destination);

        return op;
    }
sonirico commented 8 years ago

Vector2.set method is always used to copy their values. I learnt about that reference issue in the hard way, long ago xD.

junkdog commented 8 years ago

Hmm... I'd record the stack trace for each Vector2, something is going on somewhere.

junkdog commented 8 years ago

are you calling xy from somewhere in your factory or operations?

DaanVanYperen commented 8 years ago

I think what @sonirico is saying he was keeping a reference to the vector in his custom operations.

sonirico commented 8 years ago

@DaanVanYperen totally the opposite (I think XD) CustomOperationFactory.java:


public final class CustomOperationFactory {
    private CustomOperationFactory() {
    }

    /**
     * Sets a linear motion movement
     * @param vel
     * @return
     */

    public static LinearMoveByVelocityOperation move (Vector2 vel) {
        LinearMoveByVelocityOperation lmbv = operation(LinearMoveByVelocityOperation.class);
        lmbv.vel.set(vel);
        return lmbv;
    }

    /**
     * Sets a linear motion movement specified by final destination and duration
     * @param deltaSpace the distance to move
     * @param duration how long it takes to reach it
     * @return
     */

    public static LinearMoveOperation moveBy(Vector2 deltaSpace, float duration) {
        LinearMoveOperation lmo = operation(LinearMoveOperation.class);
        lmo.vel.set(deltaSpace);
        lmo.duration = duration;
        return lmo;
    }

    /**
     * Sets harmonic motion
     * @param A total displacement
     * @param nLaps period (aka T)
     * @param angle initial angle (aka phi)
     * @param duration
     * @param vel velocity for the no harmonic axis
     * @param xAxis whether perform it in the x axis
     * @param yAxis whether perform it in the y axis
     * @return
     */

    public static SinusMoveOperation sinusMove (
        float A, float nLaps, float angle, float duration, Vector2 vel, boolean xAxis, boolean yAxis
    ) {
        SinusMoveOperation smo = operation(SinusMoveOperation.class);
        smo.vel.set(vel);
        smo.xAxis = xAxis;
        smo.yAxis = yAxis;
        smo.duration = duration;
        smo.A = A;
        smo.phi = angle * MathUtils.degreesToRadians;
        float f = nLaps / duration; // frequency = laps per second
        smo.omega = MathUtils.PI2 * f;// w = 2 * PI * frequency
        return smo;
    }

    /**
     * An alternate sinusoidal movement with no complex harmonic equations
     * @param vel
     * @param radius
     * @param xAxis
     * @param yAxis
     * @param duration
     * @return
     */

    public static AlternateSinusMoveOperation alternateSinusMove (Vector2 vel, float radius, boolean xAxis, boolean yAxis, float duration) {
        AlternateSinusMoveOperation smo = operation(AlternateSinusMoveOperation.class);
        smo.radius = radius;
        smo.xAxis = xAxis;
        smo.yAxis = yAxis;
        smo.vel.set(vel);
        smo.duration = duration;
        return smo;
    }

    /**
     * Chases the target. This movement takes as long as duration is set.
     * For a standard duration value, this movement will perform faster/lower
     * depending on the distance between the pursuer and the target
     * @param entityId the target to pursue
     * @param duration how long it takes to perform the movement.
     * @return
     */

    public static ChaseByDurationOperation chaseByDuration (int entityId, float duration) {
        ChaseByDurationOperation co = operation(ChaseByDurationOperation.class);
        co.targetEntityId = entityId;
        co.duration = duration;
        return co;
    }

    /**
     * Always chases the target with the given velocity
     * @param entityId
     * @param vel
     * @return
     */

    public static ChaseByVelocityOperation chaseByVelocity (int entityId, Vector2 vel) {
        ChaseByVelocityOperation co = operation(ChaseByVelocityOperation.class);
        co.targetEntityId = entityId;
        co.vel.set(vel);
        return co;
    }

    public static AttackOperation attack (Weapon w) {
        AttackOperation ao = operation(AttackOperation.class);
        ao.weapon = w;
        return ao;
    }

    /**
     * Flips the sprite of an animated component
     * @param x whether flip on x axis
     * @param y whether flip on y axis
     * @return
     */

    public static FlipOperation flip (boolean x, boolean y) {
        FlipOperation fo = operation(FlipOperation.class);
        fo.flipX = x;
        fo.flipY = y;
        return fo;
    }

    /**
     * Makes and entity collidable with entities composed by DamageableComponents.
     * @param damage How much damage it will inflict
     * @return The operation to process
     */

    public static MeleeAttackOperation melee (int damage) {
        MeleeAttackOperation mao = operation(MeleeAttackOperation.class);
        mao.damage = damage;
        return mao;
    }

    /**
     * Removes the melee state from an entity
     * @return The operation to process
     */

    public static EndMeleeOperation endMelee() {
        EndMeleeOperation mao = operation(EndMeleeOperation.class);
        return mao;
    }

    /**
     * Sets animations for entities
     * @param animation
     * @return
     */

    public static AnimateOperation animate (Enum animation) {
        AnimateOperation ao = operation(AnimateOperation.class);
        ao.animation = animation;
        return ao;
    }

    public static ShotOperation shot (Weapon weapon) {
        ShotOperation so = operation(ShotOperation.class);
        so.weapon = weapon;
        return so;
    }

    public static ShotOperation shot (Weapon weapon, Operation moveDescriptor) {
        ShotOperation so = operation(ShotOperation.class);
        so.weapon = weapon;
        so.moveDescriptor = moveDescriptor;
        return so;
    }

    public static ShotOperation shot (Weapon weapon, Operation moveDescriptor, String... groups) {
        ShotOperation so = operation(ShotOperation.class);
        so.weapon = weapon;
        so.moveDescriptor = moveDescriptor;
        so.groups = groups;
        return so;
    }

    public static RotateOnceOperation rotateOnce ( Vector2 direction ) {
        RotateOnceOperation roo = operation(RotateOnceOperation.class);
        roo.direction.set(direction);
        return roo;
    }

    /**
     * Achieves the fancy effect of blinking.
     * @param minorA Low alpha
     * @param majorA High alpha
     * @param delay how long it takes to start blinking
     * @param interval Delta time between blinking
     * @param repeatCount How many times the Entity will blink
     * @return
     */

    public static BlinkOperation blink (
            float minorA, float majorA,
            float delay, float interval, int repeatCount)
    {
        BlinkOperation bo = operation(BlinkOperation.class);
        bo.minorAlpha = minorA;
        bo.minorAlpha = majorA;
        bo.delay = delay;
        bo.interval = interval;
        bo.repeatCount = repeatCount;
        return bo;
    }
}

On the other hand, in every operation composed by vectors, I always set them as:

Vector2
d vec = Vector2.Zero.cpy();

@junkdog using the "find usages" tool from intellij there were no results.

Sorry for the big block of code. Next time I think I should link to the repo. It's currently in gitlab though.

DaanVanYperen commented 8 years ago

I think what @sonirico is trying to say is we should rename orion to xmas, to prepare for the holidays!

(I'm sharp! :D)