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 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 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
221fn get_local_ip() -> String {
226 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://{}:{}/$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 }
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 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 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 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 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 }
360
361 pub fn live_reload_needed(&mut self, live_file_change: LiveFileChange) {
362 if let Ok(d) = self.active_build_websockets.lock() {
376 for socket in d.borrow_mut().sockets.iter_mut() {
381 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 if root == build.root{
391 file.to_string()
392 }
393 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 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 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 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 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 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 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 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 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 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.push((wrap.cmd_id, LogItem::Bare(bare)));
685 cx.action(AppAction::RedrawLog)
686 }
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 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 }
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 start_http_server(HttpServer {
733 listen_address: addr,
734 post_max_size: 1024 * 1024,
735 request: tx_request,
736 });
737 let studio_sender = self.recv_studio_msg.sender();
752 let active_build_websockets = self.active_build_websockets.clone();
753 std::thread::spawn(move || {
754 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 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 }
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 { .. } => { }
916 }
917 }
918 });
919 }
920
921 pub fn discover_external_ip(&mut self, _cx: &mut Cx) {
922 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 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 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 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 }
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}