Access resources from mission model in procedures#1715
Access resources from mission model in procedures#1715JoelCourtney wants to merge 7 commits intodevelopfrom
Conversation
4981ca6 to
92c4f31
Compare
|
mattdailis
left a comment
There was a problem hiding this comment.
No changes requested yet, just some clarifying questions so far. I haven't dug into the classloader business yet, that'll be in a follow-up review
| fun resource(instance: NameableResource<RealDynamics>) = resource(instance.name, Real.deserializer()) | ||
| fun resource(instance: NameableResource<Boolean>) = resource(instance.name, Booleans.deserializer()) | ||
| fun resource(instance: NameableResource<String>) = resource(instance.name, Strings.deserializer()) | ||
| fun resource(instance: NameableResource<Number>) = resource(instance.name, Numbers.deserializer()) |
There was a problem hiding this comment.
Kotlin newbie question: it seems like there are multiple functions defined here with the same name, and whose arguments have the same type erasure—how is this possible?
There was a problem hiding this comment.
Wow I didn't even realize how unusual this was when I wrote it. You're right, this would not work in Java. Apparently the JVM is able to differentiate overloads with the same argument type erasure if their return types are different, but Java is not. I don't understand how, but some smart people tried to explain it here
There was a problem hiding this comment.
Personally even though I don't really understand it, I'm satisfied because the test cases work in Java. And the type inference works correctly, somehow (the test cases use var, and it is still able to infer the type).
I guess this means that Java's ability to resolve overloads is more flexible/powerful than what it actually allows you to declare. Which is kinda nuts.
| * | ||
| * @throws ArithmeticException if any of the segment values are not integers | ||
| */ | ||
| @JvmStatic fun intDeserializer() = { list: List<Segment<SerializedValue>> -> Numbers(list.map { seg -> |
There was a problem hiding this comment.
The new deserializer methods in this class are unused, so I'm not totally sure how they're related to the rest of this PR
| } | ||
|
|
||
| companion object { | ||
| @JvmStatic var modelSingleton: Any? = null |
There was a problem hiding this comment.
Just for your awareness/amusement, we do employ a similar pattern on the mission model initialization side: https://github.com/NASA-AMMOS/aerie/blob/a28d46e5c44c2bdb9ce122b224415fdd19e90faa/merlin-framework/src/main/java/gov/nasa/jpl/aerie/merlin/framework/ModelActions.java#L15
The reason we do it there is to enable static methods like delay and spawn to hook into the context object.
Here, I'm less sure that model() must be a static method. Would it be much less ergonomic for it to be a member of EditablePlan?
Another aside: I recall @Twisol emphasizing that the context linked above was not so much a singleton as a "dynamically scoped variable". This meant that 1) it was scoped to a particular call stack and would definitely not survive beyond it, and 2) it was scoped to a single thread, limiting its "globalness".
There was a problem hiding this comment.
I think you're right, it probably could be an instance method on EditablePlan. I'll look into it.
| ensureRequestIsCurrent(specification, request); | ||
| //create scheduler problem seeded with initial plan | ||
| final var schedulerMissionModel = loadMissionModel(planMetadata); | ||
| WithModel.setModelSingleton(schedulerMissionModel.missionModel.getModel()); |
There was a problem hiding this comment.
If we stick with the singleton approach here, it may be worth making it an AutoCloseable and putting it in a try-with-resources here. That way we can reliably clear it out once we're done with it, so it doesn't stick around hogging memory or possibly being accidentally reused on a future run.



Description
This PR lets goals and constraints query resources by referencing them through the mission model. Meaning you can write
simResults.resource(model.fruit)instead ofsimResults.resource("/fruit", Real.deserializer()), wheremissionis a valid instance of the mission model.WithModel<M>interface, which has a mutable static fieldmodelSingleton. The scheduler or constraint action creates an instance of the model with the default sim config and sets the sim config at the beginning of the action. Goals/constraints that inherit fromWithModel<M>can then get that instance withthis.model().NameableResourceinterface andNamedResourceabstract implementor of that interface for convenience. Concrete resource types can inherit fromNamedResource, which just lets theRegistrarcallNameableResource.setName(string). This happens during the model constructor, so it happens both during simulation (which is useless) and during scheduling/constraint actions. The timeline librarySimulationResultsthen provides a fewresource(...)overloads that look for things likeNameableResource<RealDynamics>,NameableResource<Boolean>, etc. It callsNameableResource.getName()and associates the payload type with a timeline type likeReal,Booleans, etc.This required some fiddling with ClassLoaders to make sure that the mission model was not loaded twice by different loaders (which isn't just a waste, it also causes a class cast exception).
Verification
I've made two integration tests, one for scheduling and one for constraints. I've also manually verified that the constraint/goal jars don't include the mission model. (
unzip -l <jar> | grep banana)Documentation
I swear I'll update the guide docs this time.
Future work
Because Vile Hack No. 1 creates the model with the default sim config, this could cause some issues when we support other configs during scheduling. I'm assuming that the default config corresponds to the full model with no resources left out.