1#![allow(clippy::mutex_atomic)]
53
54use async_std::task;
55use dashmap::DashMap;
56use horrorshow::{html, owned_html, Raw, Render};
57use serde::{de::DeserializeOwned, Deserialize};
58use std::{cmp::Ordering, fmt::Display, string::ToString, sync::Mutex, thread};
59use tide::{Request, Response};
60
61pub use const_tweaker_attribute::tweak;
62#[doc(hidden)]
63pub use ctor::ctor;
64
65#[doc(hidden)]
67#[derive(Debug)]
68pub enum Field {
69 F32 {
70 value: f32,
71 min: f32,
73 max: f32,
75 step: f32,
77
78 module: String,
80 file: String,
82 line: u32,
84 },
85 F64 {
86 value: f64,
87 min: f64,
89 max: f64,
91 step: f64,
93
94 module: String,
96 file: String,
98 line: u32,
100 },
101 I8 {
102 value: i8,
103 min: i8,
105 max: i8,
107 step: i8,
109
110 module: String,
112 file: String,
114 line: u32,
116 },
117 U8 {
118 value: u8,
119 min: u8,
121 max: u8,
123 step: u8,
125
126 module: String,
128 file: String,
130 line: u32,
132 },
133 I16 {
134 value: i16,
135 min: i16,
137 max: i16,
139 step: i16,
141
142 module: String,
144 file: String,
146 line: u32,
148 },
149 U16 {
150 value: u16,
151 min: u16,
153 max: u16,
155 step: u16,
157
158 module: String,
160 file: String,
162 line: u32,
164 },
165 I32 {
166 value: i32,
167 min: i32,
169 max: i32,
171 step: i32,
173
174 module: String,
176 file: String,
178 line: u32,
180 },
181 U32 {
182 value: u32,
183 min: u32,
185 max: u32,
187 step: u32,
189
190 module: String,
192 file: String,
194 line: u32,
196 },
197 I64 {
198 value: i64,
199 min: i64,
201 max: i64,
203 step: i64,
205
206 module: String,
208 file: String,
210 line: u32,
212 },
213 U64 {
214 value: u64,
215 min: u64,
217 max: u64,
219 step: u64,
221
222 module: String,
224 file: String,
226 line: u32,
228 },
229 Usize {
230 value: usize,
231 min: usize,
233 max: usize,
235 step: usize,
237
238 module: String,
240 file: String,
242 line: u32,
244 },
245 Bool {
246 value: bool,
247
248 module: String,
250 file: String,
252 line: u32,
254 },
255 String {
256 value: String,
257
258 module: String,
260 file: String,
262 line: u32,
264 },
265}
266
267impl Field {
268 pub fn module_path(&self) -> &str {
270 match self {
271 Field::F32 { module, .. }
272 | Field::F64 { module, .. }
273 | Field::I8 { module, .. }
274 | Field::U8 { module, .. }
275 | Field::I16 { module, .. }
276 | Field::U16 { module, .. }
277 | Field::I32 { module, .. }
278 | Field::U32 { module, .. }
279 | Field::I64 { module, .. }
280 | Field::U64 { module, .. }
281 | Field::Usize { module, .. }
282 | Field::Bool { module, .. }
283 | Field::String { module, .. } => &*module,
284 }
285 }
286
287 pub fn file(&self) -> String {
289 match self {
290 Field::F32 { file, line, .. }
291 | Field::F64 { file, line, .. }
292 | Field::I8 { file, line, .. }
293 | Field::U8 { file, line, .. }
294 | Field::I16 { file, line, .. }
295 | Field::U16 { file, line, .. }
296 | Field::I32 { file, line, .. }
297 | Field::U32 { file, line, .. }
298 | Field::I64 { file, line, .. }
299 | Field::U64 { file, line, .. }
300 | Field::Usize { file, line, .. }
301 | Field::Bool { file, line, .. }
302 | Field::String { file, line, .. } => format!("{}:{}", file, line),
303 }
304 }
305
306 pub fn line_number(&self) -> u32 {
308 match self {
309 Field::F32 { line, .. }
310 | Field::F64 { line, .. }
311 | Field::I8 { line, .. }
312 | Field::U8 { line, .. }
313 | Field::I16 { line, .. }
314 | Field::U16 { line, .. }
315 | Field::I32 { line, .. }
316 | Field::U32 { line, .. }
317 | Field::I64 { line, .. }
318 | Field::U64 { line, .. }
319 | Field::Usize { line, .. }
320 | Field::Bool { line, .. }
321 | Field::String { line, .. } => *line,
322 }
323 }
324
325 pub fn to_html_widget(&self, key: &str) -> String {
327 match self {
328 Field::F32 {
329 value,
330 min,
331 max,
332 step,
333 ..
334 } => Field::render_slider(key, *value, *min, *max, *step, "f32").to_string(),
335 Field::F64 {
336 value,
337 min,
338 max,
339 step,
340 ..
341 } => Field::render_slider(key, *value, *min, *max, *step, "f64").to_string(),
342 Field::I8 {
343 value,
344 min,
345 max,
346 step,
347 ..
348 } => Field::render_slider(key, *value, *min, *max, *step, "i8").to_string(),
349 Field::U8 {
350 value,
351 min,
352 max,
353 step,
354 ..
355 } => Field::render_slider(key, *value, *min, *max, *step, "u8").to_string(),
356 Field::I16 {
357 value,
358 min,
359 max,
360 step,
361 ..
362 } => Field::render_slider(key, *value, *min, *max, *step, "i16").to_string(),
363 Field::U16 {
364 value,
365 min,
366 max,
367 step,
368 ..
369 } => Field::render_slider(key, *value, *min, *max, *step, "u16").to_string(),
370 Field::I32 {
371 value,
372 min,
373 max,
374 step,
375 ..
376 } => Field::render_slider(key, *value, *min, *max, *step, "i32").to_string(),
377 Field::U32 {
378 value,
379 min,
380 max,
381 step,
382 ..
383 } => Field::render_slider(key, *value, *min, *max, *step, "u32").to_string(),
384 Field::I64 {
385 value,
386 min,
387 max,
388 step,
389 ..
390 } => Field::render_slider(key, *value, *min, *max, *step, "i64").to_string(),
391 Field::U64 {
392 value,
393 min,
394 max,
395 step,
396 ..
397 } => Field::render_slider(key, *value, *min, *max, *step, "u64").to_string(),
398 Field::Usize {
399 value,
400 min,
401 max,
402 step,
403 ..
404 } => Field::render_slider(key, *value, *min, *max, *step, "usize").to_string(),
405 Field::Bool { value, .. } => Field::render_bool(key, *value).to_string(),
406 Field::String { value, .. } => Field::render_string(key, value).to_string(),
407 }
408 }
409
410 fn render_slider<'a, T>(
412 key: &'a str,
413 value: T,
414 min: T,
415 max: T,
416 step: T,
417 http_path: &'a str,
418 ) -> impl Render + ToString + 'a
419 where
420 T: Display + 'a,
421 {
422 owned_html! {
423 div (class="column") {
424 input (type="range",
425 id=key.to_string(),
426 min=min.to_string(),
427 max=max.to_string(),
428 step=step.to_string(),
429 defaultValue=value.to_string(),
430 style="width: 100%",
431 oninput=send(key, "Number(this.value)", http_path))
434 { }
435 }
436 div (class="column is-narrow") {
437 span (id=format!("{}_label", key), class="is-small")
438 { : value.to_string() }
439 }
440 }
441 }
442
443 fn render_bool<'a>(key: &'a str, value: bool) -> impl Render + ToString + 'a {
445 owned_html! {
446 div (class="column") {
447 input (type="checkbox",
448 id=key,
449 value=value.to_string(),
450 onclick=send(key, "this.checked", "bool"))
451 { }
452 }
453 div (class="column is-narrow") {
454 span (id=format!("{}_label", key))
455 { : value.to_string() }
456 }
457 }
458 }
459
460 fn render_string<'a>(key: &'a str, value: &'a str) -> impl Render + ToString + 'a {
462 owned_html! {
463 div (class="column") {
464 input (type="text",
465 id=key,
466 value=value,
467 style="width: 100%",
468 onchange=send(key, "this.value", "string"))
469 { }
470 }
471 div (class="column is-narrow") {
472 span (id=format!("{}_label", key))
473 { : value.to_string() }
474 }
475 }
476 }
477}
478
479#[derive(Debug, Deserialize)]
481struct PostData<T> {
482 key: String,
483 value: T,
484}
485
486lazy_static::lazy_static! {
487 #[doc(hidden)]
489 pub static ref DATA: DashMap<&'static str, Field> = DashMap::new();
490 static ref LAST_MAP_SIZE: Mutex<usize> = Mutex::new(0);
492}
493
494#[ctor::ctor]
498fn run() {
499 thread::spawn(|| {
501 task::block_on(async {
502 let mut app = tide::new();
503 app.at("/").get(main_site);
505 app.at("/should_refresh").get(should_refresh);
507
508 app.at("/set/f32").post(|r| handle_set_value(r, set_f32));
510 app.at("/set/f64").post(|r| handle_set_value(r, set_f64));
511 app.at("/set/bool").post(|r| handle_set_value(r, set_bool));
512 app.at("/set/string")
513 .post(|r| handle_set_value(r, set_string));
514 app.listen("127.0.0.1:9938").await
515 })
516 .expect("Running web server failed");
517 });
518}
519
520async fn main_site(_: Request<()>) -> Response {
522 let mut last_map_size = LAST_MAP_SIZE.lock().unwrap();
524 *last_map_size = DATA.len();
525
526 let body = html! {
527 style { : include_str!("bulma.css") }
528 style { : "* { font-family: sans-serif}" }
529 body {
530 section (class="hero is-primary") {
532 div (class="hero-body") {
533 div (class="container") {
534 h1 (class="title is-1") { : "Const Tweaker Web Interface" }
535 }
536 }
537 }
538 : render_widgets();
540 div (class="container") {
542 div (class="notification is-danger") {
543 span(id="status") { }
544 }
545 }
546 }
547 script { : Raw(include_str!("send.js")) }
548 };
549
550 Response::new(200)
551 .body_string(format!("{}", body))
552 .set_header("content-type", "text/html;charset=utf-8")
553}
554
555fn render_widgets() -> impl Render {
557 owned_html! {
558 @for module in modules().into_iter() {
560 section (class="section") {
561 div (class="container box") {
562 h3 (class="title is-3") { : format!("Module: \"{}\"", module) }
563 : render_module(&module)
564 }
565
566 div (class="container box") {
568 h4 (class="title is-4") { : "Changes" }
569 div (class="columns") {
570 div (class="column") {
571 textarea (
572 class="textarea",
573 style="font-family: monospace",
574 id=format!("{}_output", module.replace("::", "_")),
575 readonly,
576 placeholder="No changes")
577 }
578 div (class="column is-narrow control") {
579 button (class="button is-link", onclick=format!("copy_text(\"{}\")", module)) {
580 : "Copy"
581 }
582 }
583 }
584 }
585 }
586 }
587 }
588}
589
590fn render_module<'a>(module: &'a str) -> impl Render + 'a {
592 let mut data = DATA
593 .iter()
594 .filter(|kv| kv.value().module_path() == module)
595 .collect::<Vec<_>>();
596
597 data.sort_by(|a, b| {
598 a.value()
599 .line_number()
600 .partial_cmp(&b.value().line_number())
601 .unwrap_or(Ordering::Equal)
602 });
603
604 owned_html! {
605 @for ref_multi in data.iter() {
607 : render_widget(ref_multi.key(), ref_multi.value())
608 }
609 }
610}
611
612fn render_widget<'a>(key: &'a str, field: &'a Field) -> impl Render + 'a {
614 owned_html! {
615 div (class="columns") {
616 div (class="column is-narrow") {
617 span (class="is-small") { : key }
619
620 br {}
621 span (class="tag") { : field.file() }
623 }
624 : Raw(field.to_html_widget(key))
625 }
626 }
627}
628
629fn send(key: &str, look_for: &str, data_type: &str) -> String {
631 format!(
632 "send('{}', {}, '{}')",
633 key.replace("\\", "\\\\"),
634 look_for,
635 data_type
636 )
637}
638
639async fn should_refresh(_request: Request<()>) -> Response {
641 let mut last_map_size = LAST_MAP_SIZE.lock().unwrap();
642
643 if *last_map_size == DATA.len() {
644 Response::new(200)
646 } else {
647 *last_map_size = DATA.len();
649
650 Response::new(200).body_string("refresh".to_string())
651 }
652}
653
654async fn handle_set_value<T, F>(mut request: Request<()>, set_value: F) -> Response
656where
657 T: DeserializeOwned,
658 F: Fn(&mut Field, T),
659{
660 let post_data: PostData<T> = request.body_json().await.expect("Could not decode JSON");
661 set_value(
662 &mut DATA
663 .get_mut(&*post_data.key)
664 .expect("Could not get item from map"),
665 post_data.value,
666 );
667
668 Response::new(200)
669}
670
671fn set_f32(field: &mut Field, new_value: f32) {
673 match field {
674 Field::F32 { ref mut value, .. } => {
675 *value = new_value;
676 }
677 _ => panic!("Unexpected type, please report an issue"),
678 }
679}
680
681fn set_f64(field: &mut Field, new_value: f64) {
683 match field {
684 Field::F64 { ref mut value, .. } => {
685 *value = new_value;
686 }
687 _ => panic!("Unexpected type, please report an issue"),
688 }
689}
690
691fn set_bool(field: &mut Field, new_value: bool) {
693 match field {
694 Field::Bool { ref mut value, .. } => {
695 *value = new_value;
696 }
697 _ => panic!("Unexpected type, please report an issue"),
698 }
699}
700
701fn set_string(field: &mut Field, new_value: String) {
703 match field {
704 Field::String { ref mut value, .. } => {
705 *value = new_value;
706 }
707 _ => panic!("Unexpected type, please report an issue"),
708 }
709}
710
711fn modules() -> Vec<String> {
713 let mut modules: Vec<_> = DATA
714 .iter()
715 .map(|kv| kv.value().module_path().to_string())
716 .collect::<_>();
717
718 modules.sort();
720 modules.dedup();
721
722 modules
723}