From 99ccd510877045949b2ebf56049f6af8290f775d Mon Sep 17 00:00:00 2001
From: Shawn <shawn.r.hatch@gmail.com>
Date: Mon, 27 Feb 2023 12:03:32 -0500
Subject: [PATCH] Added SimulationTime concept to simulation to prepare for
 restarting a simulation

---
 gcm4/src/main/java/nucleus/NucleusError.java  |  3 +
 gcm4/src/main/java/nucleus/Simulation.java    | 26 ++++-
 .../src/main/java/nucleus/SimulationTime.java | 99 +++++++++++++++++++
 3 files changed, 125 insertions(+), 3 deletions(-)
 create mode 100644 gcm4/src/main/java/nucleus/SimulationTime.java

diff --git a/gcm4/src/main/java/nucleus/NucleusError.java b/gcm4/src/main/java/nucleus/NucleusError.java
index b51e12dca..6e7e78d9f 100644
--- a/gcm4/src/main/java/nucleus/NucleusError.java
+++ b/gcm4/src/main/java/nucleus/NucleusError.java
@@ -33,11 +33,13 @@ public enum NucleusError implements ContractError {
 	LABLER_GENERATED_LABEL_WITH_INCORRECT_ID("Event labler generated a label with an incorrect event labeler id"),
 	LABLER_GENERATED_LABEL_WITH_INCORRECT_PRIMARY_KEY("Event labler generated a label with an incorrect primary key"),
 	MISSING_PLUGIN("A plugin is missing"),
+	NEGATIVE_START_TIME("Simulation start time is negative"),
 	NEGATIVE_THREAD_COUNT("Negative thread count"),
 	NON_EXISTANT_SCEANARIO_PROGRESS("The scenario progress file does not exist, but is required when continuation from progress file is chosen"),
 	NULL_ACTOR_CONTEXT_CONSUMER("Null actor context consumer"),	
 	NULL_REPORT_CONTEXT_CONSUMER("Null report context consumer"),
 	NULL_ACTOR_ID("Null actor id"),
+	NULL_BASE_DATE("Null base date"),
 	NULL_DATA_MANAGER("Null data manager"),
 	NULL_DATA_VIEW("Null data view"),
 	NULL_DATA_MANAGER_CLASS("Null data manager class"),
@@ -71,6 +73,7 @@ public enum NucleusError implements ContractError {
 	NULL_SCENARIO_ID("Null scenario id"),
 	NULL_SCENARIO_PROGRESS_FILE("Null scenario progress file"),
 	NULL_SIMULATION_CONTEXT("Null simulation context"),
+	NULL_SIMULATION_TIME("Null simulation time"),
 	PAST_PLANNING_TIME("Plan execution time is in the past"),
 	PLUGIN_INITIALIZATION_CLOSED("Plugin context is no longer valid"),
 	REPEATED_EXECUTION("Attempted repeat execution of simulation engine"),
diff --git a/gcm4/src/main/java/nucleus/Simulation.java b/gcm4/src/main/java/nucleus/Simulation.java
index fd46fcbc5..f9c5c51ac 100644
--- a/gcm4/src/main/java/nucleus/Simulation.java
+++ b/gcm4/src/main/java/nucleus/Simulation.java
@@ -125,6 +125,23 @@ public Builder setOutputConsumer(Consumer<Object> outputConsumer) {
 			return this;
 		}
 
+		/**
+		 * Set the simulation time. Defaults to the current date and a start
+		 * time of zero.
+		 * 
+		 * @throws ContractException
+		 *             <li>{@link NucleusError#NULL_SIMULATION_TIME} if the
+		 *             simulation time is null
+		 * 
+		 */
+		public Builder setSimulationTime(SimulationTime simulationTime) {
+			if (simulationTime == null) {
+				throw new ContractException(NucleusError.NULL_SIMULATION_TIME);
+			}
+			data.simulationTime = simulationTime;
+			return this;
+		}
+
 		/**
 		 * Adds a plugin initializer to this builder for inclusion in the
 		 * simulation
@@ -167,6 +184,7 @@ private static enum Planner {
 	private static class Data {
 		private List<Plugin> plugins = new ArrayList<>();
 		private Consumer<Object> outputConsumer;
+		private SimulationTime simulationTime = SimulationTime.builder().build();
 	}
 
 	/**
@@ -622,6 +640,8 @@ public void execute() {
 			throw new ContractException(NucleusError.REPEATED_EXECUTION);
 		}
 		started = true;
+		
+		time = data.simulationTime.getStartTime();
 
 		// set the output consumer
 		outputConsumer = data.outputConsumer;
@@ -1141,8 +1161,8 @@ protected void releaseObservationEventForDataManager(final Event event) {
 		if (event == null) {
 			throw new ContractException(NucleusError.NULL_EVENT);
 		}
-		
-		if(!dataManagerQueueActive) {
+
+		if (!dataManagerQueueActive) {
 			throw new ContractException(NucleusError.OBSERVATION_EVENT_IMPROPER_RELEASE);
 		}
 
@@ -1181,7 +1201,7 @@ protected void releaseMutationEventForDataManager(final Event event) {
 		if (event == null) {
 			throw new ContractException(NucleusError.NULL_EVENT);
 		}
-		
+
 		if (focalReportId != null) {
 			throw new ContractException(NucleusError.REPORT_ATTEMPTING_MUTATION, focalReportId);
 		}
diff --git a/gcm4/src/main/java/nucleus/SimulationTime.java b/gcm4/src/main/java/nucleus/SimulationTime.java
new file mode 100644
index 000000000..2c7a65c99
--- /dev/null
+++ b/gcm4/src/main/java/nucleus/SimulationTime.java
@@ -0,0 +1,99 @@
+package nucleus;
+
+import java.time.LocalDate;
+
+import net.jcip.annotations.Immutable;
+import util.errors.ContractException;
+
+/**
+ * An immutable data class that holds 1) the base date aligned to simulation
+ * time zero and 2) the simulation start time as a floating point number of
+ * days.
+ * 
+ *
+ *
+ */
+@Immutable
+public class SimulationTime {
+
+	private static class Data {
+		private double startTime = 0;
+		private LocalDate baseDate = LocalDate.now();
+	}
+
+	private final Data data;
+
+	private SimulationTime(Data data) {
+		this.data = data;
+	}
+
+	public static Builder builder() {
+		return new Builder();
+	}
+
+	/**
+	 * Builder class for SimulationTime
+	 *
+	 */
+	public static class Builder {
+		private Data data = new Data();
+
+		public SimulationTime build() {
+			try {
+
+				return new SimulationTime(data);
+			} finally {
+				data = new Data();
+			}
+		}
+
+		/**
+		 * Sets the time (floating point days) of simulation start. Defaults to
+		 * zero.
+		 * 
+		 * @throws ContractException
+		 *             <li>{@linkplain NucleusError#NEGATIVE_START_TIME} if the
+		 *             start time is negative</li>
+		 */
+		public Builder setStartTime(double startTime) {
+			if (startTime < 0) {
+				throw new ContractException(NucleusError.NEGATIVE_START_TIME);
+			}
+			data.startTime = startTime;
+			return this;
+		}
+
+		/**
+		 * Sets the base date that synchronizes with simulation time zero.
+		 * Defaults to the current date.
+		 * 
+		 * @throws ContractException
+		 *             <li>{@linkplain NucleusError#NULL_BASE_DATE} if the base
+		 *             date is null</li>
+		 */
+		public Builder setBaseDate(LocalDate localDate) {
+			if (localDate == null) {
+				throw new ContractException(NucleusError.NULL_BASE_DATE);
+			}
+			data.baseDate = localDate;
+			return this;
+		}
+	}
+
+	/**
+	 * Returns the time (floating point days) of simulation start.
+	 * 
+	 */
+	public double getStartTime() {
+		return data.startTime;
+	}
+
+	/**
+	 * Returns the base date that synchronizes with simulation time zero.
+	 * 
+	 */
+	public LocalDate getBaseDate() {
+		return data.baseDate;
+	}
+
+}