Auto-Implementation
Behavior Execution Engine's auto-implementation system enables you to provide custom Java code to implement behaviors and interactions with external tools only where you need to. This frees you from the burden of having to provide code for every single block, property, and operation in your simulation.
If you do not provide a Java implementation for a block,
Behavior Execution Engine
will auto-generate an implementation for that block, including its properties and operations, in memory at runtime. It uses public
get
methods for properties, and returns the appropriate Property
type. Operations with return types will return
the default value for those return types (null
for most types). Operations with no return type will be generated
with an empty body.
If you do provide a Java implementation for a block, you can choose which properties and operations you want to implement without having to provide code for the others. You can do this by using abstract classes as discussed below.
Interfaces
Annotating an interface with @AutoDelegateImplementation
will instruct
Behavior Execution Engine
to generate a concrete implementation for it in memory at runtime. This is useful at development time when you want to reference a block in your
IDE
for which you do not have a corresponding Java delegate.
Figure 1 shows the Passenger
interface from the
hot air balloon ride example. For this example, assume there are
no concrete implementations of Passenger
. Since Passenger
is annotated with @AutoDelegateImplementation
,
Behavior Execution Engine
will auto-generate a concrete implementation for it in memory at runtime.
package com.agi.moxie.samples.balloonride.structure;
import com.agi.moxie.api.annotations.DelegateFor;
import com.agi.moxie.api.models.Property;
import com.agi.moxie.delegates.base.FixedDuration;
import com.agi.moxie.delegates.base.Iso8601TimeInstant;
import java.lang.Boolean;
@AutoDelegateImplementation
@DelegateFor("Structure::Passenger")
public interface Passenger extends SpatialEntity {
Property<Iso8601TimeInstant> appointmentTimeProperty();
Property<FixedDuration> photoDurationProperty();
Property<Boolean> isSatisfiedWithPhotoProperty();
Property<Boolean> takingPhotoProperty();
void embark(SpatialEntity vehicle);
void disembark();
void takePhoto();
}
Figure 1: The Passenger interface with auto-implementation
Partially implemented abstract classes
You may encounter situations where you want to provide a custom Java implementation for a specific block property or operation, but not all of
them. This is referred to in
Behavior Execution Engine
as partial implementation, and you do this using abstract classes. When you annotate an abstract class with @AutoDelegateImplementation
,
you can provide custom implementations for the properties and operations you choose
and just specify the signatures for the rest. This is because
Behavior Execution Engine
generates
in-memory default implementations at runtime for every property and operation you did not implement yourself.
When you annotate an abstract class with @AutoDelegateImplementation
,
Behavior Execution Engine
auto-generates concrete implementations in
memory at runtime for abstract methods, including abstract Property
methods.
Behavior Execution Engine
generates backing fields for properties, which support change tracking, using BasicProperty
by default. Support for
call events is also included
with the auto-generated implementations for block operations.
Concrete implementations provided by you remain as is, with one exception. Behavior Execution Engine overrides your custom implementation for operations with logic to support call events, followed by a callback into your custom implementation.
Since
Behavior Execution Engine
overrides your custom implementations with callbacks into them, you cannot use final
methods.
Otherwise, an exception will be thrown at runtime.
All auto-implemented properties are guaranteed to be configured and available prior to the evaluation of any other properties or operations in the
simulation. However, when implementing an injection constructor in your abstract class, it
will
get called before all of the auto-implemented properties are initialized. So, while you can accept @InjectBySlot
values to obtain property input values from the SysML instance specifications, the auto-implemented properties will not be fully configured until
all delegate instances are initialized. If you need to access an instance of a com.agi.moxie.api.models.Property
from inside a constructor, you must create and initialize your own manually implemented backing field.
Figure 2 shows the partially implemented BalloonDelegate
abstract class from the
hot air balloon ride example.
package com.agi.moxie.samples.balloonride.structure.impl;
import com.agi.moxie.api.MoxieTime;
import com.agi.moxie.api.TimeProvider;
import com.agi.moxie.api.annotations.AutoDelegateImplementation;
import com.agi.moxie.api.annotations.DefaultDelegate;
import com.agi.moxie.api.annotations.InjectByName;
import com.agi.moxie.api.models.BasicProperty;
import com.agi.moxie.api.models.Property;
import com.agi.moxie.delegates.core.DynamicScalarValue;
import com.agi.moxie.delegates.core.ScalarValue;
import com.agi.moxie.samples.balloonride.structure.Balloon;
import com.agi.moxie.samples.balloonride.structure.Passenger;
import java.util.Collection;
import java.util.function.Function;
@AutoDelegateImplementation
@DefaultDelegate
public abstract class BalloonDelegate implements Balloon {
private final TimeProvider mTimeProvider;
private final BasicProperty<ScalarValue> mAltitude;
private final Function<MoxieTime, Double> mStationaryFunction = (MoxieTime time) -> 0D;
// Injection constructor
public BalloonDelegate(@InjectByName("timeProvider") TimeProvider timeProvider) {
mTimeProvider = timeProvider;
mAltitude = new BasicProperty<>(timeProvider,
new DynamicScalarValue(mTimeProvider, mStationaryFunction, mStationaryFunction));
}
@Override
public Property<ScalarValue> altitudeProperty() {
return mAltitude;
}
public void turnBurnerOn() {
ScalarValue newAltitudeModel = adjustAltitudeModel(climbAccelerationProperty().getValue());
mAltitude.setValue(newAltitudeModel);
}
public void turnBurnerOff() {
ScalarValue newAltitudeModel = adjustAltitudeModel(-climbAccelerationProperty().getValue());
mAltitude.setValue(newAltitudeModel);
}
@Override
public void tieDown() {
mAltitude.setValue(new DynamicScalarValue(mTimeProvider, mStationaryFunction, mStationaryFunction));
}
// This will have an implementation provided automatically if not overridden in a derived class.
public abstract Collection<Passenger> passengersProperty();
/**
* Provides a new {@link DynamicScalarValue} with the given acceleration.
*
* @param climbAcceleration the acceleration to update the physics to use.
* @return the new {@link DynamicScalarValue} that reflects the given acceleration
*/
private DynamicScalarValue adjustAltitudeModel(Double climbAcceleration) {
DynamicScalarValue dist = (DynamicScalarValue) mAltitude.getValue();
MoxieTime currentTime = mTimeProvider.getCurrentTime();
double mInitialAltitude = dist.getValueAt(currentTime);
double mInitialAltitudeRate = dist.getRateAt(currentTime);
DynamicScalarValue newDistance = new DynamicScalarValue(mTimeProvider, (MoxieTime time) -> {
// Note that we are closing over 'currentTime' here, capturing the change in dynamics starting at the current event time.
double t = currentTime.getDurationTo(time).getTotalSeconds();
// The following is an extremely simplified model of the physics for a simple demonstration.
// Ideally, this code would call into a more complex model over time as the system engineering lifecycle progresses.
// Eventually, this could call into an external tool that would be executing the digital twin to analyze the full
// heating and buoyancy profile for the balloon sub-system in accordance to the characteristics of the burner sub-system.
// For now, we simply model a fixed acceleration modeling the buoyancy, air resistance, and other forces.
return mInitialAltitude + mInitialAltitudeRate * t + 0.5 * climbAcceleration * t * t;
}, (MoxieTime time) -> {
double t = currentTime.getDurationTo(time).getTotalSeconds();
return mInitialAltitudeRate + climbAcceleration * t;
});
return newDistance;
}
}
Figure 2: The BalloonDelegate partially implemented abstract class
Partially implemented concrete classes
As with interfaces and abstract classes, when you add the @AutoDelegateImplementation
annotation to a concrete
class,
Behavior Execution Engine
generates the missing block properties and operations that do not have corresponding Java properties or methods at runtime.
Behavior Execution Engine
supports call events by default in the same way as abstract classes, so you cannot use final
methods
corresponding to
block operations.
Differing signatures
Since Behavior Execution Engine implements missing properties and methods for you, any mistakes you make in the signatures of the properties and methods you do write will cause them to be ignored by Behavior Execution Engine at runtime. If you encounter cases where your code is not being called, be sure to check the spelling, casing, and type information to ensure it matches the model definition.
When using partial implementation, @InjectBySlot
and @InjectByName
are
both allowed. However, the wire-up process will operate on concretely, partially, and
automatically implemented
properties. Furthermore, the order in which
Behavior Execution Engine
does the wire-up is not guaranteed. However, all properties with slot values are guaranteed to be set prior to executing any
state machines.