Entitas-csharp: Proper way to restart a game

Created on 28 Feb 2017  路  19Comments  路  Source: sschmid/Entitas-CSharp

Hi Simon et all,

I just finished a 4 hour long migration from Entitas 0.26.1 to 0.37.0 and it wasn't half as bad as I thought.

Loving the new features and improvements, but something that I've noticed is that I've lost the old way of restarting a game. I used to be able to call the following snippet:

c# Pools.pool.ClearGroups(); systems.DeactivateReactiveSystems(); Pools.pool.DestroyAllEntities(); Pools.pool.ResetCreationIndex();

Right now it seems to me that I have to do the same for all contexts but that seems kind of error prone, I might be able to forget some.

I also don't know how to handle the generated indices and blueprints.

Any insight on this?

Thanks :)

question

Most helpful comment

When you're systems are written correctly, you can even reuse the systems.
With written correctly I mean, in case you cache any state within a system, that you do that within the Initialize method.
So all you need to do is call systems.Initialize() and systems.ActivateReactiveSystems() again, but you can reuse the contexts and systems.

But of course, it's also alright to just create everything from scratch again, depends on the use case. Personally I try to reuse the objects.

All 19 comments

Wow, that's must have been quite a migration :) Nice!
That's actually a good point. Sth like

contexts.Reset();

would be nice.

I also don't know how to handle the generated indices and blueprints

What do you mean? How to migrate them?

No no, migration was a complete success :)

I meant how do I handle indices and blueprints in terms of game restart. Do I need to call something like context.DestroyIndices or context.DestroyBlueprints ?

Blueprints are just config and don't have any state that has to be reset. Entity Indices can be reused and are empty when you destroyed all entities

I will also remove context.DeactivateAndRemoveEntityIndices() I guess, so don't use that. Doesn't make sense to remove indices since they are actually beneficial for performance.

Thanks @sschmid !

I did this in the game:

```C#
void OnDestroy() {
systems.TearDown();

    systems.DeactivateReactiveSystems();
    Contexts.sharedInstance.game.Reset();
    Contexts.sharedInstance.input.Reset();

}

```

And both contexts are emptied properly, however, they stay in the DontDestroyOnLoad part of the game and if I come back to the scene and recreate the systems etc, I get two new Game and Input contexts in the view hierarchy for every time I come back to the scene.

Any idea why that is happening?

Thanks!

@trumpets There's a new extension method to get the observer for a context. I think you need to use that one and destroy the context observer yourself. No idea if that should happen automatically in Context.Reset, though.

Also just calling

C# systems.DeactivateReactiveSystems(); Contexts.sharedInstance.game.Reset(); Contexts.sharedInstance.input.Reset(); systems.ActivateReactiveSystems(); systems.Initialize();

Without changing the scene does nothing. It's like the reactive systems don't pick up the changes for some reason :/

I have some more info on the issue but not sure how to interpret that. Basically the reactive systems are working fine but the entities are not created properly.

For example I have a SetupLevel initialize system which creates an entity with a LevelComponent (unique) which is then picked up by another reactive system which sets up the game pieces.

That system calls context.SetLevel which is just this autogenerated code:

```C#
public GameEntity SetLevel(int newLevel, int newColumns, int newRows, System.Collections.Generic.List newPieces, System.Collections.Generic.List newWalls) {
if(hasLevel) {
throw new EntitasException("Could not set level!n" + this + " already has an entity with LevelComponent!",
"You should check if the context already has a levelEntity before setting it or use context.ReplaceLevel().");
}
var entity = CreateEntity();
entity.AddLevel(newLevel, newColumns, newRows, newPieces, newWalls);
return entity;
}


I attached a debugger and everything goes smoothly the first time, but when calling my reset method which basically executes the snippet in my last comment the debugger hangs on entity.AddLevel(), like it never executes. The GameContext also register an entity but without any components on it.

I guess there is something that's bother the AddLevel method after following my reset sequence?

For reference, the AddLevel method is just this autogenerated code:

```C#
public void AddLevel(int newLevel, int newColumns, int newRows, System.Collections.Generic.List<Piece> newPieces, System.Collections.Generic.List<Wall> newWalls) {
        var component = CreateComponent<LevelComponent>(GameComponentsLookup.Level);
        component.level = newLevel;
        component.columns = newColumns;
        component.rows = newRows;
        component.pieces = newPieces;
        component.walls = newWalls;
        AddComponent(GameComponentsLookup.Level, component);
}

