Skip to content

Commit 7a3f6db

Browse files
authored
Reorganize agents module (#1202)
1 parent d55765a commit 7a3f6db

File tree

11 files changed

+1148
-1088
lines changed

11 files changed

+1148
-1088
lines changed

yew/src/agent.rs

Lines changed: 0 additions & 1086 deletions
This file was deleted.

yew/src/agent/link.rs

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
use super::*;
2+
use crate::callback::Callback;
3+
use crate::scheduler::{scheduler, Runnable, Shared};
4+
use std::cell::RefCell;
5+
use std::fmt;
6+
use std::rc::Rc;
7+
8+
/// Defines communication from Worker to Consumers
9+
pub(crate) trait Responder<AGN: Agent> {
10+
/// Implementation for communication channel from Worker to Consumers
11+
fn respond(&self, id: HandlerId, output: AGN::Output);
12+
}
13+
14+
/// Link to agent's scope for creating callbacks.
15+
pub struct AgentLink<AGN: Agent> {
16+
scope: AgentScope<AGN>,
17+
responder: Rc<dyn Responder<AGN>>,
18+
}
19+
20+
impl<AGN: Agent> AgentLink<AGN> {
21+
/// Create link for a scope.
22+
pub(crate) fn connect<T>(scope: &AgentScope<AGN>, responder: T) -> Self
23+
where
24+
T: Responder<AGN> + 'static,
25+
{
26+
AgentLink {
27+
scope: scope.clone(),
28+
responder: Rc::new(responder),
29+
}
30+
}
31+
32+
/// Send response to an agent.
33+
pub fn respond(&self, id: HandlerId, output: AGN::Output) {
34+
self.responder.respond(id, output);
35+
}
36+
37+
/// Create a callback which will send a message to the agent when invoked.
38+
pub fn callback<F, IN>(&self, function: F) -> Callback<IN>
39+
where
40+
F: Fn(IN) -> AGN::Message + 'static,
41+
{
42+
let scope = self.scope.clone();
43+
let closure = move |input| {
44+
let output = function(input);
45+
scope.send(AgentLifecycleEvent::Message(output));
46+
};
47+
closure.into()
48+
}
49+
}
50+
51+
impl<AGN: Agent> fmt::Debug for AgentLink<AGN> {
52+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
53+
f.write_str("AgentLink<_>")
54+
}
55+
}
56+
57+
impl<AGN: Agent> Clone for AgentLink<AGN> {
58+
fn clone(&self) -> Self {
59+
AgentLink {
60+
scope: self.scope.clone(),
61+
responder: self.responder.clone(),
62+
}
63+
}
64+
}
65+
/// This struct holds a reference to a component and to a global scheduler.
66+
pub(crate) struct AgentScope<AGN: Agent> {
67+
shared_agent: Shared<AgentRunnable<AGN>>,
68+
}
69+
70+
impl<AGN: Agent> fmt::Debug for AgentScope<AGN> {
71+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
72+
f.write_str("AgentScope<_>")
73+
}
74+
}
75+
76+
impl<AGN: Agent> Clone for AgentScope<AGN> {
77+
fn clone(&self) -> Self {
78+
AgentScope {
79+
shared_agent: self.shared_agent.clone(),
80+
}
81+
}
82+
}
83+
84+
impl<AGN: Agent> AgentScope<AGN> {
85+
/// Create agent scope
86+
pub fn new() -> Self {
87+
let shared_agent = Rc::new(RefCell::new(AgentRunnable::new()));
88+
AgentScope { shared_agent }
89+
}
90+
/// Schedule message for sending to agent
91+
pub fn send(&self, update: AgentLifecycleEvent<AGN>) {
92+
let envelope = AgentEnvelope {
93+
shared_agent: self.shared_agent.clone(),
94+
update,
95+
};
96+
let runnable: Box<dyn Runnable> = Box::new(envelope);
97+
scheduler().push(runnable);
98+
}
99+
}
100+
101+
impl<AGN: Agent> Default for AgentScope<AGN> {
102+
fn default() -> Self {
103+
Self::new()
104+
}
105+
}
106+
107+
struct AgentRunnable<AGN> {
108+
agent: Option<AGN>,
109+
// TODO(#939): Use agent field to control create message this flag
110+
destroyed: bool,
111+
}
112+
113+
impl<AGN> AgentRunnable<AGN> {
114+
fn new() -> Self {
115+
AgentRunnable {
116+
agent: None,
117+
destroyed: false,
118+
}
119+
}
120+
}
121+
122+
/// Local Agent messages
123+
#[derive(Debug)]
124+
pub(crate) enum AgentLifecycleEvent<AGN: Agent> {
125+
/// Request to create link
126+
Create(AgentLink<AGN>),
127+
/// Internal Agent message
128+
Message(AGN::Message),
129+
/// Client connected
130+
Connected(HandlerId),
131+
/// Received mesasge from Client
132+
Input(AGN::Input, HandlerId),
133+
/// Client disconnected
134+
Disconnected(HandlerId),
135+
/// Request to destroy agent
136+
Destroy,
137+
}
138+
139+
struct AgentEnvelope<AGN: Agent> {
140+
shared_agent: Shared<AgentRunnable<AGN>>,
141+
update: AgentLifecycleEvent<AGN>,
142+
}
143+
144+
impl<AGN> Runnable for AgentEnvelope<AGN>
145+
where
146+
AGN: Agent,
147+
{
148+
fn run(self: Box<Self>) {
149+
let mut this = self.shared_agent.borrow_mut();
150+
if this.destroyed {
151+
return;
152+
}
153+
match self.update {
154+
AgentLifecycleEvent::Create(link) => {
155+
this.agent = Some(AGN::create(link));
156+
}
157+
AgentLifecycleEvent::Message(msg) => {
158+
this.agent
159+
.as_mut()
160+
.expect("agent was not created to process messages")
161+
.update(msg);
162+
}
163+
AgentLifecycleEvent::Connected(id) => {
164+
this.agent
165+
.as_mut()
166+
.expect("agent was not created to send a connected message")
167+
.connected(id);
168+
}
169+
AgentLifecycleEvent::Input(inp, id) => {
170+
this.agent
171+
.as_mut()
172+
.expect("agent was not created to process inputs")
173+
.handle_input(inp, id);
174+
}
175+
AgentLifecycleEvent::Disconnected(id) => {
176+
this.agent
177+
.as_mut()
178+
.expect("agent was not created to send a disconnected message")
179+
.disconnected(id);
180+
}
181+
AgentLifecycleEvent::Destroy => {
182+
let mut agent = this
183+
.agent
184+
.take()
185+
.expect("trying to destroy not existent agent");
186+
agent.destroy();
187+
}
188+
}
189+
}
190+
}

