Open lukechatton opened 5 years ago
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).
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.
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.
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..
Hi @lukechatton
Have you made any progress with this or hit any roadblocks?
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. 👍
No problems @lukechatton - feel free to re-open the issue later if need be.
Cheers!
@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?
@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>
}
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..
@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?
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?
@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 :)
Thanks @reyalpsirc , i will give it a try!
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).
@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
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.
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 👍
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
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
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.
@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?
@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
@medmo7 Just a guess here but have you tried with a PureComponent class instead of a functional component?
@reyalpsirc yes i did, but the positions of entities does not update in that case. Maybe i missed something.
@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()
.
sorry how do you get access to the ball ref in the system file?
@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.
@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..
@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.
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
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
:
529
) has a stable JS-fps of 60.362
) has an average JS-fps of ~ 10.118
) has an average JS-fps of of ~ 25.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?
@rafaelmaeuer any updates on performance?
Nope - I did not test this for a long time...
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:
New:
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.