Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Android performance #18

Open
lukechatton opened this issue Mar 11, 2019 · 34 comments
Open

Android performance #18

lukechatton opened this issue Mar 11, 2019 · 34 comments

Comments

@lukechatton
Copy link

I was very satisfied with my prototype's animation performance on iOS. I used the rigid bodies scene from react-native-game-engine-handbook as my starting point (matter.js physics). I was rendering entities using View and Image components from react-native.

However, after running the app inside an android emulator I noticed pretty terrible performance. Animating a simple cloud image across the screen was not smooth at all. It was also apparent that matter.js was "slowing down" at some points.

In the rigid bodies scene, you update the matter.js engine inside a system. After some messing around, I noticed that updating matter.js outside of a system removed the matter.js "slowing down" issue.

Old:

const Physics = (state, { touches, time }) => {
    let engine = state["physics"].engine;

    Matter.Engine.update(engine, time.delta);

    return state;
};

New:

let now = Date.now();
setInterval(() => {
    Matter.Engine.update(engine, Date.now() - now);
    now = Date.now();
}, 0);

I know close to nothing about game dev. Does moving the physics updater outside of a system seem like an okay approach? I'm not sure how else to provide a playable experience on Android.

@bberak
Copy link
Owner

bberak commented Mar 11, 2019

Hmm @lukechatton, interesting find.

setInterval with 0 should place the function on the event loop, to be executed when the call stack is free. Perhaps doing this prevents MatterJS from interfering with the current frame - resulting in better perf - but I'm really just brainstorming here..

I'd also recommend trying to run your project on a physical Android device too (if at all possible) - I've found the performance on Android simulators to be poorer than on a physical device (as a rule of thumb). I just borrowed an Android device from a friend.

If you replace your cloud image with a simple coloured View - does the performance improve? I'm curious to see if the animation of the image is the cause of the performance issues (although that would surprise me).

@lukechatton
Copy link
Author

Yes I tried replacing the image with a colored View. No luck. I also tried disabling my systems to make sure it wasn't any of my code holding up the event loop.

I haven't tried running in on a physical Android device yet. I'l have to get a friend to try it out for me.

@lukechatton
Copy link
Author

When I moved matter.js updating out of a system it only prevented the physics time from slowing down. I still wasn't getting buttery smooth animations like I was getting on iOS.

@bberak
Copy link
Owner

bberak commented Mar 11, 2019

What happens if you remove the systems and the physics update altogether? Are you still getting janky animations? If so, I would definitely give a physical device a shot.

Otherwise, if you can share a very basic repo of your project with me, I'd be happy to run it on my simulator and see what results I get..

@bberak
Copy link
Owner

bberak commented Mar 21, 2019

Hi @lukechatton

Have you made any progress with this or hit any roadblocks?

@lukechatton
Copy link
Author

I haven’t been able to do much more testing since my last post. I think this can be closed though, I appreciate your help in understanding what’s going on. 👍

@bberak
Copy link
Owner

bberak commented Mar 24, 2019

No problems @lukechatton - feel free to re-open the issue later if need be.

Cheers!

@bberak bberak closed this as completed Mar 24, 2019
@reyalpsirc
Copy link

@bberak I'm having this issue on Android but in my case it is not related to matterjs at all and it happens on a real device. On iOS everything runs smooth but on Android it does not.

I made a test in debug mode and noticed that the FPS dropped from 60 to around 30 only with this code:

<View style={{ flex: 1 }}>
	<GameEngine
	style={styles.engine}
	systems={[]}
	entties={{}} />
</View>

I also checked if my render method was being called more than once and it's not. Is there a reason for this FPS drop? How can I get better results with this?

@reyalpsirc
Copy link

@bberak It seems that if I use the GameLoop instead, I get better performance.
I stored the entities data on variables of my class and then used this to test and got much more performance:

private renderEntity = (entity) => {
  if (!entity) return null
  const data = { ...entity }
  const Renderer = data.renderer
  delete data.renderer
  return <Renderer {...data} />
}

private renderMultipleEntities = (entities: any[]) => {
  if (!entities || !entities.length) return null
  return entities.map(x => this.renderEntity(x))
}

render () {
  return <GameLoop style={styles.engine} onUpdate={this.onUpdate}>
    {this.renderEntity(this.ball)}
    {this.renderEntity(this.finishDetector)}
    {this.renderMultipleEntities(this.walls)}
    {this.renderMultipleEntities(this.arcs)}
  </GameLoop>
}

@bberak
Copy link
Owner

bberak commented Apr 16, 2020

Hi @reyalpsirc,

Did your Android device have low battery whilst you were using the GameEngine? I ask because I've hear that a number of factors could cause the JS frame ticks to go from 60fps (default I believe) to 30fps (conservative).

