Simplifying AppState transitions

I’m doing some extensive AppState transitions and wrote me some little framework code to simplify it. Any interest in merging such code into jme3?



Salient points:

#1 AppState has a new member, dependentStates. It’s a Set of other AppStates that are supposed to run when this AppState is active. (E.g. the InactiveBackgroundAppState is registered for all pre-game AppStates, a FlyCamAppState is registered for all in-game AppStates.)

#2 The main AppStates use a new function, goTo(AppState), to switch to a new AppState. Dependent AppStates are detached only if the new AppState doesn’t have them in its dependentStates.

#3 Transitive closure is handled (might be overkill, but without that a hierarchy of dependencies would silently fail, which is unacceptable in my book).



The code has not been polished for integration in jme3 (mostly because I don’t need that for the things I do with jme3). I’ll fully understand if devs reject the code, or reimplement the mechanism with their own code.

Points that I know to need work are:

#4 Directly attaching and detaching AppStates does not touch dependent states. This is because goTo uses the attach/detach API, and if detach automatically detached all dependent AppStates, that would also cover those that should be left running. Cleaning that up would involve a protected internalAttach/internalDetach pair of functions.

#5 This covers only attaching and detaching, not enabling and disabling. This be a Good Thing or a Bad Thing, depending on how people use enabling and disabling.

#6 The code uses log4j since that’s what I’m using for my project.



I currently have this code as a 100-line subclass of AppState.



Feedback welcome.

1 Like

I forgot to mention the benefit:



When an AppState wishes to detach itself and all its helper AppStates, it has no way to knowing which AppStates to keep running. I.e. if it transitions to another AppState, it must know which helper AppStates the new AppState needs and refrain from detaching those.

With the dependency system, changes in what helpers an AppState needs code changes only that AppState.

Nice idea. I think this sounds like a great add-on. Maybe you could put up a project on google code/bitbucket/github and see what people think?

I think it’s probably a little too specific for core but maybe other users will find it useful if you post it somewhere as jmaasing suggests.



I’m still not entirely sure what problem it solves exactly. I also extensively use app states and have yet to need anything like this.

2 Likes

Out of interest is there any particular reason to use attach/detach rather than enable/disable?

@pspeed said:
I'm still not entirely sure what problem it solves exactly. I also extensively use app states and have yet to need anything like this.


Tentatively I agree with you, however I do sort of have a use for this. I have a bunch of app states and some of them turn on/off other dependant app states. I can see how having a system to control that might avoid any silly mistakes....however those mistakes are also easily spotted and fixed so I don't see any big need either.

@pspeed The “benefit” part.

How did you set up things so that AppStates don’t have to know about helpers that the other Appstates need?

AppStates have various dependencies that make it hard to reuse them across projects. This is one of them, and I hope it can be eliminated (and the others, in time, as well).



@jmaasing I’ll post the code in the evening or tomorrow, I think.

I just want to concentrate on approach right now; code details can come later.



@zarch I wanted to make sure that no AppStates are floating around and keep consuming resources despite being not in use.

I also suspect that enabling/disabling is a more temporary thing that can’t be easily captured in a dependency hierarchy. However, that seems to be somewhat fuzzy; I have yet to find a clear guideline what parts of setup/teardown need to go in attach/detach and what in enable/disable.

The reason I asked is because we do virtually everything in enable/disable. We also enable/disable dependencies at the same time.



We’re piggy-backed on top of Nifty though. Each “master” app state is also a screen controller. In onEndScreen it disables the state… which then disables all dependencies, in onStartScreen it enables the new one which also enables all dependencies.



Then to change screen we just do nifty.gotoScreen(X). That calls onEndScreen in current controller (disabling dependancies too) and then onStartScreen in the new controller (enabling new dependencies).



This does mean that some app states might get disabled then immediately enabled again but the states in question don’t really do much on enable/disable so it’s not a problem.







We could achieve the same with attach/detach but then we would need to keep our own separate list of app states rather than being able to query them from the state manager.







Costs of this approach I guess are one extra check on isEnabled for each inactive state per frame. Not a big cost really in the grand scheme of things.

