@debug"$(animalid(animal)) is experiencing drought with $n neighbour(s)."
@respondsowing@setphase(life)
end
end
```
The two most important macros are `@species` and `@phase`, followed `@initialise`,
`@trait`, `@respond`, and `@habitat`. Other macros provide convenience wrappers
for common functions. (See `src/nature/nature.jl` for details.)
The top-level macro is `@species`. This takes two arguments: a species name and
a definition block (enclosed in `begin` and `end` tags). At the start of the
definition block, species-specific variables can be defined that should be available
throughout a species' lifetime. Code in this section has access to the `model`
object and can thus reference the current model state. In this section, the user
also has to call the `@initialise` macro. This wraps the `initpopulation()`
function, and takes a habitat descriptor (see `@habitat` below) and several options
to specify how the species' population should be distributed in the landscape
during model initialisation.
Following this section, each species must define one or more `@phase` blocks.
The concept behind this is that species show different behaviours at different
phases of their lifecycle. Each `@phase` block defines the behaviour in one of
these phases. (Technically, it defines a function that will be called daily, so
long as the species' `phase` variable is set to the name of this phase.) Code
in this section has access to the `model` object as well as an `animal` object,
which is the currently active animal agent. Properties of the `animal` agent,
regardless of whether they were defined by the user or by Persephone, can be
accessed using the `@trait` macro. Within a phase block, `@respond` can be used
to define the species' response to a `FarmEvent` that affects the species'
current location, while a variety of other macros provide wrappers to ecological
process functions from `src/nature/populations.jl`.
Another important macro is `@habitat`. This defines a "habitat descriptor",
i.e. a predicate function that tests whether or not a given landscape pixel is
suitable for a specified purpose. Such habitat descriptors are used as arguments
to various functions, for example for population initialisation or movement.
The argument to `@habitat` consists of a logical expression, which has access
to the animal's current position (the `pos` tuple variable) and the `model`.
Various macros are available to easily reference information about the current
location, such as `@landcover` or `@distancetoedge`.
## Implementation details
## Implementation details
Due to a known [performance problem](https://juliadynamics.github.io/Agents.jl/stable/performance_tips/#Avoid-Unions-of-many-different-agent-types-(temporary!)-1)
with multi-agent models, the underlying implementation of species is
rather complicated (see `src/nature/nature.jl` for details.)
Rather than creating a new type/struct for each species, all animal agents
have the same type. Instead, they are differentiated by a `traits` dict,
which stores both species-specific parameters and run-time variables.
Note that due to a redefinition of the `getproperty()/setproperty!()`
methods, variables from the trait dict can be accessed and modified just
like normal struct fields (i.e. although `phase` is defined in the dict,
not the struct, `animal.phase = "newphase"` works just fine - one does
not have to use `animal.traits["phase"] = "newphase"`.)
Under the hood, the `@species` macro generates a function (with the name
of the species), which in turn creates the trait dict when called. Thus,
adding a new animal agent to the model involves instantiating an `Animal`
object, then calling the relevant species function and attaching the returned
dict to the agent object.
Similarly, the `@phase` macro too works by defining a new function, which
is stored in the species' trait dict. These functions take an animal object
and the model object as input, and define what the species does during its
daily update.
Once again, `@habitat` creates a function that takes `model` and `pos`
as input and returns a boolean response. Functions that require a habitat
descriptor thus take in this (anonymous) function and call it internally.
Finally, the `@initialise` macro is a wrapper around `initpopulation()`,
which (yet again) creates a function that specifies how a species' population
is to be initialised at the beginning of a simulation run. This function is
stored in the species trait dict and accessed during model setup.