yew/src/agent/local/context.rs

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
use super::*;
2+
use crate::callback::Callback;
3+
use crate::scheduler::Shared;
4+
use anymap::{self, AnyMap};
5+
use slab::Slab;
6+
use std::cell::RefCell;
7+
use std::rc::Rc;
8+
9+
thread_local! {
10+
static LOCAL_AGENTS_POOL: RefCell<AnyMap> = RefCell::new(AnyMap::new());
11+
}
12+
13+
/// Create a single instance in the current thread.
14+
#[allow(missing_debug_implementations)]
15+
pub struct Context;
16+
17+
impl Discoverer for Context {
18+
fn spawn_or_join<AGN: Agent>(callback: Option<Callback<AGN::Output>>) -> Box<dyn Bridge<AGN>> {
19+
let mut scope_to_init = None;
20+
let bridge = LOCAL_AGENTS_POOL.with(|pool| {
21+
let mut pool = pool.borrow_mut();
22+
match pool.entry::<LocalAgent<AGN>>() {
23+
anymap::Entry::Occupied(mut entry) => entry.get_mut().create_bridge(callback),
24+
anymap::Entry::Vacant(entry) => {
25+
let scope = AgentScope::<AGN>::new();
26+
let launched = LocalAgent::new(&scope);
27+
let responder = SlabResponder {
28+
slab: launched.slab(),
29+
};
30+
scope_to_init = Some((scope, responder));
31+
entry.insert(launched).create_bridge(callback)
32+
}
33+
}
34+
});
35+
if let Some((scope, responder)) = scope_to_init {
36+
let agent_link = AgentLink::connect(&scope, responder);
37+
let upd = AgentLifecycleEvent::Create(agent_link);
38+
scope.send(upd);
39+
}
40+
let upd = AgentLifecycleEvent::Connected(bridge.id);
41+
bridge.scope.send(upd);
42+
Box::new(bridge)
43+
}
44+
}
45+
46+
struct SlabResponder<AGN: Agent> {
47+
slab: Shared<Slab<Option<Callback<AGN::Output>>>>,
48+
}
49+
50+
impl<AGN: Agent> Responder<AGN> for SlabResponder<AGN> {
51+
fn respond(&self, id: HandlerId, output: AGN::Output) {
52+
locate_callback_and_respond::<AGN>(&self.slab, id, output);
53+
}
54+
}
55+
56+
impl Dispatchable for Context {}
57+
58+
struct ContextBridge<AGN: Agent> {
59+
scope: AgentScope<AGN>,
60+
id: HandlerId,
61+
}
62+
63+
impl<AGN: Agent> Bridge<AGN> for ContextBridge<AGN> {
64+
fn send(&mut self, msg: AGN::Input) {
65+
let upd = AgentLifecycleEvent::Input(msg, self.id);
66+
self.scope.send(upd);
67+
}
68+
}
69+
70+
impl<AGN: Agent> Drop for ContextBridge<AGN> {
71+
fn drop(&mut self) {
72+
let terminate_worker = LOCAL_AGENTS_POOL.with(|pool| {
73+
let mut pool = pool.borrow_mut();
74+
let terminate_worker = {
75+
if let Some(launched) = pool.get_mut::<LocalAgent<AGN>>() {
76+
launched.remove_bridge(self)
77+
} else {
78+
false
79+
}
80+
};
81+
82+
if terminate_worker {
83+
pool.remove::<LocalAgent<AGN>>();
84+
}
85+
86+
terminate_worker
87+
});
88+
89+
let upd = AgentLifecycleEvent::Disconnected(self.id);
90+
self.scope.send(upd);
91+
92+
if terminate_worker {
93+
let upd = AgentLifecycleEvent::Destroy;
94+
self.scope.send(upd);
95+
}
96+
}
97+
}
98+
99+
struct LocalAgent<AGN: Agent> {
100+
scope: AgentScope<AGN>,
101+
slab: SharedOutputSlab<AGN>,
102+
}
103+
104+
impl<AGN: Agent> LocalAgent<AGN> {
105+
pub fn new(scope: &AgentScope<AGN>) -> Self {
106+
let slab = Rc::new(RefCell::new(Slab::new()));
107+
LocalAgent {
108+
scope: scope.clone(),
109+
slab,
110+
}
111+
}
112+
113+
fn slab(&self) -> SharedOutputSlab<AGN> {
114+
self.slab.clone()
115+
}
116+
117+
fn create_bridge(&mut self, callback: Option<Callback<AGN::Output>>) -> ContextBridge<AGN> {
118+
let respondable = callback.is_some();
119+
let mut slab = self.slab.borrow_mut();
120+
let id: usize = slab.insert(callback);
121+
let id = HandlerId::new(id, respondable);
122+
ContextBridge {
123+
scope: self.scope.clone(),
124+
id,
125+
}
126+
}
127+
128+
fn remove_bridge(&mut self, bridge: &ContextBridge<AGN>) -> Last {
129+
let mut slab = self.slab.borrow_mut();
130+
let _ = slab.remove(bridge.id.raw_id());
131+
slab.is_empty()
132+
}
133+
}

