Skip to main content
Version: 🚧 Alpha 🚧

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​

AttributeMeaning
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.
conceptConcepts (constants of gama.annotations.support.IConcept) used to index the documentation.
attach_toNames of species to which the skill is automatically added.
internaltrue to hide the skill from the documentation and the modelers.
categoryCategories used to organize the documentation.
docThe 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:

AttributeMeaning
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, indexContent type and key/index type for containers (e.g. type = IType.LIST, of = IType.MESSAGE).
initInitial value, as a GAML expression in a string (e.g. init = "0").
constanttrue if the attribute cannot be modified.
depends_onNames of the attributes that must be initialized before this one.
internaltrue for an attribute not meant to be used by modelers.
docIts 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 an IScope and/or the IAgent (get(IAgent), get(IScope, IAgent)). With initializer = true, the getter is also used to compute the initial value of the attribute (as getMailbox above).
  • @setter("name") marks the method changing the value. It takes the agent and the new value, optionally preceded by an IScope (set(IAgent, T), set(IScope, IAgent, T)). An empty setter makes the attribute read-only in practice (as setMailbox).

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 attributeMeaning
name (mandatory)Name of the action in GAML.
argsIts arguments, as @arg annotations.
virtualtrue for an abstract action, which species must redefine.
internaltrue to hide the action from the documentation.
docIts documentation (value, returns, examples...).
@arg attributeMeaning
nameName of the argument. Avoid name, keyword and returns: the processor warns that they prevent calling the action with facets (do action arg1: val1;).
typeIts type, as an IType constant (IType.NONE accepts any value).
optionalWhether the argument can be omitted. Defaults to true.
docIts 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 as getCurrentAgent(scope) in Skill).
  • 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 as moving (gama.core/src/gama/gaml/skills/MovingSkill.java) or intersection_skill (gama.extension.traffic/src/gama/extension/traffic/driving/RoadNodeSkill.java).
  • Add a test model in models/Tests of the plugin (GAMA > Create GAMA artefacts > New GAML Test Model).