Jump to content

PIXI with React or Angular


alevys
 Share

Recommended Posts

Hello! Please tell me whether to combine the pixi with react or angular to build the UI. I'm a flash developer with AS3 and turn on JS in the world of flash, works very effectively bundle Starling + Feathers, Pixi, I have not found a decent replacement Feathers. Spend time creating a UI library from scratch is not very desirable. What would you recommend friends? Thank you.

Link to comment
Share on other sites

Hello, fellow Flash developer!

There're https://github.com/pixijs/pixi-ui in development and in serious need of documentation :) Poke @bQvle to dedicate more time to docs.

As for React/Angular, I'm sure @dmko can help you ;) I used pixi with angular over canvas and i know that people use react with pixi, even famous https://go.twitch.tv/adam13531

Link to comment
Share on other sites

Rendering perf for ng4, React, Vue, Aurelia etc etc etc is all much of a muchness, some other stuff like JS weight (both over the wire and parsing on mobile devices) does vary but largely if you pick any of the larger web frameworks/libs the perf is comparable.

No idea how these frameworks/libs performance stacks up against pixi-ui.

For me there are usually significant benefits to using the DOM (via vdom or not) when creating UI stuff, but your use-case might differ where the advantages are less.

Generally the UI isn't going to be a perf hit unless you've got a ton of animations etc etc but, again, depends on your particular use-case.

Link to comment
Share on other sites

I haven't done a ton of React stuff but I am tinkering a lot more lately :)

Here's a little JSFiddle to benchmark - it doesn't include PIXI stuff but just to see how long it takes to get from a render on a parent to an update on a child, and I think this is roughly the way I'd structure a React+PIXI app (the pixi updates happen in the child's componentWillReceiveProps() - and the render() from child would just always return null)

https://jsfiddle.net/dakom/e65tbxsy/

I'm getting less than .1ms between the parent's render() and the child's componentWillReceiveProps(). Assuming I benchmarked it right - that's the performance hit you get from using React vs. just straight JS., and it's way more than acceptable imho. However, even if I did benchmark it right, there's a few questions:

1. performance.now() is super fine-grained... a normal js pipeline isn't going to be 0 either, so it's a bigger win than .1ms

2. really important question - is this per component or across a render cycle? Need to test out like nesting 10 or 100 components deep. If it stays like this, then React is 100% fine imho. If it adds .1ms per layer, I dunno, gotta really think about that.

3. Realistically, a complex app is...well, complex. Performance bottlenecks and developer headache can happen in lots of ways. If using React is performant enough, even if it's not the absolute best way to squeeze every ms out, it may be a worthwhile save in other areas.

For me it really depends on #2 which hopefully I'll test in the near future just to be sure. If that turns out to be fine then I think using React with PIXI could be a really nice architecture.

Link to comment
Share on other sites

@dmko I can't remember but I think I approached this slightly differently by skipping rendering for the pixi canvas element by having `componentShouldUpdate` return false and having the render method create the initial canvas element and then set up and attach pixi in `componentDidMount`. The canvas element component will be a leaf node anyway, which I've actually enforced using the `componentShouldUpdate` short circuit, but thats fine as your UI and other elements will come from another branch.

7 hours ago, dmko said:

For me it really depends on #2 which hopefully I'll test in the near future just to be sure. If that turns out to be fine then I think using React with PIXI could be a really nice architecture.

