pixijs / pixi-react

Write PIXI apps using React declarative style
https://pixijs.io/pixi-react/
MIT License
2.36k stars 179 forks source link

Feature idea Ticker component #76

Closed EloB closed 5 years ago

EloB commented 5 years ago

I was thinking about a Ticker component. This is just a concept. What do you think about it?

I've added reduce, initialValue and data as properties so you can simply add custom logic to your animation.

reduce is more or less the same type of idea of Array.prototype.reduce. Except that the third argument is data instead of index. I don't know if index is needed maybe could be added too.

Why do this?

To remove the need of using lifecycle hooks.

Example

https://jsbin.com/zenuroqaru/edit?js,output This is using development mode dependencies so it will go much faster in production but still runs pretty good in development too :)

Usage


const SPRITES = 100;

const reducePositions = (accumulator, currentValue, data) =>
  Array.from({ length: SPRITES }, (_, i) => {
    const end = accumulator[i];
    const distanceX = data.x - end.x;
    const distanceY = data.y - end.y;
    const multiplier = ((i + 1) / (SPRITES + 1)) ** currentValue.deltaTime;
    return {
      ...end,
      x: end.x + (distanceX - distanceX * multiplier),
      y: end.y + (distanceY - distanceY * multiplier),
    };
  });

const initialPositions =
  Array.from({ length: SPRITES }, (_, i) => ({
    x: 0,
    y: 0,
    tint: +('0x' + tinycolor.fromRatio({ h: (i + 1) / (SPRITES + 1), s: 0.5, l: 0.5 }).toHex()),
    alpha: 0.2,
  }));

render(
  <Stage>
    {/* Without custom reducer */}
    <Ticker>
      {ticker => (
        <Sprite
          width={100}
          height={100}
          texture={Texture.WHITE}
          tint={0xff0000}
          x={ticker.lastTime / 10}
        />
      )}
    </Ticker>

    {/* With custom reducer */}
    <MousePosition>
      {mousePosition => (
        <Ticker
          reduce={reducePositions}
          initialValue={initialPositions}
          data={mousePosition}
        >
          {positions => positions.map((position, i) => (
            <Sprite
              key={i}
              width={100}
              height={100}
              texture={Texture.WHITE}
              {...position}
            />
          ))}
        </Ticker>
      )}
    </MousePosition>
  </Stage>,
  document.getElementById('app'),
);
inlet commented 5 years ago

Hi @EloB, great to see you are an active collab! Thanks man.

We could add a Ticker component, however I prefer to keep the lib as small and clean as possible. It's fairly easy to create a ticker using hooks: https://codepen.io/inlet/pen/f1112e5b916fe4a602e23e74f87c8192?editors=1010

I like the idea of trying to make a Ticker component that is declarative, however the ticker callback function for the ticker is imperative and having a reducer (as shown in your example) is quite complex to comprehend/read.

Personally, I really like to combine an imperative way to use the RAF method via React hooks.. -> useTick. This describes the function signature and is clear what it does.

What are your thoughts?

Thanks!

inlet commented 5 years ago

Btw. I see that I need to create a new build with updated react reconciler. Now it only supports React version 16.7.0-alpha.2. And I need to update the useTick hook to listen for changes and resubscribe the event accordingly.

EloB commented 5 years ago

Updated jsbin link above because it was wrong math so looked good on my OSX machine but terrible on the Windows machine.

https://jsbin.com/zenuroqaru/edit?js,output

EloB commented 5 years ago

Keeping libraries small made more sense before tree shaking. I really don't like the trend in package.json having 4000 dependencies but thats maybe just me πŸ˜„

I really like when people show good patterns how to use a technology inside the library itself.

That example only looks advanced because it animates multiple sprites at once.

EloB commented 5 years ago

I haven't even started with hooks yet because it's alpha and isn't it just an experiment right now? πŸ˜„It looks really promising though.

I think it makes sense to provide some way of doing animation the "react-pixi" way. Using like react-motion is not good because it's not the same ticker as PIXI.

