1

EDIT: I have now isolated the problem into this little gist and this jsfiddle.

I would like to use Redux for state management in a three.js-based real-time 3D game. I've created a small prototype for testing purposes, but even this very simple toy application shows some serious performance issues.

My idea was to build a render loop that dispatches an UPDATE action every frame, using the requestAnimationFrame() callback:

index.js

import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import * as THREE from 'three'

import rootReducer from './reducers/rootReducer'

import App from './components/App'

import { getThreeInitialState, getThreeRenderer } from './threeApp/threeApp'

const threeInitialState = getThreeInitialState();

const initialState = {
    running: false,
    ...threeInitialState
}

const store = createStore(rootReducer, initialState)

render(
    <Provider store={store}>
        <App />
    </Provider>,
    document.getElementById('root')
)

const threeRenderer = getThreeRenderer()

const updateThreeApp = () => {
    threeRenderer.render(store.getState().scene, store.getState().camera)
    const timestamp = Date.now()
    requestAnimationFrame(() => store.dispatch({ type: 'UPDATE', timestamp: timestamp }))
}

store.subscribe(updateThreeApp)

store.dispatch({ type: 'UPDATE' })

document.addEventListener('click', () => store.dispatch({ type: 'CHANGE_MATERIAL' }));

The event listener in the last line dispatches an event that changes the material of the cube which gets rendered for testig purposes. Of course in the game there will be some more listeners for processing keyboard and mouse events.

My reducer looks like this:

rootReducer.js

const changeMaterial = (state) => {
const newState = { ...state }
    newState.scene.getObjectByName('box').material.wireframe = !newState.scene.getObjectByName('box').material.wireframe
    return newState
}

const rotate = (state) => {
    const newState = { ...state }
    newState.scene.getObjectByName('box').position.y = 2 * Math.sin(Date.now() / 1000)
    return newState
}

const rootReducer = (state, action) => {
    switch (action.type) {
        case 'CHANGE_MATERIAL':
            return state.running ? changeMaterial(state) : state
        case 'RUN':
            return {
                ...state,
                running: true,
            }
        case 'UPDATE':
            return rotate(state)
        default:
            return state
    }
}

export default rootReducer;

The two functions on the top are used for changing the material and position of the rendered cube.

Updating the three.js scene every frame and moving the cube works perfectly fine. However, every time I fire the click event to change the material, the framerate goes down a bit and stays like this. So after randomly clicking for a few seconds it goes from 60 FPS down to about 20 FPS and doesn't go up anymore, although the action is finished. Looking at the Chrome performance profile, there is a significant increase in CPU usage every time the click event get's fired. CPU usage also stays on this higher level after the event.

I also use ReactDOM.render() for rendering things like the starting page, loading screens etc. but I would like to keep the three.js render loop outside of the React part to separate things and to avoid unneccesary DOM updates. The only connection between the three.js-part and React is that the container DIV for the three.js renderer is inside one of the React components.

The full code is uploaded in this repository and I have also created a small gist showing how my redux rendering loop is supposed to work.

This is the first time I am using Redux and React so I probably just overlooked something. Or is it a rather bad Idea to use this kind of architecture for a 3D game? Any help would be very appreciated!

2
  • is this newState.scene.getObjectByName('box').material.wireframe = !newState.scene.getObjectByName('box').material.wireframe a good thing? I know redux likes diffing stuff and all that, but searching the entire scene graph on both sides of this comparison, and every frame seems pretty intense? Commented Aug 17, 2017 at 17:39
  • Yes, I guess passing the whole scene graph through the reducer isn't really the most effective and most Redux-like way to handle things. It would probably be best to just store some information like wireframe: true or wireframe: false inside the state tree and then apply the state or state changes to the scene graph every update. I'm thinking about something like mapStateToProps() in react-redux, I'm just not sure yet how to solve this... Commented Aug 18, 2017 at 10:45

1 Answer 1

2

SOLVED: It seems as if calling requestAnimationFrame() on both actions, UPDATE and CHANGE_MATERIAL caused the problem. Adding a lastAction property to the state object and rendering only when it equals UPDATE solved the issue. See this gist.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.