As most frameworks do, React can suffer with lots of siblings (rather than lots of depth), such as a large list (this is also prevalent when rendering a tile map, but not really relevant here as we're talking about only UI rendering with React, not that actual game view). In this case you can limit the number of elements being rendered by providing a view into your list, i.e. if only 10 are on-screen at a time then only 10 (or maybe 11/12 for the edges) should be in the list, i.e. in the vdom and then dom, this is the approach mobile languages usually take to list rendering, react-virtualized takes this approach.

You should also ensure that all perf benchmarking is done in React's production mode, the dev/debug build is significantly slower for stuff with lots of siblings.

These optimisations (and others) are baked in to React and they didn't just appear over-night, they took a lot of iterative effort, this is where the trade-off between vanilla JS and a framework/lib comes in to play, sure, in theory a custom-built tailored vanilla solution can be quicker but it requires an awful lot of time and skill to best some of these generic solutions adopted by mature libraries.

7 hours ago, dmko said:

2. really important question - is this per component or across a render cycle? Need to test out like nesting 10 or 100 components deep. If it stays like this, then React is 100% fine imho. If it adds .1ms per layer, I dunno, gotta really think about that.

Yep, in theory React will check every node but it uses a vdom so that it can make efficient optimisations. Your dom structure is a tree, vdom models this and with strict top-down data flow you can be sure that if the props given to a node do not change then neither do any of its children, therefore if the props don't change then you can stop evaluating that entire branch. Note, however, that `setState` ruins this as it breaks top-down flow. This isn't always the easiest pattern to adopt if you're already invested in setState but it is very powerful. Not that most apps need to worry about it. Stuff like component prop evaluation and inline render functions etc etc are a thing people like to bikeshed over but rarely is it a top priority for optimisation i.e. you'll hit other bottlenecks before this becomes a problem (probably!).

Given that your UI may only change fairly infrequently (i.e. not every frame, also note that if you animate via CSS then this counts as not changing) you could get into a situation where React performs only one top-level calculation to see whether it should do any work for that frame. If structured correctly then you can even eliminate the need for this check by only attempting to render when something changes (i.e. redux adopts this pattern).

There also no need to have the pixi canvas element inside the vdom tree, it can just be an element in the page and completely separated from what React knows about, this might well even be the best solution, certainly its the simplest. Doesn't remove React rendering calcs but does help to reduce when you ask React to render stuff.

Link to comment
Share on other sites

Ah - so I'm looking at more for describing the PIXI scene itself, not for additional UI. I'll try to mock up the bunny-mark test and put it on github for all to fork and tinker with. I actually may need to use this approach for an upcoming project so I'm very interested in any gotchas

Link to comment
Share on other sites

Just pushed an update - might take a little bit for Travis/github, but testing locally - it didn't seem to fix it, I think it's probably something with React itself :\

Btw I suck with profiling, when you have a minute can you take a couple screenshots to show me where you see that 19% GC stat?

Shutting down for the night, thanks for taking a look

Link to comment
Share on other sites

Ah, I misunderstood what you were trying to do as the OP was talking about UI. Awesome throwing a project together but you're forcing a couple of things that are going to cause severe slowdowns over raw Pixi.

By throwing everything in to a component you're adding them all to the virtual dom (for other readers, a vdom is representation of the actual dom in memory, the theory being that dom access is relatively slow when compared to in-memory access), React has to evaluate all of these nodes and decide what to do next and, like all other libs/frameworks, struggles a little with large lists of sibling elements. Those nodes aren't getting rendered in to the dom (interesting, I've done this test with React, and others, using the dom and got way better performance) but React still has to traverse them all and consider them.

Aside from the array mapping (which is still slow, as is foreach) and concatting (probably slow, not sure) you're forcing React to make a diff against all those bunny nodes when it is isn't necessary. Using componentShouldUpdate you can stop evaluating nodes at a higher place in the tree (probably <Bunnies/>, but better would likely be <Renderer/> as nothing below it makes it in to the DOM), although you'd have to check as I'm not sure if React will even call though child life-cycle methods (i.e. you're reliant currently on willReceiveProps for your leaf nodes), actually, thinking about it, you're reliant on evaluating those leaf nodes and I'm guessing returning false in a parent shouldUpdate function will nuke any children from the flow.

Your problem is trying to fit a square box in to a round hole by forcing the entire React diffing engine to do work it doesn't need to, Pixi already has a representation of state internally that makes sense to it and is optimised for it. The absolute simplest way is to remove the Pixi component from doing any further work in the React world, probably easiest is returning false from shouldUpdate for that component. Once you've got access to the Pixi stage (etc) you can just manipulate them directly rather than faffing around turning them in to full blown components.

Alternatively, an even better approach would be to remove the DOM renderer from React and forcibly bolt Pixi on as the renderer. React was designed with this in mind and you have projects like React-Native that replace the DOM renderer with native classes, the awesome regl project using a 3d context (edit actually this is just an inspired-by react, it doesnt work how I thought it did! sorry, its still awesome though!) and, most relevant to you, have you seen react-pixi which bolts on pixi as the rendering layer instead of react? Not sure what the perf is like there either but might give you some ideas on how to improve things.

Link to comment
Share on other sites

Yeah - everything you're saying is spot-on @mattstyles  

The performance killer here is definitely React as far as I can tell.

This attempt to use React as-is and simply render `null` does rely on lifecycle methods so stopping propagation via shouldComponentUpdate isn't going to work completely. I did change everything except `withPixi` to be PureComponents just now though - negligible gains. 

I'm not aware of how to bypass all that evaluation stuff... I looked at react-pixi and couldn't make heads or tails of what's going on there and I'm not immediately convinced without seeing a bunnymark. Maybe I'll look more into it from a more fundamental place of like how it's done for react-native.

Though, on one foot, I haven't really come across relevant benchmarks yet. The "high load" things seem to be cases of like dealing with a few hundred elements and being blown away... ;) I want at least 10,000 bunnies at 60fps without a hiccup - not sure if react is simply the wrong tool for the job (i.e. can react-native do that? and I mean where each bunny is a distinct class - no cheating by batching draws)

From another angle - the simplicity (after all the boilerplate hackery) of being able to describe the scene in simple declarative JSX is pretty compelling.

I'm not sure how to really approach this subject since I don't know the internals of PIXI - but do you think that's something that could be done as like native sugar for the PIXI scene graph itself?

In other words something like this with built-in ability to "removeChild" when the JSX changes? I get that it's a big sweeping topic and a one liner like "yeah sure we can do that" may not be simple... feel free to discuss in larger depth :)

<Container>
   <Sprite x={10} y={10} texture={someTexture} />
</Container>

 

Link to comment
Share on other sites

I just did some brief updates to a quick POC to include a basic bunnymark (logic deliberately stolen from here), you can see it running here, performance is near identical to regular pixi alone (which, actually, raises another point, maybe @ivan.popelyshev can help, why is v4 approx half the speed of the v3 bench? is it just an issue with the bench code rather than pixi itself?). I unhelpfully called that POC react-pixi ages ago, before I realised there is a proper react-pixi project. Also, if you check the source you'll want to look in the `bunny` folder, not `src`, thats old, as I said, its a POC, and a little messy.

This isn't really what you're after though as it takes React totally out of the loop for pixi, in my example the two worlds communicate via pub/sub, you could easily implement tighter integration if you really wanted to. As React is removed from the equation Pixi just does its thing, hence the near identical perf, whilst declaratively describing the pixi scenegraph is attractive, pixi isn't optimised to work like this and I'm not sure you can patch it on and get anywhere near the same perf, react-pixi maybe does a better job as it replaces the renderer but in theory you're still asking react to traverse the entire scene graph and perform diffs and even if you take away the slower dom rendering layer React's use-cases don't really include super speedy perf over 100k ever-changing nodes.

As with all things though, if you can get 'decent' performance and you see benefits of declaratively specifying the scene graph in JSX and your use-case doesn't push the perf limit then it would work, but it kind of feels like trying to dam a river with a few old towels, not really the right tool for the job.

I'd like to actually kick the tyres on react-pixi though, as, in theory, it should remove some of the bottlenecks and still let you declaratively specify a scene graph. It'll have to wait until tonight/tomorrow though. :(

2 hours ago, dmko said:

(i.e. can react-native do that? and I mean where each bunny is a distinct class - no cheating by batching draws)

react-native is totally different, its like react-pixi in that it is (in essence at least) simply a replacement for the react-dom module.

2 hours ago, dmko said:

I'm not sure how to really approach this subject since I don't know the internals of PIXI - but do you think that's something that could be done as like native sugar for the PIXI scene graph itself?

I think you're pretty much on the right track by stopping components from doing any rendering work and just referencing the underlying pixi structures (such as Sprite) when your components update.

What might be really clever would be to create a transform that converts your scene-describing JSX into regular ole pixi calls, not sure if this would even be possible but you'd get the perf back as it would be 'regular' pixi-style code by the time it hits the browser but you'd still get the advantages of writing the scene graph using JSX. This sounds totally nuts, but sometimes nuts things can turn out awesome (sometimes rubbish!). 

Link to comment
Share on other sites

8 minutes ago, mattstyles said:

This isn't really what you're after though as it takes React totally out of the loop for pixi

Yeah, for this context I'm focused really on using it with react (or as I'm now looking into, maybe Inferno)

Cool stuff though :) and reminds me that I'm missing a baseline comparison hehe... will write that now :D

9 minutes ago, mattstyles said:

What might be really clever would be to create a transform that converts your scene-describing JSX into regular ole pixi calls

Right on, that's what I meant by like "built-in ability to "removeChild" when the JSX changes" but you nailed the overall idea in that sentence.

It would be super awesome if that were possible imho, I don't think it would be rubbish unless it introduced a bunch of restrictions like specific props you have to pass in etc (kindof like in my implementation, where you have to pass a "parent" prop)

Link to comment
Share on other sites

  • 3 weeks later...

@caymanbruce Thanks for bringing this thread up - I should note that my experiments here were superseded at this other thread:

 

I don't know anything about Angular. With React, essentially, there's two approaches :)

1. Using React as-is with the built-in React-Dom renderer. With this approach, there's nothing really special going on, you just handle all your imperative PIXI updates in render() or one of the other lifecycle methods. To make it worthwhile, you'd probably want to create some boilerplate components that do this for you and expect certain props to exist. E.g. a Sprite component that expects `texture`, `x`, `y`, etc.

2. Replacing the React-Dom renderer with a custom renderer. This is what's posted at the other thread - you can get it at https://github.com/dakom/react-pixi-renderer

However, I'm not sure how far I'll pursue that... as I'm playing with it more, it turns out writing a React-renderer is super easy. It may make more sense to have a custom per-project renderer (using that repo as a starting point) than abstracting everything into generic stuff.

For example, it may be more useful to have like a Character instance/element/component built-in to the renderer rather than abstracting over a Sprite instance/element/component. That difference won't make much sense unless you plan to go that route, but it's a reason why I'm probably not going to pursue the react-pixi-renderer too much beyond making sure it works as a boilerplate/starter package.

Link to comment
Share on other sites

20 minutes ago, caymanbruce said:

I am familiar with a single canvas on a single page. But I never know how the combination of framework work.

In this sense nothing changes. You're not adding/removing elements from the page, you're wrapping all the imperative canvas calls up in a declarative framework.

The same way React-DOM deals with all the element.innerHTML stuff, your react renderer (or components) deal with all the canvas.draw (or rather, PIXI.*) type calls - but on the app level you don't touch any of that, you just write the changes declaratively.
 

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...