Also, where you doing anything else when using the GameEngine component? Did you have any heavy-logic systems running before each render loop? How many entities are you rendering? How many entities do you have altogether (including entities that don't need to be rendered)?

Your GameLoop code does pretty much exactly what the GameEngine does, so I'm curious to find the bottleneck..

@reyalpsirc
Copy link

@bberak Nope, the device had full battery.

In my real scenario I do have more things like listening to the Accelerometer values but, the FPS drop happens even when I disabled that and created a GameEngine with empty entities and systems.

With the GameEngine and all my systems, I was getting like 10/15 FPS with my full system on debug mode while now I get around 25/30 FPS with that same system. One thing that also helped on this was that I only needed to update 2 entities (because all the others are static bodies) and so, I directly forced the update of those on the onUpdate method (using their refs that I created).

Anyway, and "empty" GameEngine should not drop to 30 FPS, specially if an empty GameLoop keeps steady on 60 FPS right?

@melkayam92
Copy link

I also have low FPS on a real device (strong device), only when adding multiple objects to the game.

@reyalpsirc, can you explain how did you manage to fix it? without GameEngine, u still manage to do physics and staff?

@reyalpsirc
Copy link

@melkayam92 What I did for the physics was to put the Matter.Engine.update(this.engine, time.delta); inside the onUpdate function, where "this.engine" is the engine you created on the constructor.

Also, all the objects that use physics and are not static will need to be force Updated. That's why in my case I also have this: if(this.entityRefs['ball']) this.entityRefs['ball'].forceUpdate() inside the same onUpdate method.

This also means that I changed my renderEntity and renderMultipleEntities to this:

protected renderEntity = (entity) => {
  if (!this.entities[entityName]) throw new Error(`Entity not found: ${entityName}`)
  const data = { ...this.entities[entityName] }
  const Renderer = data.renderer
  delete data.renderer
  return <Renderer ref={(ref) => this.entityRefs[entityName] = ref} {...data} />
}

protected renderMultipleEntities = (groupEntityName) => {
  if (!this.entities[groupEntityName]) throw new Error(`Entity group not found: ${groupEntityName}`)
  this.entityRefs[groupEntityName] = {}
  return this.entities[groupEntityName].map((x, idx) => {
    if (!x) return null
    const data = { ...x }
    const Renderer = data.renderer
    delete data.renderer
    return <Renderer key={`${groupEntityName}_${idx}`} ref={(ref) => this.entityRefs[groupEntityName][idx] = ref} {...data} />
  })
}

Note also that I'm now storing all my entities data in this.entities and the refs in this.entityRefs
Hope this helps you :)

@melkayam92
Copy link

Thanks @reyalpsirc , i will give it a try!

@bberak
Copy link
Owner

bberak commented Apr 18, 2020

Thanks for the info and help @reyalpsirc.

Quick question - were your renderers extending from Component or PureComponent?

