2025-07-21 21:38:42 -07:00

1108 lines
36 KiB
Scala

package fpp.compiler.codegen
import fpp.compiler.analysis._
import fpp.compiler.ast._
import fpp.compiler.codegen._
import fpp.compiler.util._
/** Writes out C++ for component definitions */
case class ComponentCppWriter (
s: CppWriterState,
aNode: Ast.Annotated[AstNode[Ast.DefComponent]]
) extends ComponentCppWriterUtils(s, aNode) {
private val fileName = ComputeCppFiles.FileNames.getComponent(componentName)
private val dpWriter = ComponentDataProducts(s, aNode)
private val portWriter = ComponentPorts(s, aNode)
private val cmdWriter = ComponentCommands(s, aNode)
private val internalPortWriter = ComponentInternalPort(s, aNode)
private val eventWriter = ComponentEvents(s, aNode)
private val tlmWriter = ComponentTelemetry(s, aNode)
private val paramWriter = ComponentParameters(s, aNode)
private val externalStateMachineWriter = ComponentExternalStateMachines(s, aNode)
private val stateMachineWriter = ComponentStateMachines(s, aNode)
private val kindStr = componentData.kind match {
case Ast.ComponentKind.Active => "Active"
case Ast.ComponentKind.Passive => "Passive"
case Ast.ComponentKind.Queued => "Queued"
}
private val baseClassName = s"${kindStr}ComponentBase"
private val exitConstantName = s"${componentName.toUpperCase}_COMPONENT_EXIT"
private def writeIncludeDirectives: List[String] = {
val Right(a) = UsedSymbols.defComponentAnnotatedNode(s.a, aNode)
s.writeIncludeDirectives(a.usedSymbolSet)
}
def write: CppDoc = {
val includeGuard = s.includeGuardFromQualifiedName(componentSymbol, fileName)
CppWriter.createCppDoc(
s"$componentName component base class",
fileName,
includeGuard,
getMembers,
s.toolName
)
}
private def getMembers: List[CppDoc.Member] = {
val hppIncludes = getHppIncludes
val cppIncludes = getCppIncludes
val externalSmInterfaces = externalStateMachineWriter.getSmInterfaces
val cls = classMember(
Some(
addSeparatedString(
s"\\class $componentClassName\n\\brief Auto-generated base for $componentName component",
AnnotationCppWriter.asStringOpt(aNode)
)
),
componentClassName,
Some(s"public Fw::$baseClassName$externalSmInterfaces"),
getClassMembers
)
List(
List(hppIncludes, cppIncludes),
getStaticAssertion,
wrapInNamespaces(componentNamespaceIdentList, List(cls))
).flatten
}
private def getHppIncludes: CppDoc.Member = {
// Conditional headers
val dpHeaders =
guardedList (hasDataProducts) (List("Fw/Dp/DpContainer.hpp"))
val mutexHeaders =
guardedList (hasGuardedInputPorts || hasGuardedCommands || hasParameters) (
List("Os/Mutex.hpp")
)
val cmdStrHeaders =
guardedList (hasCommands || hasParameters) (List("Fw/Cmd/CmdString.hpp"))
val tlmStrHeaders =
guardedList (hasChannels) (List("Fw/Tlm/TlmString.hpp"))
val prmStrHeaders =
guardedList (hasParameters) (List("Fw/Prm/PrmString.hpp"))
val prmExtHeaders =
guardedList (hasExternalParameters) (List("Fw/Prm/PrmExternalTypes.hpp"))
val logStrHeaders =
guardedList (hasEvents) (List("Fw/Log/LogString.hpp"))
val internalStrHeaders =
guardedList (hasInternalPorts) (List("Fw/Types/InternalInterfaceString.hpp"))
val systemHeaders =
(guardedList (hasEvents) (
List("atomic")
)).map(CppWriter.systemHeaderString).sortBy(_.toLowerCase()).map(line)
val userHeaders = {
val standardHeaders = List.concat(
List(
"Fw/FPrimeBasicTypes.hpp",
"Fw/Port/InputSerializePort.hpp",
"Fw/Port/OutputSerializePort.hpp",
"Fw/Comp/ActiveComponentBase.hpp"
),
dpHeaders,
mutexHeaders,
cmdStrHeaders,
tlmStrHeaders,
prmStrHeaders,
prmExtHeaders,
logStrHeaders,
internalStrHeaders
).map(CppWriter.headerString)
val symbolHeaders = writeIncludeDirectives
(standardHeaders ++ symbolHeaders).sorted.flatMap({
case s: "#include \"Fw/Log/LogTextPortAc.hpp\"" =>
lines(
s"""|#if FW_ENABLE_TEXT_LOGGING == 1
|$s
|#endif
|""".stripMargin
)
case s => lines(s)
})
}
linesMember(
List.concat(
addBlankPrefix(systemHeaders),
addBlankPrefix(userHeaders)
)
)
}
private def getCppIncludes: CppDoc.Member = {
val userHeaders = List(
"Fw/Types/Assert.hpp",
"Fw/Types/ExternalString.hpp",
"Fw/Types/String.hpp",
s"${s.getRelativePath(fileName).toString}.hpp"
).sorted.map(CppWriter.headerString).flatMap({
case s: "#include \"Fw/Types/String.hpp\"" =>
lines(
s"""|#if FW_ENABLE_TEXT_LOGGING
|$s
|#endif
|""".stripMargin
)
case s => lines(s)
})
linesMember(Line.blank :: userHeaders, CppDoc.Lines.Cpp)
}
private def getStaticAssertion: List[CppDoc.Member] = {
if serialInputPorts.isEmpty && serialOutputPorts.isEmpty then Nil
else List(
linesMember(
Line.blank :: lines(
s"""|static_assert(
| FW_PORT_SERIALIZATION == 1,
| \"$componentName component requires serialization\"
|);
|"""
)
)
)
}
private def getClassMembers: List[CppDoc.Class.Member] = {
List.concat(
// Friend classes
getFriendClassMembers,
// Constants
getConstantMembers,
// Anonymous namespace members
getAnonymousNamespaceMembers,
// Types
dpWriter.getTypeMembers,
stateMachineWriter.getTypeMembers,
// Public function members
getPublicComponentFunctionMembers,
portWriter.getPublicFunctionMembers,
cmdWriter.getPublicFunctionMembers,
paramWriter.getPublicFunctionMembers,
// Protected function members
getProtectedComponentFunctionMembers,
portWriter.getProtectedFunctionMembers,
internalPortWriter.getFunctionMembers,
stateMachineWriter.getProtectedFunctionMembers,
cmdWriter.getProtectedFunctionMembers,
eventWriter.getFunctionMembers,
tlmWriter.getFunctionMembers,
paramWriter.getProtectedFunctionMembers,
dpWriter.getProtectedDpFunctionMembers,
dpWriter.getVirtualFunctionMembers,
getTimeFunctionMember,
getMutexOperationMembers,
// Protected/private function members
getDispatchFunctionMember,
guardedList (componentData.kind == Ast.ComponentKind.Queued) (getDispatchCurrentMembers),
// Private function members
portWriter.getPrivateFunctionMembers,
stateMachineWriter.getPrivateFunctionMembers,
paramWriter.getPrivateFunctionMembers,
dpWriter.getPrivateDpFunctionMembers,
// Member variables
portWriter.getVariableMembers,
eventWriter.getVariableMembers,
tlmWriter.getVariableMembers,
paramWriter.getVariableMembers,
stateMachineWriter.getVariableMembers,
getMsgSizeVariableMember,
getMutexVariableMembers,
)
}
private def getConstantMembers: List[CppDoc.Class.Member] = {
val constants = List(
portWriter.getConstantMembers,
cmdWriter.getConstantMembers,
eventWriter.getConstantMembers,
tlmWriter.getConstantMembers,
paramWriter.getConstantMembers,
dpWriter.getConstantMembers,
stateMachineWriter.getConstantMembers
).flatten
if constants.isEmpty then Nil
else List(
List(
linesClassMember(
List(
CppDocHppWriter.writeAccessTag("protected"),
CppDocWriter.writeBannerComment(
"Constants"
),
).flatten
)
),
constants
).flatten
}
private def getFriendClassMembers: List[CppDoc.Class.Member] = {
List(
linesClassMember(
List(
CppDocWriter.writeBannerComment(
"Friend classes"
),
lines(
s"""|
|//! Friend class tester to support autocoded test harness
|friend class ${componentName}TesterBase;
|//! Friend class tester implementation to support white-box testing
|friend class ${componentName}Tester;
|"""
)
).flatten
)
)
}
private def getAnonymousNamespaceMembers: List[CppDoc.Class.Member] =
componentData.kind match {
case Ast.ComponentKind.Passive => Nil
case _ => {
val buffUnion = getBuffUnion
List(
linesClassMember(
Line.blank :: wrapInAnonymousNamespace(
intersperseBlankLines(
List(
stateMachineWriter.getAnonymousNamespaceLines,
getMsgTypeEnum,
buffUnion,
getComponentIpcSerializableBufferClass(buffUnion)
)
)
),
CppDoc.Lines.Cpp
)
)
}
}
private def getMsgTypeEnum: List[Line] = {
wrapInScope(
"enum MsgTypeEnum {",
List.concat(
lines(s"$exitConstantName = Fw::ActiveComponentBase::ACTIVE_COMPONENT_EXIT"),
dataProductAsyncInputPorts.map(portCppConstantName),
typedAsyncInputPorts.map(portCppConstantName),
serialAsyncInputPorts.map(portCppConstantName),
asyncCmds.map((_, cmd) => commandCppConstantName(cmd)),
internalPorts.map(internalPortCppConstantName),
guardedList (hasExternalStateMachineInstances) (List(externalStateMachineCppConstantName)),
guardedList (hasInternalStateMachineInstances) (List(internalStateMachineMsgType))
).map(s => line(s"$s,")),
"};"
)
}
/** Generates a union type that lets the compiler calculate
* the max serialized size of any list of arguments that goes
* on the queue */
private def getBuffUnion: List[Line] = {
// Collect the serialized sizes of all the async port arguments
// For each one, add a byte array of that size as a member
val internalPortsWithFormalParams: List[PortInstance.Internal] =
internalPorts.filter(p => getPortParams(p).size > 0)
val asyncInputPortsWithFormalParams =
(dataProductAsyncInputPorts ++ typedAsyncInputPorts).
filter(p => getPortParams(p).size > 0)
val members = List.concat(
// Data product and typed async input ports
asyncInputPortsWithFormalParams.flatMap(p => {
val portName = p.getUnqualifiedName
val portTypeName = getQualifiedPortTypeName(p, p.getDirection.get)
lines(s"BYTE ${portName}PortSize[${portTypeName}::SERIALIZED_SIZE];")
}),
// Command input port
guardedList (cmdRecvPort.isDefined)
(lines(s"BYTE cmdPortSize[Fw::InputCmdPort::SERIALIZED_SIZE];")),
// Internal ports
// Sum the sizes of the port arguments
internalPortsWithFormalParams.flatMap(p =>
line(s"// Size of ${p.getUnqualifiedName} argument list") ::
wrapInScope(
s"BYTE ${p.getUnqualifiedName}IntIfSize[",
lines(
p.aNode._2.data.params.map(param =>
writeSerializedSizeExpr(
s,
s.a.typeMap(param._2.data.typeName.id),
writeInternalPortParamType(param._2.data)
)
).mkString(" +\n")
),
"];"
)
),
guardedList (hasExternalStateMachineInstances) (
lines(
s"""|// Size of buffer for external state machine signals
|// The external SmSignalBuffer stores the signal data
|BYTE externalSmBufferSize[
| 2 * sizeof(FwEnumStoreType) + Fw::SmSignalBuffer::SERIALIZED_SIZE
|];"""
)
),
guardedList (hasInternalStateMachineInstances) (
lines(
s"""|// Size of buffer for internal state machine signals
|// The internal SmSignalBuffer stores the state machine id, the
|// signal id, and the signal data
|BYTE internalSmBufferSize[SmSignalBuffer::SERIALIZED_SIZE];"""
)
)
)
wrapInScope(
"""|// Get the max size by constructing a union of the async input, command, and
|// internal port serialization sizes
|union BuffUnion {""",
members,
"};"
)
}
private def getComponentIpcSerializableBufferClass(buffUnion: List[Line]): List[Line] = {
val maxDataSize = if buffUnion.nonEmpty then "sizeof(BuffUnion)" else "0"
lines(
s"""|// Define a message buffer class large enough to handle all the
|// asynchronous inputs to the component
|class ComponentIpcSerializableBuffer :
| public Fw::SerializeBufferBase
|{
|
| public:
|
| enum {
| // Offset into data in buffer: Size of message ID and port number
| DATA_OFFSET = sizeof(FwEnumStoreType) + sizeof(FwIndexType),
| // Max data size
| MAX_DATA_SIZE = $maxDataSize,
| // Max message size: Size of message id + size of port + max data size
| SERIALIZATION_SIZE = DATA_OFFSET + MAX_DATA_SIZE
| };
|
| Fw::Serializable::SizeType getBuffCapacity() const {
| return sizeof(m_buff);
| }
|
| U8* getBuffAddr() {
| return m_buff;
| }
|
| const U8* getBuffAddr() const {
| return m_buff;
| }
|
| private:
| // Should be the max of all the input ports serialized sizes...
| U8 m_buff[SERIALIZATION_SIZE];
|
|};
|"""
)
}
private def getPublicComponentFunctionMembers: List[CppDoc.Class.Member] = {
def writePortConnections(port: PortInstance) =
ComponentCppWriter.writePortConnections(
port,
portNumGetterName,
portVariableName,
inputPortCallbackName,
(p: PortInstance) => s"${p.getUnqualifiedName}_${p.getDirection.get.toString.capitalize}Port"
)
def writeStateMachineInit(smi: StateMachineInstance, name: String) =
smi.getSmKind match {
case StateMachine.Kind.External =>
line(s"this->m_stateMachine_$name.init(static_cast<FwEnumStoreType>(${writeSmIdName(name)}));")
case StateMachine.Kind.Internal =>
line(s"this->m_stateMachine_$name.init(${writeSmIdName(name)});")
}
val body = intersperseBlankLines(
List(
lines(
s"""|// Initialize base class
|Fw::$baseClassName::init(instance);
|"""
),
Line.addPrefixLine
(line("// Initialize state machine instances"))
(smInstancesByName.map((name, smi) => writeStateMachineInit(smi, name))),
intersperseBlankLines(specialInputPorts.map(writePortConnections)),
intersperseBlankLines(typedInputPorts.map(writePortConnections)),
intersperseBlankLines(serialInputPorts.map(writePortConnections)),
intersperseBlankLines(specialOutputPorts.map(writePortConnections)),
intersperseBlankLines(typedOutputPorts.map(writePortConnections)),
intersperseBlankLines(serialOutputPorts.map(writePortConnections)),
componentData.kind match {
case Ast.ComponentKind.Passive => Nil
case _ => List.concat(
if hasSerialAsyncInputPorts then lines(
"""|// Passed-in size added to port number and message type enumeration sizes.
|this->m_msgSize = FW_MAX(
| msgSize +
| static_cast<FwSizeType>(sizeof(FwIndexType)) +
| static_cast<FwSizeType>(sizeof(FwEnumStoreType)),
| static_cast<FwSizeType>(ComponentIpcSerializableBuffer::SERIALIZATION_SIZE)
|);
|
|// Create the queue
|Os::Queue::Status qStat = this->createQueue(queueDepth, this->m_msgSize);
|"""
)
else lines(
"""|// Create the queue
|Os::Queue::Status qStat = this->createQueue(
| queueDepth,
| static_cast<FwSizeType>(ComponentIpcSerializableBuffer::SERIALIZATION_SIZE)
|);
|"""
),
lines(
"""|FW_ASSERT(
| Os::Queue::Status::OP_OK == qStat,
| static_cast<FwAssertArgType>(qStat)
|);
|"""
)
)
}
)
)
addAccessTagAndComment(
"public",
"Component initialization",
List(
functionClassMember(
Some(s"Initialize $componentClassName object"),
"init",
initParams,
CppDoc.Type("void"),
body
)
)
)
}
private def getProtectedComponentFunctionMembers: List[CppDoc.Class.Member] = {
def writeChannelInit(channel: TlmChannel) = {
List(
lines(
s"""|// Write telemetry channel ${channel.getName}
|this->${channelUpdateFlagName(channel.getName)} = true;
|"""
),
channel.channelType match {
case t if s.isPrimitive(t, writeChannelType(t)) => lines(
s"this->${channelStorageName(channel.getName)} = 0;"
)
case _ => Nil
}
).flatten
}
addAccessTagAndComment(
"protected",
"Component construction and destruction",
List(
constructorClassMember(
Some(s"Construct $componentClassName object"),
List(
CppDoc.Function.Param(
CppDoc.Type("const char*"),
"compName",
Some("The component name"),
Some("\"\"")
)
),
List(s"Fw::${kindStr}ComponentBase(compName)") :::
(if (hasExternalParameters) List("paramDelegatePtr(NULL)") else Nil) :::
smInstancesByName.map { (name, smi) =>
val sm = s.a.stateMachineMap(smi.symbol)
val hasActionsOrGuards = sm.hasActions || sm.hasGuards
val args = (smi.getSmKind, hasActionsOrGuards) match {
case (StateMachine.Kind.External, _) => "this"
case (StateMachine.Kind.Internal, true) => "*this"
case (StateMachine.Kind.Internal, false) => ""
}
s"m_stateMachine_$name($args)"
},
intersperseBlankLines(
List(
intersperseBlankLines(
updateOnChangeChannels.map((_, channel) =>
writeChannelInit(channel)
)
),
throttledEvents.map((_, event) => line(
s"this->${eventThrottleCounterName(event.getName)} = 0;"
)),
sortedParams.flatMap((_, param) => guardedList(!param.isExternal) (
lines(s"this->${paramValidityFlagName(param.getName)} = Fw::ParamValid::UNINIT;")
))
)
)
),
destructorClassMember(
Some(s"Destroy $componentClassName object"),
Nil,
CppDoc.Class.Destructor.Virtual
)
)
)
}
private def getMutexOperationMembers: List[CppDoc.Class.Member] = {
if !(hasGuardedInputPorts || hasGuardedCommands) then Nil
else addAccessTagAndComment(
"protected",
"""|Mutex operations for guarded ports
|
|You can override these operations to provide more sophisticated
|synchronization
|""",
List(
functionClassMember(
Some("Lock the guarded mutex"),
"lock",
Nil,
CppDoc.Type("void"),
lines(
"this->m_guardedPortMutex.lock();"
),
CppDoc.Function.Virtual
),
functionClassMember(
Some("Unlock the guarded mutex"),
"unLock",
Nil,
CppDoc.Type("void"),
lines(
"this->m_guardedPortMutex.unLock();"
),
CppDoc.Function.Virtual
)
)
)
}
private def getDispatchFunctionMember: List[CppDoc.Class.Member] = {
def writeAsyncPortDispatch(p: PortInstance) = {
val body = p.getType.get match {
case PortInstance.Type.DefPort(_) =>
List(
intersperseBlankLines(
portParamTypeMap(p.getUnqualifiedName).map((n, tn, t) => {
val varDecl = writeVarDecl(s, tn, n, t)
lines(
s"""|// Deserialize argument $n
|$varDecl
|_deserStatus = _msg.deserialize($n);
|FW_ASSERT(
| _deserStatus == Fw::FW_SERIALIZE_OK,
| static_cast<FwAssertArgType>(_deserStatus)
|);
|"""
)
})
),
line("// Call handler function") ::
writeFunctionCall(
s"this->${inputPortHandlerName(p.getUnqualifiedName)}",
List("portNum"),
getPortParams(p).map(_._1)
),
Line.blank :: lines("break;")
).flatten
case PortInstance.Type.Serial => lines(
s"""|// Deserialize serialized buffer into new buffer
|U8 handBuff[this->m_msgSize];
|Fw::ExternalSerializeBuffer serHandBuff(
| handBuff,
| static_cast<Fw::Serializable::SizeType>(this->m_msgSize)
|);
|_deserStatus = _msg.deserialize(serHandBuff);
|FW_ASSERT(
| _deserStatus == Fw::FW_SERIALIZE_OK,
| static_cast<FwAssertArgType>(_deserStatus)
|);
|this->${inputPortHandlerName(p.getUnqualifiedName)}(portNum, serHandBuff);
|
|break;
|"""
)
}
line(s"// Handle async input port ${p.getUnqualifiedName}") ::
wrapInScope(
s"case ${portCppConstantName(p)}: {",
body,
"}"
)
}
def writeAsyncCommandDispatch(opcode: Command.Opcode, cmd: Command) = {
val cmdRespVarName = portVariableName(cmdRespPort.get)
val body = intersperseBlankLines(
List(
lines(
"""|// Deserialize opcode
|FwOpcodeType _opCode = 0;
|_deserStatus = _msg.deserialize(_opCode);
|FW_ASSERT (
| _deserStatus == Fw::FW_SERIALIZE_OK,
| static_cast<FwAssertArgType>(_deserStatus)
|);
|
|// Deserialize command sequence
|U32 _cmdSeq = 0;
|_deserStatus = _msg.deserialize(_cmdSeq);
|FW_ASSERT (
| _deserStatus == Fw::FW_SERIALIZE_OK,
| static_cast<FwAssertArgType>(_deserStatus)
|);
|
|// Deserialize command argument buffer
|Fw::CmdArgBuffer args;
|_deserStatus = _msg.deserialize(args);
|FW_ASSERT (
| _deserStatus == Fw::FW_SERIALIZE_OK,
| static_cast<FwAssertArgType>(_deserStatus)
|);
|
|// Reset buffer
|args.resetDeser();
|"""
),
intersperseBlankLines(
cmdParamTypeMap(opcode).map((n, tn, _) =>
lines(
s"""|// Deserialize argument $n
|$tn $n;
|_deserStatus = args.deserialize($n);
|if (_deserStatus != Fw::FW_SERIALIZE_OK) {
| if (this->$cmdRespVarName[0].isConnected()) {
| this->cmdResponse_out(
| _opCode,
| _cmdSeq,
| Fw::CmdResponse::FORMAT_ERROR
| );
| }
| // Don't crash the task if bad arguments were passed from the ground
| break;
|}
|"""
)
)
),
lines(
s"""|// Make sure there was no data left over.
|// That means the argument buffer size was incorrect.
|#if FW_CMD_CHECK_RESIDUAL
|if (args.getBuffLeft() != 0) {
| if (this->$cmdRespVarName[0].isConnected()) {
| this->cmdResponse_out(_opCode, _cmdSeq, Fw::CmdResponse::FORMAT_ERROR);
| }
| // Don't crash the task if bad arguments were passed from the ground
| break;
|}
|#endif
|"""
),
line("// Call handler function") ::
writeFunctionCall(
s"this->${commandHandlerName(cmd.getName)}",
List("_opCode, _cmdSeq"),
cmdParamTypeMap(opcode).map(_._1)
),
lines("break;")
)
)
line(s"// Handle command ${cmd.getName}") ::
wrapInScope(
s"case ${commandCppConstantName(cmd)}: {",
body,
"}"
)
}
def writeInternalPortDispatch(p: PortInstance.Internal) = {
val body = intersperseBlankLines(
List(
intersperseBlankLines(
portParamTypeMap(p.getUnqualifiedName).map((n, tn, _) =>
lines(
s"""|$tn $n;
|_deserStatus = _msg.deserialize($n);
|
|// Internal interface should always deserialize
|FW_ASSERT(
| Fw::FW_SERIALIZE_OK == _deserStatus,
| static_cast<FwAssertArgType>(_deserStatus)
|);
|"""
)
)
),
lines(
"""|// Make sure there was no data left over.
|// That means the buffer size was incorrect.
|FW_ASSERT(
| _msg.getBuffLeft() == 0,
| static_cast<FwAssertArgType>(_msg.getBuffLeft())
|);
|"""
),
line("// Call handler function") ::
writeFunctionCall(
s"this->${internalInterfaceHandlerName(p.getUnqualifiedName)}",
Nil,
getPortParams(p).map(_._1)
),
lines("break;")
)
)
line(s"// Handle internal interface ${p.getUnqualifiedName}") ::
wrapInScope(
s"case ${internalPortCppConstantName(p)}: {",
body,
"}"
)
}
if componentData.kind == Ast.ComponentKind.Passive then Nil
else {
val assertMsgStatus = lines(
"""|FW_ASSERT(
| _msgStatus == Os::Queue::OP_OK,
| static_cast<FwAssertArgType>(_msgStatus)
|);
|"""
)
addAccessTagAndComment(
componentData.kind match {
case Ast.ComponentKind.Active => "private"
case Ast.ComponentKind.Queued => "protected"
case _ => ""
},
"Message dispatch functions",
List(
functionClassMember(
Some("Called in the message loop to dispatch a message from the queue"),
"doDispatch",
Nil,
CppDoc.Type(
"MsgDispatchStatus",
Some("Fw::QueuedComponentBase::MsgDispatchStatus")
),
List(
if hasSerialAsyncInputPorts then lines(
"""|U8 _msgBuff[this->m_msgSize];
|Fw::ExternalSerializeBuffer _msg(
| _msgBuff,
| static_cast<Fw::Serializable::SizeType>(this->m_msgSize)
|);
|"""
)
else lines("ComponentIpcSerializableBuffer _msg;"),
lines(
s"""|FwQueuePriorityType _priority = 0;
|
|Os::Queue::Status _msgStatus = this->m_queue.receive(
| _msg,
| Os::Queue::${if componentData.kind == Ast.ComponentKind.Queued then "NON" else ""}BLOCKING,
| _priority
|);
|""".stripMargin
),
if componentData.kind == Ast.ComponentKind.Queued then wrapInIfElse(
"Os::Queue::Status::EMPTY == _msgStatus",
lines("return Fw::QueuedComponentBase::MSG_DISPATCH_EMPTY;"),
assertMsgStatus
)
else assertMsgStatus,
lines(
"""|
|// Reset to beginning of buffer
|_msg.resetDeser();
|
|FwEnumStoreType _desMsg = 0;
|Fw::SerializeStatus _deserStatus = _msg.deserialize(_desMsg);
|FW_ASSERT(
| _deserStatus == Fw::FW_SERIALIZE_OK,
| static_cast<FwAssertArgType>(_deserStatus)
|);
|
|MsgTypeEnum _msgType = static_cast<MsgTypeEnum>(_desMsg);
|"""
),
Line.blank :: wrapInIf(
s"_msgType == $exitConstantName",
lines("return MSG_DISPATCH_EXIT;")
),
lines(
"""|
|FwIndexType portNum = 0;
|_deserStatus = _msg.deserialize(portNum);
|FW_ASSERT(
| _deserStatus == Fw::FW_SERIALIZE_OK,
| static_cast<FwAssertArgType>(_deserStatus)
|);
|"""
),
Line.blank :: wrapInSwitch(
"_msgType",
intersperseBlankLines(
List(
intersperseBlankLines(dataProductAsyncInputPorts.map(writeAsyncPortDispatch)),
intersperseBlankLines(typedAsyncInputPorts.map(writeAsyncPortDispatch)),
intersperseBlankLines(serialAsyncInputPorts.map(writeAsyncPortDispatch)),
intersperseBlankLines(asyncCmds.map(writeAsyncCommandDispatch)),
intersperseBlankLines(internalPorts.map(writeInternalPortDispatch)),
stateMachineWriter.writeDispatchCases,
lines(
"""|default:
| return MSG_DISPATCH_ERROR;
|"""
)
)
)
),
Line.blank :: lines("return MSG_DISPATCH_OK;")
).flatten,
CppDoc.Function.Virtual
)
)
)
}
}
private def getDispatchCurrentMembers: List[CppDoc.Class.Member] = {
val body = lines(
"""|// Dispatch all current messages unless ERROR or EXIT occur
|const FwSizeType currentMessageCount = this->m_queue.getMessagesAvailable();
|MsgDispatchStatus messageStatus = MsgDispatchStatus::MSG_DISPATCH_EMPTY;
|for (FwSizeType i = 0; i < currentMessageCount; i++) {
| messageStatus = this->doDispatch();
| if (messageStatus != QueuedComponentBase::MSG_DISPATCH_OK) {
| break;
| }
|}
|return messageStatus;"""
)
addAccessTagAndComment(
"protected",
"Helper functions for dispatching current messages",
List(
functionClassMember(
Some(s"Dispatch all current messages unless ERROR or EXIT occurs"),
"dispatchCurrentMessages",
Nil,
CppDoc.Type(
"MsgDispatchStatus",
Some("Fw::QueuedComponentBase::MsgDispatchStatus")
),
body,
CppDoc.Function.NonSV
)
)
)
}
private def getTimeFunctionMember: List[CppDoc.Class.Member] =
if !hasTimeGetPort then Nil
else {
val name = portVariableName(timeGetPort.get)
addAccessTagAndComment(
"protected",
"Time",
List(
functionClassMember(
Some(
"""|Get the time
|
|\\return The current time
|"""
),
"getTime",
Nil,
CppDoc.Type("Fw::Time"),
wrapInIfElse(
s"this->$name[0].isConnected()",
lines(
s"""|Fw::Time _time;
|this->$name[0].invoke(_time);
|return _time;
|"""
),
lines(
"return Fw::Time(TimeBase::TB_NONE, 0, 0);"
)
),
CppDoc.Function.NonSV,
CppDoc.Function.Const
)
)
)
}
private def getMsgSizeVariableMember: List[CppDoc.Class.Member] = {
if !hasSerialAsyncInputPorts then Nil
else List(
linesClassMember(
List(
CppDocHppWriter.writeAccessTag("private"),
lines(
"""|
|//! Stores max message size
|FwSizeType m_msgSize;
|"""
)
).flatten
)
)
}
private def getMutexVariableMembers: List[CppDoc.Class.Member] = {
if !(hasGuardedInputPorts || hasGuardedCommands || hasParameters) then Nil
else List(
linesClassMember(
List(
CppDocHppWriter.writeAccessTag("private"),
CppDocWriter.writeBannerComment(
"Mutexes"
),
if !(hasGuardedInputPorts || hasGuardedCommands) then Nil
else lines(
"""|
|//! Mutex for guarded ports
|Os::Mutex m_guardedPortMutex;
|"""
),
if !hasParameters then Nil
else lines(
"""|
|//! Mutex for locking parameters during sets and saves
|Os::Mutex m_paramLock;
|"""
)
).flatten
)
)
}
}
object ComponentCppWriter extends CppWriterUtils {
sealed trait ConnectionSense
object ConnectionSense {
case object Forward extends ConnectionSense
case object Reversed extends ConnectionSense
}
def reverseDirection(direction: PortInstance.Direction) = {
import PortInstance.Direction._
direction match {
case Input => Output
case Output => Input
}
}
def writePortConnections(
port: PortInstance,
numGetterName: PortInstance => String,
variableName: PortInstance => String,
callbackName: String => String,
printName: PortInstance => String,
connectionSense: ConnectionSense = ConnectionSense.Forward
): List[Line] = {
val d = {
val trueDirection = port.getDirection.get
connectionSense match {
case ConnectionSense.Forward => trueDirection
case ConnectionSense.Reversed => reverseDirection(trueDirection)
}
}
val body = line(s"// Connect ${d.toString} port ${port.getUnqualifiedName}") ::
wrapInForLoopStaggered(
"FwIndexType port = 0",
s"port < static_cast<FwIndexType>(this->${numGetterName(port)}())",
"port++",
List(
lines(
s"|this->${variableName(port)}[port].init();"
),
d match {
case PortInstance.Direction.Input => lines(
s"""|this->${variableName(port)}[port].addCallComp(
| this,
| ${callbackName(port.getUnqualifiedName)}
|);
|this->${variableName(port)}[port].setPortNum(port);
|"""
)
case PortInstance.Direction.Output => Nil
},
Line.blank :: lines(
s"""|#if FW_OBJECT_NAMES == 1
|Fw::ObjectName portName;
|portName.format(
| "%s_${printName(port)}[%" PRI_FwIndexType "]",
| this->m_objName.toChar(),
| port
|);
|this->${variableName(port)}[port].setObjName(portName.toChar());
|#endif
|"""
)
).flatten
)
port match {
case PortInstance.Special(aNode, _, _, _, _, _) => aNode._2.data match {
case Ast.SpecPortInstance.Special(_, kind, _, _, _) => kind match {
case Ast.SpecPortInstance.TextEvent => List.concat(
lines("#if FW_ENABLE_TEXT_LOGGING == 1"),
body,
lines("#endif")
)
case _ => body
}
case _ => body
}
case _ => body
}
}
}