5
5
component:: Axis , element:: name_location:: NameLocation , series:: Line , Chart , ImageFormat ,
6
6
ImageRenderer ,
7
7
} ,
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,
13
11
} ;
14
12
15
13
pub ( crate ) struct Charming {
16
- renderer_handle : RendererHandle ,
14
+ renderer : Renderer ,
17
15
}
18
16
19
17
impl Charming {
20
18
pub ( crate ) fn new ( ) -> Self {
21
- let renderer_handle = spawn_renderer ( ) ;
19
+ let renderer = Renderer :: spawn ( ) ;
22
20
23
- Self { renderer_handle }
21
+ Self { renderer }
24
22
}
25
23
}
26
24
27
25
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 > > {
29
27
let chart = data
30
28
. iter ( )
31
29
. fold ( Chart :: new ( ) , |chart, ( label, data) | {
@@ -43,145 +41,86 @@ impl Plotter for Charming {
43
41
. name ( "累計VC時間(時)" ) ,
44
42
) ;
45
43
46
- self . renderer_handle . call ( chart)
44
+ self . renderer . render ( chart) . await
47
45
}
48
46
}
49
47
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 > ,
55
51
}
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 > > ,
88
54
}
89
55
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 < ( ) > ,
106
59
}
107
60
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 ) ;
118
64
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:#?} " ) ) ;
123
69
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
+ }
130
72
}
131
- }
132
73
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 > ( ) ;
140
76
141
- struct Unconsidered < T > ( T ) ;
77
+ let handle = std :: thread :: spawn ( || Self :: render_thread ( rx ) ) ;
142
78
143
- impl < T > Unconsidered < T > {
144
- fn wrap ( val : T ) -> Self {
145
- Self ( val)
79
+ Self {
80
+ tx,
81
+ _thread_handle : handle,
82
+ }
146
83
}
147
84
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 ( ) ;
152
87
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 ( ) ;
158
89
159
- impl < T > PartialEq for Unconsidered < T > {
160
- fn eq ( & self , _: & Self ) -> bool {
161
- true
90
+ rx. await . unwrap ( ) . image
162
91
}
163
92
}
164
93
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
+ } ) ) ;
173
121
}
174
- }
175
122
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
+ }
187
126
}
0 commit comments