Skip to content
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

Streamline system initialization refactor #1629

Draft
wants to merge 5 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package gov.nasa.jpl.aerie.contrib.streamline;

import gov.nasa.jpl.aerie.contrib.streamline.core.Resource;
import gov.nasa.jpl.aerie.contrib.streamline.debugging.Logging;
import gov.nasa.jpl.aerie.contrib.streamline.modeling.Registrar;
import gov.nasa.jpl.aerie.contrib.streamline.modeling.Registration;
import gov.nasa.jpl.aerie.contrib.streamline.modeling.clocks.Clock;
import gov.nasa.jpl.aerie.contrib.streamline.modeling.clocks.ClockResources;
import gov.nasa.jpl.aerie.contrib.streamline.modeling.clocks.InstantClock;
import gov.nasa.jpl.aerie.contrib.streamline.modeling.clocks.InstantClockResources;
import gov.nasa.jpl.aerie.merlin.protocol.types.Duration;

import java.time.Instant;
import java.util.Objects;

import static gov.nasa.jpl.aerie.contrib.streamline.core.Resources.currentValue;

public final class StreamlineSystem {
private static Resource<Clock> CLOCK;
private static Resource<InstantClock> ABSOLUTE_CLOCK;

private StreamlineSystem() {}

/**
* Arguments required for {@link StreamlineSystem#init}, packaged into an object for easier handling.
* <p>
* Can be constructed directly, or through {@link InitArgs#builder}.
* </p>
*/
public record InitArgs(
gov.nasa.jpl.aerie.merlin.framework.Registrar baseRegistrar,
Registrar.ErrorBehavior errorBehavior,
Instant planStart) {
/**
* Returns a blank {@link Builder}.
*/
public static Builder builder() {
return new Builder();
}

/**
* Returns a {@link Builder} with some fields set to defaults generally appropriate for testing.
*/
public static Builder testBuilder() {
return builder()
.errorBehavior(Registrar.ErrorBehavior.Throw)
.planStart(Instant.EPOCH);
}

public static class Builder {
private gov.nasa.jpl.aerie.merlin.framework.Registrar baseRegistrar;
private Registrar.ErrorBehavior errorBehavior;
private Instant planStart;

private Builder() {}

public Builder baseRegistrar(final gov.nasa.jpl.aerie.merlin.framework.Registrar baseRegistrar) {
this.baseRegistrar = baseRegistrar;
return this;
}

public Builder errorBehavior(final Registrar.ErrorBehavior errorBehavior) {
this.errorBehavior = errorBehavior;
return this;
}

public Builder planStart(final Instant planStart) {
this.planStart = planStart;
return this;
}

public InitArgs build() {
return new InitArgs(
Objects.requireNonNull(baseRegistrar, "baseRegistrar must be set"),
Objects.requireNonNull(errorBehavior, "errorBehavior must be set"),
Objects.requireNonNull(planStart, "planStart must be set")
);
}
}

}

/**
* Initialize all streamline singletons.
* This method should be called once as the first step of creating a model.
* <p>
* This will call the following subordinate initialization methods:
* <ul>
* <li>{@link Logging#init}</li>
* <li>{@link Registration#init}</li>
* </ul>
* as well as initialize the singletons contained within this class.
* </p>
*/
public static void init(InitArgs args) {
CLOCK = ClockResources.clock();
ABSOLUTE_CLOCK = InstantClockResources.absoluteClock(args.planStart);
Logging.init(args.baseRegistrar);
Registration.init(args.baseRegistrar, args.errorBehavior);
}

public static Duration currentTime() {
return currentValue(CLOCK);
}

public static Resource<Clock> simulationClock() {
return CLOCK;
}

public static Instant currentInstant() {
return currentValue(ABSOLUTE_CLOCK);
}

public static Resource<InstantClock> absoluteClock() {
return ABSOLUTE_CLOCK;
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
package gov.nasa.jpl.aerie.contrib.streamline.core;

import gov.nasa.jpl.aerie.contrib.streamline.modeling.clocks.Clock;
import gov.nasa.jpl.aerie.contrib.streamline.StreamlineSystem;
import gov.nasa.jpl.aerie.merlin.framework.Condition;
import gov.nasa.jpl.aerie.merlin.framework.Scoped;
import gov.nasa.jpl.aerie.merlin.protocol.types.Duration;
import gov.nasa.jpl.aerie.merlin.protocol.types.Unit;

Expand All @@ -19,7 +18,6 @@
import static gov.nasa.jpl.aerie.contrib.streamline.core.Reactions.wheneverDynamicsChange;
import static gov.nasa.jpl.aerie.contrib.streamline.debugging.Dependencies.addDependency;
import static gov.nasa.jpl.aerie.contrib.streamline.debugging.Naming.*;
import static gov.nasa.jpl.aerie.contrib.streamline.modeling.clocks.Clock.clock;
import static gov.nasa.jpl.aerie.merlin.framework.ModelActions.*;
import static gov.nasa.jpl.aerie.merlin.protocol.types.Duration.ZERO;
import static gov.nasa.jpl.aerie.contrib.streamline.modeling.discrete.Discrete.discrete;
Expand All @@ -30,32 +28,6 @@
public final class Resources {
private Resources() {}

/**
* Ensure that Resources are initialized.
*
* <p>
* This method needs to be called during simulation initialization.
* This method is idempotent; calling it multiple times is the same as calling it once.
* </p>
*/
public static void init() {
currentTime();
}

// TODO if Aerie provides either a `getElapsedTime` method or dynamic allocation of Cells, we can avoid this mutable static variable
private static Resource<Clock> CLOCK = resource(clock(ZERO));
public static Duration currentTime() {
try {
return currentValue(CLOCK);
} catch (Scoped.EmptyDynamicCellException | IllegalArgumentException e) {
// If we're running unit tests, several simulations can happen without reloading the Resources class.
// In that case, we'll have discarded the clock resource we were using, and get the above exception.
// REVIEW: Is there a cleaner way to make sure this resource gets (re-)initialized?
CLOCK = resource(clock(ZERO));
return currentValue(CLOCK);
}
}

public static <D> D currentData(Resource<D> resource) {
return data(resource.getDynamics());
}
Expand Down Expand Up @@ -96,10 +68,10 @@ public static <V, D extends Dynamics<V, D>> V value(ErrorCatching<Expiring<D>> d
*/
public static <D extends Dynamics<?, D>> Condition dynamicsChange(Resource<D> resource) {
final var startingDynamics = resource.getDynamics();
final Duration startTime = currentTime();
final Duration startTime = StreamlineSystem.currentTime();
Condition result = (positive, atEarliest, atLatest) -> {
var currentDynamics = resource.getDynamics();
var elapsedTime = currentTime().minus(startTime);
var elapsedTime = StreamlineSystem.currentTime().minus(startTime);
boolean haveChanged = startingDynamics.match(
start -> currentDynamics.match(
current -> !current.data().equals(start.data().step(elapsedTime)) ||
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,10 @@ private Logging() {}

/**
* Initialize the primary logger.
* This is called when constructing a {@link gov.nasa.jpl.aerie.contrib.streamline.modeling.Registrar},
* and does not need to be called directly by the model.
* This is called by {@link gov.nasa.jpl.aerie.contrib.streamline.StreamlineSystem#init}
* and should not be called by the model directly.
*/
public static void init(final Registrar registrar) {
if (LOGGER == null) {
LOGGER = new Logger(registrar);
} else {
LOGGER.warning("Attempting to re-initialize primary logger. This attempt is being ignored.");
}
LOGGER = new Logger(registrar);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package gov.nasa.jpl.aerie.contrib.streamline.debugging;

import gov.nasa.jpl.aerie.contrib.streamline.StreamlineSystem;
import gov.nasa.jpl.aerie.contrib.streamline.core.MutableResource;
import gov.nasa.jpl.aerie.contrib.streamline.core.Dynamics;
import gov.nasa.jpl.aerie.contrib.streamline.core.DynamicsEffect;
Expand All @@ -12,8 +13,6 @@
import java.util.Stack;
import java.util.function.Supplier;

import static gov.nasa.jpl.aerie.contrib.streamline.core.Resources.currentTime;

/**
* Functions for debugging resources by tracing their calculation.
*/
Expand Down Expand Up @@ -90,9 +89,9 @@ public static Supplier<Condition> trace(Supplier<String> name, Supplier<Conditio

private static <T> T traceAction(Supplier<String> name, Supplier<T> action) {
activeTracePoints.push(name.get());
System.out.printf("TRACE: %s - %s start...%n", currentTime(), formatStack());
System.out.printf("TRACE: %s - %s start...%n", StreamlineSystem.currentTime(), formatStack());
T result = action.get();
System.out.printf("TRACE: %s - %s: %s%n", currentTime(), formatStack(), result);
System.out.printf("TRACE: %s - %s: %s%n", StreamlineSystem.currentTime(), formatStack(), result);
activeTracePoints.pop();
return result;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,6 @@ public enum ErrorBehavior {
}

public Registrar(final gov.nasa.jpl.aerie.merlin.framework.Registrar baseRegistrar, final ErrorBehavior errorBehavior) {
Resources.init();
Logging.init(baseRegistrar);
this.baseRegistrar = baseRegistrar;
this.errorBehavior = errorBehavior;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package gov.nasa.jpl.aerie.contrib.streamline.modeling;

public final class Registration {
private Registration() {}

public static Registrar REGISTRAR;

/**
* Initialize the primary registrar.
* This is called by {@link gov.nasa.jpl.aerie.contrib.streamline.StreamlineSystem#init}
* and should not be called by the model directly.
*/
public static void init(
final gov.nasa.jpl.aerie.merlin.framework.Registrar baseRegistrar,
final Registrar.ErrorBehavior errorBehavior) {
REGISTRAR = new Registrar(baseRegistrar, errorBehavior);
}
}
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) {
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) {
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));
}
}
Loading
Loading