Also, if you get a chance to run the Handbook project (https://github.com/bberak/react-native-game-engine-handbook) on your device - I'll be curious to see what performance you get (especially from some of the physics examples, and examples that spawn lots of entities).

@reyalpsirc
Copy link

reyalpsirc commented Apr 18, 2020

@bberak They are all PureComponent's

I tried the handbook project before but I was not able to run it's non-expo version. I tried to correct it but I just wanted to check it and so I went with the expo version.

I just used the expo version again now to check the FPS and I found out that on the menu, with the falling snow, has only around 22 FPS.
Regarding the Rigidbodies example, it has around 30 FPS and once I added around 10/15 boxes, it was showing 22-25 FPS.

The device I tested this was a Nexus 5X

@bberak
Copy link
Owner

bberak commented Apr 19, 2020

Thanks for the info @reyalpsirc .. I'll head out and get a physical Android device to do some testing/debugging. I only had access to a Android simulator (which worked fine), but that cleary doesn't correlate to a physical device.

@lexengineer
Copy link

Hello @bberak,

Did you have any luck with your testing on a real Android device?

I also keep getting some hanging performance issues when running my application on Android and this happend when I added around 100 different bodies and some sprites, so this could be the reason. But on the other hand on iOS it works great without any issues.

Any advice is appreciated, thank you 👍

@bberak
Copy link
Owner

bberak commented Jul 16, 2020

Hey @reyalpsirc and @oleksiikiselov,

I also experienced the frame drop when adding lots of bodies/sprites (using a very low-end Android device).

I've found that I can improve the Android performance a fair bit (about 60%-80% on my device) by overriding the default timer that the GameEngine uses to run the game loop.

Here is a an example of how to switch out the default timer: https://github.com/bberak/rnge-particle-playground/blob/72e21513e905170e502dbf89a7b994d8224d7d17/App.js#L15

And here is a link to the timer that I use (it tries to run the game loop at about 60fps): https://github.com/bberak/rnge-particle-playground/blob/master/src/utils/perf-timer.js

Let me know how that goes - hopefully it helps..

Also, I've never tried this, but it might help to try using the Hermes engine: https://reactnative.dev/docs/hermes

@bberak bberak reopened this Jul 16, 2020
@medmo7
Copy link

medmo7 commented Nov 10, 2020

hey @bberak,
I'm experiencing the lags in android as well, i tried the timer fix but i haven't seen much improvement. I use MatterJs to handle entities positions. I have 10 entities scrolling down in the screen, basically round colored view, and when it goes out of the screen i translate the position to the top again. If i reduce the number of entities to 2 the performance is better but still not as good as on IOS.
Thanks

@medmo7
Copy link

medmo7 commented Nov 10, 2020

I just added Hermes, and i made a release Android build on my phone, the result is comparable to what i get in a debug build on an IOS device.

@reyalpsirc
Copy link

@medmo7 How are you updating the position given by MatterJS? Are you using the component’s state or are you updating each required entity individually through a ref and a call to its forceUpdate method?
Besides the moving entities, do you have any other entities?

@medmo7
Copy link

medmo7 commented Nov 11, 2020

@reyalpsirc i use component props to update moving entities. I use the same comp for all the moving entities here is it:

import React  from 'react';
import {TouchableOpacity} from 'react-native';

export default function Tap(props) {
  const width = props.size[0];
  const height = props.size[1];
  const x = props.body.position.x - width / 2;
  const y = props.body.position.y - height / 2;

  const handlePress = () => {
    props.eventDispatch({
      type: 'TAP',
      body: props.body,
      category: props.category,
    });
  };
  const color =
    props.category === 'good'
      ? 'green'
      : props.category === 'bad'
      ? 'red'
      : 'gold';
  return (
    <TouchableOpacity
      onPress={handlePress}
      style={{
        position: 'absolute',
        left: x,
        top: y,
        width: width,
        height: height,
        borderRadius: 100,
        backgroundColor: color,
      }}
    />
  );
}

Besides the moving entities i have the physics entity which includes the MatterJs engine and world. I have also a spriteSheet looping in the scene, but i think it's not causing any issue because when i remove it the perf are the same.
Thanks

@reyalpsirc
Copy link

@medmo7 Just a guess here but have you tried with a PureComponent class instead of a functional component?

@medmo7
Copy link

medmo7 commented Nov 11, 2020

@reyalpsirc yes i did, but the positions of entities does not update in that case. Maybe i missed something.

@reyalpsirc
Copy link

reyalpsirc commented Nov 11, 2020

@medmo7 in my case, I used the PureComponent for my entities and then on the PhysicsSystemUpdate method (the one where you call Matter.Engine.update) I accessed the ref of my ball (in my case it was a ball moving according to accelerometer) and forced an update there: if (this.ballRef) this.ballRef.forceUpdate().

@medmo7
Copy link

medmo7 commented Nov 11, 2020

sorry how do you get access to the ball ref in the system file?

@reyalpsirc
Copy link

@medmo7 I don’t have the system on a separate file. In my case it is a method of my Main component and so, all I need to do is set the ref on the render method and use it directly on the system method.

@bberak
Copy link
Owner

bberak commented Nov 13, 2020

@medmo7 I think if you use PureComponent you will also need to provide a shouldComponentUpdate method. My guess is that your positions did not update because the body prop itself doesn't change from frame-to-frame (even though the body.position.x and body.position.y values may change. PureComponent will only do a shallow compare as far as I'm aware - so it won't trigger an update unless the body prop itself changes..

@medmo7
Copy link

medmo7 commented Nov 13, 2020

@bberak you right, when providing shouldComponentUpdate the positions do update. I decided to change the gameplay from scrolling entities to just appearing and disappearing after an amount of time, this gives better perf result and i can use less entities.

@bembem1011
Copy link

https://github.com/bberak/react-native-game-engine-handbook
I clone and build, install above github on my device. It run so slow (added hermes), not smoothly like running on ios :(
my device is samsung S7 android 8

@rafaelmaeuer
Copy link

rafaelmaeuer commented Jul 18, 2021

When building my game-app with rn-game-engine I always noticed a bad performance on Android. I then did some performance measurements between iPhones and Android phones with Geekbench, figuring out that iPhones have much higher single core scores compared to Android phones. Android Devices seem to back more on multi-core power (most of the time 8 cores) with a low frequency while iPhones count on less cores (2-4 cores) but high frequency.

As RN doesn't make use of multiple cores due to the JS-Bridge bottleneck this explanation seemed always useful to me, why iOS performance is so much better than Android. But today and after reading this issue, I did some testing with an iPhone 6S, a Samsung Galaxy S7 Edge and a Samsung Galaxy A6 with the rn-game-engine-handbook and Geekbench 5:

IMG_8896

  • iPhone 6S [left] (seems to have a Geekbench 5 Single-Core Score of 529) has a stable JS-fps of 60.
  • Samsung Galaxy S7 Edge [middle] (Geekbench 5 Single-Core Score of 362) has an average JS-fps of ~ 10.
  • The Samsung Galaxy A6 [right] (Geekbench 5 Single-Core Score of 118) has an average JS-fps of of ~ 25.

IMG_8901

So the Single-Core Scores on Android aren't reflecting the performance of rn-game-engine on Android devices. I will try to upgrade my game-app to hermes in hope of some small performance improvements, but otherwise currently the game isn't usable on these Android devices at all... @bberak any thoughts on this?

@smerch88
Copy link

@rafaelmaeuer any updates on performance?

@rafaelmaeuer
Copy link

Nope - I did not test this for a long time...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

9 participants