Lua logo

Preface

This is the reference manual [1] of MoonAgents, which is a Lua module for event-driven concurrent programming, whose design follows the concurrency model of the ITU-T Specification and Description Language (SDL). [2]

It is assumed that the reader is familiar with both SDL and the Lua programming language.

For convenience of reference, this document contains external (deep) links to the Lua Reference Manual.

Getting and installing

For installation intructions, refer to the README file in the MoonAgents official repository on GitHub.

Module organization

The MoonAgents module is loaded using Lua’s require() and returns a table containing the functions it provides (as usual with Lua modules). This manual assumes that such table is named moonagents, i.e. that it is loaded with:

 moonagents = require("moonagents")

but nothing forbids the use of a different name.

Examples

Complete examples can be found in the examples/ directory of the release package.

License

MoonAgents is released under the MIT/X11 license (same as Lua, and with the same only requirement to give proper credits to the original author). The copyright notice is in the LICENSE file in the base directory of the official repository on GitHub.

See also

MoonAgents is part of MoonLibs, a collection of Lua libraries for graphics and audio programming.

Overview

MoonAgents extends Lua with the basic constructs for the implementation of SDL systems [3], which are systems composed of concurrent, reactive, and intercommunicating entities referred to as agents. You can think of agents as of cooperative threads of execution interleaved in a single OS-thread, akin to Lua’s native coroutines [4].

The behavior of agents is defined by agent-scripts in terms of finite state machines whose transitions are triggered by the reception of asynchronous signals (messages), which are plain Lua tables containing a signal name (a string) plus whatever else the system designer desires.

Asynchronous signals are the main communication method in the system: agents communicate mainly by exchanging signals with each other and with the application (that can be seen as what in SDL parlance is called the 'environment' [5]), and may create and arm timers whose expiries are also notified to them by means of signals.

Agents are created with the create( ) function, passing it the agent-scripts and optional parameters. Each agent is run in a dedicated Lua environment (_ENV) that provides it with an own context of execution and separates its namespace from those of other agents running in the same application. In this _ENV, MoonAgents preloads the moonagents table with the functions available to the script, and sets some special variables that it uses to share information with the agent (and some other special variables for internal use that the agent-scripts must not touch nor care about).

The main script of a typical application using MoonAgents looks like in the following example:

 local moonagents = require("moonagents")

 -- ... optional configurations and other non-MoonAgents setups...

 -- Start the SDL system, by creating the first agent:
 local pid = moonagents.create_system("MyAgentName","myagentscript")

 -- Event loop:
 while true do
    -- Trigger the signal scheduler:
    if not moonagents.trigger() then
       -- ... the SDL system has stopped ...
    end
    -- Other typical event-loop stuff goes here or above
    -- (e.g. input handling, draw calls, etc.)
 end

The application loads the MoonAgents module, optionally configures it, creates the first agent, and then enters an event loop where, among other typical event-loop stuff, it is expected to call the trigger( ) function that causes an internal scheduler to dispatch any signal pending in the SDL system to the agents.

The first created agent is called the system agent and is the only one directly created by the application. Other agents may be created by the system agent and by its descendants during the evolution of the system’s life, that coincides with that of the system agent (when this terminates, the system does also). [6]

The creator of an agent is its parent (the parent of the system agent being the application). Thus, agents are naturally organized in a hierarchical tree rooted at the application, whose only child is the system agent. Any agent other than the application has one parent and may have zero or more children.

Each agent has a process identifier (pid), an integer value unique within the SDL system and automatically assigned to the agent at its creation. The special value pid=0 identifies the application. Unique pids in the same domain are also assigned to procedures. [7]

At its creation, each agent is also assigned a name. The name passed to the create( ) function (or automatically assigned) is the agent’s local name, which is required to be unique only among siblings, i.e. in the set of children of the agent’s parent.

The relative name of the agent with respect to a given ancestor is the concatenation, dot-separated, of all the names on the lineage from the ancestor (excluded) to the agent itself (included). The relative name with respect to its parent is thus the local name. A relative name is unique only among descendants of the ancestor it is relative to.

