10 releases

new 0.1.0 Apr 25, 2024
0.0.8 Mar 22, 2024
0.0.5 Feb 29, 2024
0.0.2 Jan 28, 2024
0.0.0 Sep 19, 2023

#8 in Robotics

Download history 4/week @ 2024-01-20 4/week @ 2024-01-27 5/week @ 2024-02-03 115/week @ 2024-02-17 172/week @ 2024-02-24 177/week @ 2024-03-02 28/week @ 2024-03-09 215/week @ 2024-03-16 39/week @ 2024-03-23 59/week @ 2024-03-30 12/week @ 2024-04-06

191 downloads per month

Custom license

130KB
3.5K SLoC

dimas

DiMAS - A framework for building Distributed Multi Agent Systems

⚠️ WARNING ⚠️ : DiMAS is under active development, so expect gaps between implementation and documentation.

A distributed multi agent system is a set of independant agents that are widely distributed but somehow connected. They are designed in a way that they can solve complex tasks by working together.

The system is characterised by

  • a somewhat large and complex environment
  • containing a set of (non agent) objects that can be perceived, created, moved, modified or destroyed by the agents
  • that changes over time due to external rules

with multiple agents operating in that environment which

  • can percieve the environment to a limited extent
  • have the possibility to communicate with some or all of the other agents
  • have certain capabilities to influence the environment

This crate is available on crates.io.

DiMAS follows the semantic versioning principle with the enhancement, that until version 1.0.0 each new minor version has breaking changes, while patches are non breaking changes but may include enhancements.

Usage

DiMAS needs an async runtime. You have to define your main function as an async function.

So include dimas together with an async runtime in the dependencies section of your Cargo.toml. As DiMAS uses tokio as async runtime, so preferably use tokio for your application. Ensure that you use a multi-threaded runtime, otherwise dimas will panic.

DiMAS uses features to have some control over compile time and the size of the binary. The feature all, including all available features, is a good point to start with.

So your Cargo.toml should include:

[dependencies]
dimas = { version = "0.1.0", features = ["all"] }
tokio = { version = "1", features = ["macros"] }

It also makes sense to return a Result, as some functions may return one. DiMAS errors are always of type Box<dyn std::error::Error> and should be thread safe. DiMAS provides a type definition Result<T> to make life easier

A suitable main programm skeleton may look like:

use dimas::prelude::*;

#[tokio::main]
async fn main() -> Result<()> {

	// your code
	// ...

	Ok(())
}

Example

A very simple example consist at least of two agents, a publisher publishing messages and a subscriber that is listening to those messages.

The Cargo.toml for this publisher/subscriber example should include

[dependencies]
dimas = { version = "0.1.0", features = ["publisher", "subscriber", "timer"] }
tokio = { version = "1",features = ["macros"] }

Publisher

The publisher.rs should look like this:

use dimas::prelude::*;
use std::time::Duration;

#[derive(Debug)]
struct AgentProps {
	counter: u128,
}

#[tokio::main]
async fn main() -> Result<()> {
	// create & initialize agents properties
	let properties = AgentProps { counter: 0 };

	// create an agent with the properties and default configuration
	let mut agent = Agent::new(properties)
		.config(Config::default())?;

	// create publisher for topic "hello"
	agent
		.publisher()
		.topic("hello")
		.add()?;

	// use a timer for regular publishing of "hello" topic
	agent
		// get the TimerBuilder from the agent
		.timer()
		// set a name for the timer
		.name("timer")
		// every second
		.interval(Duration::from_secs(1))
		// the timers callback function as a closure
		.callback(
			|ctx| -> Result<()> {
				let counter = ctx
					.read()?
					.counter;
				// the message to send
				let text = format!("Hello World! [{counter}]");
				// just to see what will be sent
				println!("Sending '{}'", &text);
				// publishing with stored publisher for topic "hello"
				ctx.put_with("hello", text)?;
				// modify counter in properties
				ctx
					.write()?
					.counter += 1;
				Ok(())
			}
		)
		// finally add the timer to the agent
		// errors will be propagated to main
		.add()?;

	// start the agent
	agent.start().await?;
	Ok(())
}

Subscriber

The subscriber.rs should look like this:

//! `DiMAS` zenoh-pico example
//! Copyright © 2024 Stephan Kunz

use dimas::prelude::*;

/// The Agent's proerties
#[derive(Debug)]
pub struct AgentProps {}

fn callback(_ctx: &ArcContext<AgentProps>, message: Message) -> Result<()> {
	let message: String =	message.decode()?;
	println!("Received '{message}'");
	Ok(())
}

#[tokio::main]
async fn main() -> Result<()> {
	// create & initialize agents properties
	let properties = AgentProps {};

	// create an agent with the properties and default configuration
	let agent = Agent::new(properties)
		.config(Config::default())?;

	// subscribe to "hello" messages
	agent
		// get the SubscriberBuilder from the agent
		.subscriber()
    	//set wanted message topic (corresponding to publishers topic!)
		.topic("hello")
    	// set the callback function for put messages
		.put_callback(callback)
    	// finally add the subscriber to the agent
    	// errors will be propagated to main
		.add()?;

	// start the agent
	agent.start().await?;
	Ok(())
}

More examples

You can find some simple examples in dimas-fw/dimas/examples and more complex examples in dimas-fw/examples

Feature flags

DiMAS uses a set of feature flags to minimize the size of an agent. It is necessary to enable all those features you want to use within your Agent.

  • all: Enables all the features listed below. It's a good point to start with.
  • liveliness: Enables listening and reacting on liveliness tokens. Sending tokens is always possible.
  • publisher: Enables to store Publisher within the Agents Context.
  • query: Enables to store Querys within the Agents Context.
  • queryable: Enables to store Queryables within the Agents Context.
  • subscriber: Enables to store Subscriber within the Agents Context.
  • timer: Enables to store Timer within the Agents Context.

Dependencies

~35–51MB
~889K SLoC