yew/src/agent/local/job.rs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
use super::*;
2+
use crate::callback::Callback;
3+
4+
const SINGLETON_ID: HandlerId = HandlerId(0, true);
5+
6+
/// Create an instance in the current thread.
7+
#[allow(missing_debug_implementations)]
8+
pub struct Job;
9+
10+
impl Discoverer for Job {
11+
fn spawn_or_join<AGN: Agent>(callback: Option<Callback<AGN::Output>>) -> Box<dyn Bridge<AGN>> {
12+
let callback = callback.expect("Callback required for Job");
13+
let scope = AgentScope::<AGN>::new();
14+
let responder = CallbackResponder { callback };
15+
let agent_link = AgentLink::connect(&scope, responder);
16+
let upd = AgentLifecycleEvent::Create(agent_link);
17+
scope.send(upd);
18+
let upd = AgentLifecycleEvent::Connected(SINGLETON_ID);
19+
scope.send(upd);
20+
let bridge = JobBridge { scope };
21+
Box::new(bridge)
22+
}
23+
}
24+
25+
struct JobBridge<AGN: Agent> {
26+
scope: AgentScope<AGN>,
27+
}
28+
29+
impl<AGN: Agent> Bridge<AGN> for JobBridge<AGN> {
30+
fn send(&mut self, msg: AGN::Input) {
31+
let upd = AgentLifecycleEvent::Input(msg, SINGLETON_ID);
32+
self.scope.send(upd);
33+
}
34+
}
35+
36+
impl<AGN: Agent> Drop for JobBridge<AGN> {
37+
fn drop(&mut self) {
38+
let upd = AgentLifecycleEvent::Disconnected(SINGLETON_ID);
39+
self.scope.send(upd);
40+
let upd = AgentLifecycleEvent::Destroy;
41+
self.scope.send(upd);
42+
}
43+
}
44+
45+
struct CallbackResponder<AGN: Agent> {
46+
callback: Callback<AGN::Output>,
47+
}
48+
49+
impl<AGN: Agent> Responder<AGN> for CallbackResponder<AGN> {
50+
fn respond(&self, id: HandlerId, output: AGN::Output) {
51+
assert_eq!(id.raw_id(), SINGLETON_ID.raw_id());
52+
self.callback.emit(output);
53+
}
54+
}

yew/src/agent/local/mod.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
mod context;
2+
mod job;
3+
4+
use super::*;
5+
6+
pub use context::Context;
7+
pub use job::Job;

0 commit comments

Comments
 (0)