The full name of the agent is its name relative to the application (i.e. obtained by concatenating the names on the lineage starting from the system agent included). The full name is unique within the whole system.

The agent name may encode information such as the agent’s ‘type’ and ‘instance’ (if the system designer wants so), and provides a convenient way to identify agents in reusable agent-scripts without relying on the pid which may (and generally does) vary from application to application. Functions are provided to resolve agent names into pids and viceversa.

Agent-scripts

An agent-script defines the behavior of an agent in terms of a state machine whose transitions are triggered by the asynchronous arrival of input signals sent to the agent by other agents, by the application, by timers, or by the agent itself.

The script and the transitions for an agent are executed in the agent’s own _ENV, so global functions and variables of an agent do not collide with those of other agents (they are 'global' only in its dedicated _ENV). The same script may be used for multiple concurrently-running agents, which means it really defines 'a type of agent' rather than 'an agent'.

The moonagents table with the functions needed by the agent-script is pre-loaded in the agent’s dedicated _ENV at startup. The script can access the functions via the table, or it can use the following utility to make them global in the _ENV:

  • global_functions([prefix])
    Makes moonagents functions global in the current agent’s dedicated _ENV, optionally prendending their names with the prefix string (for example, the call global_functions("xxx") makes moonagents.stop global with the name xxxstop, and similarly for the other functions).
    This function is available only to agents and should be used at startup.

An incomplete example of an agent-script is shown below: the top section is the startup code to be executed before the start transition (e.g. timers are created here), the bottom section defines the transition table by associating Lua functions to combinations of states and input signals names, and the middle section implements those Lua functions.

 -- Example agent-script

 -- =========== Top section: startup code ==========================

 -- Make moonagents functions global in this _ENV:
 moonagents.global_functions()

 -- Create timers, set variables etc.
 local T = timer(30,"T_EXPIRED")
 local somevariable = 0

 -- =========== Middle section: the transition functions ===========

 local function Start()
   -- .. 'start transition' here ..
   next_state("Waiting")
 end

 local function Waiting_ConReq()
   -- received CONREQ signal in Waiting state ...
   send({ "CONACK" }, sender_)
   timer_start(T)
   next_state("Connecting")
 end

 local function Connecting_TExpired()
   -- received T_EXPIRED signal in Connecting state ...
   send({ "FAILURE" }, parent_)
   stop()
 end

 -- ... cut ...

 -- =========== Bottom section: the transition table ===============

 start_transition(Start)
 --         state         input signal   transition function
 transition("Waiting",    "CONREQ",      Waiting_ConReq)
 transition("Connecting", "CONCNF",      Connecting_ConCnf)
 transition("Connecting", "T_EXPIRED",   Connecting_TExpired)
 transition("Connected",  "DATA",        Connected_Data)
 transition("Any",        "STOP",        Any_Stop)
 default_state("Any")

Creating agents

