The important traits are Handler and Builder (Transact is just along for the ride) They are meant to work together. Their method calls return each other. Builder<T> takes a T which is a type that typically implements a Resource
trait Resource<'a, T> where Box<Self>: Sized {
fn get(&'a self) -> T;
}
A dumb interface like that is needed because each builder will receive a tuple containing many items of different types. A builder isn't supposed to know all the possible types, only that it needs one or more and it knows what their exact types are (the one or more). So I can basically do something like this
pub(super) struct ListenerBuilder<R> {
pub stored: Option<String>,
pub source: Source,
pub resources: PhantomData<R>
}
impl<R, R2> Builder<R2> for ListenerBuilder<R>
where
R: AsyncRead + Unpin + Send + 'static,
R2: for<'a> Resource<'a, &'a HashMap<Source, Arc<Mutex<R>>>> +
for<'a> Resource<'a, &'a HashMap<Source, Sender<String>>>
{
type Event = String;
fn build<'a>(self: Box<Self>, resources: R2) -> Box<dyn Handler<R2, Event = Self::Event> + Send + 'a> {
let receiver_listener: &HashMap<Source, Arc<Mutex<R>>> = resources.get();
let receiver_listener = receiver_listener.get(&self.source).unwrap();
let receiver_listener = Arc::clone(&receiver_listener);
let sender_ch: &HashMap<Source, Sender<String>> = resources.get();
let sender_ch = sender_ch.get(&self.source).unwrap();
let sender_ch = sender_ch.clone();
Box::new(Listener {
receiver_listener,
receiver_ch: sender_ch.subscribe(),
sender_ch,
stored: self.stored,
source: self.source
})
}
}
Someone, somewhere will get ListenerBuilder. But they will only know it by it's trait Box<dyn Builder... They will pass it a super trait
trait SuperResource<R1, R2, W1, W2>:
Resource<HashMap<Source, Arc<Mutex<R1>>> +
Resource<HashMap<Source, Sender<String>>> +
// other resources
Resource<HashMap<Source, UserAgentEndpointReader<R2>>> +
Resource<HashMap<Source, EndpointWriter<W1>>> +
Resource<HashMap<String, UserAgentEndpointWriter<W2>>>
where R1: AsyncRead + Unpin + Send,
R2: AsyncRead + Unpin + Send,
W1: AsyncWrite + Unpin + Send,
W2: AsyncWrite + Unpin + Send
{}
Notice the super trait "extends" smaller traits like for<'a> Resource<'a, &'a HashMap<Source, Arc<Mutex<R>>>>. So I can impl a tuple containing all possible resources and have it's Resource::get method return only the one tuple item that matters. This way ListenerBuilder can "pick out" exactly the 2 resources it needs without needing to know about all the other tuple item types or SuperResource. Which I show in the example build() implementation above.
This kind of resource management is important because I'll be creating all sorts of Handlers and they each need different types of resources. But I am turning the Handlers into Builders and flattening the Builders into a vec list of one type Box<dyn Builder.. which I then call build() on each one passing the one SuperResource. I was able to take an interconnected web of handlers and resources like tcp listeners and flatten it all out in my "event loop" to a simple pseudo code
let event = handlers.poll()
let mut new_handlers = Vec::new()
for builder in handlers.handle(&event) {
new_handlers.push(builders.build(super_resource))
}
// .. repeat
So far this works just fine. But I'm constantly fighting with Handler::handle, particularly, the argument which a lot of times is a reference like an &event. I have many kinds of Handlers, but I am now writing the implementation of a Handler that just so happens to use this thing called Transact. And it also happens to read &event.
impl<T> Handler<T> for SomeHandler
....
....
fn handle<'a>(self: Box<Self>, event: &Event) -> Box<dyn Builder<T, Event = Self::Event> + 'a> {
....
let new_transact self.transact.transact(event);
Box::new(SomeHandlerBuilder(new_transact)); // same error as the minimal example
SomeHandlerBuilder takes the new_transact because remember it builds the SomeHandler after getting a Resource of some kind.
I should also add that there is a lot lot more to this "architecture" then Resource management scheme I present here. But this post is already getting too long.