Table of Contents

Class Module

Namespace
Prefrontal
Assembly
Prefrontal.Core.dll

A base class for all modules that can be added to an Agent.

Refer to Agent's Lifecycle for information on what can and cannot be done in each of the agent's lifecycle stages.

Each module's constructor can have injectable dependencies in its parameters which are injected by the agent using its ServiceProvider. The module's constructor can also take the agent itself as a parameter and even other modules that it requires.

  • Modules can be added to an agent by calling AddModule<T>().
  • Modules can be removed from an agent by calling RemoveModule<T>().
  • Call Initialize() or InitializeAsync() to initialize the agent and all of its modules.
  • Modules can send signals to other modules on the same agent by calling SendSignal<TSignal>(TSignal) or SendSignalAsync<TSignal>(TSignal). These signals can be of any type, but it's best to create your own signal types.
  • Signals can also return a response that the sender can receive by calling SendSignal<TSignal, TResponse>(TSignal, bool) or SendSignalAsync<TSignal, TResponse>(TSignal). Signal processors and senders are not matched by the response type, only by the signal type. This means that if the response type of a signal processor does not match the expected response type of the sender, the response will be empty.
  • Modules can implement IDisposable if they need to clean up resources when they are removed from the agent. Modules can also block the agent from removing them by throwing an InvalidOperationException in their Dispose() method. However, doing so when the agent itself is being disposed of has no effect.
  • Modules can log messages using the Debug property.
public abstract class Module
Inheritance
Module
Derived
Inherited Members
Extension Methods

Constructors

Module()

Default base constructor.
There is no need to call this constructor in derived classes.
Do not instantiate modules directly except in the createModule callback passed to AddModule<T>(Func<Agent, T>).

protected Module()

Module(Agent)

Base constructor that takes the agent as a parameter. Call this base constructor in derived classes when you need access to Agent inside the module's constructor like so:

public class MyModule : Module
{
	public MyModule(Agent agent) : base(agent)
		=> Debug.Log("MyModule has been added to the agent: {agent}", agent.Name);
}

Do not instantiate modules directly except in the createModule callback passed to AddModule<T>(Func<Agent, T>).

protected Module(Agent agent)

Parameters

agent Agent

Properties

Agent

The agent that this module belongs to.

Beware that accessing this will throw an exception if the module has been removed from the agent or if accessed from within the module's constructor that does not pass the agent as a parameter to the base constructor. In the rare circumstance when it could be needed IsPartOfAgent can be used to check if the module has not been removed from the agent.

public Agent Agent { get; }

Property Value

Agent

Debug

The logger that this module can use to log messages.
The logger is only available after Agent has been assigned, meaning you cannot log messages in the module's constructor unless you include the agent as a parameter in the constructor and pass it to the base constructor.

protected ILogger Debug { get; }

Property Value

ILogger

IsPartOfAgent

true if the module belongs to an agent, false otherwise.
Example usage:

public class SomethingGetterModule : Module
{
	[returns: NotNullIfNotNull(nameof(defaultSomething))]
	public Something? GetSomethingOrDefault(Something? defaultSomething = null)
	{
		if(!IsPartOfAgent)
			return defaultSomething;
		return Agent.GetModule<SomethingProviderModule>()?.Something
			?? defaultSomething;
	}
}
public bool IsPartOfAgent { get; }

Property Value

bool

Methods

GetModuleOrDefault<T>()

protected T? GetModuleOrDefault<T>() where T : Module

Returns

T

Type Parameters

T

InterceptSignalsAsync<TSignal>(Func<SignalContext<TSignal, object>, IAsyncEnumerable<object>>)

Registers a signal interceptor that intercepts signals of type TSignal with responses wrapped as objects

Interceptors differ from receivers in that they can:

  1. Change the signal before passing it to subsequent processors by calling context.Next(TSignal) with the new signal.
  2. Stop subsequent signal processing by returning without calling either context.Next() nor context.Next(TSignal) .
  3. Return different response objects than the ones returned by context.Next() and context.Next(TSignal) .

The interceptor and sender are only matched by the TSignal type. If the objects returned by the interceptor cannot be cast to the response type expected by the sender, the sender will receive no response.

