Soar provides several links on various states: the io, input-link, and output-link exist on the top state, whereas a reward-link exists on every state. This page will describe how to add your own link, using the emotion link as an example (since that's what I'm working on at the moment). Note that this is actually a couple links (like io has a couple links) and it's only on the top state. If you only want a single link and/or you want links on all states, just search the code for reward-link to see how that's different (most of the differences for reward-link are because it is on every state).
When adding a new link, there are several things you need to deal with:
- adding the link on agent creation
- removing the link on agent destruction
- recreating the link during an init-soar
- reading things off of the link
- putting things on the link
Adding a new link involves several steps:
- Creating and saving the link symbols and wmes
Commonly-used symbols are often created once and stored on the agent structure. This includes link names (the attribute of the link wme). Let's suppose I want the following link structure (I'm using concrete identifiers for clarity; the actual identifiers that get created may be different):
Code:
S1 [COLOR=#666600]^[/COLOR]emotion E1 E1 [COLOR=#666600]^[/COLOR]appraisal[COLOR=#666600]-[/COLOR]link A1 [COLOR=#666600]^[/COLOR]feeling[COLOR=#666600]-[/COLOR]link F1
To do this, go to the agent structure (agent.h) and find the section labeled "Predefined Symbols". At the end of this section, add a Symbol pointer for each new symbol:
Code:
[COLOR=#660066]Symbol[/COLOR] [COLOR=#666600]*[/COLOR] emotion_symbol[COLOR=#666600];[/COLOR] [COLOR=#660066]Symbol[/COLOR] [COLOR=#666600]*[/COLOR] appraisal_link_symbol[COLOR=#666600];[/COLOR] [COLOR=#660066]Symbol[/COLOR] [COLOR=#666600]*[/COLOR] feeling_link_symbol[COLOR=#666600];[/COLOR]
Code:
Symbol * emotion_header; wme * emotion_header_link; Symbol * emotion_header_appraisal; Symbol * emotion_header_feeling;
Code:
thisAgent->emotion_symbol = make_sym_constant (thisAgent, "emotion"); thisAgent->appraisal_link_symbol = make_sym_constant( thisAgent, "appraisal-link" ); thisAgent->feeling_link_symbol = make_sym_constant( thisAgent, "feeling-link" );
Code:
thisAgent->emotion_header = get_new_io_identifier (thisAgent, 'E'); // E1 thisAgent->emotion_header_appraisal = get_new_io_identifier (thisAgent, 'A'); // A1 thisAgent->emotion_header_feeling = get_new_io_identifier (thisAgent, 'F'); // F1 // (S1 ^emotion E1) thisAgent->emotion_header_link = add_input_wme (thisAgent, thisAgent->top_state, thisAgent->emotion_symbol, thisAgent->emotion_header); // (E1 ^appraisal-link A1) add_input_wme (thisAgent, thisAgent->emotion_header, thisAgent->appraisal_link_symbol, thisAgent->emotion_header_appraisal); // (E1 ^feeling-link F1) add_input_wme (thisAgent, thisAgent->emotion_header, thisAgent->feeling_link_symbol, thisAgent->emotion_header_feeling);
On agent destruction, we need to remove all of those symbols we saved on the agent structure. To do this, go to the release_predefined_symbols function (in symtab.cpp) and add the following to the end:
Code:
release_helper( thisAgent, &( thisAgent->emotion_symbol ) ); release_helper( thisAgent, &( thisAgent->appraisal_link_symbol ) ); release_helper( thisAgent, &( thisAgent->feeling_link_symbol ) );
In general, you do not need to explicitly release the wmes, since those will automatically be cleaned up when the wme memory pool is cleaned up during agent destruction.
Recreating the link during an init-soar
Perhaps counter-intuitively, link recreation is handled in the do_input_cycle function in io.cpp. (Historically, initial link creation was handled here as well). Basically, the reinitialize_soar function calls clear_goal_stack which destroys the entire state and then calls do_input_cycle to recreate the top state.
Find the part of do_input_cycle inside the if clause labeled "top state was just removed" and release the identifier values we created and set the corresponding pointers (including to wmes) on the agent structure to NIL:
Code:
release_io_symbol (thisAgent, thisAgent->emotion_header); release_io_symbol (thisAgent, thisAgent->emotion_header_appraisal); release_io_symbol (thisAgent, thisAgent->emotion_header_feeling); thisAgent->emotion_header = NIL; thisAgent->emotion_header_appraisal = NIL; thisAgent->emotion_header_feeling = NIL; thisAgent->emotion_header_link = NIL;
Reading things off of the link
To read things off the link, you need to loop over any wmes that might be on the link. In our example, suppose the following structure exists:
Code:
S1 ^emotion E1 E1 ^appraisal-link A1 A1 ^frame F1 F1 ^conduciveness 1.0
Code:
void get_appraisals(agent* thisAgent) { if(!thisAgent->emotion_header_appraisal) return;
Code:
slot* frame_slot = thisAgent->emotion_header_appraisal->id.slots; slot* appraisal_slot; wme *frame, *appraisal; if ( frame_slot ) { for ( ; frame_slot; frame_slot = frame_slot->next ) {
Code:
if( frame_slot->attr->sc.common_symbol_info.symbol_type == SYM_CONSTANT_SYMBOL_TYPE && !strcmp(frame_slot->attr->sc.name, "frame")) /* BADBAD: should store "frame" symbol in common symbols so can do direct comparison */ {
Code:
for ( frame = frame_slot->wmes ; frame; frame = frame->next) { if (frame->value->common.symbol_type == IDENTIFIER_SYMBOL_TYPE) { for ( appraisal_slot = frame->value->id.slots; appraisal_slot; appraisal_slot = appraisal_slot->next ) { for ( appraisal = appraisal_slot->wmes; appraisal; appraisal = appraisal->next ) { // do stuff with the wmes; in this example, will get the F1 ^conduciveness 1.0 wme here }
Code:
void get_appraisals(agent* thisAgent) { if(!thisAgent->emotion_header_appraisal) return; slot* frame_slot = thisAgent->emotion_header_appraisal->id.slots; slot* appraisal_slot; wme *frame, *appraisal; if ( frame_slot ) { for ( ; frame_slot; frame_slot = frame_slot->next ) { if( frame_slot->attr->sc.common_symbol_info.symbol_type == SYM_CONSTANT_SYMBOL_TYPE && !strcmp(frame_slot->attr->sc.name, "frame")) /* BADBAD: should store "frame" symbol in common symbols so can do direct comparison */ { for ( frame = frame_slot->wmes ; frame; frame = frame->next) { if (frame->value->common.symbol_type == IDENTIFIER_SYMBOL_TYPE) { for ( appraisal_slot = frame->value->id.slots; appraisal_slot; appraisal_slot = appraisal_slot->next ) { for ( appraisal = appraisal_slot->wmes; appraisal; appraisal = appraisal->next ) { // do stuff with the wmes; in this example, will get the F1 ^conduciveness 1.0 wme here } } } } } } } }
Code:
/* --- if there is a top state, do the normal input cycle --- */ if (thisAgent->top_state) { soar_invoke_callbacks(thisAgent, INPUT_PHASE_CALLBACK, (soar_call_data) NORMAL_INPUT_CYCLE); get_appraisals(thisAgent); // added this line }
To put things on the link, you'll want to use these two functions: add_input_wme and remove_input_wme. In my example, I want to replace a wme that may exist on the feeling-link:
Code:
S1 ^emotion E1 E1 ^feeling-link F1 F1 ^frame F2 <-- I want to blink this
Code:
... wme* feeling_frame; ...
Code:
... thisAgent->feeling_frame = 0; ...
Code:
void generate_feeling_frame(agent* thisAgent) { // clear previous feeling frame (stored on agent structure) if(thisAgent->feeling_frame) { remove_input_wme(thisAgent, thisAgent->feeling_frame); } // generate new frame thisAgent->feeling_frame = add_input_wme(thisAgent, thisAgent->emotion_header_feeling, make_sym_constant(thisAgent, "frame"), make_new_identifier(thisAgent, 'F', TOP_GOAL_LEVEL)); ... }
Code:
void generate_feeling_frame(agent* thisAgent) { // clear previous feeling frame (stored on agent structure) if(thisAgent->feeling_frame) { remove_input_wme(thisAgent, thisAgent->feeling_frame); } // generate new frame Symbol* frame_att = make_sym_constant(thisAgent, "frame"); thisAgent->feeling_frame = add_input_wme(thisAgent, thisAgent->emotion_header_feeling, frame_att, make_new_identifier(thisAgent, 'F', TOP_GOAL_LEVEL)); symbol_remove_ref(thisAgent, frame_att); ... }
Code:
/* --- if there is a top state, do the normal input cycle --- */ if (thisAgent->top_state) { soar_invoke_callbacks(thisAgent, INPUT_PHASE_CALLBACK, (soar_call_data) NORMAL_INPUT_CYCLE); get_appraisals(thisAgent); // added this line above generate_feeling_frame(thisAgent); // added this line }