Getting Started with Mango.jl

In this getting started guide, we will explore the essential features of Mango.jl by creating a simple simulation of two ping pong agents that exchange messages in a container. We will set up a container with the TCP protocol, define ping pong agents, and enable them to exchange messages. You can also find working examples of the following code in examples.jl.

1. Creating a Container with a TCP Protocol

To get started, we need to create a container to manage ping pong agents and facilitate communication using the TCP protocol:

using Mango

# Create the container instances with TCP protocol
container = Container()
container.protocol = TCPProtocol(address=InetAddr(ip"127.0.0.1", 5555))

container2 = Container()
container2.protocol = TCPProtocol(address=InetAddr(ip"127.0.0.1", 5556))

# Start the container
wait(Threads.@spawn start(container))
wait(Threads.@spawn start(container2))

2. Defining Ping Pong Agents

Let's define agent structs to represent the ping pong agents. Every new agent struct should be defined using the @agent macro to ensure compatibility with the mango container:

using Mango

# Define the ping pong agent
@agent struct TCPPingPongAgent
    counter::Int
end

3. Sending and Handling Messages

Ping pong agents can exchange messages and they can keep track of the number of messages received. Let's implement message handling for the agents. To achieve this a new method handle_message from Mango has to be added:

import Mango.handle_message

# Override the default handle_message function for ping pong agents
function handle_message(agent::TCPPingPongAgent, message::Any, meta::Any)
    if message == "Ping"
        agent.counter += 1
        t = AgentAddress(meta["sender_id"], meta["sender_addr"], nothing)
        send_message(agent, "Pong", t)
    elseif message == "Pong"
        agent.counter += 1
        t = AgentAddress(meta["sender_id"], meta["sender_addr"], nothing)
        send_message(agent, "Ping", t)
    end
end

4. Sending Messages

Now let's simulate the ping pong exchange by sending messages between the ping pong agents. Addresses are provided to the send_message function via the AgentAddress struct.

@kwdef struct AgentAddress <: Address
    aid::Union{String,Nothing}
    address::Any = nothing
    tracking_id::Union{String,Nothing} = nothing
end

The send_message method here will automatically insert the agent as sender:

# Define the ping pong agent
# Create instances of ping pong agents
ping_agent = TCPPingPongAgent(0)
pong_agent = TCPPingPongAgent(0)

# register each agent to a container
register(container, ping_agent)
register(container2, pong_agent)

# Send the first message to start the exchange
target = AgentAddress(pong_agent.aid, InetAddr(ip"127.0.0.1", 5556), nothing)
send_message(ping_agent, "Ping", target)

# Wait for a moment to see the result
# In general you want to use a Condition() instead to
# Define a clear stopping signal for the agents
wait(Threads.@spawn begin
    while ping_agent.counter < 5 
        sleep(1)
    end
end)

@sync begin
    Threads.@spawn shutdown(container)
    Threads.@spawn shutdown(container2)
end

In this example, the ping pong agents take turns sending "Ping" and "Pong" messages to each other, incrementing their counters. After a short moment, we can see the result of the ping pong process.

5. Using the MQTT Protocol

To use an MQTT messsage broker instead of a direkt TCP connection, you can use the MQTTProtocol.

broker_addr = InetAddr(ip"127.0.0.1", 1883)

c1 = Container()
c1.protocol = MQTTProtocol("PingContainer", broker_addr)

c2 = Container()
c2.protocol = MQTTProtocol("PongContainer", broker_addr)

The topics each agent subscribes to on the broker are provided during registration to the container. All messages on these topics will then be forwarded as messages to the agent.

# Define the ping pong agent
@agent struct MQTTPingPongAgent
    counter::Int
end

# Define the ping pong agent
# Create instances of ping pong agents
ping_agent = MQTTPingPongAgent(0)
pong_agent = MQTTPingPongAgent(0)

# register each agent to a container
# For the MQTT protocol, topics for each agent have to be passed here.
register(c1, ping_agent; topics=["pongs"])
register(c2, pong_agent; topics=["pings"])

Just like the TCPProtocol, the MQTTProtocol has an associated struct for providing address information:

@kwdef struct MQTTAddress <: Address
    broker::Any = nothing
    topic::String
end

Thus, sending of the first message becomes:

# Send the first message to start the exchange
wait(send_message(ping_agent, "Ping", MQTTAddress(broker_addr, "pings")))

Lastly, handle_message has to be altered to send the corresponding answers correctly:

# Override the default handle_message function for ping pong agents
function handle_message(agent::MQTTPingPongAgent, message::Any, meta::Any)
    broker_addr = agent.context.container.protocol.broker_addr

    if message == "Ping"
        agent.counter += 1
        send_message(agent, "Pong", MQTTAddress(broker_addr, "pongs"))
    elseif message == "Pong"
        agent.counter += 1
        send_message(agent, "Ping", MQTTAddress(broker_addr, "pings"))
    end
end