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

Can't destroy previous entities when game reloads #8

Closed
vonqo opened this issue May 24, 2018 · 22 comments
Closed

Can't destroy previous entities when game reloads #8

vonqo opened this issue May 24, 2018 · 22 comments

Comments

@vonqo
Copy link

vonqo commented May 24, 2018

I've got system function to handle events like this way:

export default (entities, { events }) => {
  events.forEach((event) => {
    switch (event.type) {
      case EV_CONTROL_CAMERA_SCALE:
        world_scale(entities, event.value, event.duration);
        break;
      default:
        break;
    }
  });

  return entities;
};

And function world_scale looks like:

const world_scale = (entities, scale_value, duration) => {
  const interval_timing = 30;
  const tick = Math.floor(duration / interval_timing);
  const ratio = (scale_value - 1) / tick;
  let counter = 0;
  const interval = setInterval(() => {
    Object.keys(entities).map((objectKey) => {
      entities[objectKey].body.scale += ratio;
    });
    counter++;
    if (counter === tick) {
      clearInterval(interval);
    }
  }, interval_timing);
};

This function scales all the entities smoothly in given duration, constantly. Like camera zooming. Function works perfectly until i exit the game by navigating into the other component and re-enter to the game component. (I'm using react-native-router-flux to navigate)
The problem is when i re-navigate into the game and dispatch the event EV_CONTROL_CAMERA_SCALE, then function world_scale will be working with previous entities (which should be not existing). But in the meantime entities already rendered on the screen and not scaling.
I think navigation is not the problem source. Every time when i navigate, i clear the previous state by using Actions.reset

@vonqo vonqo changed the title Can't destroy dispatched events and previous entities when game reloads Can't destroy previous entities when game reloads May 24, 2018
@bberak
Copy link
Owner

bberak commented May 24, 2018

Hi @lupino22,

Thanks for getting in touch. One of the the things I'm not currently happy with in the React Native Game Engine API is how the entities are updated externally.

Currently, the game engine will hold a reference to your initial entities and only reload when the whole GameEngine unmounts and remounts or when the swap method is called this.refs.gameEngine.swap( { NEW_ENTITIES }). This isn't intuitive at all - and I'll try dedicate some effort into fixing this.

Back to your issue in particular.. When you navigate away from your game and back again, is the component that contains your <GameEngine ref="gameEngine" ... /> being unmounted and remounted? If not, it means that your entities are not refreshing and you will need to swap them manually this.refs.gameEngine.swap(...)

Another thing to keep in mind is that your setInterval callback will always and only have a reference to the entities that get passed into world_scale the first time it is called. If the entities get swapped - and the timer is not finished - it will still have a reference to the old entities.

Lastly, what version of RNGE are you using? I pushed an update about a week ago that clears the events every time the swap and start methods are called. I don't think this is related, but it might help us figure out what is going wrong here.

Cheers!

@vonqo
Copy link
Author

vonqo commented May 24, 2018

Hi @bberak

Game Engine component is remounted when i navigate back and i use this.refs.engine.swap(new World()); when components is mounted.

Also i managed the way to interrupt the intervals when game engine is unmounted.
i am currently using version 0.9.0. I will update the package as soon as possible.

I'll keep in touch. Thanks for the response.

@vonqo
Copy link
Author

vonqo commented May 24, 2018

Is there any way to make timed animation (like world_scale). So i can replace my function without doing external update for entities. And i updated my RNGE, still same. i appreciated that swap function update to clear events. No needed to clear events by manually.

@bberak
Copy link
Owner

bberak commented May 24, 2018

Hi @lupino22,

I've built an "animation" system for one of my games that avoids the need to use external timers and callbacks. Perhaps you can adapt it to your needs. Here is the source of the animation: https://github.com/bberak/react-native-donkey-kong/blob/master/src/systems/animation.js

And here is an example of how to use the system:

export default (entities, { events }) => {
	const mario = entities.mario

        //-- Make sure your entity has an "animations" component, other wise you will get an undefined error
	mario.animations.jump = {
		args: {},
		duration: 700,
		animate(mario, percent, args) {
			//-- This will be called every frame 
			//-- whilst the animation is running. 
			//-- "percent" indicates how much of the 700ms animation has been completed
		},
		complete(mario) {
			//-- This will be called when the animation is completed
		}
	}
}

See https://github.com/bberak/react-native-donkey-kong/blob/master/src/systems/platforms.js for the full usage.

@vonqo
Copy link
Author

vonqo commented May 24, 2018

Thanks @bberak

I believe doing by your "animation" system is more proper way than mine. I'll try your "animation" system. Thanks for your effort.

@bberak
Copy link
Owner

bberak commented May 24, 2018

No worries @lupino22, let me know how it goes!

@vonqo
Copy link
Author

vonqo commented May 24, 2018

Hello @bberak
I replaced my functions using "animation". But still same :(
Entities are recreated every time when i reload RNGE, seems fine there. But system functions still working with previous entities. Is this might be navigation related problem?

@bberak
Copy link
Owner

bberak commented May 25, 2018

Hi @lupino22,

Well at least we can cross the animations off the list :)

You mentioned that the component is unmounted and re-mounted during the navigation process? Can you show me your render function? In particular, if you have something like this:

<GameEngine 
    entities={new World()} //-- The engine will receive new World() when it mounts
 />

Or

<GameEngine 
    entities={this.state.currentLevel} //-- The engine will receive this.state.currentLevel when it mounts
 />

Also, what does new World() return? Can you share a quick snippet to indicate the general code?

If you're using a third party library like ThreeJS or something - you might need to clean up your scene.. If your scene is holding references to the old objects - it could still get rendered.

Another way we can debug this would be to add a distinct entity into your world { "test": "Find me" } when the navigation occurs, and find and log this entity in your systems - that way you can be sure whether or not your systems are receiving the new entities or not.

Also make sure your systems are not holding any references to the entities after they are done. The systems should always process the entities being passed into them by the engine.

@vonqo
Copy link
Author

vonqo commented May 25, 2018

Redering

componentDidUpdate() { // -- when audio asset is ready i will load my engine
	if (this.isReady && !this.state.running) { // -- audio ready && game engine is not running
		this.prepareGameEngine();
	}
}

prepareGameEngine = () => { // -- load the entities
	this.refs.engine.swap(new World());
	this.setState({  // -- GameEngine 'running' property gets this state
		running: true
	});
};

isGameReady() {  // -- Return false until audio asset is ready
	if (this.props.audio_ready) { // -- comes from redux
		this.playBackgroundAudio();
		this.isReady = true;
		return this.isReady;
	}
	this.isReady = false;
	return this.isReady;
}

render() {
	if (this.isGameReady()) {
		return (
			<GameEngine
				ref={'engine'}
				systems={System}
				running={this.state.running}
				onEvent={this.handleEvent} >
				<StatusBar hidden />
			</GameEngine>
		);
	}
	return (
		<LoadingScreen />
	);
}

World:

export default () => {
    const starting_scale = 1;
    return {
        background: new Background(
          { x: 0,
            y: 0 },
          starting_scale, 'night', { opacity: 1 }
        ),
        treeStack1: new TreeStack(
          { x: 800,
            y: 200 },
          starting_scale
        ),
        bird1: new Bird(
          { x: 680,
            y: 300 },
          starting_scale,
          [{ rotate: '0 deg' }]
        ),
    };
};

@vonqo
Copy link
Author

vonqo commented May 25, 2018

Also i made something like this :p

export default (entities, { events, dispatch }) => {
  ...
  some_function(entities, events, dispatch);
  return entities;
};

const some_function = (entities, events, dispatch) => {
  ...
  dispatch({ type: EV_CONTROL_CAMERA_SCALE, value: 2, duration });
  dispatch({ type: EV_CONTROL_CAMERA_MOVE, value: { x: -250, y: -50 }, duration });
}

@bberak
Copy link
Owner

bberak commented May 25, 2018

Hi @lupino22,

Can you try changing your code to (just for debugging) to something like this:

<GameEngine
				ref={'engine'}
				systems={System}
				running={this.state.running}
                               entities={new World()}
				onEvent={this.handleEvent} >
				<StatusBar hidden />
			</GameEngine>

I say this because if your component is mounting and unmounting the game engine - then you probably don't need to swap manually.

Also change your entities to:

export default () => {
    const starting_scale = 1;
    return {
       test: { date: new Date() }, //-- I added this..
        background: new Background(
          { x: 0,
            y: 0 },
          starting_scale, 'night', { opacity: 1 }
        ),
        treeStack1: new TreeStack(
          { x: 800,
            y: 200 },
          starting_scale
        ),
        bird1: new Bird(
          { x: 680,
            y: 300 },
          starting_scale,
          [{ rotate: '0 deg' }]
        ),
    };
};

Then check the value of test in your systems and see what is being printed:

export default (entities, { events, dispatch }) => {
  console.log(entities.test.date)
  return entities;
};

Hopefully that will get us more info for debugging..

@vonqo
Copy link
Author

vonqo commented May 25, 2018

Hi @bberak

I made changes that you've made and debugging
First time console shows date:

20:07:19
20:07:19
20:07:19
...

When i re navigate into game component

20:07:19
20:07:44
20:07:19
20:07:44
20:07:19
20:07:44
...

Look like i'm having multiple test entity. If re-navigate the component n times, then i'll have n times of individual time.

@bberak
Copy link
Owner

bberak commented May 25, 2018

So I wonder if it's possible that you have multiple instances of the GameEngine running? Could it be that each time your are navigating, your navigation system is adding another GameEngine into the viewstack, and not popping off the current GameEngine when you are navigating away?

@vonqo
Copy link
Author

vonqo commented May 25, 2018

Yes i think so. I'm about to change my navigation library which is react-native-router-flux to react-native-navigation. Because react-native-navigation is this is the most actively maintained navigation library implemented natively. That makes sense to me. Also i was put some variables in "system file" to save the story sequence state. I should've put those things as "entity".

@bberak
Copy link
Owner

bberak commented May 25, 2018

Yeah that makes sense @lupino22. It might be worthwhile to put the componentWillUnmount() function on your component to determine whether or not the unmounting is happening or not:

componentWillUnmount() {
   console.log("I am unmounting")
}

@vonqo
Copy link
Author

vonqo commented May 25, 2018

That is the one thing make me so confused. Component life cycle is just fine. When i re-navigate componentWillMount is working. When i exit componentWillUnmount is working too.

@bberak
Copy link
Owner

bberak commented May 25, 2018

After the unmount happens, does you see logs in the console that indicate that the engine is still running?

Also, do you need the new keyword when setting up your entities? It looks like a regular function call World() will be sufficient (but I don't this this is related)..

Can you tell if componentWillMount() is being called once or multiple times?

@vonqo
Copy link
Author

vonqo commented May 25, 2018

Once i unmount happens, game engine will be not running.

Just in case I put the new keyword to make sure creating new object. Yes it is irrelevant in JSX.

componentWillMount is called once like expected.

@bberak
Copy link
Owner

bberak commented May 25, 2018

Hmm, I'd suggest the following:

  • Stripping out the navigation and verifying that everything is working as expected (or not as expected) using vanilla react components.
  • Sharing a stripped down version of your code with me (either via a repo or zip file) and I'll see if I can debug it on my side
  • Having a chat over Skype or something to see if we can debug together.

@vonqo
Copy link
Author

vonqo commented May 25, 2018

That sounds really nice. Let me talk with my team about sharing my source code.

How about I'll mail my response about this.

@bberak
Copy link
Owner

bberak commented May 25, 2018

Sounds good - @lupino22, if you do share some code - just completely strip out the proprietary stuff, a skeleton will do. Cheers.

@bberak bberak closed this as completed in a350e37 May 26, 2018
@bberak
Copy link
Owner

bberak commented May 26, 2018

I've found the bug.. The GameEngine uses a timer to run the systems at ~60 fps. However, there is a bug with how I'm clearing subscribers during unmounting which means a reference to the update function (and potentially the entire GameEngine) was being kept in memory - hence multiple GameEngines could be running at the same time.

This bug has been fixed in version 0.9.5

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

2 participants