Agents are created using the following functions:

  • pid = create_system([name], scriptname, …​)
    pid = create_system_s([name], scriptcode, …​)
    Creates the system agent, starts the SDL system, and returns the system agent’s pid.
    The parameters have the same meaning as for the create( ) function.
    These functions are available only to the application and can be used only when the system is not running.

  • pid = create([name], scriptname, …​)
    pid = create_s([name], scriptcode, …​)
    Creates an agent other than the system agent.
    name: the agent’s name (a string not containing dots). If name=nil, the default name "agent<pid>" is used (e.g. "agent1", agent2", etc.)
    scriptname: the agent-script defining the agent’s behaviour (see below for how the script is searched).
    scriptcode: the code for the agent script (a string).
    …​: optional arguments to be passed to the start transition.
    These functions are available only to agents and should be used within transitions.

When creating an agent or a procedure, the script can be provided either as as a string containing Lua code, or by passing the name of the file that contains the code.

In the latter case, the same mechanism that Lua uses to find modules and packages is used to find the correct file from its name.

More precisely, the scriptname argument (a string) is resolved by invoking the standard Lua package.searchpath( ) function, passing it the templates contained in the variable moonagents.path as the argument for the path parameter.

By default, moonagents.path="?;?.lua" so that if, for example, an agent is created like in the example below then MoonAgents searches for a file named "mydir/myagentscript" or mydir/myagentscript.lua" (in this order).

 pid = moonagents.create("myagentname","mydir.myagentscript")

The default moonagents.path can be overridden by setting the MOONAGENTS_PATH environment variable with the desired path templates, in the same way one sets the standard LUA_PATH variable to override its default package.path (alternatively, one can directly change it from the application).

State machines

During its life, an agent goes through a series of states. Each state is represented by a string that may be any valid Lua string except the reserved (yet configurable) special values for startup and dash.

An agent is idle for most of the time and is awakened only when it receives a signal. Signals are dispatched by an internal scheduler that is periodically activated by the application by calling the trigger( ) function in its event loop.

The reception of a signal causes an agent to execute a transition, which is a function set by the agent-script as the code to be executed when signals having the input signal’s name arrive with the agent being in its current state.

A special transition is the start transition, set by the agent-script with start_transition( ), that is automatically executed at the end of the agent’s startup. The start transition must contain a next_state( ) call to set the first proper state entered by the agent, or it can end with a stop( ) call so to terminate the agent right after creation.

Transitions are executed in the agent’s dedicated _ENV and receive no arguments, except for the start transition that receives the arguments (if any) that were passed to the create( ) function in its variable part.

The signal contents and other relevant information such as the sender’s pid are passed to the agent by means of some special variables that are properly set by MoonAgents before executing a transition. [8]

When executed, a transition is expected to perform some task depending on the input signal and on the state the agent is in (e.g., it may process input data, set timers, send signals, and possibly change the agent’s state), and then return.

Important
A transition is required to return as soon as possible, which means that it must not contain infinite loops or calls to blocking functions, otherwise the application would hang (the SDL concurrency model is cooperative).

Once a transition is done, it returns the control to the internal scheduler which possibly dispatches any pending signal and eventually returns the control to the application, until the next trigger( ) call.

To define the state machine, an agent-script uses the following functions, where state is a string denoting an agent’s state, signame is the name of a signal (also a string), and func is a Lua function:

  • start_transition(func)
    Sets func as the agent’s start transition.
    This function is available only to agents and can be used only at agent startup (not in transitions).

  • transition(state, signame, func)
    Sets func as the transition to be executed when a signame signal arrives with the agent being in state.
    If signame is the asterisk special value, the transition applies to any signal name except those for which an explicit transition is set.
    The transition executed when a signame signal arrives with the agent being in state is the first found in the following list:
    - the one set with transition(state, signame, func), if any, or
    - the one set with transition(state, asterisk, func), if any, or
    - the one set with transition(default_state, signame, func), if any, or
    - the one set with transition(default_state, asterisk, func), if any, or
    - the so-called empty transition, which basically means that the signal is silently discarded.
    This function is available only to agents and should be used only at agent startup (not in transitions).

  • default_state(state)
    Sets state as the agent’s default state, which defines transitions for signals that are not catched with transition( ) in the state the agent is in when they arrive.
    This function is available only to agents and should be used only at agent startup (not in transitions).

  • next_state([state])
    Changes the agent’s state to state.
    If the state actually changes, i.e. if the agent was not already in that state, any input signal previously saved with save( ) is re-scheduled.
    If state is the dash special value this function immediately returns with no effects.
    This function is available only to agents and should be used only within transitions.

Stopping

An agent terminates by calling the stop( ) function in a transition. This function causes the the agent’s state machine to stop, and puts the agent in a stopping condition which preludes its termination. The agent remains in the stopping condition until all its children have terminated, then it terminates too.

While in the stopping condition, an agent will not receive any input signal because its state machine has stopped. It will, however, remain available for remote functions calls.

If a finalizer function is passed to stop( ), it is executed (with no arguments) right before the agent actually terminates, that is after all his children have terminated too.

If the stopping agent is the system agent, its termination causes also the termination of the SDL system. Note that this happens when the system agent actually terminates, i.e. when all its descendants have already terminated (and thus it is the last agent left in the system).

  • stop([finalizer])
    Gracefully stops the agent’s state machine and causes the termination of the agent itself.
    The optional finalizer parameter is a function to be called when the agent actually terminates.
    This function is available only to agents and should be used only within transitions.

Trigger

  • n = trigger([canblock=false])
    This function is available only to the application, which is expected to call it in its event loop to trigger the SDL system.
    It checks for timer expiries, dispatches pending signals causing the destination agents to execute the associated transitions, and executes the callback of any monitored socket that is detected to be ready.
    If canblock is true the function is allowed to block, otherwise it returns as soon as possible (you usually don’t want it to block, unless your application only inputs and outputs through monitored sockets in which case blocking on socket.select( ) can be beneficial).
    Returns the number of scheduled signals that will be dispatched at the next call, or nil if the system is not running (i.e. if there are no, or no more, agents alive in the system).

  • reset( )
    Resets the SDL system, ungracefully destroying all the agents and their timers. Optional configurations are not reset.
    This function can be called only by the application (not by agents) and should only be used on system error.

Signals

SDL signals exchanged between agents, as well as those generated by timers, are Lua tables whose first array element (i.e. the element with numeric key=1) is a string and denotes the signal name. For example:

 mysignal = { "MYSIGNAL", "hello", 1, 2, 3, self_, true }

Here "MYSIGNAL" is the signal name while the other values are content information (the example uses only array elements, but the record part of the table can also be used).

Signals are sent by reference, which means that the receiving agent gets a reference to the same table that was sent by the sender (not a copy).

Besides expecting the signal name as the first array element, MoonAgents makes no other assumptions regarding the format and meaning of signals and of their contents: it just delivers them between agents.

To send signals, agents use the following functions:

  • now = send(signal, dstpid, [priority])
    now = send(signal, {dstpid}, [priority])
    Sends signal to the agent identified by dstpid, with the specified priority, and returns the current time.
    If dstpid is a list of pids, the signal is sent to all the corresponding agents.
    priority is an integer between 1 (high) and N (low), where N is the number of priority levels.
    If priority is not specified, or if it is greater than N, the signal is sent without priority (which means lowest).
    This function is available both to agents and to the application, that can use it to send signals to the system.

  • now = send_out(signal)
    Sends signal out to the application (this is equivalent to send(signal, 0)).
    Signals addressed to the application are sent out immediately (without scheduling) and synchronously.
    To receive signals sent to it, the application must set a non-blocking callback function using set_receive_callback( ).
    This function is available only to agents.

Agents and the application can also schedule signals (time-triggered signals [9]) to be sent at a point in time in the future, without the need to create timers for this purpose:

  • send_at(signal, dstpid, at, [maxdelay])
    send_at(signal, {dstpid}, at, [maxdelay])
    A signal sent with this function is retained in an internal queue and dispatched (with maximum priority) to the agent identified by dstpid only when the point in time given by at arrives. If, for some reason, the signal can not be delivered before the point in time given by at+maxdelay, it is considered stale and silently discarded (maxdelay defaults to infinity, i.e. the signal is never considered stale).
    If dstpid is a list of pids, the signal is sent to all the corresponding agents.
    This function is available both to agents and to the application, that can use it to send signals to the system.

An agent can impose the priority of input signals by using this function:

  • input_priority(signame, [priority])
    Set or cancel the imposed priority for signame input signals.
    If priority is an integer between 1 (high) and N (low), where N is the number of priority levels, then any signame signal addressed to this agent will be sent with that priority, no matter what specified by the sender.
    If priority=nil, any previously set priority for signame signals is cancelled, and the priority specified by the sender applies.
    Signals that are already sent but not yet dispatched at the time this function is called are not affected. Signals generated by timers, as well as time-triggered signals and re-scheduled signals are not affected either.
    This function is available only to agents.

Using the following, an agent can save input signals so to receive them again at its next change of state, or when it explicitly decides to:

  • save( )
    Saves the current input signal (i.e. the content of the signal_ special variable) in the agent’s saved queue, for later re-scheduling.
    Saved signals are automatically re-scheduled when the agent changes its state, and can be re-scheduled explicitly with restore( ).
    This function is available only to agents and should be used only within transitions.

  • restore( )
    Re-schedules all the signals currently saved by the agent. A state change has the same effect.
    In both cases, signals are re-scheduled with maximum priority and in the same order they were saved.
    This function is available only to agents and should be used only within transitions.

To receive signals addressed to pid=0, the application must set a callback function using the following:

  • set_receive_callback([callback])
    Sets the passed function as the application’s receive callback for signals sent to pid=0.
    The callback is executed as callback(signal, sender), where sender is the pid of the agent that sent the signal, and it is required to return as soon as possible (no blocking calls). If callback is nil, any signal sent to the application will be silently discarded (default behavior).
    This function is available only to the application, and can be used at any time.

System time

Agents and the application have access via the now( ) function to a wallclock that gives the so-called SDL system time. All MoonAgents functions that accept or return a timestamp rely on this wallclock.

The system time is relative to an unspecified point in the past, that depends on the underlying function used to retrieve time from the operating system.

  • timestamp = now( )
    Returns the current system time, in seconds.

  • dt = since(timestamp)
    Returns the time elapsed from the point in time given by timestamp.

Timers

Agents can create and manage timers using the functions described below. A timer is owned by the agent that created it, and can be managed only by its owner or by procedures that act on the owner’s behalf.

An agent can create timers only before the execution of its start transition, i.e. at the agent’s startup. Timers can not be created in state transitions nor within procedures. Procedures can, however, use timers owned by their calling agent.

When a timer expires, a signal containing only the associated signame is sent to the owner agent, or to the procedure that is acting on its behalf, if any. The signal is subject to queuing delays in the internal signal scheduler, as any other signal.

  • tid = timer(duration, signame)
    Creates an SDL timer and returns a unique timer identifier (tid).
    duration: the timer’s default timeout (seconds),
    signame: the name (a string) of the signal that will be sent to the owner agent when the timer expires.
    This function is available only to agents, which can create timers only at startup (not within transitions).

  • timer_modify(tid, [duration], [signame])
    Modifies the default duration and/or the signal name of the timer identified by tid.
    If the timer is running, this function stops it.
    duration and signame: same as for the timer( ) function (pass nil to preserve the old value).

  • at = timer_start(tid, [at])
    Starts the timer identified by tid so to expire at the point in time given by at (defaults to now+duration).

  • now = timer_stop(tid)
    Stops the timer identified by tid.
    If the timer is not running, this function has no effects and generates no errors.
    Note that a timer is regarded as 'running' from when it is started until the owner agent receives the associated signal. As a consequence, if the timer is stopped after its expiry but before the signal is received, the signal is discarded by the internal signal scheduler.

  • isrunning, at = timer_running(tid)
    Returns a boolean indicating whether the timer identified by tid is running or not, and the point in time at which the timer is expected to expire (or math.huge, if the timer is not running).

Procedures

SDL procedures are sub-parts of state machines, that can be reused in different agent-scripts.

An SDL procedure in MoonAgents is as a special kind of agent, created with procedure( ), that replaces its caller agent from when it is called until it returns.

More precisely, a procedure replaces its caller as destination and source of signals. This means that all the signals addressed to the caller are redirected to the procedure, which may consume them or save them, and all the signals sent by the procedure are sent on behalf of the caller (i.e. with the caller’s pid as sender).

When a procedure returns, all the signal it saved are automatically moved in its caller’s saved queue and the normal addressing of signals is re-established (so the restored signals will be dispatched to the caller).

Procedures can be nested, which means that a procedure may call another procedure and be replaced by it (note that each agent or procedure can directly execute only one procedure at a time, though, being replaced by it). Nested procedures all act on behalf of their original caller, which is the first ancestor in their lineage that is not a procedure, and whose pid they find in the caller_ special variable.

State machines for procedures are defined in scripts similar to regular agent-scripts, using the same functions except for stop( ): to terminate, procedure scripts shall instead use procedure_return( ).

Another difference with regular agents is that procedures can not create timers. They can use timers owned by the agent they act on behalf of, though.

  • pid = procedure([atreturn], [name], scriptname, …​ )
    pid = procedure_s([atreturn], [name], scriptcode, …​ )
    Executes (i.e. creates) an SDL procedure as described above.
    The name, scriptname (or scriptcode) and …​ parameters have the same meanings as in the functions that create regular agents, with the difference that if name=nil, the name defaults to "procedure<pid>" instead of "agent<pid>".
    The atreturn parameter can be optionally used to define actions to be executed in the caller agent’s _ENV when the procedure returns, which it does by calling procedure_return(returnvalues):
    - if atreturn is a function, it is executed as atreturn(returnvalues),
    - if atreturn is a string (denoting a state name), then next_state(atreturn) is executed,
    - if atreturn is nil, then no actions are executed at return.
    This function is available only to agents (including procedures), and must be used within transitions.

  • procedure_return(…​)
    Returns from a procedure. This function is to be used in the procedure script instead of stop( ), to terminate the procedure and possibly return values to the caller agent.
    The arguments passed to this function (if any) are in turn passed to the atreturn function set by the caller when the procedure was created. No script code should follow a call to this function in the transition it is in: any action to be performed when the procedure returns shall be instead coded in the atreturn argument passed to procedure( ).

Remote functions

Remote functions are Lua functions defined in the _ENV of an agent (the exporting agent) and exported so that they can be synchronously invoked by another agent (the invoking agent). [10]

When the invoking agent calls a remote function, MoonAgents immediately switches to the exporting agent’s _ENV, executes the function there, and then switches back to the invoking agent’s _ENV returning it any value returned by the function. (That is, the remote function is executed synchronously and immediately.)

This mechanism relies on the following two functions:

  • export_function(funcname, [func])
    Exports the function func with the name funcname (a string), so that it can be invoked by other agents using remote_call( ).
    If func is nil, the function exported with the name funcname is revoked.
    Since the exported function is intended to be executed from other agents and usually within transitions, it is required to return as soon as possible.

  • …​ = remote_call(pid, funcname, …​)
    Executes the function exported with the name funcname by the agent identified by pid, in its _ENV.
    The …​ variable arguments, if any, are passed as arguments to the remote function.
    Returns the values returned by the remote function, if any.

Sockets

This functionality requires LuaSocket.

  • socket_add(socket, mode, callback)
    Adds socket (created with LuaSocket, or a compatible object) to the list of sockets monitored for reading (mode='r') or for writing (mode='w').
    The callback is a function that is executed - immediately and synchronously - in the dedicated _ENV of the agent that called this function, whenever the socket is detected to be ready within a trigger( ) call. It is executed as callback(socket) and must not block.
    The socket is forced by MoonAgents to be non-blocking (socket:settimeout(0), see the LuaSocket documentation for more details).
    To monitor a socket for both reading and writiting, add (and remove) it twice, i.e. independently once per mode.

  • socket_remove(socket, mode)
    Removes socket from the list of sockets monitored for reading (mode='r') or for writing (mode='w').

Agent information

The functions described below provide information about SDL agents and help agents in locating each other.

All functions raise an error on failure.

  • pid = pid_of(name, [rpid=0])
    Returns the pid corresponding to the given name, relative to the agent identified by rpid.
    To get the pid of an agent from its full name, pass rpid=0 (which is the default).
    To get the pid of a sibling agent from its local name, pass rpid=parent_.

  • name = name_of(pid, [rpid=0])
    Returns the name, relative to rpid, of the agent identified by pid. (by default rpid=0 so the full name is returned).

  • ppid = parent_of (pid)
    Returns the pid of the parent of the agent identified by pid.

  • state = state_of(pid)
    Returns the current state (a string) of the agent identified by pid.

  • {pid} = children_of(pid)
    Returns the list of pids of the children created by the agent identified by pid (note that this includes procedures).

  • {tid} = timers_of(pid)
    Returns the list of identifiers (tid) of the timers owned by the agent identified by pid.

  • string = tree_of(pid)
    Returns a string containing a description of the sub-tree of agents rooted at the agent identified by pid.

Logs and Traces

A MoonAgents application can open a system logfile and use the functions described here to write on it. The system logfile is used by MoonAgents as destination both for regular logs and for internal traces.

  • filehandle = log_open(filename)
    Opens the file named filename to be used as system logfile, and enables logs.
    The file is opened in write mode, using the standard Lua io.open.
    If the system logfile is already open, it is closed and re-opened.
    Any previously existing file with the same name is overwritten.

  • filehandle, filename = log_file( )
    Returns the file handle and the file name of the system logfile (if it is open, otherwise it returns nil and an error message).

If the system logfile is not open, or if it is disabled, calls of the functions below are silently ignored and raise no errors.

  • log_enable(onoff)
    Enable/disable logs on the system logfile, depending on the boolean value onoff.
    Logs are enabled by default at the opening of the system logfile.

  • trace_enable(onoff)
    Enable/disable traces on the system logfile, depending on the boolean value onoff.
    Traces are produced internally by the MoonAgents engine to log relevant events such as the creation of agents, state changes, the delivery and reception of signals, timer events, and so on. They are intended mainly for troubleshooting, time consuming, and disabled by default.
    If the logfile is not open, this function has no effects.

  • log_flush( )
    Flushes the system logfile.
    MoonAgents automatically flushes it only at its closure or when the system agent stops.

  • log_close( )
    Flushes and closes the system logfile.

  • log(formatstring, …​)
    Formats its arguments using the standard Lua string.format, and writes the resulting message to the system logfile, prepending it with a timestamp and the pid of the current SDL agent.

  • log_print(formatstring, …​)
    Similar to log( ), it additionally writes the message also on stdout (but without the timestamp and the preamble). If the system logfile is not open or if logs are disabled, it writes on stdout only.

  • set_log_preamble([func])
    Override the default log preamble function with the passed func (a function).
    The preamble function is called by log( ) and is expected to return a string to prepend to the log output. If func is nil, nothing is prepended.
    The default preamble is the system time followed by the pid of the current agent between square brackets (e.g. '184445.048954 [8] ' is the preamble of a log produced by the agent with pid=8 at time 184445.048954).

Text style control

The functions described here provide agents and the application with means to control the style of text output (stdout) on terminals that support ANSI escape sequences. For quick and dirty text-based applications.

  • text_style_enable(onoff)
    Enables/disables the text style control capability, depending on the boolean value onoff.
    If the capability is disabled (default), any call of the set_text_style( ) function does nothing.
    This function is available only to the application, while the other ones are available also to agents.

  • set_text_style([{color, mode, bgcolor}])
    Sets the text style, or resets it to the terminal’s default if the argument is nil or missing.
    color: nil (to preserve current), or one among 'black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white', 'bright black', 'bright red', 'bright green', 'bright yellow', 'bright blue', 'bright magenta', 'bright cyan', 'bright white'.
    mode: nil (to preserve current), or one among 'normal', 'bold', 'faint', 'italic', 'underline', 'blink', 'inverted',
    bgcolor: same values as color, but controls the background color.

  • style_write([{color, mode, bgcolor}], …​)
    Writes on stdout with the specified style.
    Equivalent to "set_text_style({color, mode, bgcolor}); io.write(…​); set_text_style(nil)".

Optional configurations

The functions below allow to optionally configure some aspects of MoonAgents. They can be used only when the system is not running, i.e. before the creation of the system agent or after this has terminated.

  • set_env_template(env)
    Sets the template for the Lua environments dedicated to agents (e.g. for sandboxing).
    By default, the template environment is a shallow copy of the main environment (_ENV) as it is when create_system( ) is called.
    Note that the moonagents global table containing the functions described in this manual is automatically loaded in each agent’s dedicated environment, so it need not be added to the template environment (and it need not be loaded explicitly in agent-scripts either).

  • set_priority_levels([nlevels])
    Sets the number of priority levels to be used in the MoonAgents scheduler for priority signals.
    nlevels: an integer between 1 and 16 (inclusive), or nil to restore the default.
    (The default is 1, i.e. there is one level of priority plus the no-priority level, or 'normal' level).

  • set_special_value(name, value)
    Set the string value as the value reserved for name, which may be one of the following:
    'startup': denotes the initial state an agent is in at startup. Defaults to '?' (a question mark).
    'dash': denotes the 'dash nextstate'. Defaults to '-' (a dash).
    'asterisk': denotes the 'asterisk input'. Defaults to '*' (an asterisk).

Special variables

Important
In MoonAgents, identifiers composed of one or more lower-case letters and ending with an underscore (e.g., self_) are reserved for special variables. Agent-scripts code must not set or change variables with such identifiers.

The special variables listed below are set in each agent’s _ENV, when relevant:

  • self_: the pid of the agent itself.

  • name_: the name of the agent.

  • parent_: the pid of the parent agent.

  • offspring_: the pid of the last child agent created by this one (or nil if none).

  • caller_: the pid of the original caller if the agent is a procedure (or nil if it is not).

  • state_: the agent’s current state (or nil if the agent is stopping).

In addition, the following special variables are set before any transition is executed:

  • signal_: the signal that triggered the current transition.

  • signame_: the signal name (same as signal_[1]).

  • istimer_: true if the signal was generated by a timer.

  • sender_: the sender of the signal (pid, or tid if the signal was generated by a timer).

  • sendtime_: the point in time when the signal was sent.[11]

  • recvtime_: the point in time when the signal was received.

  • exptime_: the point in time when the signal becomes stale.

All special variables are to be considered read-only. Care should be taken not to change their values from agent-script code.


1. This manual is written in AsciiDoc, rendered with AsciiDoctor and a CSS from the AsciiDoctor Stylesheet Factory.
2. The SDL specifications are freely available on the ITU site. Introductive papers to SDL, a bit outdated but still very good, can be found in the Telektronikk journal, Volume 4.2000 "Languages for Telecommunication Applications", Ed: Rolv Bræk.)
3. MoonAgents is not an implementation of the SDL specifications, just inspired by them. It deviates from standard SDL in (at least) the following: it uses an all-to-all pid-based communication model, with a single signal queue instead of per-agent input queues (although one could argue that the per-agent queues ar just merged but still distinguishable by the destination pid); it makes no distinction between block and process agents; it has no explicit definitions of channels and gates; it allows any agent (not only the system agent) to communicate with the SDL environment; it uses Lua data types and has no built-in support for ADT and ASN.1; it has additional non-SDL constructs like priority outputs, time-triggered signals and remote synchronous functions with immediate return.
4. For comparison, the examples/web-download directory contains the same example (from the PIL book) implemented both with coroutines and with MoonAgents. With respect to Lua’s native coroutines, MoonAgents provides built-in constructs to define state machines using a well established formalism (SDL), to make them communicate via asynchronous signals, timers and some other facilities. All this at the price of some overhead, so if your application doesn’t need these things then coroutines are a better alternative.
5. The term 'environment' is used in SDL to denote the outer world with respect to an SDL system. To avoid confusion with the concept of 'Lua environment' we will refer to the latter as _ENV and to the former as 'the SDL environment' (or simply as 'the application', since agents inevitably communicate with the outer world via the application).
6. Standard SDL makes also a distinction between block agents, which are containers of agents, and process agents, which are not. MoonAgents makes no such distinction, and any agent may be (also) a container, if the system designer wants so.
7. Procedures and the application itself are not really agents, but to some extent are regarded as such beause they take part in the signal-based communication, where pids are used as addresses.
8. The special variables are those prescribed by the SDL specifications, with a few additions.
9. The time-triggered signal construct is not part of the SDL standard. It is inspired by a proposal contained in the paper “Real-time signaling in SDL”, by M. Krämer, T. Braun, D. Christmann and R. Gotzhein, published in SDL'11 Proceedings of the 15th international conference on Integrating System and Software Modeling, Springer-Verlag, 2011.
10. Remote functions are not an SDL construct. They are not to be confused with SDL remote procedures (see ITU-T Z.102/10.5) that have a different mechanism involving exchange of signals. These are not supported by MoonAgents (at least not directly)
11. The sendtime_ timestamp refers to the time when the signal was inserted in the internal scheduler for dispatching: if the signal was sent with send( ), this is when the function was called; for timer-generated signals, it is the point in time the timer expiry was detected; for time-triggered signals it is the point in time the signal was actually sent after its time ('at') had come.