There is no need to dispose of the returned IDisposable unless you want to stop intercepting signals. This is because the agent will automatically unsubscribe the interceptor when the module is removed.

protected IDisposable InterceptSignalsAsync<TSignal>(Func<SignalContext<TSignal, object>, IAsyncEnumerable<object>> interceptor)

Parameters

interceptor Func<SignalContext<TSignal, object>, IAsyncEnumerable<object>>

A function that takes a SignalContext<TSignal, TResponse> and returns an IAsyncEnumerable<T>. Calling context.Next() will continue the signal processing.

Returns

IDisposable

Type Parameters

TSignal
See Also
SignalContext<TSignal, TResponse>

InterceptSignalsAsync<TSignal, TResponse>(Func<SignalContext<TSignal, TResponse>, IAsyncEnumerable<TResponse>>)

Registers a signal interceptor that intercepts signals of type TSignal with responses cast to TResponse.

Interceptors differ from receivers in that they can:

  1. Change the signal before passing it to subsequent processors by calling context.Next(TSignal) with the new signal.
  2. Stop subsequent signal processing by returning without calling either context.Next() nor context.Next(TSignal) .
  3. Return a different TResponse than the ones returned by context.Next() and context.Next(TSignal) .

The interceptor and sender are only matched by the TSignal type and not by the TResponse type. If the TResponses returned by the interceptor cannot be cast to the response type expected by the sender, the sender will receive no response.

There is no need to dispose of the returned IDisposable unless you want to stop intercepting signals. This is because the agent will automatically unsubscribe the interceptor when the module is removed.

protected IDisposable InterceptSignalsAsync<TSignal, TResponse>(Func<SignalContext<TSignal, TResponse>, IAsyncEnumerable<TResponse>> interceptor)

Parameters

interceptor Func<SignalContext<TSignal, TResponse>, IAsyncEnumerable<TResponse>>

A function that takes a SignalContext<TSignal, TResponse> and returns an IAsyncEnumerable<T>. Calling context.Next() will continue the signal processing.

Returns

IDisposable

Type Parameters

TSignal
TResponse
See Also
SignalContext<TSignal, TResponse>

ReceiveSignalsAsync<TSignal>(Func<TSignal, Task>)

Registers a receiver for signals of type TSignal.

The receiver will be called even if the sender expects a response. However, the sender will receive no response unless another receiver returns a response of that type.

There is no need to dispose of the returned IDisposable unless you want to stop receiving signals. This is because the agent will automatically unsubscribe the receiver when the module is removed.

protected IDisposable ReceiveSignalsAsync<TSignal>(Func<TSignal, Task> receiver)

Parameters

receiver Func<TSignal, Task>

Returns

IDisposable

Type Parameters

TSignal

ReceiveSignalsAsync<TSignal, TResponse>(Func<TSignal, Task<TResponse>>)

Registers a receiver for signals of type TSignal that returns a response of type TResponse.

The receiver and sender are only matched by the TSignal type and not by the TResponse type. If the TResponses returned by the receiver cannot be cast to the response type expected by the sender, the sender will receive no response.

There is no need to dispose of the returned IDisposable unless you want to stop receiving signals. This is because the agent will automatically unsubscribe the receiver when the module is removed.

protected IDisposable ReceiveSignalsAsync<TSignal, TResponse>(Func<TSignal, Task<TResponse>> receiver)

Parameters

receiver Func<TSignal, Task<TResponse>>

Returns

IDisposable

Type Parameters

TSignal
TResponse

ReceiveSignals<TSignal>(Action<TSignal>)

Registers a receiver for signals of type TSignal.

The receiver will be called even if the sender expects a response. However, the sender will receive no response unless another receiver returns a response of that type.

There is no need to dispose of the returned IDisposable unless you want to stop receiving signals. This is because the agent will automatically unsubscribe the receiver when the module is removed.

protected IDisposable ReceiveSignals<TSignal>(Action<TSignal> receiver)

Parameters

receiver Action<TSignal>

Returns

IDisposable

Type Parameters

TSignal

ReceiveSignals<TSignal, TResponse>(Func<TSignal, TResponse>)

Registers a receiver for signals of type TSignal that returns a response of type TResponse.