inlet commented 5 years ago

Keeping libraries small made more sense before tree shaking. I really don't like the trend in package.json having 4000 dependencies but thats maybe just me πŸ˜„

Totally agree! Although a lot of users are using the umd version directly in the browser.

I really like when people show good patterns how to use a technology inside the library itself.

+1!

I haven't even started with hooks yet because it's alpha and isn't it just an experiment right now? πŸ˜„It looks really promising though.

It will be in the stable version soon, check out this medium post of Dan Abramov.

I think it makes sense to provide some way of doing animation the "react-pixi" way. Using like react-motion is not good because it's not the same ticker as PIXI.

The ticker is nothing more than a request animation frame, what I don't like about having a Ticker component (as Render Prop) is that you are forced to write imperative code. and you should have a mental model about what it produces. To be honest I don't see a solid way to provide the Ticker as a Render Prop component passing down something to its children. The hook useTick is a more stand-alone implementation and it simply accepts a javascript function where you can do the imperative stuff based on props/states etc.

EloB commented 5 years ago

I think it's good to support both render prop and hook implementations for animations. πŸ˜ƒ

I'm not sure everyone will use hooks and hooks require that the component is stateless component right? Also really interested in how it will perform in terms of GC. I will soon tryout hooks when it's more stable. I mainly work with low end machines with old webkit engines so it will be very interesting to see the performance. Smart TVs are sometimes really terrible at performance. Like 100 times slower than high end laptops.

By the way. A bit of topic. Looked at your sprit app. Looks really cool. Shared it with a designer friend! πŸ‘

inlet commented 5 years ago

With hooks you can actually create state/logic in function components. It's less error-prone because it takes care of unsubscribes automatically (if you use hooks properly of course).

Sounds like testing for Smart TV's is the best way to test performance indeed!

Second thought, let's add a Ticker component but it might be better to make it more agnostic.. and transforms data and returns it to its children, something like this:

const tick = data => {
  return {
    x: data.x * 10,
    y: data.y * 10,
  };
};

const App = () => (
  <Stage>
    <SomeCompomentWithState>
      {state => (
        <Ticker data={...state} process={tick}>
          { output => <Sprite x={output.x} y={output.y} />}
        </Ticker>
      )}
    </SomeCompomentWithState>
  </Stage>
);

What do you think?

inlet commented 5 years ago

By the way. A bit of topic. Looked at your sprit app. Looks really cool. Shared it with a designer friend! πŸ‘

Awesome, thanks! And it uses react-pixi πŸ™Œ

EloB commented 5 years ago

How do you do a lerp with your render prop implementation there? :)

inlet commented 5 years ago

I guess that's our new Ticker component, but the idea is the same ;)

EloB commented 5 years ago

Awesome, thanks! And it uses react-pixi πŸ™Œ

Amazing! Is it possible and easy to do something like this? https://web.archive.org/web/20150316064434/http://www.flatvsrealism.com/

inlet commented 5 years ago

Wayback machine FTW! And yes that kind of things is super easy to do with Spirit. You have a timeline editor and you can make changes anytime. Spirit uses GSAP as the rendering part and should run smoothly.

EloB commented 5 years ago

Wayback machine FTW! And yes that kind of things is super easy to do with Spirit. You have a timeline editor and you can make changes anytime. Spirit uses GSAP as the rendering part and should run smoothly.

πŸ‘

inlet commented 5 years ago

I think we should not add a Ticker component, in my opinion it doesn't fit the declarative paradigm of React. With hooks you can mix imperative code more easily thats why a useTick hook is good fit to tap-in the request animation frame loop. (it takes care of lifecycle events like unsubscribing the raf listener on unmount etc).

I think solving #78 would overrule the need of this component entirely (even the useTick hook), what do you think? So basically update the renderer whenever a PIXI component prop is changed. Not sure how this effects the performance though.

I'll close this issue, but feel free to create a PR for the Ticker component and I'll merge it once reviewed.