Developing Skills
A skill adds attributes and actions to the species that declare it, e.g.
species robot skills: [moving, messaging] { ... }. In Java, a skill is a class extending
gama.api.kernel.skill.Skill, annotated with @skill, that declares its attributes with @vars/@variable
(implemented by @getter/@setter methods) and its actions with @action methods.
The skill must be in a GAMA plugin (see Developing Plugins). All the annotations used
below are in the package gama.annotations; their full list of attributes is given in the
Index of annotations.
Create a skill with the wizard​
Choose GAMA > Create GAMA artefacts > New GAMA Skill (or File > New > Other... > GAMA Development), then
select the GAMA plugin, the Package, the Class name (e.g. GreetingSkill) and the Skill name
used in GAML (computed from the class name, e.g. greeting).
The generated class contains a documented skill with one attribute (greetings_count, with its getter and
setter) and one action (greet, with an optional argument to). Rename and adapt them. New GAMA Action
generates a skill with a single action, which can also be copied into an existing skill.
To write a skill by hand, create a class extending Skill in the src/ folder of the plugin and add the
annotations described below. In both cases, saving the file rebuilds the plugin and regenerates its
GamlAdditions; the skill is then available in GAML when GAMA is launched from the workspace.
Example: the messaging skill​
The following class, simplified from gama.core/src/gama/core/util/messaging/MessagingSkill.java, gives agents
a mailbox attribute and a send action:
import gama.annotations.action;
import gama.annotations.arg;
import gama.annotations.doc;
import gama.annotations.example;
import gama.annotations.getter;
import gama.annotations.setter;
import gama.annotations.skill;
import gama.annotations.variable;
import gama.annotations.vars;
import gama.annotations.constants.IKeyword;
import gama.annotations.support.IConcept;
import gama.api.exceptions.GamaRuntimeException;
import gama.api.gaml.types.IType;
import gama.api.kernel.agent.IAgent;
import gama.api.kernel.skill.Skill;
import gama.api.runtime.scope.IScope;
import gama.api.types.message.GamaMessageFactory;
import gama.api.types.message.IMessage;
@skill (
name = MessagingSkill.SKILL_NAME,
concept = { IConcept.COMMUNICATION, IConcept.SKILL })
@doc ("A simple skill that provides agents with a mailbox than can be filled with messages")
@vars ({ @variable (
name = "mailbox",
type = IType.LIST,
of = IType.MESSAGE,
doc = @doc ("The list of messages that can be consulted by the agent")) })
public class MessagingSkill extends Skill {
public static final String SKILL_NAME = "messaging";
public static final String MAILBOX = "mailbox";
public static final String MAILBOX_ATTRIBUTE = "messaging_skill_mailbox";
@getter (value = MAILBOX, initializer = true)
public GamaMailbox getMailbox(final IScope scope, final IAgent agent) {
GamaMailbox mailbox = (GamaMailbox) agent.getAttribute(MAILBOX_ATTRIBUTE);
if (mailbox == null) {
mailbox = createNewMailbox();
agent.setAttribute(MAILBOX_ATTRIBUTE, mailbox);
}
return mailbox;
}
@setter (MAILBOX)
public void setMailbox(final IAgent agent, final GamaMailbox mailbox) {
// not allowed
}
@action (
name = "send",
args = { @arg (
name = IKeyword.TO,
type = IType.NONE,
optional = true,
doc = @doc ("The agent, or server, to which this message will be sent to")),
@arg (
name = IMessage.CONTENTS,
type = IType.NONE,
optional = false,
doc = @doc ("The contents of the message, an arbitrary object")) },
doc = @doc (
value = "Action used to send a message (that can be of any kind of object) to an agent or a server.",
examples = { @example ("do send to:dest contents:\"This message is sent by \" + name + \" to \" + dest;") }))
public IMessage primSendMessage(final IScope scope) throws GamaRuntimeException {
final IAgent sender = scope.getAgent();
Object receiver = scope.getArg("to", IType.NONE);
if (receiver == null) { receiver = sender; }
final Object contents = effectiveContents(scope, scope.getArg(IMessage.CONTENTS, IType.NONE));
if (contents == null) return null;
final IMessage message = GamaMessageFactory.create(scope, sender, receiver, contents);
effectiveSend(scope, message, receiver);
return message;
}
// createNewMailbox(), effectiveContents(...) and effectiveSend(...) omitted
}
It is used in GAML as follows:
species speaker skills: [messaging] {
reflex talk {
do send to: one_of(speaker) contents: "Hello from " + name;
write length(mailbox);
}
}
Declaring the skill: @skill​
| Attribute | Meaning |
|---|---|
name (mandatory) | Name of the skill in GAML; must be unique. Declaring it as a constant of the class (SKILL_NAME) lets Java code refer to it. |
concept | Concepts (constants of gama.annotations.support.IConcept) used to index the documentation. |
attach_to | Names of species to which the skill is automatically added. |
internal | true to hide the skill from the documentation and the modelers. |
category | Categories used to organize the documentation. |
doc | The documentation (an @doc; it can also be placed directly on the class, as above). |
A single instance of the skill class is shared by all the agents using it: do not store the state of an
agent in fields of the skill, but in the agent itself (agent.getAttribute(...) / agent.setAttribute(...)),
as MAILBOX_ATTRIBUTE above.
Declaring attributes: @vars, @variable, @getter, @setter​
@vars (on the class) contains one @variable per attribute added to the agents:
| Attribute | Meaning |
|---|---|
name (mandatory) | Name of the attribute in GAML. |
type (mandatory) | Its type, as an IType constant (IType.INT, IType.FLOAT, IType.LIST, IType.AGENT...). |
of, index | Content type and key/index type for containers (e.g. type = IType.LIST, of = IType.MESSAGE). |
init | Initial value, as a GAML expression in a string (e.g. init = "0"). |
constant | true if the attribute cannot be modified. |
depends_on | Names of the attributes that must be initialized before this one. |
internal | true for an attribute not meant to be used by modelers. |
doc | Its documentation, as @doc. |
Without getter or setter, the value is simply stored in the agent. To compute or control it, annotate methods of the skill:
@getter("name")marks the method returning the value. It may take anIScopeand/or theIAgent(get(IAgent),get(IScope, IAgent)). Withinitializer = true, the getter is also used to compute the initial value of the attribute (asgetMailboxabove).@setter("name")marks the method changing the value. It takes the agent and the new value, optionally preceded by anIScope(set(IAgent, T),set(IScope, IAgent, T)). An empty setter makes the attribute read-only in practice (assetMailbox).
The value passed to @getter/@setter must be exactly the name of a @variable of the class.
Declaring actions: @action, @arg​
An action is a public method taking a single IScope parameter, annotated with @action. Its return value
is the result of the action in GAML (void for actions that return nothing).
@action attribute | Meaning |
|---|---|
name (mandatory) | Name of the action in GAML. |
args | Its arguments, as @arg annotations. |
virtual | true for an abstract action, which species must redefine. |
internal | true to hide the action from the documentation. |
doc | Its documentation (value, returns, examples...). |
@arg attribute | Meaning |
|---|---|
name | Name of the argument. Avoid name, keyword and returns: the processor warns that they prevent calling the action with facets (do action arg1: val1;). |
type | Its type, as an IType constant (IType.NONE accepts any value). |
optional | Whether the argument can be omitted. Defaults to true. |
doc | Its documentation. |
Accessing the agent and the arguments​
Everything is obtained from the IScope passed to the action (see IScope):
scope.getAgent()returns the agent executing the action (also available asgetCurrentAgent(scope)inSkill).scope.hasArg("to")tells whether an argument has been passed. Always test optional arguments.scope.getArg("to", IType.NONE)returns the value of the argument, converted to the given type. Typed variants are available:getIntArg,getFloatArg,getStringArg,getBoolArg,getArgIfExists(name, type),getTypedArg(name, type),getTypedArgIfExists(name, type, defaultValue),getBoolArgIfExists(name, defaultValue).
Errors must be reported by throwing a GamaRuntimeException (e.g. throw GamaRuntimeException.error("...", scope));
wrap other exceptions in it.
Documenting: @doc​
@doc can be attached to the skill, to each @variable, @action and @arg. Its main attributes are value
(the description), returns, examples (@example), see, comment, special_cases, usages (@usage) and
deprecated. Missing documentation is reported by a processor warning, with a quick fix (Ctrl/Cmd+1) adding
a @doc skeleton; GAMA > Report > Missing annotations lists all of them.
Checking the result​
- After a build, the skill appears in
gaml/gaml/additions/<shortcut>/GamlAdditions.java(as a_skill(...)registration). If not, run GAMA > Clean GAMA plugins. - GAMA > Search GAML keyword... finds the Java code of a skill, an attribute (
attr:mailbox) or an action (action:send): use it to study existing skills such asmoving(gama.core/src/gama/gaml/skills/MovingSkill.java) orintersection_skill(gama.extension.traffic/src/gama/extension/traffic/driving/RoadNodeSkill.java). - Add a test model in
models/Testsof the plugin (GAMA > Create GAMA artefacts > New GAML Test Model).