/*
* JaamSim Discrete Event Simulation
* Copyright (C) 2014 Ausenco Engineering Canada Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jaamsim.states;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import com.jaamsim.Graphics.DisplayEntity;
import com.jaamsim.basicsim.Entity;
import com.jaamsim.basicsim.FileEntity;
import com.jaamsim.events.EventManager;
import com.jaamsim.input.BooleanInput;
import com.jaamsim.input.InputAgent;
import com.jaamsim.input.Keyword;
import com.jaamsim.input.Output;
import com.jaamsim.input.StringKeyInput;
import com.jaamsim.input.StringListInput;
import com.jaamsim.units.DimensionlessUnit;
import com.jaamsim.units.TimeUnit;
public class StateEntity extends DisplayEntity {
@Keyword(description = "A list of state/DisplayEntity pairs. For each state," +
" the graphics will be changed to those for the corresponding DisplayEntity.",
example = "Object1 StateGraphics { { idle DisplayEntity1 } { working DisplayEntity2 }")
protected final StringKeyInput<DisplayEntity> stateGraphics;
@Keyword(description = "If TRUE, a log file (.trc) will be printed with the time of every state change during the run.",
example = "Object1 TraceState { TRUE }")
private final BooleanInput traceState;
@Keyword(description = "A list of states for which the entity is considered working.",
exampleList = "'Transit - Seg1L' 'Transit - Seg1B'")
protected final StringListInput workingStateListInput;
private StateRecord presentState; // The present state of the entity
private final HashMap<String, StateRecord> states;
private final ArrayList<StateEntityListener> stateListeners;
private long lastStateCollectionTick;
private long workingTicks;
protected FileEntity stateReportFile; // The file to store the state information
{
stateGraphics = new StringKeyInput<>(DisplayEntity.class, "StateGraphics", "Key Inputs");
stateGraphics.setHidden(true);
this.addInput(stateGraphics);
traceState = new BooleanInput("TraceState", "Key Inputs", false);
traceState.setHidden(true);
this.addInput(traceState);
workingStateListInput = new StringListInput("WorkingStateList", "Maintenance", new ArrayList<String>(0));
this.addInput(workingStateListInput);
}
public StateEntity() {
states = new HashMap<>();
stateListeners = new ArrayList<>();
}
@Override
public void earlyInit() {
super.earlyInit();
this.initStateData();
if (testFlag(FLAG_GENERATED))
return;
// Create state trace file if required
if (traceState.getValue()) {
String fileName = InputAgent.getReportFileName(InputAgent.getRunName() + "-" + this.getName() + ".trc");
stateReportFile = new FileEntity( fileName);
}
}
@Override
public void lateInit() {
super.lateInit();
stateListeners.clear();
for (Entity ent : Entity.getClonesOfIterator(Entity.class, StateEntityListener.class)) {
StateEntityListener sel = (StateEntityListener)ent;
if (sel.isWatching(this))
stateListeners.add(sel);
}
}
private void initStateData() {
lastStateCollectionTick = 0;
if (EventManager.hasCurrent())
lastStateCollectionTick = getSimTicks();
workingTicks = 0;
states.clear();
String initState = getInitialState().intern();
StateRecord init = new StateRecord(initState, isValidWorkingState(initState));
init.startTick = lastStateCollectionTick;
presentState = init;
states.put(init.name, init);
this.setGraphicsForState(initState);
}
public ArrayList<StateEntityListener> getStateListeners() {
return stateListeners;
}
/**
* Get the name of the initial state this Entity will be initialized with.
* @return
*/
public String getInitialState() {
return "Idle";
}
/**
* Tests the given state name to see if it is valid for this Entity.
* @param state
* @return
*/
public boolean isValidState(String state) {
return "Idle".equals(state) || "Working".equals(state);
}
/**
* Tests the given state name to see if it is counted as working hours when in
* that state..
* @param state
* @return
*/
public boolean isValidWorkingState(String state) {
if( workingStateListInput.getValue().size() > 0 )
return workingStateListInput.getValue().contains( state );
return "Working".equals(state);
}
/**
* Sets the state of this Entity to the given state.
*/
public final void setPresentState( String state ) {
if (presentState == null)
this.initStateData();
if (presentState.name.equals(state))
return;
StateRecord nextState = states.get(state);
if (nextState == null) {
if (!isValidState(state))
error("Specified state: %s is not valid", state);
String intState = state.intern();
nextState = new StateRecord(intState, isValidWorkingState(intState));
states.put(nextState.name, nextState);
}
this.setGraphicsForState(state);
updateStateStats();
nextState.startTick = lastStateCollectionTick;
StateRecord prev = presentState;
presentState = nextState;
stateChanged(prev, presentState);
}
private void setGraphicsForState(String state) {
if (stateGraphics.getValue() == null)
return;
DisplayEntity ent = stateGraphics.getValueFor(state);
if (ent == null) {
this.resetGraphics();
return;
}
this.setDisplayModelList(ent.getDisplayModelList());
this.setSize(ent.getSize());
this.setOrientation(ent.getOrientation());
this.setAlignment(ent.getAlignment());
}
/**
* A callback subclasses can override that is called on each state transition.
*
* The state has already been updated when this is called so presentState == next
* @param prev the state this Entity was in previously
* @param next the state this Entity is currently in
*/
public void stateChanged(StateRecord prev, StateRecord next) {
if (traceState.getValue()) {
long curTick = EventManager.simTicks();
EventManager evt = EventManager.current();
double duration = evt.ticksToSeconds(curTick - prev.getStartTick());
double timeOfPrevStart = evt.ticksToSeconds(prev.getStartTick());
stateReportFile.format("%.5f %s.setState( \"%s\" ) dt = %g\n",
timeOfPrevStart, this.getName(),
prev.name, duration);
stateReportFile.flush();
}
for (StateEntityListener each : stateListeners) {
each.updateForStateChange(this, prev, next);
}
}
/**
* Update the statistics kept for ticks in the presentState
*/
private void updateStateStats() {
long curTick = getSimTicks();
if (curTick == lastStateCollectionTick)
return;
long durTicks = curTick - lastStateCollectionTick;
lastStateCollectionTick = curTick;
presentState.totalTicks += durTicks;
presentState.currentCycleTicks += durTicks;
if (presentState.working)
workingTicks += durTicks;
}
/**
* Runs after initialization period
*/
public void collectInitializationStats() {
updateStateStats();
for (StateRecord each : states.values()) {
each.initTicks = each.totalTicks;
each.totalTicks = 0;
each.completedCycleTicks = 0;
}
}
/**
* Runs after each report interval
*/
public void clearReportStats() {
updateStateStats();
// clear totalHours for each state record
for (StateRecord each : states.values()) {
each.totalTicks = 0;
each.completedCycleTicks = 0;
}
}
/**
* Clear the current cycle hours, also reset the start of cycle time
*/
public void clearCurrentCycleStats() {
updateStateStats();
// clear current cycle hours for each state record
for (StateRecord each : states.values()) {
each.currentCycleTicks = 0;
}
}
/**
* Runs when cycle is finished
*/
public void collectCycleStats() {
updateStateStats();
// finalize cycle for each state record
for (StateRecord each : states.values()) {
each.completedCycleTicks += each.currentCycleTicks;
each.currentCycleTicks = 0;
}
}
public void addState(String str) {
if (states.get(str) != null)
return;
if (!isValidState(str))
error("Specified state: %s is not valid", str);
String state = str.intern();
StateRecord stateRec = new StateRecord(state, isValidWorkingState(state));
states.put(stateRec.name, stateRec);
}
public StateRecord getState(String state) {
return states.get(state);
}
public StateRecord getState() {
return presentState;
}
private static class StateRecSort implements Comparator<StateRecord> {
@Override
public int compare(StateRecord sr1, StateRecord sr2) {
return sr1.name.compareTo(sr2.name);
}
}
public ArrayList<StateRecord> getStateRecs() {
ArrayList<StateRecord> recs = new ArrayList<>(states.size());
for (StateRecord rec : states.values())
recs.add(rec);
Collections.sort(recs, new StateRecSort());
return recs;
}
/**
* Return true if the entity is working
*/
public boolean isWorking() {
return presentState.working;
}
/**
* A helper used to implement some of the state-based outputs, likely not
* useful for model code.
* @param simTicks
* @param state
* @return
*/
public long getTicksInState(long simTicks, StateRecord state) {
if (state == null)
return 0;
long ticks = state.totalTicks;
if (getState() == state)
ticks += (simTicks - lastStateCollectionTick);
return ticks;
}
public long getTicksInState(StateRecord state) {
if (state == null)
return 0;
long ticks = state.totalTicks;
if (getState() == state)
ticks += (getSimTicks() - lastStateCollectionTick);
return ticks;
}
public long getCurrentCycleTicks(StateRecord state) {
if (state == null)
return 0;
long ticks = state.currentCycleTicks;
if (getState() == state)
ticks += (getSimTicks() - lastStateCollectionTick);
return ticks;
}
public long getCompletedCycleTicks(StateRecord state) {
if (state == null)
return 0;
return state.completedCycleTicks;
}
public long getInitTicks(StateRecord state) {
if (state == null)
return 0;
return state.initTicks;
}
private long getWorkingTicks(long simTicks) {
long ticks = workingTicks;
if (presentState.working)
ticks += (simTicks - lastStateCollectionTick);
return ticks;
}
/**
* Returns the number of seconds that the entity has been in use.
*/
public double getWorkingTime() {
long ticks = getWorkingTicks(getSimTicks());
return EventManager.ticksToSecs(ticks);
}
public void setPresentState() {}
/**
* Returns the elapsed time in seconds after the completion of the initialisation period
* that the entity has been in the specified state.
* @param simTime - present simulation time
* @param state - string representing the state
* @return
*/
public double getTimeInState(double simTime, String state) {
long simTicks = EventManager.secsToNearestTick(simTime);
StateRecord rec = states.get(state.intern());
if (rec == null)
return 0.0;
long ticks = getTicksInState(simTicks, rec);
return EventManager.ticksToSecs(ticks);
}
@Output(name = "State",
description = "The present state for the object.",
unitType = DimensionlessUnit.class,
sequence = 0)
public String getPresentState(double simTime) {
if (presentState == null) {
return this.getInitialState();
}
return presentState.name;
}
@Output(name = "WorkingState",
description = "Returns TRUE if the present state is one of the working states.",
sequence = 1)
public boolean isWorking(double simTime) {
if (presentState == null) {
return this.isValidWorkingState(this.getInitialState());
}
return presentState.working;
}
@Output(name = "WorkingTime",
description = "The total time recorded for the working states, including the "
+ "initialisation period. Breakdown events can be triggered by elapsed "
+ "working time instead of calendar time.",
unitType = TimeUnit.class,
sequence = 2)
public double getWorkingTime(double simTime) {
if (presentState == null) {
return 0.0;
}
long simTicks = EventManager.secsToNearestTick(simTime);
long ticks = getWorkingTicks(simTicks);
return EventManager.ticksToSecs(ticks);
}
@Output(name = "StateTimes",
description = "The total time recorded for each state after the completion of "
+ "the initialisation period.",
unitType = TimeUnit.class,
reportable = true,
sequence = 3)
public LinkedHashMap<String, Double> getStateTimes(double simTime) {
long simTicks = EventManager.secsToNearestTick(simTime);
LinkedHashMap<String, Double> ret = new LinkedHashMap<>(states.size());
for (StateRecord stateRec : this.getStateRecs()) {
long ticks = getTicksInState(simTicks, stateRec);
Double t = EventManager.ticksToSecs(ticks);
ret.put(stateRec.name, t);
}
return ret;
}
}