Skip to content

Commit 9b7c673

Browse files
kawaemonnanai10a
authored andcommitted
refactor: use oneshot to sync
1 parent 01beb0d commit 9b7c673

File tree

8 files changed

+107
-140
lines changed

8 files changed

+107
-140
lines changed

Cargo.lock

+23
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ async-trait = "0.1"
2929
chrono = { version = "0.4", features = ["serde"] }
3030
chrono-tz = "0.8"
3131
clap = { version = "4", features = ["derive"] }
32+
crossbeam = "0.8"
3233
derivative = "2"
3334
dotenv = "0.15"
3435
hex = "0.4"

src/bot/genkai_point/mod.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ use {
1818
chrono::{DateTime, Duration, Utc},
1919
clap::ValueEnum,
2020
once_cell::sync::Lazy,
21-
std::{cmp::Ordering, collections::HashMap, fmt::Write},
21+
std::{cmp::Ordering, collections::HashMap, fmt::Write, future::Future},
2222
tokio::sync::Mutex,
2323
};
2424

@@ -160,7 +160,7 @@ pub(crate) trait GenkaiPointDatabase: Send + Sync {
160160
}
161161

162162
pub(crate) trait Plotter: Send + Sync + 'static {
163-
fn plot(&self, data: Vec<(String, Vec<f64>)>) -> Result<Vec<u8>>;
163+
fn plot(&self, data: Vec<(String, Vec<f64>)>) -> impl Future<Output = Result<Vec<u8>>> + Send;
164164
}
165165

166166
#[derive(Debug)]

src/bot/genkai_point/plot/charming.rs

+66-127
Original file line numberDiff line numberDiff line change
@@ -5,27 +5,25 @@ use {
55
component::Axis, element::name_location::NameLocation, series::Line, Chart, ImageFormat,
66
ImageRenderer,
77
},
8-
std::{
9-
sync::{Arc, Condvar, Mutex},
10-
thread::{spawn, JoinHandle},
11-
time::Instant,
12-
},
8+
crossbeam::channel::{Receiver, Sender},
9+
std::thread,
10+
tokio::sync::oneshot,
1311
};
1412

1513
pub(crate) struct Charming {
16-
renderer_handle: RendererHandle,
14+
renderer: Renderer,
1715
}
1816

1917
impl Charming {
2018
pub(crate) fn new() -> Self {
21-
let renderer_handle = spawn_renderer();
19+
let renderer = Renderer::spawn();
2220

23-
Self { renderer_handle }
21+
Self { renderer }
2422
}
2523
}
2624

