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
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
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
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:
- Change the signal before passing it to subsequent processors by calling context.Next(TSignal) with the new signal.
- Stop subsequent signal processing by returning without calling either context.Next() nor context.Next(TSignal) .
- 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
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:
- Change the signal before passing it to subsequent processors by calling context.Next(TSignal) with the new signal.
- Stop subsequent signal processing by returning without calling either context.Next() nor context.Next(TSignal) .
-
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 TResponse
s 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
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
Returns
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 TResponse
s 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
Returns
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
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 TResponse
s 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
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
CancellationTokenA CancellationToken whose cancellation signals that the module should stop running.
Returns
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
TSignalThe 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
TSignalThe 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
TSignalThe 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
TSignalThe signal to send.
synchronous
boolWhether 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()