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 Handler
s and they each need different types of resources. But I am turning the Handler
s into Builder
s and flattening the Builder
s 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 Handler
s, 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.