// Called when an event occurs in the kernel void StringListener::OnKernelEvent(int eventIDIn, AgentSML* pAgentSML, void* pCallData) { // There are currently no kernel events corresponding to this SML event. // They are all directly generated from SML. If we later add kernel callbacks // for this class of events they would come here. smlStringEventId eventID = static_cast<smlStringEventId>(eventIDIn); StringListenerCallbackData* pCallbackData = static_cast<StringListenerCallbackData*>(pCallData); assert(pCallbackData); memset(pCallbackData->pReturnStringBuffer, 0, pCallbackData->maxLengthReturnStringBuffer); // Get the first listener for this event (or return if there are none) ConnectionListIter connectionIter ; if (!EventManager<smlStringEventId>::GetBegin(eventID, &connectionIter)) { return; } // We need the first connection for when we're building the message. Perhaps this is a sign that // we shouldn't have rolled these methods into Connection. Connection* pConnection = *connectionIter ; // Convert eventID to a string char const* event = m_pKernelSML->ConvertEventToString(eventID) ; // Build the SML message we're doing to send. soarxml::ElementXML* pMsg = pConnection->CreateSMLCommand(sml_Names::kCommand_Event) ; pConnection->AddParameterToSMLCommand(pMsg, sml_Names::kParamEventID, event) ; if (pCallbackData->pData) { pConnection->AddParameterToSMLCommand(pMsg, sml_Names::kParamValue, pCallbackData->pData) ; } // Note: we should be telling the client the maximum length of the result, // however, we're planning on changing this so there is no maximum length // so we're not implementing this. // Send the message out AnalyzeXML response ; SendEvent(pAgentSML, pConnection, pMsg, &response, connectionIter, GetEnd(eventID)) ; char const* pResult = response.GetResultString() ; if (pResult != NULL) { // If the listener returns a result then take that // value and return it in "pReturnValue" to the caller. // If the client returns a longer string than the caller allowed we just truncate it. // (In practice this shouldn't be a problem--just need to make sure nobody crashes on a super long return string). strncpy(pCallbackData->pReturnStringBuffer, pResult, pCallbackData->maxLengthReturnStringBuffer) ; pCallbackData->pReturnStringBuffer[ pCallbackData->maxLengthReturnStringBuffer - 1 ] = 0 ; // Make sure it's NULL terminated } // Clean up delete pMsg ; }
smlRunState Agent::GetRunState() { AnalyzeXML response ; bool ok = GetConnection()->SendAgentCommand(&response, sml_Names::kCommand_GetRunState, GetAgentName(), sml_Names::kParamValue, sml_Names::kParamRunState) ; if (!ok) return smlRunState(0) ; return smlRunState(response.GetResultInt(0)) ; }
int Agent::GetDecisionCycleCounter() { AnalyzeXML response ; bool ok = GetConnection()->SendAgentCommand(&response, sml_Names::kCommand_GetRunState, GetAgentName(), sml_Names::kParamValue, sml_Names::kParamDecision) ; if (!ok) return 0 ; return response.GetResultInt(0) ; }
/************************************************************* * @brief Takes an incoming SML message and responds with * an appropriate response message. * * @param pConnection The connection this message came in on. * @param pIncoming The incoming message *************************************************************/ soarxml::ElementXML* KernelSML::ProcessIncomingSML(Connection* pConnection, soarxml::ElementXML* pIncomingMsg) { if (!pIncomingMsg || !pConnection) return NULL ; // Make sure only one thread is executing commands in the kernel at a time. // This is really just an insurance policy as I don't think we'll ever execute // commands on different threads within kernelSML because we // only allow one embedded connection to the kernel, but it's nice to be sure. soar_thread::Lock lock(m_pKernelMutex) ; #ifdef DEBUG // For debugging, it's helpful to be able to look at the incoming message as an XML string char* pIncomingXML = pIncomingMsg->GenerateXMLString(true) ; #endif soarxml::ElementXML* pResponse = pConnection->CreateSMLResponse(pIncomingMsg) ; // Fatal error creating the response if (!pResponse) return NULL ; // Analyze the message and find important tags AnalyzeXML msg ; msg.Analyze(pIncomingMsg) ; // Get the "name" attribute from the <command> tag char const* pCommandName = msg.GetCommandName() ; if (pCommandName) { ProcessCommand(pCommandName, pConnection, &msg, pResponse) ; } else { // The message wasn't something we recognize. if (!msg.GetCommandTag()) AddErrorMsg(pConnection, pResponse, "Incoming message did not contain a <command> tag") ; else AddErrorMsg(pConnection, pResponse, "Incoming message did not contain a name attribute in the <command> tag") ; } #ifdef DEBUG // For debugging, it's helpful to be able to look at the response as XML char* pResponseXML = pResponse->GenerateXMLString(true) ; // Set a break point on this next line if you wish to see the incoming // and outgoing as XML before they get deleted. soarxml::ElementXML::DeleteString(pIncomingXML) ; soarxml::ElementXML::DeleteString(pResponseXML) ; #endif return pResponse ; }
bool Agent::WasAgentOnRunList() { AnalyzeXML response ; bool ok = GetConnection()->SendAgentCommand(&response, sml_Names::kCommand_WasAgentOnRunList, GetAgentName()) ; if (!ok) return false ; bool wasRun = response.GetResultBool(false) ; return wasRun ; }
// Execute the command line by building up an XML message and submitting it to our regular command processor. bool RhsListener::ExecuteCommandLine(AgentSML* pAgent, char const* pFunctionName, char const* pArgument, int maxLengthReturnValue, char* pReturnValue) { KernelSML* pKernel = m_pKernelSML ; // We'll pretend this came from the local (embedded) connection. Connection* pConnection = pKernel->GetEmbeddedConnection() ; // Build up a single command line from our functionName + argument combination std::stringstream commandLine; commandLine << pFunctionName; if (pArgument) { commandLine << " " ; commandLine << pArgument ; } // Build up a message to execute the command line bool rawOutput = true ; soarxml::ElementXML* pMsg = pConnection->CreateSMLCommand(sml_Names::kCommand_CommandLine, rawOutput) ; pConnection->AddParameterToSMLCommand(pMsg, sml_Names::kParamAgent, pAgent->GetName()); pConnection->AddParameterToSMLCommand(pMsg, sml_Names::kParamLine, commandLine.str().c_str()) ; AnalyzeXML incoming ; incoming.Analyze(pMsg) ; // Create a response object which the command line can fill in soarxml::ElementXML* pResponse = pConnection->CreateSMLResponse(pMsg) ; // Execute the command line bool ok = pKernel->ProcessCommand(sml_Names::kCommand_CommandLine, pConnection, &incoming, pResponse) ; if (ok) { // Take the result from executing the command line and fill it in to our "pReturnValue" array. AnalyzeXML response ; response.Analyze(pResponse) ; char const* pResult = response.GetResultString() ; if (pResult) { strncpy(pReturnValue, pResult, maxLengthReturnValue) ; pReturnValue[maxLengthReturnValue-1] = 0 ; } } // Clean up delete pMsg ; delete pResponse ; return ok ; }
smlPhase Agent::GetCurrentPhase() { AnalyzeXML response ; bool ok = GetConnection()->SendAgentCommand(&response, sml_Names::kCommand_GetRunState, GetAgentName(), sml_Names::kParamValue, sml_Names::kParamPhase) ; if (!ok) return sml_INPUT_PHASE ; smlPhase phase = smlPhase(response.GetResultInt(int(sml_INPUT_PHASE))) ; return phase ; }
smlRunResult Agent::GetResultOfLastRun() { AnalyzeXML response ; bool ok = GetConnection()->SendAgentCommand(&response, sml_Names::kCommand_GetResultOfLastRun, GetAgentName()) ; if (!ok) return sml_RUN_ERROR ; smlRunResult result = smlRunResult(response.GetResultInt(int(sml_RUN_ERROR))) ; return result ; }
bool Agent::IsProductionLoaded(char const* pProductionName) { if (!pProductionName) return false ; AnalyzeXML response ; bool ok = GetConnection()->SendAgentCommand(&response, sml_Names::kCommand_IsProductionLoaded, GetAgentName(), sml_Names::kParamName, pProductionName) ; if (!ok) return false ; return response.GetResultBool(false) ; }
char const* Agent::ConvertIdentifier(char const* pClientIdentifier) { // need to keep the result around after the function returns // bad static std::string kernelIdentifier; AnalyzeXML response; // Send the command to the kernel bool ret = m_Kernel->GetConnection()->SendAgentCommand(&response, sml_Names::kCommand_ConvertIdentifier, GetAgentName(), sml_Names::kParamName, pClientIdentifier); if (ret) { // Get the result as a string char const *pResult = response.GetResultString(); if (pResult && strlen(pResult)) { kernelIdentifier.assign(pResult); return kernelIdentifier.c_str(); } } return pClientIdentifier; }
void InputListener::ProcessPendingInput(AgentSML* pAgentSML, int ) { PendingInputList* pPending = pAgentSML->GetPendingInputList() ; bool ok = true ; for (PendingInputListIter iter = pPending->begin() ; iter != pPending->end() ; iter = pPending->erase(iter)) { soarxml::ElementXML* pInputMsg = *iter ; // Analyze the message and find important tags AnalyzeXML msg ; msg.Analyze(pInputMsg) ; // Get the "name" attribute from the <command> tag char const* pCommandName = msg.GetCommandName() ; // Only input commands should be stored in the pending input list (void)pCommandName; // silences warning in release mode assert(!strcmp(pCommandName, "input")) ; soarxml::ElementXML const* pCommand = msg.GetCommandTag() ; int nChildren = pCommand->GetNumberChildren() ; soarxml::ElementXML wmeXML(NULL) ; soarxml::ElementXML* pWmeXML = &wmeXML ; if (kDebugInput) PrintDebugFormat("--------- %s starting input ----------", pAgentSML->GetName()) ; for (int i = 0 ; i < nChildren ; i++) { pCommand->GetChild(&wmeXML, i) ; // Ignore tags that aren't wmes. if (!pWmeXML->IsTag(sml_Names::kTagWME)) continue ; // Find out if this is an add or a remove char const* pAction = pWmeXML->GetAttribute(sml_Names::kWME_Action) ; if (!pAction) continue ; bool add = IsStringEqual(pAction, sml_Names::kValueAdd) ; bool remove = IsStringEqual(pAction, sml_Names::kValueRemove) ; if (add) { char const* pID = pWmeXML->GetAttribute(sml_Names::kWME_Id) ; // May be a client side id value (e.g. "o3" not "O3") char const* pAttribute = pWmeXML->GetAttribute(sml_Names::kWME_Attribute) ; char const* pValue = pWmeXML->GetAttribute(sml_Names::kWME_Value) ; char const* pType = pWmeXML->GetAttribute(sml_Names::kWME_ValueType) ; // Can be NULL (=> string) char const* pTimeTag = pWmeXML->GetAttribute(sml_Names::kWME_TimeTag) ; // May be a client side time tag (e.g. -3 not +3) // Set the default value if (!pType) pType = sml_Names::kTypeString ; // Check we got everything we need if (!pID || !pAttribute || !pValue || !pTimeTag) continue ; if (kDebugInput) { PrintDebugFormat("%s Add %s ^%s %s (type %s tag %s)", pAgentSML->GetName(), pID, pAttribute, pValue, pType, pTimeTag) ; } // Add the wme ok = pAgentSML->AddInputWME( pID, pAttribute, pValue, pType, pTimeTag) && ok ; } else if (remove) { char const* pTimeTag = pWmeXML->GetAttribute(sml_Names::kWME_TimeTag) ; // May be (will be?) a client side time tag (e.g. -3 not +3) if (kDebugInput) { PrintDebugFormat("%s Remove tag %s", pAgentSML->GetName(), pTimeTag) ; } // Remove the wme ok = pAgentSML->RemoveInputWME(pTimeTag) && ok ; } } delete pInputMsg ; } std::list<DirectInputDelta>* pBufferedDirect = pAgentSML->GetBufferedDirectList(); for (std::list<DirectInputDelta>::iterator iter = pBufferedDirect->begin() ; iter != pBufferedDirect->end() ; iter = pBufferedDirect->erase(iter)) { DirectInputDelta& delta = *iter; switch (delta.type) { case DirectInputDelta::kRemove: pAgentSML->RemoveInputWME(delta.clientTimeTag); break; case DirectInputDelta::kAddString: pAgentSML->AddStringInputWME(delta.id.c_str(), delta.attribute.c_str(), delta.svalue.c_str(), delta.clientTimeTag); break; case DirectInputDelta::kAddInt: pAgentSML->AddIntInputWME(delta.id.c_str(), delta.attribute.c_str(), delta.ivalue, delta.clientTimeTag); break; case DirectInputDelta::kAddDouble: pAgentSML->AddDoubleInputWME(delta.id.c_str(), delta.attribute.c_str(), delta.dvalue, delta.clientTimeTag); break; case DirectInputDelta::kAddId: pAgentSML->AddIdInputWME(delta.id.c_str(), delta.attribute.c_str(), delta.svalue.c_str(), delta.clientTimeTag); break; default: assert(false); break; } } }
bool RhsListener::ExecuteRhsCommand(AgentSML* pAgentSML, smlRhsEventId eventID, std::string const& functionName, std::string const& arguments, std::string* pResultStr) { bool result = false ; // Get the list of connections (clients) who have registered to implement this right hand side (RHS) function. ConnectionList* pList = GetRhsListeners(functionName.c_str()) ; // If nobody is listening we're done (not a bug as we register for all rhs functions and only forward specific ones that the client has registered) if (!pList || pList->size() == 0) return result ; ConnectionListIter connectionIter = pList->begin() ; // We need the first connection for when we're building the message. Perhaps this is a sign that // we shouldn't have rolled these methods into Connection. Connection* pConnection = *connectionIter ; // Convert eventID to a string char const* event = m_pKernelSML->ConvertEventToString(eventID) ; // Build the SML message we're doing to send. // Pass the agent in the "name" parameter not the "agent" parameter as this is a kernel // level event, not an agent level one (because you need to register with the kernel to get "agent created"). soarxml::ElementXML* pMsg = pConnection->CreateSMLCommand(sml_Names::kCommand_Event) ; if (pAgentSML) pConnection->AddParameterToSMLCommand(pMsg, sml_Names::kParamName, pAgentSML->GetName()) ; pConnection->AddParameterToSMLCommand(pMsg, sml_Names::kParamEventID, event) ; pConnection->AddParameterToSMLCommand(pMsg, sml_Names::kParamFunction, functionName.c_str()) ; pConnection->AddParameterToSMLCommand(pMsg, sml_Names::kParamValue, arguments.c_str()) ; #ifdef _DEBUG // Generate a text form of the XML so we can look at it in the debugger. char* pStr = pMsg->GenerateXMLString(true) ; #endif AnalyzeXML response ; // We want to call embedded connections first, so that we get the best performance // for these functions. I don't want to sort the list or otherwise change it so // instead we'll just use a rather clumsy outer loop to do this. for (int phase = 0 ; phase < 2 && !result ; phase++) { // Only call to embedded connections bool embeddedPhase = (phase == 0) ; // Reset the iterator to the beginning of the list connectionIter = pList->begin(); // Keep looping until we get a result while (connectionIter != pList->end() && !result) { pConnection = *connectionIter ; // We call all embedded connections (same process) first before // trying any remote methods. This ensures that if multiple folks register // for the same function we execute the fastest one (w/o going over a socket for the result). if (pConnection->IsRemoteConnection() && embeddedPhase) { connectionIter++ ; continue ; } // It would be faster to just send a message here without waiting for a response // but that could produce incorrect behavior if the client expects to act *during* // the event that we're notifying them about (e.g. notification that we're in the input phase). bool ok = pConnection->SendMessageGetResponse(&response, pMsg) ; if (ok) { char const* pResult = response.GetResultString() ; if (pResult != NULL) { (*pResultStr) = pResult ; result = true ; } } connectionIter++ ; } } #ifdef _DEBUG // Release the string form we generated for the debugger pMsg->DeleteString(pStr) ; #endif // Clean up delete pMsg ; return result ; }
bool RhsListener::HandleFilterEvent(smlRhsEventId eventID, AgentSML* pAgent, char const* pArgument, std::string &pReturnValue) { // Currently only supporting one event here, but that could change in time. assert(eventID == smlEVENT_FILTER) ; // Filters are handled as a RHS function call internally, using a special reserved name. char const* pFunctionName = sml_Names::kFilterName ; // Get the list of connections (clients) who have registered to implement this right hand side (RHS) function. ConnectionList* pList = GetRhsListeners(pFunctionName) ; bool result = false ; // If nobody is listening we're done (not a bug as we register for all rhs functions and only forward specific ones that the client has registered) if (!pList || pList->size() == 0) return result ; ConnectionListIter connectionIter = pList->begin() ; // We need the first connection for when we're building the message. Perhaps this is a sign that // we shouldn't have rolled these methods into Connection. Connection* pConnection = *connectionIter ; // Convert eventID to a string char const* event = m_pKernelSML->ConvertEventToString(eventID) ; // Copy the initial command line into the return buffer and send that over. // This will be sequentially replaced by each filter in turn and whatever // is left in here at the end is the result of the filtering. // This allows multiple filters to act on the data, each modifying it as it goes. // A side effect of this is that the order the filters work on the data is significant. // Anyone in the chain can set the string to be the empty string, which brings the // whole process to a halt (they have eaten the command line at that point). pReturnValue.reserve(strlen(pArgument) + 1); //adding 1 because strlen does not count the terminating null character pReturnValue.assign(pArgument); bool stop = false ; while (connectionIter != pList->end() && !stop) { // Build the SML message we're doing to send. // Pass the agent in the "name" parameter not the "agent" parameter as this is a kernel // level event, not an agent level one (because you need to register with the kernel to get "agent created"). soarxml::ElementXML* pMsg = pConnection->CreateSMLCommand(sml_Names::kCommand_Event) ; pConnection->AddParameterToSMLCommand(pMsg, sml_Names::kParamName, pAgent ? pAgent->GetName() : "") ; pConnection->AddParameterToSMLCommand(pMsg, sml_Names::kParamEventID, event) ; pConnection->AddParameterToSMLCommand(pMsg, sml_Names::kParamFunction, sml_Names::kFilterName) ; pConnection->AddParameterToSMLCommand(pMsg, sml_Names::kParamValue, pReturnValue.c_str()) ; // We send the current command line over #ifdef _DEBUG // Generate a text form of the XML so we can look at it in the debugger. char* pStr = pMsg->GenerateXMLString(true) ; #endif AnalyzeXML response ; pConnection = *connectionIter ; bool ok = pConnection->SendMessageGetResponse(&response, pMsg) ; if (ok) { char const* pResult = response.GetResultString() ; if (pResult != NULL) { // If the listener returns a result then take that // value and return it in "pReturnValue" to the caller. // If the client returns a longer string than the caller, pReturnValue is resized to fit it. pReturnValue.reserve(strlen(pResult) + 1); //adding 1 because strlen does not count the terminating null character pReturnValue.assign(pResult); result = true ; } else { // If one of the filters returns an empty string, stop the process at that point // because the command has been "eaten". Make the result the empty string. pReturnValue[0] = 0 ; result = true ; stop = true ; } } connectionIter++ ; #ifdef _DEBUG // Release the string form we generated for the debugger pMsg->DeleteString(pStr) ; #endif // Clean up delete pMsg ; } return result ; }