Attribute Macro interthread::group

source ·
#[group]
Expand description

§A set of actors sharing a single thread

In the realm of concurrent programming, creating a separate thread for each individual actor can sometimes incur a significant overhead. In such scenarios, developers may opt to populate an actor’s definition with complex encapsulations (potential actors), effectively creating a collection of objects running within a single thread. While this approach proves resource-efficient, it does come with a trade-off: accessing the methods of field types must be explicitly written within the main actor implementation block.

This is where the group macro comes into play. A group is a set of actors that share a single thread, consisting of a main-actor and group-actors contained within the main-actor’s fields. Developers can now bypass the need to rewrite method wrappers, gaining direct access to group-actor methods via dot notation, as seamlessly as if these group-actors were actors in their own right.

In this scenario, the methods of the main-actor take on the responsibility for interaction within and between group-actors, while the latter primarily serve to export their functionality.

Before delving into further details, let’s explore an example of a group. Assuming there is a good understanding of how an actor operates, once the Live instance is returned after invoking the new method, the actor is already running in a separate thread. Therefore, there’s no need to complicate the example with extra thread spawning just for visual clarity.

§Examples


pub struct Aa(u8);
impl Aa {
    pub fn add(&mut self, v: u8){
        self.0 += v;
    }
}

pub struct Bb(u8);
impl Bb {
    pub fn add(&mut self, v: u8){
        self.0 += v;
    }
}

pub struct AaBb {
    pub a: Aa,
    pub b: Bb,
}

#[interthread::group( file= "path/to/file.rs")]
impl AaBb {

    pub fn new( ) -> Self {
        let a = Aa(0);
        let b = Bb(0);
        Self{ a,b}
    }

    pub fn add(&mut self, v:u8){
        self.a.0 += v;
        self.b.0 += v;
    }

    pub fn get_value(&mut self) -> (u8,u8) {
        (self.a.0,self.b.0)
    }
}


pub fn main(){

    let mut group = AaBbGroupLive::new();
    
    // access to group method
    group.add(1);
    assert_eq!((1,1),group.get_value());

    // access to field `a` method
    group.a.add(10);
    assert_eq!((11,1),group.get_value());

    // access to field `b` method
    group.b.add(100);
    assert_eq!((11,101),group.get_value());
}

Behind the scenes, the macro will generate some additional types very similar to actor’s types, for group-actor NameScriptGroup and NameLiveGroup:

  • Aa - AaScriptGroup, AaLiveGroup
  • Bb - BbScriptGroup, BbLiveGroup

For main-actor itself: NameGroupScript and NameGroupLive:

  • AaBb - AaBbGroupScript, AaBbGroupLive

In the context of the SDPL framework, group-actors are designated as SDL (Script, Direct, Live) and will share the play method with the main-actor, which is full SDPL. The following is a type schema of the group model in relation to the above example:


struct Aa;
struct Bb;

enum AaScriptGroup;
struct AaLiveGroup;

enum BbScriptGroup;
struct BbLiveGroup;

struct AaBb {
    pub a: Aa,
    pub b: Bb,
}

enum AaBbGroupScript;
struct AaBbGroupLive {
    pub a: AaLiveGroup,
    pub b: BbLiveGroup,
}

To view all the generated code by group, you can either utilize the example macro or employ the edit option within the group macro. For a convenient shortcut to see the full example using edit, simply use edit(file).“

#[interthread::group( file="path/to/file.rs",edit(file))]

To inspect the generated code for field a type from the above example, utilize the edit option as edit(a::edit(file)), for struct AaBb itself use edit(self::edit(file)).

§Requirements for Using the group Macro

Much like individual actors, the group macro enables a set of actors to run collectively within a shared thread. While many requirements align with those of individual actors, there are some distinctions to be aware of. Below are the crucial conditions that need to be satisfied for the group macro to operate :

  • The object must be a struct with named fields.
  • As an actor impl block must contain a method named new returning a self-instance or try_new if it may fail to return.
  • The macro requires a file field with a file path to the current file at all times.
  • Fields in the definition block that are intended to act as group-actors should have non-private visibility (public or restricted). Private fields will not be considered as group-actors by the macro.

§Configuration Options

The configuration options for a group are slightly different, but consist of the same arguments as those used for an actor except couple of them. In some cases (see notation (AA) in the table below), the argument is a list of the same arguments, specified as argument(field_name::argument,..). In context of the example code from above, if we wanted to include any hypothetical static (associated) methods of struct Aa, we would use the assoc argument, like so:

assoc(a::assoc)

To include the same argument for main-actor itself, we would write

assoc(a::assoc, self::assoc)

The following is the full table of configuration options:


#[interthread::group( 
   
AA  channel = 0 * 
             n (usize)

AA      lib = "std" *
              "smol"
              "tokio"
              "async_std"

AA     file = "path/to/current/file.rs"

AA     debut(
            legend
            )   

(AA)   assoc(
            self::assoc,
            ..
            )

(AA)    edit( 
            self::edit(
                      script(..)
                      live(..)
                      ),
            ..
            ) 

(AA)    name(
            self::name = "",
            ..
            )

(AA)    path(
            a::path = "path/to/type.rs",
            ..
            )       
   )
]

  *     -  default 
  AA    -  similar to `actor` attribute argument.
 (AA)   -  a list of similar to `actor` attribute arguments.

All group configuration options (arguments) are the same as actor’s arguments, except for path and allow, which are unique to group.

§Arguments

§path

Argument path is used when a group-actor is defined in a file different from the group itself.

§allow

Argument allow is used when a non-private field of the group is necessary but should not be included as a group-actor.

§Handling Identical Types in the group Model

In certain situations, the group model may encounter a scenario where the main-actor possesses multiple fields of the same type. Let’s consider an example:

struct AaBb {
    pub a:  Aa,
    pub a1: Aa,
    pub b:  Bb,
}

Due to the model naming convention which is based on type names, both fields a and a1 generate identical model names for both the Script and Live components. This leads to a compilation error:

the name `AaScriptGroup` is defined multiple times
`AaScriptGroup` must be defined only once in the type namespace of this module

To resolve this scenario, adjust the names for identical types as follows:

 struct AaBb {
     pub a:  Aa,
     pub a1: Aa,
     pub b:  Bb,
 }
 
 // Usage of the macro may be as follows:
 
 #[interthread::group(
     file="path/to/file.rs",
     name( a1::name="Aa1" )
 )]
 impl AaBb {
     // ...
 }