Skip to content

Commit

Permalink
Create CompositeAppState.java (jMonkeyEngine#2193)
Browse files Browse the repository at this point in the history
A composite app state which auto manages child app states.
  • Loading branch information
scenemax3d authored Feb 8, 2024
1 parent a2c7b5a commit 53994eb
Showing 1 changed file with 236 additions and 0 deletions.
236 changes: 236 additions & 0 deletions jme3-core/src/main/java/com/jme3/app/state/CompositeAppState.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
/*
*
* Copyright (c) 2014-2024 jMonkeyEngine
* Copied with Paul Speed's permission from: https://github.com/Simsilica/SiO2
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package com.jme3.app.state;

import com.jme3.app.Application;
import com.jme3.util.SafeArrayList;

/**
* An AppState that manages a set of child app states, making sure
* they are attached/detached and optional enabled/disabled with the
* parent state.
*
* @author Paul Speed
*/
public class CompositeAppState extends BaseAppState {

private final SafeArrayList<AppStateEntry> states = new SafeArrayList<>(AppStateEntry.class);
private boolean childrenEnabled;

/**
* Since we manage attachmend/detachment possibly before
* initialization, we need to keep track of the stateManager we
* were given in stateAttached() in case we have to attach another
* child prior to initialization (but after we're attached).
* It's possible that we should actually be waiting for initialize
* to add these but I feel like there was some reason I did it this
* way originally. Past-me did not leave any clues.
*/
private AppStateManager stateManager;
private boolean attached;

public CompositeAppState( AppState... states ) {
for( AppState a : states ) {
this.states.add(new AppStateEntry(a, false));
}
}

private int indexOf( AppState state ) {
for( int i = 0; i < states.size(); i++ ) {
AppStateEntry e = states.get(i);
if( e.state == state ) {
return i;
}
}
return -1;
}

private AppStateEntry entry( AppState state ) {
for( AppStateEntry e : states.getArray() ) {
if( e.state == state ) {
return e;
}
}
return null;
}

protected <T extends AppState> T addChild( T state ) {
return addChild(state, false);
}

protected <T extends AppState> T addChild( T state, boolean overrideEnable ) {
if( indexOf(state) >= 0 ) {
return state;
}
states.add(new AppStateEntry(state, overrideEnable));
if( attached ) {
stateManager.attach(state);
}
return state;
}

protected void removeChild( AppState state ) {
int index = indexOf(state);
if( index < 0 ) {
return;
}
states.remove(index);
if( attached ) {
stateManager.detach(state);
}
}

protected <T extends AppState> T getChild( Class<T> stateType ) {
for( AppStateEntry e : states.getArray() ) {
if( stateType.isInstance(e.state) ) {
return stateType.cast(e.state);
}
}
return null;
}

protected void clearChildren() {
for( AppStateEntry e : states.getArray() ) {
removeChild(e.state);
}
}

@Override
public void stateAttached( AppStateManager stateManager ) {
this.stateManager = stateManager;
for( AppStateEntry e : states.getArray() ) {
stateManager.attach(e.state);
}
this.attached = true;
}

@Override
public void stateDetached( AppStateManager stateManager ) {
// Reverse order
for( int i = states.size() - 1; i >= 0; i-- ) {
stateManager.detach(states.get(i).state);
}
this.attached = false;
this.stateManager = null;
}

protected void setChildrenEnabled( boolean b ) {
if( childrenEnabled == b ) {
return;
}
childrenEnabled = b;
for( AppStateEntry e : states.getArray() ) {
e.setEnabled(b);
}
}

/**
* Overrides the automatic synching of a child's enabled state.
* When override is true, a child will remember its old state when
* the parent's enabled state is false so that when the parent is
* re-enabled the child can resume its previous enabled state. This
* is useful for the cases where a child may want to be disabled
* independent of the parent... and then not automatically become
* enabled just because the parent does.
* Currently, the parent's disabled state always disables the children,
* too. Override is about remembering the child's state before that
* happened and restoring it when the 'family' is enabled again as a whole.
*/
public void setOverrideEnabled( AppState state, boolean override ) {
AppStateEntry e = entry(state);
if( e == null ) {
throw new IllegalArgumentException("State not managed:" + state);
}
if( override ) {
e.override = true;
} else {
e.override = false;
e.state.setEnabled(isEnabled());
}
}

@Override
protected void initialize(Application app) {
}

@Override
protected void cleanup(Application app) {
}

@Override
protected void onEnable() {
setChildrenEnabled(true);
}

@Override
protected void onDisable() {
setChildrenEnabled(false);
}

private class AppStateEntry {
AppState state;
boolean enabled;
boolean override;

public AppStateEntry( AppState state, boolean overrideEnable ) {
this.state = state;
this.override = overrideEnable;
this.enabled = state.isEnabled();
}

public void setEnabled( boolean b ) {

if( override ) {
if( b ) {
// Set it to whatever its enabled state
// was before going disabled last time.
state.setEnabled(enabled);
} else {
// We are going to set enabled to false
// but keep track of what it was before we did
// that
this.enabled = state.isEnabled();
state.setEnabled(false);
}
} else {
// Just synch it always
state.setEnabled(b);
}
}
}
}

0 comments on commit 53994eb

Please sign in to comment.