This tutorial explains the concept and usage of actions. Actions are executable mechanisms. We support a lot of different actions which support some basic functionality.
Actions are an important part within a multi-agent system e.g. for communication, modifying the environment or the internal state of the agent.
The AgentSpeak 15min tutorial defines the basic knowledge and working scenario.
For actions, there exist two points of view:
Based on these two definitions, on the Java-side a method must be written and be combined with the literal information for the AgentSpeak(L++) script, so that the agent can get access to the method. LightJason supports for this structure an interface, method annotations and reading mechanism to create all action objects.
There are two kinds of actions, both can also be used in similar context. The usage of an action is based on the software-design, so there is no general approach to use an action.
Standalone actions, which are named in the Jason definition external actions, are classes which implement the IAction interface. But we recommand to use the base implementation IBaseAction. For getting an overview of the structure, take a look on the IBaseAction inheritance diagram. The recommand parts of an action are:
name
which represents the call on the agent scriptminimal arguments
for checking the correct number of arguments during parsingexecution
which defines the functionality of the actionThere are some other methods inside the definition:
variables
method returns a list of variables if the action generates variablesObject actions are methods inside the agent class or super classes of the agent class. LightJason can read all class methods (not static or abstract methods) and create the actions on-the-fly. With black- and white-listing you can define a very detailed action generation. The visibility of a method can be public, private or protected. For the usage there exist three types of annotations:
@IAgentActionName
for defining action name@IAgentAction
, which can get two arguments:access
with the values BLACKLIST
or WHITELIST
to define the filter mechanism (default: BLACKLIST
)classes
a list of class objects for which the filtering should be defined (default empty for all classes)@IAgentActionFilter
with the classes
argument for defining class filtering of a method (see @IAgentAction
)Parameters of the method will be packed / unpacked into terms automatically.
Within this section both kinds of actions are shown. The code of an action should be very efficient and minimalistic, because an action will be called multiple times from an agent, because plan execution and also agent execution are in parallel.
The agent script shows the usage of a standalone action, it can be used like a built-in action. The naming is equal to the name
-method or annotation.
!main.
+!main <-
I = math/statistic/randomsimple() * 12 + 4;
R = string/random( "abcdefghijklmnopqrstuvwxyz", I );
N = my/standalone-action( R );
L = my/object-action( N );
generic/print( "agent uses string", R, "gets from standalone action", N, "and from object action", L )
.
The data representation can be comprehended by the standalone action. A necessary property is the serialVersionUID
which is defined by the Java Serializable interface, this allows to serialise the action.
package myagentproject;
import org.lightjason.agentspeak.action.IBaseAction;
import org.lightjason.agentspeak.common.CPath;
import org.lightjason.agentspeak.common.IPath;
import org.lightjason.agentspeak.language.CRawTerm;
import org.lightjason.agentspeak.language.ITerm;
import org.lightjason.agentspeak.language.execution.IContext;
import org.lightjason.agentspeak.language.fuzzy.CFuzzyValue;
import org.lightjason.agentspeak.language.fuzzy.IFuzzyValue;
import javax.annotation.Nonnull;
import java.text.MessageFormat;
import java.util.List;
final class CStandAloneAction extends IBaseAction
{
private static final long serialVersionUID = 2237639821101860757L;
@Nonnull
@Override
public final IPath name()
{
return CPath.from( "my/standalone-action" );
}
@Override
public final int minimalArgumentNumber()
{
return 1;
}
@Override
public final IFuzzyValue<Boolean> execute( final boolean p_parallel, @Nonnull final IContext p_context,
@Nonnull final List<ITerm> p_argument, @Nonnull final List<ITerm> p_return
)
{
System.out.println(
MessageFormat.format(
"agent {0} calls standalone-action with parameter {1}",
p_context.agent().hashCode(),
p_argument.get( 0 ).<String>raw()
)
);
p_return.add(
CRawTerm.from(
p_argument.get( 0 ).<String>raw().length()
)
);
return CFuzzyValue.from( true );
}
}
In object / internal actions, the pack & unpack process of values is done automatically. The actions are written inside the agent class and must be annotated
package myagentproject;
import org.lightjason.agentspeak.action.binding.IAgentAction;
import org.lightjason.agentspeak.action.binding.IAgentActionFilter;
import org.lightjason.agentspeak.action.binding.IAgentActionName;
import org.lightjason.agentspeak.agent.IBaseAgent;
import org.lightjason.agentspeak.configuration.IAgentConfiguration;
import javax.annotation.Nonnull;
@IAgentAction
final class MyAgent extends IBaseAgent<MyAgent>
{
private static final long serialVersionUID = -2111543876806742109L;
MyAgent( @Nonnull final IAgentConfiguration<MyAgent> p_configuration )
{
super( p_configuration );
}
@Nonnull
@IAgentActionFilter
@IAgentActionName( name = "my/object-action" )
private Number calculate( @Nonnull final Number p_value )
{
return p_value.intValue() ^ this.hashCode();
}
}
The action instantiation is done by the generator in
package myagentproject;
import org.lightjason.agentspeak.common.CCommon;
import org.lightjason.agentspeak.generator.IBaseAgentGenerator;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.InputStream;
import java.util.stream.Collectors;
import java.util.stream.Stream;
final class MyAgentGenerator extends IBaseAgentGenerator<MyAgent>
{
MyAgentGenerator( @Nonnull final InputStream p_stream ) throws Exception
{
super(
p_stream,
Stream.concat(
CCommon.actionsFromPackage(),
Stream.concat(
CCommon.actionsFromAgentClass( MyAgent.class ),
Stream.of( new CStandAloneAction() )
)
).collect( Collectors.toSet() )
);
}
@Nullable
@Override
public final MyAgent generatesingle( @Nullable final Object... p_data )
{
return new MyAgent( m_configuration );
}
}
Regarding the standalone action, keep in mind that the action is called multiple times because the plan and rule execution is done in parallel and multiple agents can run the action in parallel. The synchronized
keyword is not a general solution for avoiding race condition because synchronisation slows down the performance.
In common work, the object-orientated design of the action class can be changed to remove synchronisation. If you get race condition exceptions or performance problems, just redesign your architecture. A good design of concurrency architecture can be found in all built-in actions of the framework.
This tutorial depends on the tutorial AgentSpeak-in-15min, so the whole build process is explained within the basic tutorial. If you struggled at some point or wish to obtain our exemplary solution with code documentation of this tutorial, you can download the archive containing the source code and an executable jar file:
We run the example with 5 agents and 1 cycle
agent 981.414.120 calls standalone-action with parameter rvuoaiwqa
agent 263.847.538 calls standalone-action with parameter tqhkgkrcpziag
agent 1.675.634.764 calls standalone-action with parameter aefrnaopw
agent uses string aefrnaopw gets from standalone action 9 and from object action 1675634757
agent uses string tqhkgkrcpziag gets from standalone action 13 and from object action 263847551
agent 1.053.779.425 calls standalone-action with parameter dwyziieavx
agent uses string rvuoaiwqa gets from standalone action 9 and from object action 981414113
agent uses string dwyziieavx gets from standalone action 10 and from object action 1053779435
agent 1.128.132.589 calls standalone-action with parameter urm
agent uses string urm gets from standalone action 3 and from object action 1128132590