DevelopingIScope - gama-platform/gama GitHub Wiki
An IScope (gama.api.runtime.scope.IScope) is the execution context of GAML code: the agent (or object) currently executing, the stack of agents, the local variables of the current block, the arguments of the current action, the flow status (break, return, die...), and access to services such as the random number generator, the clock, the topology and the user interface. Every statement execution, expression evaluation and action call happens in a scope.
The main implementation is gama.api.runtime.scope.ExecutionScope; GraphicsScope adds what is needed by displays (IGraphicsScope, gama.api.ui.displays).
You almost never create a scope: you receive one.
-
Operators receive it as their first parameter (
gama.extension.stats/src/gama/extension/stats/Stats.java):public static Double opAutoCorrelation(final IScope scope, final IContainer data, final Integer lag) { ... }
-
Actions of skills and species receive it as their parameter, and read their arguments from it (
gama.extension.pedestrian/src/gama/extension/pedestrian/skills/PedestrianRoadSkill.java):public boolean primInitialize(final IScope scope) throws GamaRuntimeException { final IAgent agent = getCurrentAgent(scope); double distAdd = scope.hasArg("distance_extremity") ? scope.getFloatArg("distance_extremity") : 0.0; ...
-
Statements receive it in
privateExecuteIn(IScope scope)(gama.api.gaml.statements.AbstractStatement); getters/setters of attributes may declare it as a parameter. -
Agents and top-level agents keep their own scope:
agent.getScope(). - Outside of any execution (UI code, listeners, threads...),
GAMA.getRuntimeScope()returns the scope of the current simulation, experiment or platform agent. To run code in a temporary copy that is closed automatically, useGAMA.run(InScope<T>):return GAMA.run(this::getValue); // gama.core/src/gama/core/experiment/parameters/ExperimentParameter.java
Always pass the scope you received down to the methods you call (casts, factories, evaluations): do not store it in a field unless you own a copy (see below).
The full list, with Javadoc, is in gama.api/src/gama/api/runtime/scope/IScope.java.
| Purpose | Methods |
|---|---|
| Current agent / object |
getAgent(), getCurrentObjectOrAgent(), getAgentsStack(), push(IObject), pop(IObject)
|
| Top-level agents and model |
getRoot(), getSimulation(), getExperiment(), getModel()
|
| Services |
getRandom() (IRandom), getClock() (IClock: getCycle(), getCurrentDate()...), getTopology(), getGui() (IGui), getTypes()
|
| Action arguments |
hasArg(name), getArg(name, type), getIntArg, getFloatArg, getStringArg, getBoolArg, getListArg, getTypedArg, getArgIfExists, getBoolArgIfExists
|
| Variables |
getVarValue(name), setVarValue(name, value), addVarWithValue(name, value), getEach(name) / setEach(name, value) (iterators) |
| Executing GAML |
evaluate(IExpression, IObject), execute(IExecutable, IObject, Arguments), init(agent), step(agent); they return an IExecutionResult (passed(), getValue()) |
| Flow control |
interrupted(), setBreakStatus(), setReturnStatus(), setDeathStatus(), getAndClearReturnStatus()... |
| Errors |
reportErrors(), disableErrorReporting() / enableErrorReporting(), isInTryMode(), getCurrentError()
|
| Lifecycle |
copy(name), fork(name), detachedCopy(name), copyForGraphics(name), clear() / close(), isClosed()
|
Evaluating an expression in the context of another agent (gama.extension.rds/src/gama/extension/rds/RdsAgents.java):
Object value = scope.evaluate(expression, agent).getValue();Agent attributes should be read and written through the scope, e.g. agent.getDirectVarValue(scope, name) / agent.setDirectVarValue(scope, name, value), rather than by mutating the agent as a container.
A scope belongs to the thread that executes it. To execute code in another thread, or to keep a context for later (an output, a display, a listener), make a copy. Copies may share frames (local variables, agent stack) with the original scope, and shared frames stay retained, with synchronized accesses, until the copy is cleared. Choose the right method and always release copies:
| Method | Shares | Use for |
|---|---|---|
copy(name) |
Locals and agent stack if called by the thread executing the scope, otherwise only the root agent and services | Short-lived copies. Must be closed. |
fork(name) |
Locals and agent stack, whatever the thread | Parallel execution of agents, while the original thread waits. Must be cleared when the fork and its tasks are finished. |
detachedCopy(name) |
Only the root agent and services | Long-lived copies (outputs, monitors, executers...) |
copyForGraphics(name) |
— (returns an IGraphicsScope) |
Long-lived scopes of displays and layers |
IScope implements Closeable (close() calls clear()), so a short-lived copy is best used with try-with-resources (gama.core/src/gama/gaml/skills/ThreadSkill.java):
try (IScope copy = scope.copy("ThreadScope")) {
copy.execute(action, agent, null);
}When try-with-resources is not convenient, release the copy explicitly with GAMA.releaseScope(copy) (which calls clear() and accepts null) in a finally block, as in gama.extension.bdi/src/gama/extension/bdi/BdiUtils.java.
Long-lived copies are created with detachedCopy (e.g. gama.core/src/gama/core/outputs/AbstractOutput.java, MonitorOutput.java, gama.core/src/gama/core/experiment/tools/ActionExecuter.java) or copyForGraphics (e.g. gama.core/src/gama/core/outputs/LayeredDisplayOutput.java, gama.extension.image/src/gama/extension/image/display/ImageDisplaySurface.java), and cleared when their owner is disposed. Do not keep a copy(...) in a field.
interrupted() returns true when the flow status of the scope is one of IScope.INTERRUPTING_STATUSES: BREAK, RETURN, CONTINUE, DIE (the agent is dying) or DISPOSE (the scope has been cleared/closed; see also isClosed()). Long loops in Java code, and code that runs in its own thread, should test it and stop early:
IScope scope = agent.getScope();
if (scope == null || scope.interrupted()) return false; // ThreadSkill.javaStatements that implement GAML control flow use setBreakStatus(), setReturnStatus()... and the corresponding getAndClear...Status() methods.
Runtime errors are gama.api.exceptions.GamaRuntimeExceptions, always built with the current scope so that GAMA can report the agent, cycle and GAML statement involved:
if (data == null) throw GamaRuntimeException.error("auto_correlation requires non-nil data", scope);-
GamaRuntimeException.error(message, scope)andGamaRuntimeException.warning(message, scope)create errors and warnings. -
GamaRuntimeException.create(throwable, scope)wraps a Java exception (I/O exceptions becomeGamaRuntimeFileExceptions):try { ... } catch (final Exception e) { throw GamaRuntimeException.create(e, scope); }
- Throwing is the normal way to abort: the enclosing statement reports the error in the GAMA UI (or the headless log), and the GAML
trystatement can catch it (scope.isInTryMode()). - To report an error without aborting, use
GAMA.reportError(scope, exception, shouldStopSimulation);GAMA.reportAndThrowIfNeeded(...)reports and throws according to the user preferences. Only use them when the following code can continue safely.