/* * JaamSim Discrete Event Simulation * Copyright (C) 2016 JaamSim Software 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.ProcessFlow; import com.jaamsim.BasicObjects.DowntimeEntity; import com.jaamsim.basicsim.EntityTarget; import com.jaamsim.events.EventHandle; import com.jaamsim.events.EventManager; import com.jaamsim.events.ProcessTarget; public abstract class Device extends StateUserEntity { private double lastUpdateTime; // simulation time at which the process was updated last private double duration; // calculated duration of the process time step private long endTicks; // planned simulation time in ticks at the end of the next process step private boolean downtimePending; // indicates that a downtime event is ready to start private boolean stepCompleted; // indicates that the last process time step was completed public Device() {} @Override public void earlyInit() { super.earlyInit(); duration = 0.0; endTicks = 0L; lastUpdateTime = 0.0d; downtimePending = false; stepCompleted = true; } /** * Starts the next time step for the process. */ public final void startStep() { if (isTraceFlag()) { trace(0, "startStep"); traceLine(1, "endActionHandle.isScheduled=%s, isAvailable=%s, forcedDowntimePending=%s", endStepHandle.isScheduled(), this.isAvailable(), downtimePending); } double simTime = this.getSimTime(); // Is the process loop is already working? if (endStepHandle.isScheduled()) { this.setPresentState(); return; } // Stop if any of the thresholds, maintenance, or breakdowns close the operation // or if a forced downtime is about to begin if (!this.isAvailable() || downtimePending) { downtimePending = false; this.stopProcessing(); return; } // Set the state if (!isBusy()) { this.setBusy(true); this.setPresentState(); } // Set the last update time in case processing is restarting after a stoppage lastUpdateTime = simTime; // Start the next time step if (this.isNewStepReqd(stepCompleted)) { boolean bool = this.startProcessing(simTime); if (!bool) { this.stopProcessing(); return; } duration = this.getStepDuration(simTime); } // Trap errors if (Double.isNaN(duration)) error("Cannot calculate duration"); if (duration == Double.POSITIVE_INFINITY) error("Infinite duration"); // Schedule the completion of the time step stepCompleted = false; endTicks = EventManager.calcSimTicks(duration); if (isTraceFlag()) traceLine(1, "duration=%.6f", duration); this.scheduleProcess(duration, 5, endStepTarget, endStepHandle); // Notify other processes that are dependent on this one if (this.isNewStepReqd(stepCompleted)) { this.processChanged(); } } /** * EndActionTarget */ private static class EndStepTarget extends EntityTarget<Device> { EndStepTarget(Device ent) { super(ent, "endStep"); } @Override public void process() { ent.endStep(); } } private final ProcessTarget endStepTarget = new EndStepTarget(this); private final EventHandle endStepHandle = new EventHandle(); /** * Completes the processing of an entity. */ final void endStep() { if (isTraceFlag()) trace(0, "endStep"); double simTime = this.getSimTime(); // Update the process for the time that has elapsed this.updateProgress(); // If the full step was completed or if there was an immediate release type threshold // closure, then determine whether to change state and/or to continue to the next step if (this.getSimTicks() == endTicks || this.isImmediateReleaseThresholdClosure()) { stepCompleted = true; boolean bool = this.processStep(simTime); if (!bool) return; } // Start the next time step this.startStep(); } /** * Updates the process calculations at the end of the time step. */ protected final void updateProgress() { if (isTraceFlag()) trace(1, "updateProgress"); double simTime = this.getSimTime(); if (this.isBusy()) { double dt = simTime - lastUpdateTime; duration -= dt; this.updateProgress(dt); } lastUpdateTime = simTime; } /** * Halts further processing. */ private final void stopProcessing() { if (isTraceFlag()) trace(0, "stopProcessing"); // Update the state this.setBusy(false); this.setPresentState(); // Notify other processes that are dependent on this one this.processChanged(); // Set the process to its stopped condition this.setProcessStopped(); } /** * Interrupts the present time step for the process so that a new one can be started based on * new conditions. */ final void unscheduledUpdate() { if (isTraceFlag()) trace(0, "unscheduledUpdate"); // If the process is working, perform its next update immediately if (endStepHandle.isScheduled()) { EventManager.interruptEvent(endStepHandle); return; } // If the process is stopped, then restart it this.startStep(); } /** * Schedules an update */ public final void performUnscheduledUpdate() { if (isTraceFlag()) trace(0, "performUnscheduledUpdate"); if (!unscheduledUpdateHandle.isScheduled()) { EventManager.scheduleTicks(0, 2, false, unscheduledUpdateTarget, unscheduledUpdateHandle); } } /** * ProcessTarget for the unscheduledUpdate method */ private static class UnscheduledUpdateTarget extends EntityTarget<Device> { UnscheduledUpdateTarget(Device ent) { super(ent, "unscheduledUpdate"); } @Override public void process() { ent.unscheduledUpdate(); } } private final ProcessTarget unscheduledUpdateTarget = new UnscheduledUpdateTarget(this); private final EventHandle unscheduledUpdateHandle = new EventHandle(); /** * Revises the time for the next event by stopping the present process and starting a new one. */ protected final void resetProcess() { if (isTraceFlag()) { trace(0, "resetProcess"); traceLine(1, "endActionHandle.isScheduled()=%s", endStepHandle.isScheduled()); } // Set the present process to completed stepCompleted = true; // End the present process prematurely if (endStepHandle.isScheduled()) { EventManager.interruptEvent(endStepHandle); } } /** * Performs the process calculations at the start of a new process time step. * @param simTime - present simulation time * @return indicates whether to continue processing */ protected abstract boolean startProcessing(double simTime); /** * Returns the duration of the next process time step. * @param simTime - present simulation time * @return time step duration */ protected abstract double getStepDuration(double simTime); /** * Performs the process calculations at the end of the time step. * @param dt - elapsed simulation time */ protected abstract void updateProgress(double dt); /** * Performs any calculations related to the state of the process and returns a boolean to * specify whether to start a new time step. * @param simTime - present simulation time * @return indicates whether to start a new time step */ protected abstract boolean processStep(double simTime); /** * Alerts other processes that the present process has changed. */ protected abstract void processChanged(); /** * Determines whether to start a new time step or to complete the present one. * @param completed - indicate whether the present time step duration was completed in full * @return whether to start a new time step */ protected abstract boolean isNewStepReqd(boolean completed); /** * Set the process to its stopped condition. */ protected abstract void setProcessStopped(); /** * Returns the time at which the last update was performed. * @return time for the last update */ protected final double getLastUpdateTime() { return lastUpdateTime; } protected final void setStepCompleted(boolean bool) { stepCompleted = bool; } protected final boolean isStepCompleted() { return stepCompleted; } // ******************************************************************************************** // THRESHOLDS // ******************************************************************************************** @Override public void thresholdChanged() { if (isTraceFlag()) { trace(0, "thresholdChanged"); traceLine(1, "isImmediateReleaseThresholdClosure=%s, isImmediateThresholdClosure=%s", isImmediateReleaseThresholdClosure(), isImmediateThresholdClosure()); } // If an interrupt closure, interrupt the present activity and release the entity if (isImmediateReleaseThresholdClosure()) { if (endStepHandle.isScheduled()) { EventManager.interruptEvent(endStepHandle); } return; } // If an immediate closure, interrupt the present activity and hold the entity if (isImmediateThresholdClosure()) { this.performUnscheduledUpdate(); return; } // Otherwise, check whether processing can be restarted this.startStep(); } // ******************************************************************************************** // MAINTENANCE AND BREAKDOWNS // ******************************************************************************************** @Override public boolean canStartDowntime(DowntimeEntity down) { // Downtime can start when any work in progress has been interrupted and there are no // other maintenance or breakdown activities that are being performed. It is okay to start // downtime when one or more thresholds are closed. return !isBusy() && !isMaintenance() && !isBreakdown(); } @Override public void prepareForDowntime(DowntimeEntity down) { if (isTraceFlag()) { trace(0, "prepareForDowntime(%s)", down); traceLine(1, "isImmediateDowntime=%s, isForcedDowntime=%s, isBusy=%s", isImmediateDowntime(down), isForcedDowntime(down), isBusy()); } // If the device is idle already, then downtime can start right away if (!this.isBusy()) return; // For an opportunistic downtime, do nothing and wait for the process to finish if (isOpportunisticDowntime(down)) { return; } // For a forced downtime, set the flag to stop further processing if (isForcedDowntime(down)) { downtimePending = true; return; } // For an immediate downtime, set the flag and interrupt the present process if (isImmediateDowntime(down)) { downtimePending = true; this.performUnscheduledUpdate(); return; } } @Override public void startDowntime(DowntimeEntity down) { if (isTraceFlag()) trace(0, "startDowntime(%s)", down); this.setPresentState(); } @Override public void endDowntime(DowntimeEntity down) { if (isTraceFlag()) trace(0, "endDowntime(%s)", down); this.startStep(); } }