@sschmid does anything catch your eye? :)

Haven't had the time to look into it yet, but here are my thoughts on resetting (not your code, just general thoughts):

1.
I will remove context.DeactivateAndRemoveEntityIndices() since it doesn't make sense to remove or deactivate indices. Entity indices are a performance optimization and I don't see any reason to remove them on reset. When all entities are destroyed, all indices are empty anyway.

2.
I probably remove ClearGroups()from the context.Reset() method. When all entities are destroyed, all groups are empty anyway and can be reused. ClearGroups was added because I saw people registering to event like group.OnEntityAdded += ... but never removed the event handler. ClearGroups will call RemoveAllEventHandlers on each group and basically clean up for you in case you didn't unsubscribe from event yourself. Cutting off all registered eventhandlers might be unexpected behaviour when you reset the context, so I will remove it. E.g. ScoreLabelController.cs wouldn't work anymore after clearing groups, unless you re-enter the scene and start gets called again.

3.
Contexts stay in the hierarchy (DontDestroy) even after you reset, because you're just resetting and intend to reuse all the allocated structures.
If you really want to do a hard-reset, you can do this by writing this

Contexts.sharedInstance = new Contexts();

Make sure to manually remove the previous ContextObserver if you're using VisualDebugging

So after those changes i mentioned above, resetting should be as easy as writing this

systems.DeactivateReactiveSystems();
contexts.Reset();

That does make sense, but I still don't understand why the code above does not work.

It is pretty weird. If I restart the scene I end up with duplicate contexts in DontDestroy in the hierarchy and if I don't restart the scene then components are not added to entities :D

Try not to call ClearGroups. Uncomment this line in Context.Reset()
If you reset the groups, existing contextObservers (the things in the hierarchy) won't update anymore because we cut off the event handlers. So your components are most likely added, but you cannot see it with visual debugging

How do you initially create the Contexts? Do you do it manually with new Contexts()? If so, you'll get new gameObjects in the hierarchy. When you lazily create them by calling Contexts.sharedInstance you should be fine

I will try without ClearGroups in a minute. I am not manually creating the contexts, I just use Contexts.sharedInstance to create the systems

```C#
systems.TearDown();

systems.DeactivateReactiveSystems();
Contexts.sharedInstance.game.Reset();
Contexts.sharedInstance.input.Reset();
systems.ActivateReactiveSystems();

systems.Initialize();


This seems to do it with `ClearGroups` removed from the `Reset` method. This is when I don't recreate the scene. So pretty much fits my use case. However if I just write this code in my `OnDestroy` method of the mono behaviour

```C#
void OnDestroy() {
        systems.TearDown();

        systems.DeactivateReactiveSystems();
        Contexts.sharedInstance.game.Reset();
        Contexts.sharedInstance.input.Reset();
}

with ClearGroups removed and reset the scene I still get duplicate contexts :/ They are all empty all the time but not sure if that's an issue.

As a reference, here is my Start method of the mono behaviour.

C# void Start() { var contexts = Contexts.sharedInstance; contexts.SetAllContexts(); systems = CreateSystems(contexts); systems.Initialize(); }

Yeah, you're setting the contexts again with SetAllContexts(). That's why you get the second gameObject.
You should only create contexts once in the lifecycle of the app. I mean you can totally create them again, but then you have to cleanup the previous gameObjects yourself

When you're systems are written correctly, you can even reuse the systems.
With written correctly I mean, in case you cache any state within a system, that you do that within the Initialize method.
So all you need to do is call systems.Initialize() and systems.ActivateReactiveSystems() again, but you can reuse the contexts and systems.

But of course, it's also alright to just create everything from scratch again, depends on the use case. Personally I try to reuse the objects.

Ref to an older conversation #82

Thanks for the info @sschmid :) I'll do a PR removing the ClearGroups() from Reset() and also adding Contexts.Reset() in the next day or so :)

Was this page helpful?
0 / 5 - 0 ratings