The receiver and sender are only matched by the TSignal type and not by the TResponse type. If the TResponses returned by the receiver cannot be cast to the response type expected by the sender, the sender will receive no response.

There is no need to dispose of the returned IDisposable unless you want to stop receiving signals. This is because the agent will automatically unsubscribe the receiver when the module is removed.

protected IDisposable ReceiveSignals<TSignal, TResponse>(Func<TSignal, TResponse> receiver)

Parameters

receiver Func<TSignal, TResponse>

Returns

IDisposable

Type Parameters

TSignal
TResponse

RunAsync(CancellationToken)

Override this method in a derived class to run the module's main logic.

  • Do not call this method directly.
  • Call Agent.RunAsync() to run all the modules in parallel.
  • Returning from this method means the module has completed its primary task and won't be called again during the same Agent.RunAsync() call.
  • The RunningModuleExceptionPolicy passed to Agent.RunAsync() controls what happens when this method throws an exception.
  • The CancellationToken passed to this method signals that the method should stop running and return. This can happen when module is removed, the Stop() method is called or the agent is disposed of.
  • To keep the module's logic running indefinitely, you can override this method and run a loop like so:
    protected override async Task RunAsync(CancellationToken cancellationToken)
    {
    	while(!cancellationToken.IsCancellationRequested)
    	{
    		// Your module's logic here
    	}
    }
protected virtual Task RunAsync(CancellationToken cancellationToken)

Parameters

cancellationToken CancellationToken

A CancellationToken whose cancellation signals that the module should stop running.

Returns

Task

SendSignalAsync<TSignal>(TSignal)

Sends a signal asynchronously to all modules on the agent and observers.

It's perfectly fine to add and remove modules during signal processing, but be aware that modules that are removed will not receive the signal and modules that are added will only receive future signals, i.e. sent after they were added.

protected Task SendSignalAsync<TSignal>(TSignal signal)

Parameters

signal TSignal

The signal to send.

Returns

Task

A task that completes when all modules have processed the signal.

Type Parameters

TSignal

The type of signal to send.

See Also
SendSignal<TSignal>(TSignal)

SendSignalAsync<TSignal, TResponse>(TSignal)

Sends a signal asynchronously to all modules on the agent and observers.

It's perfectly fine to add and remove modules during signal processing, but be aware that modules that are removed will not receive the signal and modules that are added will only receive future signals, i.e. sent after they were added.

protected IAsyncEnumerable<TResponse> SendSignalAsync<TSignal, TResponse>(TSignal signal)

Parameters

signal TSignal

The signal to send.

Returns

IAsyncEnumerable<TResponse>

All responses to the signal of type TResponse. XAsyncEnumerable offers a variety of methods to work with the responses.

Type Parameters

TSignal

The type of signal to send.

TResponse
See Also
SendSignal<TSignal>(TSignal)

SendSignal<TSignal>(TSignal)

Sends a TSignal signal to all modules and observers on the agent.

This method is non-blocking and returns immediately because it defers the signal processing to a background task.

It's perfectly fine to add and remove modules during signal processing, but be aware that modules that are removed will not receive the signal and modules that are added will only receive future signals, i.e. sent after they were added.

protected void SendSignal<TSignal>(TSignal signal)

Parameters

signal TSignal

The signal to send.

Type Parameters

TSignal

The type of signal to send.

SendSignal<TSignal, TResponse>(TSignal, bool)

Sends a TSignal signal to all modules and observers on the agent.

This method is non-blocking and returns immediately because it defers the signal processing to a background task.

It's perfectly fine to add and remove modules during signal processing, but be aware that modules that are removed will not receive the signal and modules that are added will only receive future signals, i.e. sent after they were added.

protected IEnumerable<TResponse> SendSignal<TSignal, TResponse>(TSignal signal, bool synchronous)

Parameters

signal TSignal

The signal to send.

synchronous bool

Whether to wait for all the responses before returning.

Returns

IEnumerable<TResponse>

All responses to the signal of type TResponse. Enumerable offers a variety of methods to work with the responses.

Type Parameters

TSignal

The type of signal to send.

TResponse

The type of response to expect.

ToString()

Unless overridden, returns the name of the module's type (without the "Module" suffix).
Generic type arguments are also included in the output, if any.

public override string ToString()

Returns

string