Skip to content
Open
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
3 changes: 2 additions & 1 deletion .prettierrc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ overrides:
printWidth: 100
tabWidth: 2
useTabs: false
trailingComma: "none"
trailingComma: "none"
endOfLine: crlf
16 changes: 14 additions & 2 deletions src/main/java/at/ac/uibk/dps/cirrina/cirrina/Runtime.kt
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,15 @@ class Runtime(
fun findInstance(stateMachineId: Id): StateMachine? =
stateMachines.firstOrNull { it.stateMachineInstanceId == stateMachineId }

/**
* Find a state machine instance by its ID.
* @param stateMachineId The ID of the state machine instance as a string.
* @return The state machine instance, or null if not found.
*/
fun findInstance(stateMachineId: String): StateMachine? =
stateMachines.firstOrNull { it.stateMachineInstanceId.toString() == stateMachineId }


/**
* Run the specified state machines defined in a CSML project.
*
Expand Down Expand Up @@ -91,16 +100,18 @@ class Runtime(
}
},
null, // Top-level state machine has no parent
null
)
.map { callable -> async(Dispatchers.Default) { callable() } }
.awaitAll()
}
}

// Creates new state machine instances, including nested state machines.
private fun newInstances(
fun newInstances(
stateMachineClasses: List<StateMachineClass>,
parentInstanceId: Id?,
creationExtent: Extent?
): List<() -> Id> =
stateMachineClasses.map { stateMachineClass ->
{
Expand All @@ -109,7 +120,7 @@ class Runtime(

// Create the nested state machines, run them, and store their instance IDs
val nestedStateMachineIds =
newInstances(stateMachineClass.nestedStateMachineClasses, parentStateMachineInstanceId)
newInstances(stateMachineClass.nestedStateMachineClasses, parentStateMachineInstanceId, creationExtent)
.map { it() }

// Associate the nested state machines with the parent state machine
Expand Down Expand Up @@ -139,6 +150,7 @@ class Runtime(
serviceImplementationSelector,
openTelemetry, // TODO: Switching to dependency injection would clean this up
parentInstance,
null
)

// Add the state machine as an event listener
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package at.ac.uibk.dps.cirrina.execution.command;

import at.ac.uibk.dps.cirrina.cirrina.Runtime;
import at.ac.uibk.dps.cirrina.execution.object.action.SpawnAction;
import at.ac.uibk.dps.cirrina.execution.object.statemachine.StateMachine;
import at.ac.uibk.dps.cirrina.utils.Id;
import java.util.List;
import java.util.Optional;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class ActionSpawnCommand extends ActionCommand {

private static final Logger logger = LogManager.getLogger();

private final SpawnAction spawnAction;

ActionSpawnCommand(ExecutionContext executionContext, SpawnAction spawnAction) {
super(executionContext);
this.spawnAction = spawnAction;
}

@Override
public List<ActionCommand> execute() throws UnsupportedOperationException {
final var stateMachine = spawnAction.getStateMachine();
final Runtime runtime = executionContext.runtime();

// Retrieve the parent state machine
final String parentStateMachineId = executionContext.scope().getId();
final StateMachine parentStateMachine = runtime.findInstance(parentStateMachineId);

// A parent instance needs to exist for spawning a new state machine instance
if (parentStateMachine == null) {
logger.error("Parent instance with ID {} not found", parentStateMachineId);
return List.of();
}

final Id parentId = parentStateMachine.getStateMachineInstanceId();

final var instanceId = runtime.newInstances(
List.of(stateMachine),
parentId,
executionContext.scope().getExtent()
);

return List.of();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@
import at.ac.uibk.dps.cirrina.execution.object.action.InvokeAction;
import at.ac.uibk.dps.cirrina.execution.object.action.MatchAction;
import at.ac.uibk.dps.cirrina.execution.object.action.RaiseAction;
import at.ac.uibk.dps.cirrina.execution.object.action.SpawnAction;
import at.ac.uibk.dps.cirrina.execution.object.action.TimeoutAction;
import at.ac.uibk.dps.cirrina.execution.object.action.TimeoutResetAction;


public class CommandFactory {

private ExecutionContext executionContext;
Expand Down Expand Up @@ -37,6 +39,9 @@ public ActionCommand createActionCommand(Action action) {
case TimeoutAction timeoutAction -> {
return new ActionTimeoutCommand(executionContext, timeoutAction);
}
case SpawnAction spawnAction -> {
return new ActionSpawnCommand(executionContext, spawnAction);
}
case TimeoutResetAction timeoutResetAction -> {
return new ActionTimeoutResetCommand(executionContext, timeoutResetAction);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package at.ac.uibk.dps.cirrina.execution.command;

import at.ac.uibk.dps.cirrina.cirrina.Runtime;
import at.ac.uibk.dps.cirrina.execution.object.event.Event;
import at.ac.uibk.dps.cirrina.execution.object.event.EventListener;
import at.ac.uibk.dps.cirrina.execution.object.statemachine.StateMachineEventHandler;
Expand All @@ -17,7 +18,8 @@ public record ExecutionContext(
EventListener eventListener,
Gauges gauges,
Counters counters,
boolean isWhile
boolean isWhile,
Runtime runtime
) {
public ExecutionContext {
Objects.requireNonNull(scope, "Scope cannot be null");
Expand All @@ -40,7 +42,8 @@ public ExecutionContext withScope(Scope scope) {
eventListener,
gauges,
counters,
isWhile
isWhile,
runtime
);
}

Expand All @@ -53,7 +56,8 @@ public ExecutionContext withIsWhile(boolean isWhile) {
eventListener,
gauges,
counters,
isWhile
isWhile,
runtime
);
}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
package at.ac.uibk.dps.cirrina.execution.object.action;

import at.ac.uibk.dps.cirrina.classes.statemachine.StateMachineClassBuilder;
import at.ac.uibk.dps.cirrina.csml.description.Csml.ActionDescription;
import at.ac.uibk.dps.cirrina.csml.description.Csml.AssignActionDescription;
import at.ac.uibk.dps.cirrina.csml.description.Csml.ContextVariableDescription;
import at.ac.uibk.dps.cirrina.csml.description.Csml.CreateActionDescription;
import at.ac.uibk.dps.cirrina.csml.description.Csml.EventDescription;
import at.ac.uibk.dps.cirrina.csml.description.Csml.InvokeActionDescription;
import at.ac.uibk.dps.cirrina.csml.description.Csml.TimeoutResetActionDescription;
import at.ac.uibk.dps.cirrina.csml.description.Csml.SpawnActionDescription;
import at.ac.uibk.dps.cirrina.csml.description.Csml.MatchActionDescription;
import at.ac.uibk.dps.cirrina.csml.description.Csml.MatchCaseDescription;
import at.ac.uibk.dps.cirrina.csml.description.Csml.RaiseActionDescription;
import at.ac.uibk.dps.cirrina.csml.description.Csml.TimeoutActionDescription;
import at.ac.uibk.dps.cirrina.csml.description.Csml.TimeoutResetActionDescription;
import at.ac.uibk.dps.cirrina.execution.object.context.ContextVariable;
import at.ac.uibk.dps.cirrina.execution.object.context.ContextVariableBuilder;
import at.ac.uibk.dps.cirrina.execution.object.event.Event;
Expand Down Expand Up @@ -197,6 +199,13 @@ public Action build() throws IllegalArgumentException, IllegalStateException {
// Construct the timeout reset action
return new TimeoutResetAction(parameters);
}
case SpawnActionDescription spawn -> {
final var stateMachine = StateMachineClassBuilder.from(spawn.getStateMachine()).build();
// Construct parameters
final var parameters = new SpawnAction.Parameters(stateMachine);

return new SpawnAction(parameters);
}
default -> throw new UnsupportedOperationException(
"Action type '%s' is not known".formatted(actionDescription.getType())
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package at.ac.uibk.dps.cirrina.execution.object.action;

import at.ac.uibk.dps.cirrina.classes.statemachine.StateMachineClass;

public class SpawnAction extends Action {

private final StateMachineClass stateMachine;

SpawnAction(Parameters parameters) {
this.stateMachine = parameters.stateMachine();
System.out.println("SpawnAction created with stateMachine: " + stateMachine.getName());
}

public StateMachineClass getStateMachine() {
return stateMachine;
}

public record Parameters(StateMachineClass stateMachine) {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ public class ContextBuilder {

private Context context;

private Extent extent;

/**
* Initializes this context builder object.
*/
Expand Down Expand Up @@ -78,6 +80,11 @@ public ContextBuilder natsContext(boolean isLocal, String natsUrl, String bucket
return this;
}

public ContextBuilder withExtent(Extent extent) {
this.extent = extent;
return this;
}

/**
* Builds the current context.
*
Expand All @@ -97,9 +104,9 @@ public Context build() throws IOException {
var name = contextVariable.getName();

// Acquire the variable value
// We pass an empty extent here, I don't think that it makes too much sense to provide anything other than an empty extent here,
// because I currently don't see a use case for looking up variables in scope while constructing a context
var value = expression.execute(new Extent());
// We pass the provided extent which will often be null
// It may not be null, if this new context belongs to a state machine that gets created by a SpawnAction command
var value = expression.execute(extent);

// Create the variable
context.create(name, value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,8 @@ public StateMachine(
StateMachineClass stateMachineClass,
ServiceImplementationSelector serviceImplementationSelector,
OpenTelemetry openTelemetry,
@Nullable StateMachine parentStateMachine
@Nullable StateMachine parentStateMachine,
@Nullable Extent extent
) {
this.runtime = runtime;
this.stateMachineClass = stateMachineClass;
Expand All @@ -148,6 +149,7 @@ public StateMachine(
.map(ContextBuilder::from)
.orElseGet(ContextBuilder::from)
.inMemoryContext(true)
.withExtent(extent != null ? extent : new Extent())
.build();
} catch (IOException ignored) {
throw new IllegalStateException(); // This should not happen
Expand Down Expand Up @@ -264,7 +266,8 @@ private CommandFactory stateMachineScopedCommandFactory(
this, // Event listener
gauges, // Gauges
counters, // Counters
false // Is while?
false, // Is while?
runtime // Runtime
)
);
}
Expand All @@ -291,7 +294,8 @@ private CommandFactory stateScopedCommandFactory(
this, // Event listener
gauges, // Gauges
counters, // Counters
false // Is while?
false, // Is while?
runtime // Runtime
)
);
}
Expand Down
7 changes: 6 additions & 1 deletion src/main/resources/pkl/csm/Csml.pkl
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ abstract class ActionDescription {
type: Type
}

typealias Type = "invoke"|"create"|"assign"|"raise"|"timeout"|"timeoutReset"|"match"
typealias Type = "invoke"|"create"|"assign"|"raise"|"timeout"|"timeoutReset"|"match"|"spawn"

open class AssignActionDescription extends ActionDescription {
type: Type = "assign"
Expand Down Expand Up @@ -102,6 +102,11 @@ open class TimeoutResetActionDescription extends ActionDescription {
action: String
}

open class SpawnActionDescription extends ActionDescription {
type: Type = "spawn"
stateMachine: StateMachineDescription
}

typealias EventChannel = "internal"|"external"|"global"|"peripheral"

class EventDescription {
Expand Down