2725
impl Plotter for Charming {
28-
fn plot(&self, data: Vec<(String, Vec<f64>)>) -> Result<Vec<u8>> {
26+
async fn plot(&self, data: Vec<(String, Vec<f64>)>) -> Result<Vec<u8>> {
2927
let chart = data
3028
.iter()
3129
.fold(Chart::new(), |chart, (label, data)| {
@@ -43,145 +41,86 @@ impl Plotter for Charming {
4341
.name("累計VC時間(時)"),
4442
);
4543

46-
self.renderer_handle.call(chart)
44+
self.renderer.render(chart).await
4745
}
4846
}
4947

50-
struct RendererHandle {
51-
port: Arc<Mutex<Job>>,
52-
cond: Arc<Condvar>,
53-
54-
_handle: JoinHandle<()>,
48+
struct Request {
49+
data: Chart,
50+
bell: oneshot::Sender<Response>,
5551
}
56-
57-
impl RendererHandle {
58-
fn call(&self, chart: Chart) -> Result<Vec<u8>> {
59-
let mut guard = self
60-
.port
61-
.lock()
62-
.map_err(|_| anyhow!("failed to lock job queue"))?;
63-
64-
let chart = Unconsidered::wrap(chart);
65-
let session = Session::new();
66-
67-
let Job::Idle = std::mem::replace(&mut *guard, Job::Queued(chart, session)) else {
68-
unreachable!()
69-
};
70-
71-
self.cond.notify_all();
72-
73-
let mut guard = self
74-
.cond
75-
.wait_while(
76-
guard,
77-
|job| !matches!(job, Job::Finished(_, s) if session == *s),
78-
)
79-
.map_err(|_| anyhow!("failed to lock job queue"))?;
80-
81-
let Job::Finished(result, _) = std::mem::replace(&mut *guard, Job::Idle) else {
82-
dbg!(&*guard);
83-
unreachable!()
84-
};
85-
86-
result.unwrap()
87-
}
52+
struct Response {
53+
image: Result<Vec<u8>>,
8854
}
8955

90-
fn spawn_renderer() -> RendererHandle {
91-
let port = Arc::new(Mutex::new(Job::Idle));
92-
let cond = Arc::new(Condvar::new());
93-
94-
let _handle = {
95-
let port = port.clone();
96-
let cond = cond.clone();
97-
98-
spawn(|| renderer_main(port, cond))
99-
};
100-
101-
RendererHandle {
102-
port,
103-
cond,
104-
_handle,
105-
}
56+
struct Renderer {
57+
tx: Sender<Request>,
58+
_thread_handle: thread::JoinHandle<()>,
10659
}
10760

108-
fn renderer_main(port: Arc<Mutex<Job>>, cond: Arc<Condvar>) {
109-
let mut renderer = ImageRenderer::new(1280, 720);
110-
111-
while let Ok(Ok(mut job)) = port
112-
.lock()
113-
.map(|guard| cond.wait_while(guard, |job| !matches!(job, Job::Queued(..))))
114-
{
115-
let Job::Queued(arg0, session) = std::mem::replace(&mut *job, Job::Running) else {
116-
unreachable!()
117-
};
61+
impl Renderer {
62+
fn render_thread(rx: Receiver<Request>) {
63+
let mut renderer = ImageRenderer::new(1280, 720);
11864

119-
let arg0 = arg0.unwrap();
120-
let ret = renderer
121-
.render_format(ImageFormat::Png, &arg0)
122-
.map_err(|_| anyhow!("no detail provided"));
65+
for req in rx {
66+
let image = renderer
67+
.render_format(ImageFormat::Png, &req.data)
68+
.map_err(|e| anyhow!("charming error: {e:#?}"));
12369

124-
let ret = Unconsidered::wrap(ret);
125-
let Job::Running = std::mem::replace(&mut *job, Job::Finished(ret, session)) else {
126-
unreachable!()
127-
};
128-
129-
cond.notify_all();
70+
req.bell.send(Response { image }).ok();
71+
}
13072
}
131-
}
13273

133-
#[derive(Debug, PartialEq, Eq)]
134-
enum Job {
135-
Idle,
136-
Queued(Unconsidered<Chart>, Session),
137-
Running,
138-
Finished(Unconsidered<Result<Vec<u8>>>, Session),
139-
}
74+
fn spawn() -> Self {
75+
let (tx, rx) = crossbeam::channel::unbounded::<Request>();
14076

141-
struct Unconsidered<T>(T);
77+
let handle = std::thread::spawn(|| Self::render_thread(rx));
14278

143-
impl<T> Unconsidered<T> {
144-
fn wrap(val: T) -> Self {
145-
Self(val)
79+
Self {
80+
tx,
81+
_thread_handle: handle,
82+
}
14683
}
14784

148-
fn unwrap(self) -> T {
149-
self.0
150-
}
151-
}
85+
async fn render(&self, data: Chart) -> Result<Vec<u8>> {
86+
let (tx, rx) = oneshot::channel();
15287

153-
impl<T> std::fmt::Debug for Unconsidered<T> {
154-
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
155-
write!(f, "{{unconsidered}}")
156-
}
157-
}
88+
self.tx.send(Request { data, bell: tx }).unwrap();
15889

159-
impl<T> PartialEq for Unconsidered<T> {
160-
fn eq(&self, _: &Self) -> bool {
161-
true
90+
rx.await.unwrap().image
16291
}
16392
}
16493

165-
impl<T> Eq for Unconsidered<T> {}
166-
167-
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
168-
struct Session(Instant);
169-
170-
impl Session {
171-
fn new() -> Self {
172-
Self(Instant::now())
94+
#[tokio::test]
95+
async fn test() {
96+
let charming = std::sync::Arc::new(Charming::new());
97+
98+
let mut handles = vec![];
99+
100+
#[allow(unused_variables)]
101+
for i in 0..10 {
102+
let charming = charming.clone();
103+
104+
handles.push(tokio::spawn(async move {
105+
let result = charming
106+
.plot(vec![
107+
("kawaemon".into(), vec![1.0, 4.0, 6.0, 7.0]),
108+
("kawak".into(), vec![2.0, 5.0, 11.0, 14.0]),
109+
])
110+
.await
111+
.unwrap();
112+
113+
// should we assert_eq with actual png?
114+
assert_ne!(result.len(), 0);
115+
116+
// uncomment this to see image artifacts
117+
// tokio::fs::write(format!("./out{i}.png"), result)
118+
// .await
119+
// .unwrap();
120+
}));
173121
}
174-
}
175122

176-
#[test]
177-
fn test() {
178-
let result = Charming::new()
179-
.plot(vec![
180-
("kawaemon".into(), vec![1.0, 4.0, 6.0, 7.0]),
181-
("kawak".into(), vec![2.0, 5.0, 11.0, 14.0]),
182-
])
183-
.unwrap();
184-
185-
// should we assert_eq with actual png?
186-
assert_ne!(result.len(), 0);
123+
for h in handles {
124+
h.await.unwrap();
125+
}
187126
}

src/bot/genkai_point/plot/matplotlib.rs

+9-7
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ impl Matplotlib {
2020
}
2121

2222
impl Plotter for Matplotlib {
23-
fn plot(&self, data: Vec<(String, Vec<f64>)>) -> Result<Vec<u8>> {
23+
async fn plot(&self, data: Vec<(String, Vec<f64>)>) -> Result<Vec<u8>> {
2424
let result: Result<PythonContext, _> = std::panic::catch_unwind(|| {
2525
python! {
2626
import io
@@ -47,12 +47,14 @@ impl Plotter for Matplotlib {
4747
}
4848
}
4949

50-
#[test]
51-
fn test_plot_to_image() {
52-
let result = Matplotlib.plot(vec![
53-
("kawaemon".into(), vec![1.0, 4.0, 6.0, 7.0]),
54-
("kawak".into(), vec![2.0, 5.0, 11.0, 14.0]),
55-
]);
50+
#[tokio::test]
51+
async fn test_plot_to_image() {
52+
let result = Matplotlib {}
53+
.plot(vec![
54+
("kawaemon".into(), vec![1.0, 4.0, 6.0, 7.0]),
55+
("kawak".into(), vec![2.0, 5.0, 11.0, 14.0]),
56+
])
57+
.await;
5658

5759
// should we assert_eq with actual png?
5860
assert_ne!(result.unwrap().len(), 0);

src/bot/genkai_point/plot/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ pub(super) async fn plot<P: Plotter + Send>(
8383
// use tokio::task::spawn_blocking to solve this problem.
8484
let image = plotter
8585
.plot(prottable_data)
86+
.await
8687
.context("failed to plot graph")?;
8788

8889
Ok(Some(image))

src/bot/genkai_point/plot/plotters.rs

+4-3
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ impl Plotters {
3232
}
3333

3434
impl Plotter for Plotters {
35-
fn plot(&self, data: Vec<(String, Vec<f64>)>) -> Result<Vec<u8>> {
35+
async fn plot(&self, data: Vec<(String, Vec<f64>)>) -> Result<Vec<u8>> {
3636
const SIZE: (usize, usize) = (1280, 720);
3737

3838
let mut buffer = vec![0; SIZE.0 * SIZE.1 * 3];
@@ -108,13 +108,14 @@ impl Plotter for Plotters {
108108
}
109109
}
110110

111-
#[test]
112-
fn test() {
111+
#[tokio::test]
112+
async fn test() {
113113
let result = Plotters::new()
114114
.plot(vec![
115115
("kawaemon".into(), vec![1.0, 4.0, 6.0, 7.0]),
116116
("kawak".into(), vec![2.0, 5.0, 11.0, 14.0]),
117117
])
118+
.await
118119
.unwrap();
119120

120121
// should we assert_eq with actual png?

src/main.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ async fn async_main() -> Result<()> {
7474
#[cfg(feature = "plot_plotters")]
7575
let plotter = plot::plotters::Plotters::new();
7676
#[cfg(feature = "plot_matplotlib")]
77-
let plotter = plot::plotters::Matplotlib::new();
77+
let plotter = plot::matplotlib::Matplotlib::new();
7878
#[cfg(feature = "plot_charming")]
7979
let plotter = plot::charming::Charming::new();
8080

0 commit comments

Comments
 (0)