Skip to main content

fyrox_build_tools/
build.rs

1// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in all
11// copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19// SOFTWARE.
20
21use fyrox_core::algebra::{Matrix3, Vector2};
22use fyrox_core::Uuid;
23use fyrox_resource::untyped::ResourceKind;
24use fyrox_ui::button::Button;
25use fyrox_ui::image::Image;
26use fyrox_ui::scroll_viewer::ScrollViewer;
27use fyrox_ui::text::Text;
28use fyrox_ui::window::{Window, WindowAlignment};
29use fyrox_ui::{
30    border::BorderBuilder,
31    button::{ButtonBuilder, ButtonMessage},
32    core::{parking_lot::Mutex, pool::Handle, SafeLock},
33    grid::{Column, GridBuilder, Row},
34    image::ImageBuilder,
35    message::UiMessage,
36    scroll_viewer::{ScrollViewerBuilder, ScrollViewerMessage},
37    stack_panel::StackPanelBuilder,
38    style::{resource::StyleResourceExt, Style},
39    text::{TextBuilder, TextMessage},
40    texture::{
41        TextureImportOptions, TextureMagnificationFilter, TextureMinificationFilter,
42        TextureResource, TextureResourceExtension,
43    },
44    widget::{WidgetBuilder, WidgetMessage},
45    window::{WindowBuilder, WindowMessage, WindowTitle},
46    BuildContext, HorizontalAlignment, Orientation, Thickness, UserInterface,
47};
48use std::{
49    io::{BufRead, BufReader, Read},
50    process::{ChildStderr, ChildStdout},
51    sync::{
52        atomic::{AtomicBool, Ordering},
53        Arc,
54    },
55};
56
57pub struct BuildWindow {
58    window: Handle<Window>,
59    active: Arc<AtomicBool>,
60    changed: Arc<AtomicBool>,
61    log: Arc<Mutex<String>>,
62    log_text: Handle<Text>,
63    stop: Handle<Button>,
64    scroll_viewer: Handle<ScrollViewer>,
65    progress_indicator: Handle<Image>,
66    angle: f32,
67}
68
69impl Drop for BuildWindow {
70    fn drop(&mut self) {
71        // Prevent the listen thread from being alive after the build window is destroyed.
72        self.active.store(false, Ordering::SeqCst);
73    }
74}
75
76impl BuildWindow {
77    pub fn new(project_name: &str, ctx: &mut BuildContext) -> Self {
78        let progress_image = TextureResource::load_from_memory(
79            Uuid::new_v4(),
80            ResourceKind::Embedded,
81            include_bytes!("resources/progress.png"),
82            TextureImportOptions::default()
83                .with_minification_filter(TextureMinificationFilter::LinearMipMapLinear)
84                .with_magnification_filter(TextureMagnificationFilter::Linear)
85                .with_lod_bias(-1.0),
86        )
87        .ok();
88
89        let log_text;
90        let stop;
91        let scroll_viewer;
92        let progress_indicator;
93        let window = WindowBuilder::new(WidgetBuilder::new().with_width(420.0).with_height(200.0))
94            .can_minimize(false)
95            .can_close(false)
96            .can_maximize(false)
97            .open(false)
98            .with_content(
99                GridBuilder::new(
100                    WidgetBuilder::new()
101                        .with_child(
102                            GridBuilder::new(
103                                WidgetBuilder::new()
104                                    .with_child(
105                                        TextBuilder::new(
106                                            WidgetBuilder::new()
107                                                .with_margin(Thickness {
108                                                    left: 5.0,
109                                                    top: 1.0,
110                                                    right: 1.0,
111                                                    bottom: 1.0,
112                                                })
113                                                .on_column(0),
114                                        )
115                                        .with_text(format!(
116                                            "Please wait while {project_name} is building..."
117                                        ))
118                                        .build(ctx),
119                                    )
120                                    .with_child({
121                                        progress_indicator = ImageBuilder::new(
122                                            WidgetBuilder::new()
123                                                .with_width(16.0)
124                                                .with_height(16.0)
125                                                .on_column(1)
126                                                .with_margin(Thickness {
127                                                    left: 1.0,
128                                                    top: 1.0,
129                                                    right: 4.0,
130                                                    bottom: 1.0,
131                                                })
132                                                .with_clip_to_bounds(false),
133                                        )
134                                        .with_opt_texture(progress_image)
135                                        .build(ctx);
136                                        progress_indicator
137                                    }),
138                            )
139                            .add_row(Row::stretch())
140                            .add_column(Column::stretch())
141                            .add_column(Column::auto())
142                            .build(ctx),
143                        )
144                        .with_child(
145                            BorderBuilder::new(
146                                WidgetBuilder::new()
147                                    .on_row(1)
148                                    .with_margin(Thickness::uniform(2.0))
149                                    .with_background(ctx.style.property(Style::BRUSH_DARKEST))
150                                    .with_child({
151                                        scroll_viewer =
152                                            ScrollViewerBuilder::new(WidgetBuilder::new())
153                                                .with_content({
154                                                    log_text =
155                                                        TextBuilder::new(WidgetBuilder::new())
156                                                            .build(ctx);
157                                                    log_text
158                                                })
159                                                .build(ctx);
160                                        scroll_viewer
161                                    }),
162                            )
163                            .build(ctx),
164                        )
165                        .with_child(
166                            StackPanelBuilder::new(
167                                WidgetBuilder::new()
168                                    .with_horizontal_alignment(HorizontalAlignment::Right)
169                                    .on_row(2)
170                                    .with_child({
171                                        stop = ButtonBuilder::new(
172                                            WidgetBuilder::new()
173                                                .with_width(100.0)
174                                                .with_margin(Thickness::uniform(4.0)),
175                                        )
176                                        .with_text("Stop")
177                                        .build(ctx);
178                                        stop
179                                    }),
180                            )
181                            .with_orientation(Orientation::Horizontal)
182                            .build(ctx),
183                        ),
184                )
185                .add_row(Row::auto())
186                .add_row(Row::stretch())
187                .add_row(Row::strict(28.0))
188                .add_column(Column::stretch())
189                .build(ctx),
190            )
191            .with_title(WindowTitle::text(format!("Building {project_name}...")))
192            .build(ctx);
193
194        Self {
195            window,
196            log_text,
197            log: Arc::new(Default::default()),
198            active: Arc::new(AtomicBool::new(false)),
199            changed: Arc::new(AtomicBool::new(false)),
200            stop,
201            scroll_viewer,
202            progress_indicator,
203            angle: 0.0,
204        }
205    }
206
207    pub fn listen(&mut self, (stderr, stdout): (ChildStderr, ChildStdout), ui: &UserInterface) {
208        let log = self.log.clone();
209        self.active.store(true, Ordering::SeqCst);
210        let reader_active = self.active.clone();
211        let log_changed = self.changed.clone();
212
213        Self::spawn_pipe_pump(stderr, &reader_active, &log_changed, &log);
214        Self::spawn_pipe_pump(stdout, &reader_active, &log_changed, &log);
215
216        ui.send(
217            self.window,
218            WindowMessage::Open {
219                alignment: WindowAlignment::Center,
220                modal: true,
221                focus_content: true,
222            },
223        );
224    }
225
226    fn spawn_pipe_pump(
227        mut pipe: impl Read + Send + 'static,
228        reader_active: &Arc<AtomicBool>,
229        log_changed: &Arc<AtomicBool>,
230        log: &Arc<Mutex<String>>,
231    ) {
232        let reader_active = reader_active.clone();
233        let log_changed = log_changed.clone();
234        let log = log.clone();
235        std::thread::spawn(move || {
236            let pipe: &mut dyn BufRead = &mut BufReader::new(&mut pipe);
237            while reader_active.load(Ordering::SeqCst) {
238                for line in pipe.lines().take(10).flatten() {
239                    let mut log_guard = log.safe_lock();
240                    log_guard.push_str(&line);
241                    log_guard.push('\n');
242                    log_changed.store(true, Ordering::SeqCst);
243                }
244            }
245        });
246    }
247
248    pub fn reset(&mut self, ui: &UserInterface) {
249        self.active.store(false, Ordering::SeqCst);
250        self.changed.store(false, Ordering::SeqCst);
251        self.log.safe_lock().clear();
252        ui.send(self.log_text, TextMessage::Text(Default::default()));
253    }
254
255    pub fn destroy(mut self, ui: &UserInterface) {
256        self.reset(ui);
257        ui.send(self.window, WindowMessage::Close);
258    }
259
260    pub fn update(&mut self, ui: &UserInterface, dt: f32) {
261        if self.changed.load(Ordering::SeqCst) {
262            ui.send(
263                self.log_text,
264                TextMessage::Text(self.log.safe_lock().clone()),
265            );
266            ui.send(self.scroll_viewer, ScrollViewerMessage::ScrollToEnd);
267
268            self.changed.store(false, Ordering::SeqCst);
269        }
270
271        self.angle += 10.0 * dt;
272        ui.send(
273            self.progress_indicator,
274            WidgetMessage::RenderTransform(
275                Matrix3::new_translation(&Vector2::new(8.0, 8.0))
276                    * Matrix3::new_rotation(self.angle)
277                    * Matrix3::new_translation(&Vector2::new(-8.0, -8.0)),
278            ),
279        );
280    }
281
282    pub fn handle_ui_message(
283        self,
284        message: &UiMessage,
285        ui: &UserInterface,
286        on_stop: impl FnOnce(),
287    ) -> Option<Self> {
288        if let Some(ButtonMessage::Click) = message.data() {
289            if message.destination() == self.stop {
290                on_stop();
291                self.destroy(ui);
292                return None;
293            }
294        }
295        Some(self)
296    }
297}