Writing and Modifying Delegates
Overview
Now you need to provide the code implementation for overriding the distance covered by the racers. Building a delegate module with the SysML v2 API has never been easier. The code required to get up is small and only needs to be setup once. The <BEE Install Directory>/samples/sysml2/tutorialTortoiseVsHare/part-2 contains the completed delegate module that you can reference at any time.
Prerequisites
| Prerequisite | Description |
|---|---|
| Behavior Execution Engine Installation | You must have installed Behavior Execution Engine. |
| Tutorial Project | You must start this section with the Behavior Execution Engine simulation project from the previous section. |
Instructions
Initial Delegate Module Setup
- Create a package called
com.agi.mbse.tutorial.tortoisevsharepackage under thesrcfolder. - Create a Java Class with the name
TortoiseVsHareDelegateModuleProvider. - Modify the class signature to implement the
com.agi.mbse.sysml2.spi.DelegateModuleProviderinterface. -
Create an override method for the interface’s
provideDelegateModulemethod. The class should look like the following:public class TortoiseVsHareDelegateModuleProvider implements DelegateModuleProvider {
@Override
public DelegateModule provideDelegateModule() {
}
}The delegate module provider just does one thing. It provides the delegate module to Behavior Execution Engine when the system loads. To keep things concise, we are going to define the delegate module in the same file as the provider.
- Return a new DelegateProperty for the previously defined
provideDelegateModulemethod. - Create an override method for the DelegateProperty interface’s
getIdentitymethod. -
Return a new DelegateIdentity for the previously defined
getIdentitymethod using the following constructor arguments:UUID.fromString("d547ca5c-3fb7-11f0-ac13-325096b39f47")"TortoiseVsHare Part 2 Module""The delegate module for the Tortoise vs Hare part 2 sample."
The class should now look like the following:
public class TortoiseVsHareDelegateModuleProvider implements DelegateModuleProvider {
@Override
public DelegateModule provideDelegateModule() {
return new DelegateModule() {
@Override
public DelegateIdentity getIdentity() {
return new DelegateIdentity(
UUID.fromString("d547ca5c-3fb7-11f0-ac13-325096b39f47"),
"TortoiseVsHare Part 2 Module",
"The delegate module for the Tortoise vs Hare part 2 sample."
);
}
};
}
}
The DelegateIdentity is a class that represents the identifying information for a delegate module. The constructor we just used takes in three arguments: the UUID – universally unique identifier, the name of the module, and a short description of what the module does. Keep in mind this identifier should be a different generated UUID for every delegate module. No two modules should share the same UUID.
For the delegate module to compile, we need to add one more required method to the DelegateProperty.
- Create an override method for the interface’s
registerDelegatemethod just below thegetIdentitymethod.
At this point the class should look like the following:
public class TortoiseVsHareDelegateModuleProvider implements DelegateModuleProvider {
@Override
public DelegateModule provideDelegateModule() {
return new DelegateModule() {
@Override
public DelegateIdentity getIdentity() {
return new DelegateIdentity(
UUID.fromString("d547ca5c-3fb7-11f0-ac13-325096b39f47"),
"TortoiseVsHare Part 2 Module",
"The delegate module for the Tortoise vs Hare part 2 sample."
);
}
@Override
public void registerDelegate(CustomCodeRegistry codeRegistry) {
}
};
}
}
That’s all the code Behavior Execution Engine needs to load in the delegate module. The rest of the code will be using the new opt-in system where you can specify specific elements in the model that you want to delegate for.
Finally, for Behavior Execution Engine to see the delegate module, you need to make an addition to the
com.agi.mbse.sysml2.spi.DelegateModuleProvider file.
- Go to the directory
DelegateSource/resources/META-INF/services. - Open the file
com.agi.mbse.sysml2.spi.DelegateModuleProvider. - Set the content to:
com.agi.mbse.tutorial.tortoisevshare.TortoiseVsHareDelegateModuleProvider.
Now you are ready to build and test to see if Behavior Execution Engine can see your new delegate module.
- Run the
gradle installtask again. - Verify that the delegate module has been placed in the expected directory. By default, it should be <BEE Install Directory>/delegates.
- Create a new config file on your computer called
TortoiseVsHare Part 2.config. -
Copy and paste the snippet below into the config file:
{
"configVersion" : "0.1.0",
"delegateModules" : [
{
"identifier" : "d547ca5c-3fb7-11f0-ac13-325096b39f47",
"name" : "TortoiseVsHare Part 2 Module"
}
],
"additionalDelegateModuleSearchDirectories" : [],
"simConfigs" : [
{
"name" : "Part-2 Delegate Version",
"startTime" : "2000-01-01T12:00:00.000000Z",
"stopTime" : "2000-01-02T12:00:00.000000Z",
"targetElement" : "TortoiseVsHare Part 2::infamousRaceCase"
}
]
}If you do not have
com.agi.mbse.delegateModuleDirproperty set to the <BEE Install Directory>/delegates directory, then you'll need to add your custom location to theadditionalDelegateModuleSearchDirectorieslist. - Open the
TortoiseVsHare Part 2file for model execution. - Select the configuration file that you created in the previous steps.
- Execute the simulation.
Example Execution with Initial Module
If everything worked as expected, you should see a line that says,
Executing simulation with delegate modules: [TortoiseVsHare Part 2 Module].
This indicates that Behavior Execution Engine was able to find and load the delegate module.
You might have also noticed that there the simulation failed with an exception. Don’t worry, this is expected.
Stubbing out a Scalar Value Feature Accessor
Previously in this tutorial, we had you remove the feature value expression from the Racer’s distanceCovered attribute.
Feature value expressions for attribute are normally required for execution but in the case where you want to delegate for them,
only default values are allowed.
Since we don't want to use a fixed rate check-in system for keeping track of the distance covered by each racer, we are going to use delegate code to handle this.
-
Create a new java class file in your delegate module project called
DistanceCoveredAccessorand have it implement theScalarValueFeatureAccessorinterface with the generic type set to ScalarQuantityValue. -
Create override methods for the two required methods on the
ScalarValueFeatureAccessorinterface:getCurrentValueandsetCurrentValue. It should look like the following:public class DistanceCoveredAccessor implements ScalarValueFeatureAccessor<ScalarQuantityValue> {
@Override
public List<ScalarQuantityValue> getCurrentValue(@NotNull InstanceContext context) {
}
@Override
public void setCurrentValue(@NotNull List<Object> value, @NotNull InstanceContext context) {
}
} -
3. Throw an IllegalStartException for the
setCurrentValuemethod. In this delegate, we will not be allowing the sysml model to modify the value of thedistanceCoveredattribute. This means that any assignment actions in the model would be unexpected and should throw an error. It should look like the following:@Override
public void setCurrentValue(@NotNull List<Object> value, @NotNull InstanceContext context) {
throw new IllegalStateException("Setting is not allowed in this delegate.");
}
Before we get to determining what the current value of the racer’s distanceCovered attribute actually is,
we need to come up with a system for keeping track of it.
Registering Delegation Data
In the system we are designing as a part of this tutorial, the racers will change their speeds in response to specific events. Recording a history of how fast the racer was going, how far the racer has traveled, and the time at which the racer’s speed changed will enable us to calculate where the racer actually is at any point in the race. Furthermore, we will be able to calculate at what time intervals the racer will be at specific points of interest in the race. To do this, let’s create a simple structure for keeping track of the racer’s movement history.
- Create a new java class file called
RacerData. -
Set the content of the
RacerDataclass to the following:public record RacerData(AbsoluteTime time, ScalarQuantityValue speed, ScalarQuantityValue distanceRan) {
}
This Java record class will act as our individual data points for keeping track of the state changes in the race. Now we will need something to keep track of those points to tie it together.
- Create a new java class file called
RacerDataHistory. -
Set the contents of the
RacerDataHistoryclass to the following:public class RacerDataHistory {
private final List<RacerData> mHistory = new ArrayList<>();
public RacerData getLastRecordedRacerData() {
if (!mHistory.isEmpty()) {
return mHistory.get(mHistory.size() - 1);
} else {
return null;
}
}
public void recordRacerData(AbsoluteTime time, ScalarQuantityValue speed, ScalarQuantityValue distanceRan) {
mHistory.add(new RacerData(time, speed, distanceRan));
}
}
Now we have class capable of recording when the racers speed changes. Behavior Execution Engine provides a way of registering a
copy of this class for every instance of an element. If we register this class for the Racer part, then we will ensure that
every instance of Racer (and by extension Tortoise and Hare)
will have a RacerDataHistory instance created alongside.
- Open the
TortoiseVsHareDelegateModuleProvider. -
Add the following line above the
provideDelegateModulemethod:public static final String rootPackageName = "TortoiseVsHare Part 2";If you gave the project a different name, change the string to match.
-
Add the following lines inside the
registerDelegatemethod:codeRegistry.registerInstanceDataFor(rootPackageName + "::Racer", RacerDataHistory.class, context -> {
return new RacerDataHistory();
});
codeRegistry.delegateScalarValueForFeature(rootPackageName + "::Racer::distanceCovered", new DistanceCoveredAccessor());
The first call to the codeRegistry is how we ensure that the tortoise and
hare parts will have a RacerDataHistory instance
that will be available for access later in our delegate code. The second call is telling Behavior Execution Engine that
instead of the default implementation, we want to delegate for the Racer::distanceCovered
attribute using the DistanceCoveredAccessor class.
Working with units
This model uses ScalarQuantityValues to represent various attributes of the race and racers. To work with these, we need to keep in mind that there are two pieces that we need to work with – a number and a unit. Using Indriya, you have access to an abundance of standard units to use and perform simple unit conversions with to ensure all of our calculations are in the same unit of reference. See this page about working with units.
- Create a new java class file called
HelperCode. -
Add the following static method inside the class for use across the delegate module:
public static double convertScalarQuantityUnitValue(ScalarQuantityValue value, Unit<?> unit) {
try {
UnitConverter converter = value.getRefUnit().getConverterToAny(unit);
return converter.convert(value.getValue()).doubleValue();
} catch (IncommensurableException e) {
throw new RuntimeException(e);
}
} - Open the
RacerDataHistoryclass. -
Add the following method below the
recordRacerDatamethod:public ScalarQuantityValue calculateDistanceAtTime(AbsoluteTime time) {
RacerData lastRecordedRacerData = getLastRecordedRacerData();
double previousSpeedMetersPerSecond = HelperCode. convertScalarQuantityUnitValue(lastRecordedRacerData.speed(), Units.METRE_PER_SECOND);
double previousDistanceRanMeters = HelperCode. convertScalarQuantityUnitValue(lastRecordedRacerData.distanceRan(), Units.METRE);
long deltaTimeSeconds = Duration.between(lastRecordedRacerData.time().toJavaInstant(), time.toJavaInstant()).toSeconds();
double distanceMeters = previousDistanceRanMeters + previousSpeedMetersPerSecond * deltaTimeSeconds;
return new ScalarQuantityValue(distanceMeters, Units.METRE);
}
After making these changes, the delegate module has a reusable way of converting units and calculating where a racer
would be at a given future time. Since the accessor has the ability to call on the RacerDataHistory,
we are now ready to integrate the two together.
- Open the
DistanceCoveredAccessorclass. -
Add the following lines to the
getCurrentValuemethod:AbsoluteTime currentTime = context.getTimeService().getCurrentSimulationTime();
RacerDataHistory racerDataHistory = context.getDataService().getDelegationData(RacerDataHistory.class);
return List.of(racerDataHistory.calculateDistanceAtTime(currentTime));
At this point the delegate module is in a compilable state again. Install the delegate module
and run the simulation again. You will see that there is now a new exception. The log will report that:
Exception encountered during implementation of delegate module., along with a null pointer exception because
lastRecordedRacerData is null. This makes sense because we haven’t actually recorded any speeds yet.
Delegating for actions
In the TortoiseVsHare Part 2 model, we have effect actions on
transitions that don’t currently do anything other than show up
in the log when they occur. Let’s start by creating an organized file for the racer.
- Create a java class file called
RacerCode. -
Create two static methods that take in an ActionContext as a parameter. One should be called
startRacing(as it is meant to delegate for thestartRacingaction) and one forstopRacing(forstopRacing, naturally). TheRacerCodeclass should look like the following:public class RacerCode {
public static void startRacing(ActionContext context) {
}
public static void stopRacing(ActionContext context) {
}
} - Open the
TortoiseVsHareDelegateModuleProviderclass. -
Add the following lines to the end of the
registerDelegatemethod:codeRegistry.delegateExecutionOfAction(rootPackageName + "::Racer::startRacing", context -> {
RacerCode.startRacing(context);
return DelegationResult.NoAdditionalEvents.INSTANCE;
});
codeRegistry.delegateExecutionOfAction(rootPackageName + "::Racer::stopRacing", context -> {
RacerCode.stopRacing(context);
return DelegationResult.NoAdditionalEvents.INSTANCE;
});
These calls to codeRegistry will tell Behavior Execution Engine that whenever the Racer’s startRacing
or stopRacing actions would occur, to instead execute the delegated action.
You can verify this behavior by installing
and running again while looking for the DelegatedActionExecuted log in the trace.
Keep in mind that delegating for actions will replace all the original behavior of the action. This means that any owned
actions will not occur. This is the reason why we removed those assignment actions from the model earlier.
The delegateExecutionOfAction method expects a return of a DelegationResult.
This result is used to tell Behavior Execution Engine if there are any future events that the delegated action should wait on before performing.
Dealing with future events is out of scope of this tutorial so we use the singleton NoAdditionalEvents instance to tell
Behavior Execution Engine that after performing this delegated step, the action is ready to complete.
More information can be found on the delegate module page.
Recording information during the race
With the basic stubs in place, we can now add in the logic for changing the racer’s speed over time.
- Open the
RacerCodeclass. -
Add the following static helper method for when the racers start running:
public static void recordRunningStart(ActionContext context, ScalarQuantityValue distanceRan, AbsoluteTime time) {
RacerDataHistory racerDataHistory = context.getDataService().getDelegationData(RacerDataHistory.class);
ScalarQuantityValue topSpeed = (ScalarQuantityValue) context.getAccessorService().requestFeature(rootPackageName + "::Racer::topSpeed").getValue().get(0);
racerDataHistory.recordRacerData(time, topSpeed, distanceRan);
} -
Change the
startRacingmethod to the following:public static void startRacing(ActionContext context) {
AbsoluteTime time = context.getTimeService().getCurrentSimulationTime();
recordRunningStart(context, new ScalarQuantityValue(0, Units.METRE), time);
} -
Add the following static helper method for when the racers stop running:
public static void recordRunningStop(ActionContext context, ScalarQuantityValue distanceRan, AbsoluteTime time) {
RacerDataHistory racerDataHistory = context.getDataService().getDelegationData(RacerDataHistory.class);
racerDataHistory.recordRacerData(time, new ScalarQuantityValue(0, Units.METRE_PER_SECOND), distanceRan);
} -
Change the
stopRacingmethod to the following:public static void stopRacing(ActionContext context) {
AbsoluteTime time = context.getTimeService().getCurrentSimulationTime();
FeatureReadWriteAccessor raceFinishedAccessor = context.getAccessorService().requestMutableFeature(rootPackageName + "::Racer::raceFinished");
raceFinishedAccessor.setValue(List.of(true));
FeatureReadAccessor distanceCoveredAccessor = context.getAccessorService().requestFeature(rootPackageName + "::Racer::distanceCovered");
ScalarQuantityValue distanceCovered = (ScalarQuantityValue) distanceCoveredAccessor.getValue().get(0);
recordRunningStop(context, distanceCovered, time);
} - Install and execute the simulation again.
After making these basic changes, the racers now mark when they start racing and how fast they are going.
We are going to have to add in more logic for the hare since there are additional behaviors at play for napping during the race.
However, this change brought the racers another step closer to finishing the race. You will notice that the exception that was
raised this time was about not being able to create an evaluator for the distanceCovered attribute. This new exception occurs when
you use a delegated scalar value in a change trigger. Behavior Execution Engine needs a way to determine when some predicate
of the change event will change from false to true. More information on change events can be found
here.
Delegating for a scalar threshold comparison
Normally, the threshold system for ScalarValueFeatureAccessors is defaulted to have no implementation.
However, in this case, we will need an implementation for one.
- Open the
DistanceCoveredAccessorclass. - Create an override method for the interface’s
canCreateEvaluatormethod. -
Have the method return true in every case. This model doesn’t require any sophisticated checking, and we therefore know we’ll
always be able to evaluate the
distanceCoveredfor any given time. The method should look like the following:@Override
public boolean canCreateEvaluator(@NotNull TimeInterval analysisInterval) {
return true;
} - Create an override method for the interface’s
createEvaluatormethod. -
Return a new
ScalarValueFeatureEvaluatorfor the previously defined method. It should look like the following:@Override
public ScalarValueFeatureEvaluator<ScalarQuantityValue> createEvaluator(@NotNull TimeInterval analysisInterval) {
return new ScalarValueFeatureEvaluator<ScalarQuantityValue>() {
}
} -
Create an override method for the
ScalarValueFeatureEvaluatorinterface’sgetValuemethod with the following implementation:@Override
public List<ScalarQuantityValue> getValue(@NotNull InstanceContext context) {
AbsoluteTime currentTime = context.getTimeService().getCurrentSimulationTime();
RacerDataHistory racerDataHistory = context.getDataService().getDelegationData(RacerDataHistory.class);
return List.of(racerDataHistory.calculateDistanceAtTime(currentTime));
}The point of the evaluator is to compare possible futures together until the earliest event is determined.
-
Create an override method for the
ScalarValueFeatureEvaluatorinterface’scomputeSatisfactionIntervalsmethod with the following implementation :@Override
public List<TimeInterval> computeSatisfactionIntervals(
@NotNull InstanceContext context,
Object threshold,
ComparisonOperator comparisonOperator
) {
AbsoluteTime currentTime = context.getTimeService().getCurrentSimulationTime();
ScalarQuantityValue thresholdDistance = (ScalarQuantityValue) threshold;
double thresholdDistanceMeters = HelperCode.convertScalarQuantityUnitValue(thresholdDistance, Units.METRE);
RacerDataHistory racerDataHistory = context.getDataService().getDelegationData(RacerDataHistory.class);
RacerData lastRecordedRacerData = racerDataHistory.getLastRecordedRacerData();
double previousSpeedMetersPerSecond = convertScalarQuantityUnitValue(lastRecordedRacerData.speed(), Units.METRE_PER_SECOND);
double analysisStartDistanceMeters = racerDataHistory.calculateDistanceAtTime(currentTime).getValue().doubleValue();
double deltaTimeSeconds = Math.ceil((thresholdDistanceMeters - analysisStartDistanceMeters) / previousSpeedMetersPerSecond);
AbsoluteTime satisfiedTime = currentTime.addTotalSeconds(deltaTimeSeconds);
List<TimeInterval> satisfiedIntervals = null;
switch (comparisonOperator) {
case GREATER_THAN ->
satisfiedIntervals = List.of(new TimeInterval(satisfiedTime, analysisInterval.getStop(), false, true));
case GREATER_THAN_OR_EQUAL ->
satisfiedIntervals = List.of(new TimeInterval(satisfiedTime, analysisInterval.getStop()));
case LESS_THAN ->
satisfiedIntervals = List.of(new TimeInterval(analysisInterval.getStart(), satisfiedTime, true, false));
case LESS_THAN_OR_EQUAL ->
satisfiedIntervals = List.of(new TimeInterval(analysisInterval.getStart(), satisfiedTime));
case EQUAL -> satisfiedIntervals = List.of(new TimeInterval(satisfiedTime, satisfiedTime));
case NOT_EQUAL -> satisfiedIntervals = List.of(
new TimeInterval(analysisInterval.getStart(), satisfiedTime, true, false),
new TimeInterval(satisfiedTime, analysisInterval.getStop(), false, true)
);
}
return satisfiedIntervals;
}
In this method we are gathering historical information on where the racer was, how fast the racer was going, and when this information was recorded from the history. The threshold represents a ScalarQuantityValue distance that we are comparing to. We use the history information to calculate when the racer will have reached that threshold distance. After we calculate when the racer reaches the threshold, we can use the provided comparison operator to contextualize exactly when the comparison is satisfied.
Adding Logic for the Hare
If you run the simulation again now, you can look for the Winner and Loser
StateStarted messages. You will see that the hare will win the race and the tortoise loses. This is because the hare is faster than
the tortoise, and we are still missing the custom logic for napping.
- Create a new java class file called
HareCode. -
Create a static method called
startNappingwith the following implementation:public static void startNapping(ActionContext context) {
AbsoluteTime time = context.getTimeService().getCurrentSimulationTime();
FeatureReadAccessor distanceCoveredAccessor = context.getAccessorService().requestFeature(rootPackageName + "::Racer::distanceCovered");
ScalarQuantityValue distanceCovered = (ScalarQuantityValue) distanceCoveredAccessor.getValue().get(0);
FeatureReadWriteAccessor isNappingAccessor = context.getAccessorService().requestMutableFeature(rootPackageName + "::Hare::isNapping");
isNappingAccessor.setValue(List.of(true));
RacerCode.recordRunningStop(context, distanceCovered, time);
} -
Create a static method called
stopNappingwith the following implementation:public static void stopNapping(ActionContext context) {
AbsoluteTime time = context.getTimeService().getCurrentSimulationTime();
FeatureReadAccessor distanceCoveredAccessor = context.getAccessorService().requestFeature(rootPackageName + "::Racer::distanceCovered");
ScalarQuantityValue distanceCovered = (ScalarQuantityValue) distanceCoveredAccessor.getValue().get(0);
FeatureReadWriteAccessor isNappingAccessor = context.getAccessorService().requestMutableFeature(rootPackageName + "::Hare::isNapping");
isNappingAccessor.setValue(List.of(false));
FeatureReadWriteAccessor wantsToNapAccessor = context.getAccessorService().requestMutableFeature(rootPackageName + "::Hare::hasTakenANap");
wantsToNapAccessor.setValue(List.of(true));
RacerCode.recordRunningStart(context, distanceCovered, time);
} - Open the
TortoiseVsHareDelegateModuleProviderclass. -
Add the following lines to the end of the
registerDelegatemethod:codeRegistry.delegateExecutionOfAction(rootPackageName + "::Hare::startNapping", context -> {
HareCode.startNapping(context);
return DelegationResult.NoAdditionalEvents.INSTANCE;
});
codeRegistry.delegateExecutionOfAction(rootPackageName + "::Hare::stopNapping", context -> {
HareCode.stopNapping(context);
return DelegationResult.NoAdditionalEvents.INSTANCE;
});
As you can see, the logic from the previous tutorial’s assignment actions is now faithfully recreated in the delegated actions.
Now that the hare’s speed can be zero when resting, we need to add more logic to that threshold evaluator to avoid a division by zero.
- Open the
DistanceCoveredAccessorclass. -
Add the following method below the
computeSatisfactionIntervalsmethod:private boolean isSatisfiedWithCurrentDistance(
ComparisonOperator comparisonOperator,
double referenceDistanceMeters,
double thresholdDistanceMeters
) {
boolean satisfied = false;
switch (comparisonOperator) {
case GREATER_THAN -> satisfied = referenceDistanceMeters > thresholdDistanceMeters;
case GREATER_THAN_OR_EQUAL -> satisfied = referenceDistanceMeters >= thresholdDistanceMeters;
case LESS_THAN -> satisfied = referenceDistanceMeters < thresholdDistanceMeters;
case LESS_THAN_OR_EQUAL -> satisfied = referenceDistanceMeters <= thresholdDistanceMeters;
case EQUAL -> satisfied = referenceDistanceMeters == thresholdDistanceMeters;
case NOT_EQUAL -> satisfied = referenceDistanceMeters != thresholdDistanceMeters;
}
return satisfied;
} -
Add the following lines below the declaration of the
analysisStartDistanceMetersvariable:if (previousSpeedMetersPerSecond == 0.0) {
boolean satisfied = isSatisfiedWithCurrentDistance(comparisonOperator, analysisStartDistanceMeters, thresholdDistanceMeters);
if (satisfied) {
return List.of(analysisInterval);
} else {
return List.of();
}
}
With these last additions, the delegate module’s logic is complete. Reinstalling the delegate module and running the simulation again will reveal that the tortoise finishes the race before the hare, since the hare cannot make up the distance after the nap.
Adding Logging to the Module
You might find it difficult to sort through all the messages that show up in the trace in the log. To remedy this, you can add in your own custom log messages that will add in more useful and contextualized information for your delegate module during simulation.
- Open the
HelperCodeclass. -
Add in the following method to the class:
public static void logHareAndTortoiseComparison(ActionContext context, AbsoluteTime time, ScalarQuantityValue distanceCovered, ContextLogger logger) {
FeatureReadAccessor tortoiseAccessor = context.getAccessorService().requestFeature(rootPackageName + "::Hare::tortoise");
InstanceValue tortoise = (InstanceValue) tortoiseAccessor.getValue().get(0);
RacerDataHistory tortoiseInformation = tortoise.getContext().getDataService().getDelegationData(RacerDataHistory.class);
ScalarQuantityValue tortoiseDistanceCovered = tortoiseInformation.calculateDistanceAtTime(time);
double distanceAheadMeters =
convertScalarQuantityUnitValue(distanceCovered, Units.METRE) - convertScalarQuantityUnitValue(tortoiseDistanceCovered, Units.METRE);
if (distanceAheadMeters > 0) {
logger.info("The hare is " + Math.abs(distanceAheadMeters) + " meters behind the tortoise.");
} else {
logger.info("The hare is " + distanceAheadMeters + " meters ahead of the tortoise.");
}
}
These new log messages will give us a good indication of where the hare is in comparison to the tortoise during the race. Now we can add that call and more diagnostic information to the other methods.
- Open the
RacerCodeclass. -
Add the following lines to the
startRacingmethod:ContextLogger logger = context.getContextLogger();
String racerName = (String) context.getAccessorService().requestFeature(rootPackageName + "::Racer::name").getValue().get(0);
logger.info("The " + racerName + " is starting to run the race at: " + time.toIso8601String());
if (racerName.equals("hare")) {
HelperCode.logHareAndTortoiseComparison(context, time, new ScalarQuantityValue(0, Units.METRE), logger);
} -
Add the following lines to the
stopRacingmethod:ContextLogger logger = context.getContextLogger();
String racerName = (String) context.getAccessorService().requestFeature(rootPackageName + "::Racer::name").getValue().get(0);
logger.info("The " + racerName + " stopped running the race at: " + time.toIso8601String()); - Open the
HareCodeclass. -
Add the following lines to the
startNappingmethod:ContextLogger logger = context.getContextLogger();
HelperCode.logHareAndTortoiseComparison(context, time, distanceCovered, logger);
logger.info("The hare is taking a nap at: " + time.toIso8601String());
logger.info("The hare has run " + distanceCovered.getValue() + " " + distanceCovered.getRefUnit().getName() + "s."); -
Add the following lines to the
stopNappingmethod:ContextLogger logger = context.getContextLogger();
logger.info("The hare woke up from the nap at: " + time.toIso8601String());
HelperCode.logHareAndTortoiseComparison(context, time, distanceCovered, logger); - Install and execute the simulation.
Now when you execute the simulation, you will see your custom log messages that Behavior Execution Engine sends to the console window, telling you the exact times the racers start and stop racing, how far they have progressed at each stoppage, and how far the hare is away from the tortoise.
You have now successfully adjusted the previous iteration of the race simulation to use delegates and then modified those delegates to provide custom functionality to meet your needs.
Next Tutorial >