1 /** 2 * Retrograde Engine 3 * 4 * Authors: 5 * Mike Bierlee, m.bierlee@lostmoment.com 6 * Copyright: 2014-2021 Mike Bierlee 7 * License: 8 * This software is licensed under the terms of the MIT license. 9 * The full terms of the license can be found in the LICENSE.txt file. 10 */ 11 12 module retrograde.state; 13 14 import retrograde.entity; 15 import retrograde.option; 16 17 import std.typecons; 18 19 alias ExitDiscardedStates = Flag!"exitDiscardedStates"; 20 alias EnableStateSuspension = Flag!"enableStateSuspension"; 21 22 class StateMachine { 23 private State[] stateStack; 24 25 private bool stateSuspensionEnabled = false; 26 27 this(EnableStateSuspension enableStateSuspension = EnableStateSuspension.no) { 28 stateSuspensionEnabled = enableStateSuspension; 29 } 30 31 public void setState(State state, ExitDiscardedStates exitDiscardedStates = ExitDiscardedStates.no) { 32 clearStates(exitDiscardedStates); 33 pushState(state); 34 } 35 36 public void switchState(State state) { 37 if (stateStack.length == 0) { 38 setState(state); 39 } else { 40 getCurrentState().ifNotEmpty(state => state.exitState(this)); 41 stateStack[$ - 1] = state; 42 state.enterState(this); 43 } 44 } 45 46 public void pushState(State state) { 47 getCurrentState().ifNotEmpty((state) { 48 stateSuspensionEnabled ? state.suspendState(this) : state.exitState(this); 49 }); 50 stateStack ~= state; 51 state.enterState(this); 52 } 53 54 public Option!State popState() { 55 auto poppedState = getCurrentState(); 56 poppedState.ifNotEmpty(state => state.exitState(this)); 57 stateStack = stateStack[0 .. $-1]; 58 getCurrentState().ifNotEmpty((state) { 59 stateSuspensionEnabled ? state.resumeState(this) : state.enterState(this); 60 }); 61 return poppedState; 62 } 63 64 public void truncateStates(ExitDiscardedStates exitDiscardedStates = ExitDiscardedStates.no) { 65 if (stateStack.length > 0) { 66 auto topState = getCurrentState().get(); 67 if (exitDiscardedStates) { 68 foreach (state; stateStack[0 .. $-1]) { 69 state.exitState(this); 70 } 71 } 72 clearStates(); 73 stateStack ~= topState; 74 } 75 } 76 77 public void clearStates(ExitDiscardedStates exitDiscardedStates = ExitDiscardedStates.no) { 78 if (exitDiscardedStates) { 79 foreach (state; stateStack) { 80 state.exitState(this); 81 } 82 } 83 84 stateStack.destroy(); 85 } 86 87 public Option!State getCurrentState() { 88 return stateStack.length > 0 ? new Some!State(stateStack[$ - 1]) : new None!State(); 89 } 90 } 91 92 class StateMachineEntityComponent : EntityComponent, Snapshotable { 93 mixin EntityComponentIdentity!"StateMachineEntityComponent"; 94 95 private StateMachine _stateMachine; 96 97 this(StateMachine stateMachine) { 98 this._stateMachine = stateMachine; 99 } 100 101 public @property StateMachine stateMachine() { 102 return _stateMachine; 103 } 104 105 public string[string] getSnapshotData() { 106 string currentStateName = ""; 107 _stateMachine.getCurrentState().ifNotEmpty((s) { currentStateName = s.stateName; }); 108 109 return [ 110 "currentState": currentStateName 111 ]; 112 } 113 } 114 115 class StateMachineProcessor : EntityProcessor { 116 public override bool acceptsEntity(Entity entity) { 117 return entity.hasComponent!StateMachineEntityComponent; 118 } 119 120 public override void update() { 121 foreach (entity; _entities.getAll()) { 122 auto stateMachine = entity.getFromComponent!StateMachineEntityComponent(component => component.stateMachine); 123 stateMachine.getCurrentState().ifNotEmpty(state => state.update(stateMachine, entity)); 124 } 125 } 126 } 127 128 abstract class State { 129 private string _stateName; 130 131 public @property string stateName() { 132 return _stateName; 133 } 134 135 this(string stateName) { 136 this._stateName = stateName; 137 } 138 139 public void enterState(StateMachine stateMachine){} 140 public void update(StateMachine stateMachine, Entity entity){} 141 public void exitState(StateMachine stateMachine){} 142 public void suspendState(StateMachine stateMachine){} 143 public void resumeState(StateMachine stateMachine){} 144 }