makepad_studio/build_manager/
build_manager.rs

1use {
2    crate::{
3        app::AppAction,
4        build_manager::{build_client::BuildClient, build_protocol::*},
5        file_system::file_system::FileSystem,
6        makepad_file_server::FileSystemRoots,
7        makepad_micro_serde::*,
8        makepad_platform::makepad_live_compiler::LiveFileChange,
9        makepad_platform::os::cx_stdin::{
10            HostToStdin, StdinKeyModifiers, StdinMouseDown, StdinMouseMove, StdinMouseUp,
11            StdinScroll, StdinToHost,
12        },
13        makepad_platform::studio::{
14            DesignerComponentPosition,
15            DesignerZoomPan,
16            AppToStudio, AppToStudioVec, EventSample, GPUSample, StudioToApp, StudioToAppVec,
17        },
18        makepad_shell::*,
19        makepad_widgets::*,
20    },
21    makepad_code_editor::{
22        decoration::{Decoration, DecorationType},
23        text,
24    },
25    makepad_http::server::*,
26    std::{
27        cell::RefCell,
28        collections::{hash_map, HashMap},
29        fs::File,
30        io::prelude::*,
31        net::{SocketAddr, UdpSocket},
32        sync::mpsc,
33        sync::{Arc, Mutex},
34        thread, time,
35        time::{Duration, Instant},
36    },
37};
38
39pub const MAX_SWAPCHAIN_HISTORY: usize = 4;
40pub struct ActiveBuild {
41    pub root: String,
42    pub log_index: String,
43    pub process: BuildProcess,
44    pub swapchain: HashMap<usize, Option<cx_stdin::Swapchain<Texture>>>,
45    pub last_swapchain_with_completed_draws: HashMap<usize, Option<cx_stdin::Swapchain<Texture>>>,
46    pub app_area: HashMap<usize, Area>,
47    /// Some previous value of `swapchain`, which holds the image still being
48    /// the most recent to have been presented after a successful client draw,
49    /// and needs to be kept around to avoid deallocating the backing texture.
50    ///
51    /// While not strictly necessary, it can also accept *new* draws to any of
52    /// its images, which allows the client to catch up a frame or two, visually.
53    pub aux_chan_host_endpoint: Option<cx_stdin::aux_chan::HostEndpoint>,
54}
55impl ActiveBuild {
56    pub fn swapchain_mut(&mut self, index: usize) -> &mut Option<cx_stdin::Swapchain<Texture>> {
57        match self.swapchain.entry(index) {
58            hash_map::Entry::Occupied(o) => o.into_mut(),
59            hash_map::Entry::Vacant(v) => v.insert(None),
60        }
61    }
62    pub fn last_swapchain_with_completed_draws_mut(
63        &mut self,
64        index: usize,
65    ) -> &mut Option<cx_stdin::Swapchain<Texture>> {
66        match self.last_swapchain_with_completed_draws.entry(index) {
67            hash_map::Entry::Occupied(o) => o.into_mut(),
68            hash_map::Entry::Vacant(v) => v.insert(None),
69        }
70    }
71    pub fn swapchain(&self, index: usize) -> Option<&cx_stdin::Swapchain<Texture>> {
72        if let Some(e) = self.swapchain.get(&index) {
73            if let Some(e) = e {
74                return Some(e);
75            }
76        }
77        None
78    }
79    pub fn last_swapchain_with_completed_draws(
80        &mut self,
81        index: usize,
82    ) -> Option<&cx_stdin::Swapchain<Texture>> {
83        if let Some(e) = self.last_swapchain_with_completed_draws.get(&index) {
84            if let Some(e) = e {
85                return Some(e);
86            }
87        }
88        None
89    }
90}
91
92#[derive(Default)]
93pub struct ActiveBuilds {
94    pub builds: HashMap<LiveId, ActiveBuild>,
95}
96
97impl ActiveBuilds {
98    pub fn builds_with_root(&self, root:String)->impl Iterator<Item = (&LiveId,&ActiveBuild)>{
99        self.builds.iter().filter(move |(_,b)| b.root == root)
100    }
101    
102    pub fn item_id_active(&self, item_id: LiveId) -> bool {
103        self.builds.get(&item_id).is_some()
104    }
105
106    pub fn any_binary_active(&self, binary: &str) -> bool {
107        for (_k, v) in &self.builds {
108            if v.process.binary == binary {
109                return true;
110            }
111        }
112        false
113    }
114}
115
116#[derive(Default)]
117pub struct ProfileSampleStore {
118    pub event: Vec<EventSample>,
119    pub gpu: Vec<GPUSample>,
120}
121
122#[derive(Default)]
123pub struct BuildManager {
124    roots: FileSystemRoots,
125    http_port: usize,
126    pub clients: Vec<BuildClient>,
127    pub log: Vec<(LiveId, LogItem)>,
128    pub profile: HashMap<LiveId, ProfileSampleStore>,
129    recompile_timeout: f64,
130    recompile_timer: Timer,
131    pub binaries: Vec<BuildBinary>,
132    pub active: ActiveBuilds,
133    pub studio_http: String,
134    pub recv_studio_msg: ToUIReceiver<(LiveId, AppToStudioVec)>,
135    pub recv_external_ip: ToUIReceiver<SocketAddr>,
136    pub tick_timer: Timer,
137    pub designer_state: DesignerState,
138    pub websocket_alive_timer: Timer,
139    //pub send_file_change: FromUISender<LiveFileChange>,
140    pub active_build_websockets: Arc<Mutex<RefCell<ActiveBuildWebSockets>>>,
141}
142
143#[derive(Default)]
144pub struct ActiveBuildWebSockets{
145    pub sockets: Vec<ActiveBuildSocket>
146}
147
148impl ActiveBuildWebSockets{
149    pub fn send_studio_to_app(&mut self, build_id: LiveId, msg:StudioToApp){
150        if let Some(socket) = self.sockets.iter_mut().find(|v| v.build_id == build_id){
151            let data = StudioToAppVec(vec![msg]).serialize_bin();
152            let _ = socket.sender.send(data.clone());
153        }
154    }
155}
156
157pub struct ActiveBuildSocket{
158    web_socket_id: u64, 
159    build_id: LiveId, 
160    sender: mpsc::Sender<Vec<u8>>
161}
162
163#[derive(Default, SerRon, DeRon)]
164pub struct DesignerState{
165    state: HashMap<LiveId, DesignerStatePerBuildId>
166}
167
168#[derive(Default, SerRon, DeRon)]
169pub struct DesignerStatePerBuildId{
170    selected_file: String,
171    zoom_pan: DesignerZoomPan,
172    component_positions: Vec<DesignerComponentPosition>
173}
174
175impl DesignerState{
176    fn save_state(&self){
177        let saved = self.serialize_ron();
178        let mut f = File::create("makepad_designer.ron").expect("Unable to create file");
179        f.write_all(saved.as_bytes()).expect("Unable to write data");
180    }
181        
182    fn load_state(&mut self){
183        if let Ok(contents) = std::fs::read_to_string("makepad_designer.ron") {
184            match DesignerState::deserialize_ron(&contents) {
185                Ok(state)=>{
186                    *self = state
187                }
188                Err(e)=>{
189                    println!("ERR {:?}",e);
190                }
191            }
192        }
193    }
194    
195    fn get_build_storage<F:FnOnce(&mut DesignerStatePerBuildId)>(&mut self, build_id: LiveId, f:F){
196        match self.state.entry(build_id) {
197            hash_map::Entry::Occupied(mut v) => {
198                f(v.get_mut());
199            },
200            hash_map::Entry::Vacant(v) => {
201                let mut db = DesignerStatePerBuildId::default();
202                f(&mut db);
203                v.insert(db);
204            }
205        }
206    }
207}
208
209pub struct BuildBinary {
210    pub open: f64,
211    pub root: String,
212    pub name: String,
213}
214
215#[derive(Clone, Debug, DefaultNone)]
216pub enum BuildManagerAction {
217    StdinToHost { build_id: LiveId, msg: StdinToHost },
218    None,
219}
220
221// Cross-platform
222// Able to dynamically adapt to the current network environment
223// whether it is a wired connection, Wi-Fi or VPN.
224// But it requires the ability to access external networks.
225fn get_local_ip() -> String {
226    /*let ipv6 = UdpSocket::bind("[::]:0")
227        .and_then(|socket| {
228            socket.connect("[2001:4860:4860::8888]:80")?;
229            socket.local_addr()
230        })
231        .ok();
232*/
233    let ipv4 = UdpSocket::bind("0.0.0.0:0")
234        .and_then(|socket| {
235            socket.connect("8.8.8.8:80")?;
236            socket.local_addr()
237        })
238        .ok();
239
240    match ipv4 {
241        Some(SocketAddr::V4(addr)) if !addr.ip().is_loopback() => addr.ip().to_string(),
242        _ => "127.0.0.1".to_string(),
243    }
244}
245
246impl BuildManager {
247    pub fn init(&mut self, cx: &mut Cx, roots: FileSystemRoots) {
248        self.http_port = if std::option_env!("MAKEPAD_STUDIO_HTTP").is_some() {
249            8002
250        } else {
251            8001
252        };
253
254        let local_ip = get_local_ip();
255        //self.studio_http = format!("http://172.20.10.4:{}/$studio_web_socket", self.http_port);
256        // self.studio_http = format!("http://127.0.0.1:{}/$studio_web_socket", self.http_port);
257        self.studio_http = format!("http://{}:{}/$studio_web_socket", local_ip, self.http_port);
258        
259        println!("Studio http : {:?}", self.studio_http);
260        self.tick_timer = cx.start_interval(0.008);
261        self.roots = roots;
262        self.clients = vec![BuildClient::new_with_local_server(self.roots.clone())];
263        self.designer_state.load_state();
264        self.update_run_list(cx);
265        self.websocket_alive_timer = cx.start_interval(1.0);
266        //self.recompile_timer = cx.start_timeout(self.recompile_timeout);
267    }
268
269    pub fn send_host_to_stdin(&self, item_id: LiveId, msg: HostToStdin) {
270        self.clients[0].send_cmd_with_id(item_id, BuildCmd::HostToStdin(msg.to_json()));
271    }
272
273    pub fn update_run_list(&mut self, _cx: &mut Cx) {
274        self.binaries.clear();
275        for (root_name, root_path) in &self.roots.roots{
276            match shell_env_cap(&[], root_path, "cargo", &["run", "--bin"]) {
277                Ok(_) => {}
278                // we expect it on stderr
279                Err(e) => {
280                    let mut after_av = false;
281                    for line in e.split("\n") {
282                        if after_av {
283                            let binary = line.trim().to_string();
284                            if binary.len() > 0 {
285                                self.binaries.push(BuildBinary {
286                                    open: 0.0,
287                                    root: root_name.clone(),
288                                    name: binary,
289                                });
290                            }
291                        }
292                        if line.contains("Available binaries:") {
293                            after_av = true;
294                        }
295                    }
296                }
297            }
298        }
299    }
300
301    pub fn process_name(&mut self, tab_id: LiveId) -> Option<String> {
302        if let Some(build) = self.active.builds.get(&tab_id) {
303            return Some(build.process.binary.clone());
304        }
305        None
306    }
307
308    pub fn handle_tab_close(&mut self, tab_id: LiveId) -> bool {
309        let len = self.active.builds.len();
310        if self.active.builds.remove(&tab_id).is_some() {
311            self.clients[0].send_cmd_with_id(tab_id, BuildCmd::Stop);
312        }
313        if len != self.active.builds.len() {
314            self.log.clear();
315            true
316        } else {
317            false
318        }
319    }
320
321    pub fn start_recompile(&mut self, _cx: &mut Cx) {
322        // alright so. a file was changed. now what.
323        for (build_id, active_build) in &mut self.active.builds {
324            self.clients[0].send_cmd_with_id(*build_id, BuildCmd::Stop);
325            self.clients[0].send_cmd_with_id(
326                *build_id,
327                BuildCmd::Run(active_build.process.clone(), self.studio_http.clone()),
328            );
329
330            active_build.swapchain.clear();
331            active_build.last_swapchain_with_completed_draws.clear();
332            active_build.aux_chan_host_endpoint = None;
333        }
334    }
335
336    pub fn clear_active_builds(&mut self) {
337        // alright so. a file was changed. now what.
338        for build_id in self.active.builds.keys() {
339            self.clients[0].send_cmd_with_id(*build_id, BuildCmd::Stop);
340        }
341        self.active.builds.clear();
342    }
343
344    pub fn clear_log(&mut self, cx: &mut Cx, dock: &DockRef, file_system: &mut FileSystem) {
345        // lets clear all log related decorations
346        file_system.clear_all_decorations();
347        file_system.redraw_all_views(cx, dock);
348        self.log.clear();
349        self.profile.clear();
350    }
351
352    pub fn start_recompile_timer(&mut self, cx: &mut Cx) {
353        cx.stop_timer(self.recompile_timer);
354        self.recompile_timer = cx.start_timeout(self.recompile_timeout);
355        /*for item_id in self.active.builds.keys() {
356            let view = ui.run_view(&[*item_id]);
357            view.recompile_started(cx);
358        }*/
359    }
360    
361    pub fn live_reload_needed(&mut self, live_file_change: LiveFileChange) {
362        // lets send this filechange to all our stdin stuff
363        /*for item_id in self.active.builds.keys() {
364            self.clients[0].send_cmd_with_id(*item_id, BuildCmd::HostToStdin(HostToStdin::ReloadFile {
365                file: live_file_change.file_name.clone(),
366                contents: live_file_change.content.clone()
367            }.to_json()));
368        }*/
369        // alright what do we need to do here.
370        
371        // so first off we need to find the root this thing belongs to
372        // if its 'makepad' we might need to send over 2 file names
373        // one local to the repo and one full path
374        
375        if let Ok(d) = self.active_build_websockets.lock() {
376            // ok so. if we have a makepad repo file
377            // we send over the full path and the stripped path
378            // if not makepad, we have to only send it to the right project
379            
380            for socket in d.borrow_mut().sockets.iter_mut() {
381                // alright so we have a file_name which includes a 'root'
382                // we also have this build_id which contains a root.
383                // if they are the same, we strip it
384                // if they are not, we send over the full path
385                let file_name = if let Some(build) = self.active.builds.get(&socket.build_id){
386                    let mut parts = live_file_change.file_name.splitn(2,"/");
387                    let root = parts.next().unwrap();
388                    let file = parts.next().unwrap();
389                    // file local to the connection
390                    if root == build.root{ 
391                        file.to_string()
392                    }
393                    // nonlocal file, make full path
394                    else if let Ok(root) = self.roots.find_root(root){
395                        root.join(file).into_os_string().into_string().unwrap()
396                    }
397                    else{
398                        file.to_string()
399                    }
400                }
401                else{
402                    live_file_change.file_name.clone()
403                };
404                let data = StudioToAppVec(vec![StudioToApp::LiveChange {
405                    file_name,
406                    content: live_file_change.content.clone(),
407                }])
408                .serialize_bin();
409                let _ = socket.sender.send(data.clone());
410            }
411        }
412    }
413
414    pub fn broadcast_to_stdin(&mut self, msg: HostToStdin) {
415        for build_id in self.active.builds.keys() {
416            self.clients[0].send_cmd_with_id(*build_id, BuildCmd::HostToStdin(msg.to_json()));
417        }
418    }
419
420    pub fn handle_event(&mut self, cx: &mut Cx, event: &Event, file_system: &mut FileSystem) {
421        if let Some(_) = self.tick_timer.is_event(event) {
422            self.broadcast_to_stdin(HostToStdin::Tick);
423        }
424        
425        if let Some(_) = self.websocket_alive_timer.is_event(event){
426            if let Ok(d) = self.active_build_websockets.lock() {
427                for socket in d.borrow_mut().sockets.iter_mut() {
428                    let data = StudioToAppVec(vec![StudioToApp::KeepAlive])
429                    .serialize_bin();
430                    let _ = socket.sender.send(data.clone());
431                }
432            }
433        }
434        
435        match event {
436            Event::MouseDown(e) => {
437                // we should only send this if it was captured by one of our runviews
438                for (build_id, build) in &self.active.builds {
439                    for area in build.app_area.values() {
440                        if e.handled.get() == *area {
441                            self.clients[0].send_cmd_with_id(
442                                *build_id,
443                                BuildCmd::HostToStdin(
444                                    HostToStdin::MouseDown(StdinMouseDown {
445                                        time: e.time,
446                                        x: e.abs.x,
447                                        y: e.abs.y,
448                                        button_raw_bits: e.button.bits(),
449                                        modifiers: StdinKeyModifiers::from_key_modifiers(
450                                            &e.modifiers,
451                                        ),
452                                    })
453                                    .to_json(),
454                                ),
455                            );
456                            break;
457                        }
458                    }
459                }
460            }
461            Event::MouseMove(e) => {
462                // we send this one to what window exactly?
463                self.broadcast_to_stdin(HostToStdin::MouseMove(StdinMouseMove {
464                    time: e.time,
465                    x: e.abs.x,
466                    y: e.abs.y,
467                    modifiers: StdinKeyModifiers::from_key_modifiers(&e.modifiers),
468                }));
469            }
470            Event::MouseUp(e) => {
471                self.broadcast_to_stdin(HostToStdin::MouseUp(StdinMouseUp {
472                    time: e.time,
473                    button_raw_bits: e.button.bits(),
474                    x: e.abs.x,
475                    y: e.abs.y,
476                    modifiers: StdinKeyModifiers::from_key_modifiers(&e.modifiers),
477                }));
478            }
479            Event::Scroll(e) => {
480                self.broadcast_to_stdin(HostToStdin::Scroll(StdinScroll {
481                    is_mouse: e.is_mouse,
482                    time: e.time,
483                    x: e.abs.x,
484                    y: e.abs.y,
485                    sx: e.scroll.x,
486                    sy: e.scroll.y,
487                    modifiers: StdinKeyModifiers::from_key_modifiers(&e.modifiers),
488                }));
489            }
490            _ => (),
491        }
492
493        if let Event::Signal = event {
494            let log = &mut self.log;
495            let active = &mut self.active;
496
497            if let Ok(mut addr) = self.recv_external_ip.try_recv() {
498                addr.set_port(self.http_port as u16);
499                self.studio_http = format!("http://{}/$studio_web_socket", addr);
500            }
501
502            while let Ok((build_id, msgs)) = self.recv_studio_msg.try_recv() {
503                for msg in msgs.0 {
504                    match msg {
505                        AppToStudio::LogItem(item) => {
506                            let file_name = if let Some(build) = active.builds.get(&build_id){
507                                self.roots.map_path(&build.root, &item.file_name)
508                            }
509                            else{
510                                self.roots.map_path("", &item.file_name)
511                            };
512                            
513                            let start = text::Position {
514                                line_index: item.line_start as usize,
515                                byte_index: item.column_start as usize,
516                            };
517                            let end = text::Position {
518                                line_index: item.line_end as usize,
519                                byte_index: item.column_end as usize,
520                            };
521                            //log!("{:?} {:?}", pos, pos + loc.length);
522                            if let Some(file_id) = file_system.path_to_file_node_id(&file_name)
523                            {
524                                match item.level {
525                                    LogLevel::Warning => {
526                                        file_system.add_decoration(
527                                            file_id,
528                                            Decoration::new(0, start, end, DecorationType::Warning),
529                                        );
530                                        cx.action(AppAction::RedrawFile(file_id))
531                                    }
532                                    LogLevel::Error => {
533                                        file_system.add_decoration(
534                                            file_id,
535                                            Decoration::new(0, start, end, DecorationType::Error),
536                                        );
537                                        cx.action(AppAction::RedrawFile(file_id))
538                                    }
539                                    _ => (),
540                                }
541                            }
542                            log.push((
543                                build_id,
544                                LogItem::Location(LogItemLocation {
545                                    level: item.level,
546                                    file_name,
547                                    start,
548                                    end,
549                                    message: item.message,
550                                    explanation: item.explanation
551                                }),
552                            ));
553                            cx.action(AppAction::RedrawLog)
554                        }
555                        AppToStudio::Screenshot(screenshot)=>{
556                                                        
557                            // lets throw the screenshot to disk as jpg
558                            // alright lets save it for fun
559                            if let Some(build) = active.builds.get(&build_id){
560                                if let Some(image) = screenshot.image{
561                                    file_system.save_snapshot_image(cx, &build.root,"qtest",screenshot.width as _, screenshot.height as _, image)
562                                }
563                            }
564                        }
565                        AppToStudio::EventSample(sample) => {
566                            // ok lets push this profile sample into the profiles
567                            let values = self.profile.entry(build_id).or_default();
568                            values.event.push(sample);
569                            cx.action(AppAction::RedrawProfiler)
570                        }
571                        AppToStudio::GPUSample(sample) => {
572                            // ok lets push this profile sample into the profiles
573                            let values = self.profile.entry(build_id).or_default();
574                            values.gpu.push(sample);
575                            cx.action(AppAction::RedrawProfiler)
576                        }
577                        AppToStudio::FocusDesign => cx.action(AppAction::FocusDesign(build_id)),
578                        AppToStudio::PatchFile(ef) => cx.action(AppAction::PatchFile(ef)),
579                        AppToStudio::EditFile(ef) => cx.action(AppAction::EditFile(ef)),
580                        AppToStudio::JumpToFile(jt) => {
581                            cx.action(AppAction::JumpTo(jt));
582                        }
583                        AppToStudio::SelectInFile(jt) => {
584                            cx.action(AppAction::SelectInFile(jt));
585                        }
586                        AppToStudio::SwapSelection(ss) => {
587                            // alright now what do we do
588                            cx.action(AppAction::SwapSelection(ss));
589                        }
590                        AppToStudio::DesignerComponentMoved(mv)=>{
591                            self.designer_state.get_build_storage(build_id, |bs|{
592                                if let Some(v) =  bs.component_positions.iter_mut().find(|v| v.id == mv.id){
593                                    *v = mv;
594                                }
595                                else{
596                                    bs.component_positions.push(mv);
597                                }
598                            });
599                            self.designer_state.save_state();
600                        }
601                        AppToStudio::DesignerZoomPan(zp)=>{
602                            self.designer_state.get_build_storage(build_id, |bs|{
603                                bs.zoom_pan = zp;
604                            });
605                            self.designer_state.save_state();
606                        }
607                        AppToStudio::DesignerStarted=>{
608                            // send the app the select file init message
609                            if let Ok(d) = self.active_build_websockets.lock() {
610                                if let Some(bs) = self.designer_state.state.get(&build_id){
611                                    let data = StudioToAppVec(vec![
612                                        StudioToApp::DesignerLoadState{
613                                            zoom_pan: bs.zoom_pan.clone(),
614                                            positions: bs.component_positions.clone()
615                                        },
616                                        StudioToApp::DesignerSelectFile {
617                                            file_name: bs.selected_file.clone()
618                                        },
619                                    ]).serialize_bin();
620                                    
621                                    for socket in d.borrow_mut().sockets.iter_mut() {
622                                        if socket.build_id == build_id{
623                                            let _ = socket.sender.send(data.clone());
624                                        }
625                                    }
626                                }
627                            }
628                            
629                        }
630                        AppToStudio::DesignerFileSelected{file_name}=>{
631                            // alright now what. lets 
632                            self.designer_state.get_build_storage(build_id, |bs|{
633                                bs.selected_file = file_name;
634                            });
635                            self.designer_state.save_state();
636                        }
637                    }
638                }
639            }
640
641            while let Ok(wrap) = self.clients[0].msg_receiver.try_recv() {
642                match wrap.message {
643                    BuildClientMessage::LogItem(LogItem::Location(mut loc)) => {
644                        loc.file_name = if let Some(build) = active.builds.get(&wrap.cmd_id){
645                            self.roots.map_path(&build.root, &loc.file_name)
646                        }
647                        else{
648                            self.roots.map_path("", &loc.file_name)
649                        };
650                        if let Some(file_id) = file_system.path_to_file_node_id(&loc.file_name) {
651                            match loc.level {
652                                LogLevel::Warning => {
653                                    file_system.add_decoration(
654                                        file_id,
655                                        Decoration::new(
656                                            0,
657                                            loc.start,
658                                            loc.end,
659                                            DecorationType::Warning,
660                                        ),
661                                    );
662                                    cx.action(AppAction::RedrawFile(file_id))
663                                }
664                                LogLevel::Error => {
665                                    file_system.add_decoration(
666                                        file_id,
667                                        Decoration::new(
668                                            0,
669                                            loc.start,
670                                            loc.end,
671                                            DecorationType::Error,
672                                        ),
673                                    );
674                                    cx.action(AppAction::RedrawFile(file_id))
675                                }
676                                _ => (),
677                            }
678                        }
679                        log.push((wrap.cmd_id, LogItem::Location(loc)));
680                        cx.action(AppAction::RedrawLog)
681                    }
682                    BuildClientMessage::LogItem(LogItem::Bare(bare)) => {
683                        //log!("{:?}", bare);
684                        log.push((wrap.cmd_id, LogItem::Bare(bare)));
685                        cx.action(AppAction::RedrawLog)
686                        //editor_state.messages.push(wrap.msg);
687                    }
688                    BuildClientMessage::LogItem(LogItem::StdinToHost(line)) => {
689                        let msg: Result<StdinToHost, DeJsonErr> = DeJson::deserialize_json(&line);
690                        match msg {
691                            Ok(msg) => cx.action(BuildManagerAction::StdinToHost {
692                                build_id: wrap.cmd_id,
693                                msg,
694                            }),
695                            Err(_) => {
696                                // we should output a log string
697                                log.push((
698                                    wrap.cmd_id,
699                                    LogItem::Bare(LogItemBare {
700                                        level: LogLevel::Log,
701                                        line: line.trim().to_string(),
702                                    }),
703                                ));
704                                cx.action(AppAction::RedrawLog)
705                                /*editor_state.messages.push(BuildMsg::Bare(BuildMsgBare {
706                                    level: BuildMsgLevel::Log,
707                                    line
708                                }));*/
709                            }
710                        }
711                    }
712                    BuildClientMessage::AuxChanHostEndpointCreated(aux_chan_host_endpoint) => {
713                        if let Some(active_build) = active.builds.get_mut(&wrap.cmd_id) {
714                            active_build.aux_chan_host_endpoint = Some(aux_chan_host_endpoint);
715                        }
716                    }
717                }
718            }
719        }
720
721        if self.recompile_timer.is_event(event).is_some() {
722            self.start_recompile(cx);
723            cx.action(AppAction::RecompileStarted);
724            cx.action(AppAction::ClearLog);
725        }
726    }
727
728    pub fn start_http_server(&mut self) {
729        let addr = SocketAddr::new("0.0.0.0".parse().unwrap(), self.http_port as u16);
730        let (tx_request, rx_request) = mpsc::channel::<HttpServerRequest>();
731        //log!("Http server at http://127.0.0.1:{}/ for wasm examples and mobile", self.http_port);
732        start_http_server(HttpServer {
733            listen_address: addr,
734            post_max_size: 1024 * 1024,
735            request: tx_request,
736        });
737        /*
738        let rx_file_change = self.send_file_change.receiver();
739        //let (tx_live_file, rx_live_file) = mpsc::channel::<HttpServerRequest> ();
740
741        let active_build_websockets = self.active_build_websockets.clone();
742        // livecoding observer
743        std::thread::spawn(move || {
744            loop{
745                if let Ok(_change) = rx_file_change.recv() {
746                    // lets send this change to all our websocket connections
747                }
748            }
749        });*/
750
751        let studio_sender = self.recv_studio_msg.sender();
752        let active_build_websockets = self.active_build_websockets.clone();
753        std::thread::spawn(move || {
754            // TODO fix this proper:
755            let makepad_path = "./".to_string();
756            let abs_makepad_path = std::env::current_dir()
757                .unwrap()
758                .join(makepad_path.clone())
759                .canonicalize()
760                .unwrap()
761                .to_str()
762                .unwrap()
763                .to_string();
764            let mut root = "./".to_string();
765            for arg in std::env::args() {
766                if let Some(prefix) = arg.strip_prefix("--root=") {
767                    root = prefix.to_string();
768                    break;
769                }
770            }
771            let remaps = [
772                (
773                    format!("/makepad/{}/", abs_makepad_path),
774                    makepad_path.clone(),
775                ),
776                (
777                    format!("/makepad/{}/", std::env::current_dir().unwrap().display()),
778                    "".to_string(),
779                ),
780                (
781                    "/makepad//".to_string(),
782                    format!("{}/{}", root, makepad_path.clone()),
783                ),
784                (
785                    "/makepad/".to_string(),
786                    format!("{}/{}", root, makepad_path.clone()),
787                ),
788                ("/".to_string(), "".to_string()),
789            ];
790            let mut socket_id_to_build_id = HashMap::new();
791            while let Ok(message) = rx_request.recv() {
792                // only store last change, fix later
793                match message {
794                    HttpServerRequest::ConnectWebSocket {
795                        web_socket_id,
796                        response_sender,
797                        headers,
798                    } => {
799                        if let Some(id) = headers.path.rsplit("/").next() {
800                            if let Ok(id) = id.parse::<u64>() {
801                                socket_id_to_build_id.insert(web_socket_id, LiveId(id));
802                                active_build_websockets
803                                    .lock()
804                                    .unwrap()
805                                    .borrow_mut()
806                                    .sockets.push(ActiveBuildSocket{
807                                        web_socket_id, 
808                                        build_id: LiveId(id), 
809                                        sender: response_sender
810                                    });
811                            }
812                        }
813                    }
814                    HttpServerRequest::DisconnectWebSocket { web_socket_id } => {
815                        socket_id_to_build_id.remove(&web_socket_id);
816                        active_build_websockets
817                            .lock()
818                            .unwrap()
819                            .borrow_mut()
820                            .sockets.retain(|v| v.web_socket_id != web_socket_id);
821                    }
822                    HttpServerRequest::BinaryMessage {
823                        web_socket_id,
824                        response_sender: _,
825                        data,
826                    } => {
827                        if let Some(id) = socket_id_to_build_id.get(&web_socket_id) {
828                            if let Ok(msg) = AppToStudioVec::deserialize_bin(&data) {
829                                let _ = studio_sender.send((*id, msg));
830                            }
831                        }
832                        // new incombing message from client
833                    }
834                    HttpServerRequest::Get {
835                        headers,
836                        response_sender,
837                    } => {
838                        let path = &headers.path;
839                        if path == "/$watch" {
840                            let header = "HTTP/1.1 200 OK\r\n\
841                                Cache-Control: max-age:0\r\n\
842                                Connection: close\r\n\r\n"
843                                .to_string();
844                            let _ = response_sender.send(HttpServerResponse {
845                                header,
846                                body: vec![],
847                            });
848                            continue;
849                        }
850                        if path == "/favicon.ico" {
851                            let header = "HTTP/1.1 200 OK\r\n\r\n".to_string();
852                            let _ = response_sender.send(HttpServerResponse {
853                                header,
854                                body: vec![],
855                            });
856                            continue;
857                        }
858
859                        let mime_type = if path.ends_with(".html") {
860                            "text/html"
861                        } else if path.ends_with(".wasm") {
862                            "application/wasm"
863                        } else if path.ends_with(".css") {
864                            "text/css"
865                        } else if path.ends_with(".js") {
866                            "text/javascript"
867                        } else if path.ends_with(".ttf") {
868                            "application/ttf"
869                        } else if path.ends_with(".png") {
870                            "image/png"
871                        } else if path.ends_with(".jpg") {
872                            "image/jpg"
873                        } else if path.ends_with(".svg") {
874                            "image/svg+xml"
875                        } else if path.ends_with(".md") {
876                            "text/markdown"
877                        } else {
878                            continue;
879                        };
880
881                        if path.contains("..") || path.contains('\\') {
882                            continue;
883                        }
884
885                        let mut strip = None;
886                        for remap in &remaps {
887                            if let Some(s) = path.strip_prefix(&remap.0) {
888                                strip = Some(format!("{}{}", remap.1, s));
889                                break;
890                            }
891                        }
892                        if let Some(base) = strip {
893                            if let Ok(mut file_handle) = File::open(base) {
894                                let mut body = Vec::<u8>::new();
895                                if file_handle.read_to_end(&mut body).is_ok() {
896                                    let header = format!(
897                                        "HTTP/1.1 200 OK\r\n\
898                                            Content-Type: {}\r\n\
899                                            Cross-Origin-Embedder-Policy: require-corp\r\n\
900                                            Cross-Origin-Opener-Policy: same-origin\r\n\
901                                            Content-encoding: none\r\n\
902                                            Cache-Control: max-age:0\r\n\
903                                            Content-Length: {}\r\n\
904                                            Connection: close\r\n\r\n",
905                                        mime_type,
906                                        body.len()
907                                    );
908                                    let _ =
909                                        response_sender.send(HttpServerResponse { header, body });
910                                }
911                            }
912                        }
913                    }
914                    HttpServerRequest::Post { .. } => { //headers, body, response}=>{
915                    }
916                }
917            }
918        });
919    }
920
921    pub fn discover_external_ip(&mut self, _cx: &mut Cx) {
922        // figure out some kind of unique id. bad but whatever.
923        let studio_uid = LiveId::from_str(&format!(
924            "{:?}{:?}",
925            Instant::now(),
926            std::time::SystemTime::now()
927        ));
928        let http_port = self.http_port as u16;
929        let write_discovery = UdpSocket::bind(SocketAddr::new(
930            "0.0.0.0".parse().unwrap(),
931            http_port * 2 as u16 + 1,
932        ));
933        if write_discovery.is_err() {
934            return;
935        }
936        let write_discovery = write_discovery.unwrap();
937        write_discovery
938            .set_read_timeout(Some(Duration::new(0, 1)))
939            .unwrap();
940        write_discovery.set_broadcast(true).unwrap();
941        // start a broadcast
942        std::thread::spawn(move || {
943            let dummy = studio_uid.0.to_be_bytes();
944            loop {
945                let _ = write_discovery.send_to(
946                    &dummy,
947                    SocketAddr::new("0.0.0.0".parse().unwrap(), http_port * 2 as u16),
948                );
949                thread::sleep(time::Duration::from_millis(100));
950            }
951        });
952        // listen for bounced back udp packets to get our external ip
953        let ip_sender = self.recv_external_ip.sender();
954        std::thread::spawn(move || {
955            let discovery = UdpSocket::bind(SocketAddr::new(
956                "0.0.0.0".parse().unwrap(),
957                http_port * 2 as u16,
958            ))
959            .unwrap();
960            discovery
961                .set_read_timeout(Some(Duration::new(0, 1)))
962                .unwrap();
963            discovery.set_broadcast(true).unwrap();
964
965            let mut other_uid = [0u8; 8];
966            'outer: loop {
967                while let Ok((_, addr)) = discovery.recv_from(&mut other_uid) {
968                    let recv_uid = u64::from_be_bytes(other_uid);
969                    if studio_uid.0 == recv_uid {
970                        let _ = ip_sender.send(addr);
971                        break 'outer;
972                    }
973                }
974                std::thread::sleep(Duration::from_millis(50));
975            }
976        });
977    }
978    
979            
980    pub fn binary_name_to_id(&self, name:&str)->Option<usize>{
981        self.binaries.iter().position(|v| v.name == name)
982    }
983        
984    pub fn run_app(&mut self, cx:&mut Cx, binary_name:&str){
985        let binary_id = self.binary_name_to_id(binary_name).unwrap();
986        self.start_active_build(cx, binary_id, BuildTarget::Release);
987    }
988            
989    pub fn start_active_build(&mut self, _cx:&mut Cx, binary_id:usize, target: BuildTarget) {
990        let binary = &self. binaries[binary_id];
991        let process = BuildProcess {
992            root: binary.root.clone(),
993            binary: binary.name.clone(),
994            target
995        };
996        let item_id = process.as_id();
997        self.clients[0].send_cmd_with_id(item_id, BuildCmd::Run(process.clone(),self.studio_http.clone()));
998        //let run_view_id = LiveId::unique();
999        if self.active.builds.get(&item_id).is_none() {
1000            let index = self.active.builds.len();
1001            self.active.builds.insert(item_id, ActiveBuild {
1002                root: binary.root.clone(),
1003                log_index: format!("[{}]", index),
1004                process: process.clone(),
1005                app_area: Default::default(),
1006                swapchain: Default::default(),
1007                last_swapchain_with_completed_draws: Default::default(),
1008                aux_chan_host_endpoint: None,
1009            });
1010        }
1011        //if process.target.runs_in_studio(){
1012            // create the runview tab
1013        //    cx.action(AppA::Create(item_id, process.binary.clone()))
1014        //}
1015    }
1016    
1017    pub fn stop_all_active_builds(&mut self, cx:&mut Cx){
1018        while self.active.builds.len()>0{
1019            let build = &self.active.builds.values().next().unwrap();
1020            let binary_id = self.binary_name_to_id(&build.process.binary).unwrap();
1021            let target = build.process.target;
1022            self.stop_active_build(cx, binary_id, target);
1023        }
1024    }
1025        
1026    pub fn stop_active_build(&mut self, cx:&mut Cx, binary_id: usize, target: BuildTarget) {
1027        let binary = &self. binaries[binary_id];
1028                
1029        let process = BuildProcess {
1030            root: binary.root.clone(),
1031            binary: binary.name.clone(),
1032            target
1033        };
1034        let build_id = process.as_id().into();
1035        if let Some(_) = self.active.builds.remove(&build_id) {
1036            self.clients[0].send_cmd_with_id(build_id, BuildCmd::Stop);
1037            if process.target.runs_in_studio(){
1038                cx.action(AppAction::DestroyRunViews{run_view_id:build_id})
1039            }
1040        }
1041    }
1042    
1043}