422 lines · 12.9 KB · Rust
RawDownload
use gpui::prelude::*;use gpui::{    App, Bounds, Context, ElementId, SharedString, Task, Window, WindowBounds, WindowOptions, div,    px, rgb, size,}; // ---------------------------------------------------------------------------// Prime counting (intentionally brute-force so it hammers the CPU)// --------------------------------------------------------------------------- fn is_prime(n: u64) -> bool {    if n < 2 {        return false;    }    if n < 4 {        return true;    }    if n % 2 == 0 || n % 3 == 0 {        return false;    }    let mut i = 5;    while i * i <= n {        if n % i == 0 || n % (i + 2) == 0 {            return false;        }        i += 6;    }    true} fn count_primes_in_range(start: u64, end: u64) -> u64 {    let mut count = 0;    for n in start..end {        if is_prime(n) {            count += 1;        }    }    count} // ---------------------------------------------------------------------------// App state// --------------------------------------------------------------------------- const NUM_CHUNKS: u64 = 12; #[derive(Clone, Copy, PartialEq, Eq)]enum Preset {    TenMillion,    FiftyMillion,    HundredMillion,} impl Preset {    fn label(self) -> &'static str {        match self {            Preset::TenMillion => "10 M",            Preset::FiftyMillion => "50 M",            Preset::HundredMillion => "100 M",        }    }     fn value(self) -> u64 {        match self {            Preset::TenMillion => 10_000_000,            Preset::FiftyMillion => 50_000_000,            Preset::HundredMillion => 100_000_000,        }    }     const ALL: [Preset; 3] = [        Preset::TenMillion,        Preset::FiftyMillion,        Preset::HundredMillion,    ];} struct ChunkResult {    count: u64,} struct Run {    limit: u64,    chunks_done: u64,    chunk_results: Vec<ChunkResult>,    total: Option<u64>,    elapsed: Option<f64>,} struct HelloWeb {    selected_preset: Preset,    current_run: Option<Run>,    history: Vec<SharedString>,    _tasks: Vec<Task<()>>,} impl HelloWeb {    fn new(_cx: &mut Context<Self>) -> Self {        Self {            selected_preset: Preset::TenMillion,            current_run: None,            history: Vec::new(),            _tasks: Vec::new(),        }    }     fn start_search(&mut self, cx: &mut Context<Self>) {        let limit = self.selected_preset.value();        let chunk_size = limit / NUM_CHUNKS;         self.current_run = Some(Run {            limit,            chunks_done: 0,            chunk_results: Vec::new(),            total: None,            elapsed: None,        });        self._tasks.clear();        cx.notify();         let start_time = web_time::Instant::now();         for i in 0..NUM_CHUNKS {            let range_start = i * chunk_size;            let range_end = if i == NUM_CHUNKS - 1 {                limit            } else {                range_start + chunk_size            };             let task = cx.spawn(async move |this, cx| {                let count = cx                    .background_spawn(async move { count_primes_in_range(range_start, range_end) })                    .await;                 this.update(cx, |this, cx| {                    if let Some(run) = &mut this.current_run {                        run.chunk_results.push(ChunkResult { count });                        run.chunks_done += 1;                         if run.chunks_done == NUM_CHUNKS {                            let total: u64 = run.chunk_results.iter().map(|r| r.count).sum();                            let elapsed_ms = start_time.elapsed().as_secs_f64() * 1000.0;                            run.total = Some(total);                            run.elapsed = Some(elapsed_ms);                            this.history.push(                                format!(                                    "π({}) = {} ({:.0} ms, {} chunks)",                                    format_number(run.limit),                                    format_number(total),                                    elapsed_ms,                                    NUM_CHUNKS,                                )                                .into(),                            );                        }                        cx.notify();                    }                })                .ok();            });             self._tasks.push(task);        }    }} fn format_number(n: u64) -> String {    let s = n.to_string();    let mut result = String::new();    for (i, ch) in s.chars().rev().enumerate() {        if i > 0 && i % 3 == 0 {            result.push(',');        }        result.push(ch);    }    result.chars().rev().collect()} // ---------------------------------------------------------------------------// Render// --------------------------------------------------------------------------- const BG_BASE: u32 = 0x1e1e2e;const BG_SURFACE: u32 = 0x313244;const BG_OVERLAY: u32 = 0x45475a;const TEXT_PRIMARY: u32 = 0xcdd6f4;const TEXT_SECONDARY: u32 = 0xa6adc8;const TEXT_DIM: u32 = 0x6c7086;const ACCENT_YELLOW: u32 = 0xf9e2af;const ACCENT_GREEN: u32 = 0xa6e3a1;const ACCENT_BLUE: u32 = 0x89b4fa;const ACCENT_MAUVE: u32 = 0xcba6f7; impl Render for HelloWeb {    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {        let is_running = self.current_run.as_ref().is_some_and(|r| r.total.is_none());         // -- Preset buttons --        let preset_row = Preset::ALL.iter().enumerate().fold(            div().flex().flex_row().gap_2(),            |row, (index, &preset)| {                let is_selected = preset == self.selected_preset;                let (bg, text_color) = if is_selected {                    (ACCENT_BLUE, BG_BASE)                } else {                    (BG_OVERLAY, TEXT_SECONDARY)                };                row.child(                    div()                        .id(ElementId::NamedInteger("preset".into(), index as u64))                        .px_3()                        .py_1()                        .rounded_md()                        .bg(rgb(bg))                        .text_color(rgb(text_color))                        .text_sm()                        .cursor_pointer()                        .when(!is_running, |this| {                            this.on_click(cx.listener(move |this, _event, _window, _cx| {                                this.selected_preset = preset;                            }))                        })                        .child(preset.label()),                )            },        );         // -- Go button --        let (go_bg, go_text, go_label) = if is_running {            (BG_OVERLAY, TEXT_DIM, "Running…")        } else {            (ACCENT_GREEN, BG_BASE, "Count Primes")        };        let go_button = div()            .id("go")            .px_4()            .py(px(6.))            .rounded_md()            .bg(rgb(go_bg))            .text_color(rgb(go_text))            .cursor_pointer()            .when(!is_running, |this| {                this.on_click(cx.listener(|this, _event, _window, cx| {                    this.start_search(cx);                }))            })            .child(go_label);         // -- Progress / result area --        let status_area = if let Some(run) = &self.current_run {            let progress_fraction = run.chunks_done as f32 / NUM_CHUNKS as f32;            let progress_pct = (progress_fraction * 100.0) as u32;             let status_text: SharedString = if let Some(total) = run.total {                format!(                    "Found {} primes below {} in {:.0} ms",                    format_number(total),                    format_number(run.limit),                    run.elapsed.unwrap_or(0.0),                )                .into()            } else {                format!(                    "Searching up to {} … {}/{} chunks  ({}%)",                    format_number(run.limit),                    run.chunks_done,                    NUM_CHUNKS,                    progress_pct,                )                .into()            };             let bar_color = if run.total.is_some() {                ACCENT_GREEN            } else {                ACCENT_BLUE            };             let chunk_dots =                (0..NUM_CHUNKS as usize).fold(div().flex().flex_row().gap_1().mt_2(), |row, i| {                    let done = i < run.chunks_done as usize;                    let color = if done { ACCENT_MAUVE } else { BG_OVERLAY };                    row.child(div().size(px(10.)).rounded_sm().bg(rgb(color)))                });             div()                .flex()                .flex_col()                .w_full()                .gap_2()                .child(div().text_color(rgb(TEXT_PRIMARY)).child(status_text))                .child(                    div()                        .w_full()                        .h(px(8.))                        .rounded_sm()                        .bg(rgb(BG_OVERLAY))                        .child(                            div()                                .h_full()                                .rounded_sm()                                .bg(rgb(bar_color))                                .w(gpui::relative(progress_fraction)),                        ),                )                .child(chunk_dots)        } else {            div().flex().flex_col().w_full().child(                div()                    .text_color(rgb(TEXT_DIM))                    .child("Select a range and press Count Primes to begin."),            )        };         // -- History log --        let history_section = if self.history.is_empty() {            div()        } else {            self.history                .iter()                .rev()                .fold(div().flex().flex_col().gap_1(), |col, entry| {                    col.child(                        div()                            .text_sm()                            .text_color(rgb(TEXT_SECONDARY))                            .child(entry.clone()),                    )                })        };         // -- Layout --        div()            .flex()            .flex_col()            .size_full()            .bg(rgb(BG_BASE))            .justify_center()            .items_center()            .gap_4()            .p_4()            // Title            .child(                div()                    .text_xl()                    .text_color(rgb(TEXT_PRIMARY))                    .child("Prime Sieve — GPUI Web"),            )            .child(div().text_sm().text_color(rgb(TEXT_DIM)).child(format!(                "Background threads: {} · Chunks per run: {}",                std::thread::available_parallelism().map_or(2, |n| n.get().max(2)),                NUM_CHUNKS,            )))            // Controls            .child(                div()                    .flex()                    .flex_col()                    .items_center()                    .gap_3()                    .p_4()                    .w(px(500.))                    .rounded_lg()                    .bg(rgb(BG_SURFACE))                    .child(                        div()                            .text_sm()                            .text_color(rgb(ACCENT_YELLOW))                            .child("Count primes below:"),                    )                    .child(preset_row)                    .child(go_button),            )            // Status            .child(                div()                    .flex()                    .flex_col()                    .w(px(500.))                    .p_4()                    .rounded_lg()                    .bg(rgb(BG_SURFACE))                    .child(status_area),            )            // History            .when(!self.history.is_empty(), |this| {                this.child(                    div()                        .flex()                        .flex_col()                        .w(px(500.))                        .p_4()                        .rounded_lg()                        .bg(rgb(BG_SURFACE))                        .gap_2()                        .child(div().text_sm().text_color(rgb(TEXT_DIM)).child("History"))                        .child(history_section),                )            })    }} // ---------------------------------------------------------------------------// Entry point// --------------------------------------------------------------------------- fn main() {    gpui_platform::web_init();    gpui_platform::application().run(|cx: &mut App| {        let bounds = Bounds::centered(None, size(px(640.), px(560.)), cx);        cx.open_window(            WindowOptions {                window_bounds: Some(WindowBounds::Windowed(bounds)),                ..Default::default()            },            |_, cx| cx.new(HelloWeb::new),        )        .expect("failed to open window");        cx.activate(true);    });}