This is easily handled by user code without forcing the user into a certain pattern, I don’t think we’ll add anything like this.

@zarch Hm… I’ll have to think a bit more about this.



One question I have right now is this:

Is it a problem if each Appstate has its own set of Nifty screens?



@normen I was having problems not polluting each AppState with the list of what helpers other AppStates needed.

If it’s easy to avoid that: What’s your approach that I overlooked?

@toolforger said:
@normen I was having problems not polluting each AppState with the list of what helpers other AppStates needed.
If it's easy to avoid that: What's your approach that I overlooked?

100 ways.. You have this issue because of your design decisions earlier.
@toolforger said:

One question I have right now is this:
Is it a problem if each Appstate has its own set of Nifty screens?



I'm not sure what you mean?

If you have multiple nifty screens per "master" app state then that's a different design decision from the one we made and might complicate things for you if you tried using our approach.

@normen What change in what decision would avoid this kind of problem?

@zarch right now, I have a separate Nifty screen per AppState. I was under the impression that the usual approach would be having a single XML with all screens, and fromXml-ing that and using gotoScreen afterwards.



I wanted to organize the screens with the AppStates that use them, so I put each screen in a separate XML, but I’m somewhat wary that I might be running into problems with that.

Possibly performance (multiple Nifty instances processing input and such, stuff getting set up and torn down needlessly). Possibly architectural problems (multiple AppStates or Nifty structures fighting over control of, say, input).

Now if you say that one Nifty screen per AppState is the norm, then that’s fine by me :slight_smile:



I’m doing one thing differently than the norm right now: I first built screens in XML, then I tried the Builder API, now I’m using Creators and can finally factor out recurring screen parts in common code.

Which means I’m not using fromXml.

I think you missed “addXml” :slight_smile:

Hehe, no, I started using it but quickly dropped it.

ID name conflicts all over the place.

Besides, that doesn’t allow reusing stuff like groups of OK/Cancel buttons. Or just a standard spacing panel. (Okay, I could define a control for that all. I’m just not ready to delve into that yet; the docs for Nifty are somewhat incomplete and I’ve already spent too much time reading sources instead of simply calling it.)

@toolforger said:
@normen What change in what decision would avoid this kind of problem?

Not putting the logic into the AppStates. If you have e.g. An entity system all your logic and data is in there. AppStates are a tool to access the update loop, nothing else.
@zarch said:
Tentatively I agree with you, however I do sort of have a use for this. I have a bunch of app states and some of them turn on/off other dependant app states. I can see how having a system to control that might avoid any silly mistakes....however those mistakes are also easily spotted and fixed so I don't see any big need either.


My gui library has a GuiAppState that keeps a reference to a regular 2D guiNode and to a 3D guiNode and passes mouse events to spatials under these nodes that have the right controls. This state is always attached but enables and disables itself based on whether there are other states,etc using it. So it has a generic set of dependents and addDependent/removeDependent and enables/disables itself based on the size of the set. Other things that need the gui app state simply add and remove themselves.

The pattern is so simple that I've never bothered to create a base class for that. I've only ever needed it once and "three uses" is my magic number for creating utility code.

When I actually detach some app states wholesale and attach others it's rare and usually a pretty specific transition in which I'd rather have all of the detach + attach calls right in the same block of code. Sometimes ordering is important and it's always nice to see everything together.

@normen I fail to understand your answer.

In my book, the question when to have an AppState active isn’t game logic. It certainly isn’t for AppStates like stats display!

So, what did you mean with “game logic”?

@toolforger said:
@normen I fail to understand your answer.
In my book, the question when to have an AppState active isn't game logic. It certainly isn't for AppStates like stats display!
So, what did you mean with "game logic"?


It does sound on the surface like you have a lot of app states... perhaps more than needed. It's your design so we can't comment but it just feels that way.

For example, there are some users who create a different app state class for every level of their game. Personally, I think that's wrong but it also comes down to style. I wouldn't want to encourage it in the core system, though.

Still other users will use an app state where a control would have been more appropriate.

When we are in the land of hypothetical situations it becomes difficult to make concrete statements one way or the other.