-
Notifications
You must be signed in to change notification settings - Fork 21
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add InstantClock and VariableInstantClock support #1589
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package gov.nasa.jpl.aerie.contrib.streamline.modeling.clocks; | ||
|
||
import gov.nasa.jpl.aerie.contrib.streamline.core.Dynamics; | ||
import gov.nasa.jpl.aerie.merlin.protocol.types.Duration; | ||
|
||
import java.time.Instant; | ||
import java.time.temporal.ChronoUnit; | ||
|
||
import static gov.nasa.jpl.aerie.merlin.protocol.types.Duration.addToInstant; | ||
|
||
/** | ||
* A variation on {@link Clock} that represents an absolute {@link Instant} | ||
* instead of a relative {@link Duration}. | ||
*/ | ||
public record InstantClock(Instant extract) implements Dynamics<Instant, InstantClock> { | ||
@Override | ||
public InstantClock step(Duration t) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just musing - would it make any sense for InstantClock to be derived from the Duration Clock? Or do you find that separating them is cleaner? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Java won't let us derive one from the other, because they both implement Dynamics but with different type arguments. I also kind of like that an Instant clock doesn't necessarily have a preferred "zero" / "epoch" time, and you can just create a Duration clock by giving a zero time that makes sense in context. |
||
return new InstantClock(addToInstant(extract, t)); | ||
} | ||
|
||
// TODO - this method belongs somewhere else... | ||
// Making it package-private at least lets us move it later without dependency issues outside the library. | ||
static Duration durationBetween(Instant start, Instant end) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In a perfect world, we'd have the Duration class in merlin-sdk be the thinnest possible wrapper around an integer in microseconds, and the framework would define decorator methods like this one that would enhance its usability. As it stands... it probably makes the most sense to add this method to the Duration class itself. |
||
return Duration.of(ChronoUnit.MICROS.between(start, end), Duration.MICROSECONDS); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
package gov.nasa.jpl.aerie.contrib.streamline.modeling.clocks; | ||
|
||
import gov.nasa.jpl.aerie.contrib.streamline.core.*; | ||
import gov.nasa.jpl.aerie.contrib.streamline.core.monads.ResourceMonad; | ||
import gov.nasa.jpl.aerie.contrib.streamline.modeling.discrete.Discrete; | ||
import gov.nasa.jpl.aerie.merlin.protocol.types.Duration; | ||
|
||
import java.time.Instant; | ||
|
||
import static gov.nasa.jpl.aerie.contrib.streamline.core.MutableResource.resource; | ||
import static gov.nasa.jpl.aerie.contrib.streamline.core.monads.ResourceMonad.map; | ||
import static gov.nasa.jpl.aerie.contrib.streamline.debugging.Naming.name; | ||
import static gov.nasa.jpl.aerie.contrib.streamline.modeling.discrete.DiscreteResources.constant; | ||
|
||
public class InstantClockResources { | ||
/** | ||
* Create an absolute clock that starts now at the given start time. | ||
*/ | ||
public static MutableResource<InstantClock> absoluteClock(Instant startTime) { | ||
return resource(new InstantClock(startTime)); | ||
} | ||
|
||
public static Resource<InstantClock> addToInstant(Instant zeroTime, Resource<Clock> relativeClock) { | ||
return addToInstant(constant(zeroTime), relativeClock); | ||
} | ||
|
||
public static Resource<InstantClock> addToInstant(Resource<Discrete<Instant>> zeroTime, Resource<Clock> relativeClock) { | ||
return name( | ||
map(zeroTime, relativeClock, (zero, clock) -> | ||
new InstantClock(Duration.addToInstant(zero.extract(), clock.extract()))), | ||
"%s + %s", | ||
zeroTime, | ||
relativeClock); | ||
} | ||
|
||
public static Resource<Clock> relativeTo(Resource<InstantClock> clock, Resource<Discrete<Instant>> zeroTime) { | ||
return name(ResourceMonad.map(clock, zeroTime, (c, t) -> new Clock(InstantClock.durationBetween(t.extract(), c.extract()))), | ||
"%s relative to %s", clock, zeroTime); | ||
} | ||
|
||
public static Resource<Discrete<Boolean>> lessThan(Resource<InstantClock> clock, Resource<Discrete<Instant>> threshold) { | ||
return ClockResources.lessThan(relativeTo(clock, threshold), constant(Duration.ZERO)); | ||
} | ||
|
||
public static Resource<Discrete<Boolean>> lessThanOrEquals(Resource<InstantClock> clock, Resource<Discrete<Instant>> threshold) { | ||
return ClockResources.lessThanOrEquals(relativeTo(clock, threshold), constant(Duration.ZERO)); | ||
} | ||
|
||
public static Resource<Discrete<Boolean>> greaterThan(Resource<InstantClock> clock, Resource<Discrete<Instant>> threshold) { | ||
return ClockResources.greaterThan(relativeTo(clock, threshold), constant(Duration.ZERO)); | ||
} | ||
|
||
public static Resource<Discrete<Boolean>> greaterThanOrEquals(Resource<InstantClock> clock, Resource<Discrete<Instant>> threshold) { | ||
return ClockResources.greaterThanOrEquals(relativeTo(clock, threshold), constant(Duration.ZERO)); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package gov.nasa.jpl.aerie.contrib.streamline.modeling.clocks; | ||
|
||
import gov.nasa.jpl.aerie.contrib.streamline.core.Dynamics; | ||
import gov.nasa.jpl.aerie.merlin.protocol.types.Duration; | ||
|
||
import java.time.Instant; | ||
|
||
import static gov.nasa.jpl.aerie.merlin.protocol.types.Duration.addToInstant; | ||
|
||
/** | ||
* A variation on {@link VariableClock} that represents an absolute {@link Instant} | ||
* instead of a relative {@link Duration}. | ||
*/ | ||
public record VariableInstantClock(Instant extract, int multiplier) implements Dynamics<Instant, VariableInstantClock> { | ||
@Override | ||
public VariableInstantClock step(Duration t) { | ||
return new VariableInstantClock(addToInstant(extract, t.times(multiplier)), multiplier); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
package gov.nasa.jpl.aerie.contrib.streamline.modeling.clocks; | ||
|
||
import gov.nasa.jpl.aerie.contrib.streamline.core.MutableResource; | ||
|
||
import java.time.Instant; | ||
|
||
import static gov.nasa.jpl.aerie.contrib.streamline.core.monads.DynamicsMonad.effect; | ||
import static gov.nasa.jpl.aerie.contrib.streamline.debugging.Naming.name; | ||
|
||
public final class VariableInstantClockEffects { | ||
private VariableInstantClockEffects() {} | ||
|
||
/** | ||
* Stop the clock without affecting the current time. | ||
*/ | ||
public static void pause(MutableResource<VariableInstantClock> clock) { | ||
clock.emit("Pause", effect($ -> new VariableInstantClock($.extract(), 0))); | ||
} | ||
|
||
/** | ||
* Start the clock without affecting the current time. | ||
*/ | ||
public static void start(MutableResource<VariableInstantClock> clock) { | ||
clock.emit("Start", effect($ -> new VariableInstantClock($.extract(), 1))); | ||
} | ||
|
||
/** | ||
* Reset the clock to the given time, without affecting how fast it's running. | ||
*/ | ||
public static void reset(MutableResource<VariableInstantClock> clock, Instant newTime) { | ||
clock.emit(name(effect($ -> new VariableInstantClock(newTime, $.multiplier())), "Reset to %s", newTime)); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
package gov.nasa.jpl.aerie.contrib.streamline.modeling.clocks; | ||
|
||
import gov.nasa.jpl.aerie.contrib.streamline.core.Resource; | ||
import gov.nasa.jpl.aerie.contrib.streamline.modeling.discrete.Discrete; | ||
import gov.nasa.jpl.aerie.merlin.protocol.types.Duration; | ||
|
||
import java.time.Instant; | ||
|
||
import static gov.nasa.jpl.aerie.contrib.streamline.core.monads.ResourceMonad.map; | ||
import static gov.nasa.jpl.aerie.contrib.streamline.debugging.Naming.name; | ||
import static gov.nasa.jpl.aerie.contrib.streamline.modeling.clocks.InstantClock.durationBetween; | ||
import static gov.nasa.jpl.aerie.contrib.streamline.modeling.discrete.DiscreteResources.constant; | ||
|
||
public final class VariableInstantClockResources { | ||
private VariableInstantClockResources() {} | ||
|
||
public static Resource<VariableClock> relativeTo(Resource<VariableInstantClock> clock, Resource<Discrete<Instant>> zeroTime) { | ||
return name(map(clock, zeroTime, (c, t) -> | ||
new VariableClock(durationBetween(c.extract(), t.extract()), c.multiplier())), | ||
"%s relative to %s", clock, zeroTime); | ||
} | ||
|
||
public static Resource<Discrete<Boolean>> lessThan(Resource<VariableInstantClock> clock, Resource<Discrete<Instant>> threshold) { | ||
return VariableClockResources.lessThan(relativeTo(clock, threshold), constant(Duration.ZERO)); | ||
} | ||
|
||
public static Resource<Discrete<Boolean>> lessThanOrEquals(Resource<VariableInstantClock> clock, Resource<Discrete<Instant>> threshold) { | ||
return VariableClockResources.lessThanOrEquals(relativeTo(clock, threshold), constant(Duration.ZERO)); | ||
} | ||
|
||
public static Resource<Discrete<Boolean>> greaterThan(Resource<VariableInstantClock> clock, Resource<Discrete<Instant>> threshold) { | ||
return VariableClockResources.greaterThan(relativeTo(clock, threshold), constant(Duration.ZERO)); | ||
} | ||
|
||
public static Resource<Discrete<Boolean>> greaterThanOrEquals(Resource<VariableInstantClock> clock, Resource<Discrete<Instant>> threshold) { | ||
return VariableClockResources.greaterThanOrEquals(relativeTo(clock, threshold), constant(Duration.ZERO)); | ||
} | ||
|
||
public static Resource<VariableClock> between(Resource<VariableInstantClock> start, Resource<VariableInstantClock> end) { | ||
return map(start, end, (s, e) -> new VariableClock(durationBetween(s.extract(), e.extract()), e.multiplier() - s.multiplier())); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice to see this exception handler go away. Not entirely sure I follow what the paradigm shift was that made it obsolete
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In hindsight, it was obvious. I just had to put a call to
Resources.init()
(or build a streamline registrar) in the constructor for my unit tests, so the unit tests would re-init the system at the correct time. The unit test is just a model, so it's exactly the same paradigm that we use in production. I don't know why I didn't realize I could do that before. I think maybe I was originally trying to avoid the need for aninit
method altogether?