imaphatduc/cubecubed

[bug] - Extreme delay on interactive

Closed this issue · 11 comments

Describe the bug
If I hook up a rotate animation to onClick, it has an extreme delay

To Reproduce
My code (I edited example.js that can with npx ccw):

import {
    COLOR,
    CreateShape,
    DrawGrid,
    Grid,
    Group,
    Rotate,
    Scene,
    Square,
    VectorShape,
    Vector2,
    CreateVectorShape,
} from "cubecubed";

import $ from 'jquery'

// By convention, first-class functions represent `scenes`,
// and each function inside it represents a `group`.
function drawShapes() {
    const scene = new Scene("draw-shapes-scene");
    (() => {
        const group = new Group("squares", scene);

        const square1 = new Square({
            group: group,
            sideLength: 2,
            CONFIG: {
                strokeColor: COLOR.PINK_1,
            },
        }).render();

        const square2 = new Square({
            group: group,
            sideLength: 2,
            CONFIG: {
                strokeColor: COLOR.PINK_1,
            },
        }).render();

        group.play([new CreateShape({ cubicon: square1 })]);

        group.play([
            new CreateShape({ cubicon: square2 }),
            new Rotate({ cubicon: square1, degree: 45 }),
        ]);
    })();

    (() => {
        const group = new Group("vectors", scene);
        
        const vector = new VectorShape({
            group: group,
            endPoint: new Vector2(4, 4),
            CONFIG: {
                lineColor: COLOR.TEAL_1,
            },
        }).render();

        group.play([new CreateVectorShape({ cubicon: vector })]);

            $(() => {
                $('#cubecubed').on('click', (e => {
                    console.time('interactive')
                    group.play([new Rotate({cubicon: vector, degree: 45})])
                    console.timeEnd('interactive')
                }))
            })
        })();

        (() => {
            const group = new Group("plane-grid-group", scene);

            const grid = new Grid({ group: group }).render();

            group.play([new DrawGrid({ cubicon: grid })]);
        })();
}

drawShapes();

Your browser
Chrome and Edge

Can you make a guess?
Maybe it looks at .play() that was executed previously and adds the delay incorrectly?

Expected behavior
The vector rotating immediately

I can't record it right now, (I might a bit later) but if you look in Inspector, the svg rotate values do not change until ~7 seconds pass

One question, how do you add jQuery to your workspace (npm install or include script tag from a CDN)?

npm install, but i think I found the problem: animations base the delay on group.groupElapsed, which is not 0 when all animations finish, so even if they are all done there is still a delay, e.g.
Animations scheduled at start: 7500ms
when they are done, groupElapsed = 7500, so when I click on the page to play another animation, the delay method would look like this: .delay(7500).

Maybe instead of relying on groupElapsed, it could play the next animation on the 'end' event? somthing like this:

//Rotate.ts, rotate()
private rotate() {
        const { xGtoW, yGtoW } = this.cubicon.group;

        this.cubicon.moveAngle += this.degree;

        const v = this.cubicon.moveVector;

        this.cubicon.g_cubiconWrapper
            .transition()
            .ease(this.ease)
            .delay(this.sleepTime)
            .duration(this.duration)
            .attrTween("transform", () =>
                interpolate(
                    `translate(${xGtoW(v.x)}, ${yGtoW(v.y)}) rotate(${
                        this.cubicon.angle
                    }, ${xGtoW(this.origin.x)}, ${yGtoW(this.origin.y)})`,

                    `translate(${xGtoW(v.x)}, ${yGtoW(v.y)}) rotate(${
                        this.cubicon.angle + this.degree
                    }, ${xGtoW(this.origin.x)}, ${yGtoW(this.origin.y)})`
                )
            )
            .on("end", () => this.group.playNextAnimation());
    }

//Group.ts
private currentAnimationIdx = 0;

//Group.ts playNextAnimation()
playNextAnimation() {
    this.animations[currentAnimationIdx].play();
    currentAnimationIdx++;
}

Working on a PR for this now (there were a few errors in the code I put in my last comment, please ignore it)

@kaptsanovb ooh this is a rough one.

There were 2 ways to handle the animations, yours and the one Cubecubed has implemented. I chose the latter because I want the possibility of overlapping animations, which means animations having negative delay time. The implementation you mentioned can string the animations together, but lack the ability to control the delay time because it remains constant. I might be wrong, but is there any solutions you come up with this?

Even if you have solutions to this, the thing is it would override the entire Cubecubed animation engine.

I didnt think about that, mine would make animations linear.
A possible solution (I think) is having a function (e.g. getGroupPlayed) which operates on Date.now() like this:

Animation class has a field called startTime which is Date.now() + this.sleepTime called when the animation is initialised

Variables -
groupPlayed (time in ms that has been spent playing animations that have finished at the time when getGroupPlayed was called) : number;
groupCurrentAnimation: Animation?;

  1. groupPlayed = 0, groupCurrentAnimation = null;
  2. When an animation starts, groupCurrentAnimation will equal the animation
  3. When an animation finishes, the duration of the animation will be added to groupPlayed
  4. If no animation is playing, groupCurrentAnimation = null
  5. if getGroupPlayed is called while an animation is playing, groupPlayed + (Date.now() - groupCurrentAnimation.startTime) is returned
  6. When a new animation is initialised, the delay will be this.sleepTime - this.cubicon.group.getGroupPlayed()

I'll try implement this and make a PR if it works

Accidentally closed it, sorry

Or maybe, instead of using groupElapsed, Tuples could be used to signify animation's .play() to be called together or sequentially, e.g.:
group.play([(a1, a2, a3], [a4, a5], [a6]]) would play a1, a2 and a3 together, when all of them are finished it would play a4 and a5 together, then play a6. If group.play is called again while the original was not finished, it would have a separate queue, which means that the second call's animations would play immediately and possibly at the same time as the first call's animations. You could still have sleepTime be negative to allow only a4, for example, to be played before a1, a2 and a3 finish.

@kaptsanovb I really appreciate your efforts to come up with this implementation, but I would recommend you keep it for your fork of this project, because the current engine is stable and I cannot see any problems going on, also no need for jQuery to work with Cubecubed.

Alright 👍

Or maybe, instead of using groupElapsed, Tuples could be used to signify animation's .play() to be called together or sequentially, e.g.: group.play([(a1, a2, a3], [a4, a5], [a6]]) would play a1, a2 and a3 together, when all of them are finished it would play a4 and a5 together, then play a6. If group.play is called again while the original was not finished, it would have a separate queue, which means that the second call's animations would play immediately and possibly at the same time as the first call's animations. You could still have sleepTime be negative to allow only a4, for example, to be played before a1, a2 and a3 finish.

Btw this syntax is so simple yet I have not thought of it. I will consider it and close this issue now.