In Java, objects have properties which have values that are usually obtained and changed with get/set methods, or used internally in the class. For a simulation the values of properties typically represent the current state. In case of perception it may be required to know a past state, for instance to account for a reaction time. In OTS a small toolkit is available to make properties historical within the
org.opentrafficsim.core.perception package. The basic concept is that the property is defined as a typed Historical. For example, below is the odometer property of a GTU.
private Historical<Length> odometer;
Any information that perception may require in the past, needs to be defined like this. The interface
Historical defines three simple methods, where
T is the generic type of the value which is
Length in the above example:
void set(T value); T get(); T get(Time time);
The first two set and get the value at the current time, while the latter obtains an historical value, i.e. from the past. The class
AbstractHistorical performs most of the hard work regarding bookkeeping of previous values. Internally it maintains a list of timed events from which past values can be reconstructed. Each event represents a mutation to the value. An important part of the functionality is coupling with a
HistoryManager, which performs cleaning up of old events. The default implementation within OTS is
HistoryManagerDEVS, which uses events, a clean-up interval, and a guaranteed history duration. (Only if the object itself exists for a shorter time in simulation, is the history duration not guaranteed. In these cases the oldest state is returned.) This history duration should be set to a value that is larger than the maximum perception delay that can occur in simulation, while being as small as possible to limit memory usage.
Obviously, the example of a GTUs odometer value is of a type that has a single immutable value (rather than being a set or a complex object) at any time, which is the easiest case. We can use the implementation HistoricalValue for this.
this.odometer = new HistoricalValue<>(historyManager, Length.ZERO);
Historical is automatically coupled to the
HistoryManager. The class
HistoricalValue internally uses the events from
AbstractHistorical (its superclass) to return the value at any particular time.
EventValue<T> event = getEvent(time); return event == null ? null : event.getValue();
Besides the method
getEvent(Time), several other methods are available for subclasses of
AbstractHistorical to reconstruct the value at the given time. For properties that are of a type from the Java Collections Framework (like
Maps), there is a shadow package available for historical versions of all of these utility classes in
org.opentrafficsim.core.perception.collections. At both interface and implementation level historical shadows are available. For example we have
HistoricalSet<E> which respectively extend the interfaces
Set<E>. Because the shadow package extends the Java Collections Framework, all the regular methods still work and function at the current time, such as
Converting a property of type
HistoricalSet<E>, or any other collection or map, thus requires little change in code. Additionally the interfaces define a method
get(Time) to obtain a paste state of the collection or map.
For usages other than an immutable value, collection or map, it may be required to have a new implementation of
AbstractHistorical. Creating such a class requires some comprehension of the events that are used to reconstruct past states of values. To this end available event classes in OTS are explained.
AbstractHistorical has a generic type argument for the events used such that utility methods for sub-classes to return events, return the correct type of events. Such events have to extend the interface
AbstractHistorical.Event. A simple implementation of this is
AbstractHistorical.EventValue, which stores a time and a value. This information is sufficient for
HistoricalValue, which uses this event class.
For maps this class is further extended by
AbstractHistoricalMap.EventMap, which additionally stores whether the map contained a key prior to a mutation, and optionally the previously associated value (with the key stored as the value in superclass
AbstractHistorical.EventValue). This information is sufficient to reverse any mutation, which is done by the
restore(…) method by putting the previous value or by removing the key and associated value (whichever is the reverse of the original mutation). Reversing events is done on a copy of the current map, such that the current map is not affected. Filling an empty map with the contents of the current map and then reversing all applicable events is done in the
fill(…) method. This method is used in implementations of the
getTime(…) method of subclasses (e.g.
HistoricalHashMap), and expects an empty
Map of the correct
Map implementation (which is unknown to
AbstractHistoricalMap due to type erasure in java generics).
For collections the class
AbstractHistorical.EventValue is extended by
AbstractHistoricalCollection.EventCollection, which defines an abstract
restore(…) method. The class is further extended by
AbstractHistoricalCollection.RemoveEvent. The implementation of
restore(…) performs a
remove(…) for the former, and an
add(…) for the latter, so the reverse of the original mutation. Note that events are only created if there is an effective mutation. Adding an entry to a set that already contains the entry will not result in an event. Finally
AbstractHistoricalCollection.EventCollection is further extended by
AbstractHistoricalList.EventList which adds an index and is further extended by
AbstractHistoricalList.RemoveEvent. All this operates the same as explained above, with the addition that entries are removed or added at specific indices. All these event classes are sufficient for the shadow collections framework.
AbstractHistorical ⌊ Event ⌊ EventValue ⌊ EventMap ⌊ EventCollection ⌊ AddEvent ⌊ RemoveEvent ⌊ EventList ⌊ AddEvent ⌊ RemoveEvent