From b7f9fefd786d7ee9bb34f78d03836c86f3f8a426 Mon Sep 17 00:00:00 2001 From: portersky <24420859+portersky@users.noreply.github.com> Date: Sat, 5 Sep 2026 05:09:50 +0200 Subject: [PATCH] feat: add loading animation example Add a busy-wait sleep on a monotonic clock and re-export String, format!, and Duration from the library. The example animates a progress bar with a spinner and logs each step in a Vec. Co-Authored-By: qwen (lmstudio/qwen3.8-27b@q3_k_xl): wrote example --- AGENTS.md | 18 ++++++++++------- README.md | 8 +++++--- runtime/main.rs | 50 +++++++++++++++++++++++++++++++++++++++++++++--- src/lib.rs | 3 +++ src/runtime.rs | 51 +++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 117 insertions(+), 13 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index ad090ba..a512ba0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -6,16 +6,17 @@ This project is a small Rust common library and runtime built without Cargo. CMake invokes `rustc` directly. The library provides a low-footprint subset of standard-library-like functionality: -- `Result` and formatting from `core` -- `Vec` and `vec![]` from `alloc` +- `Result`, `Duration`, and formatting from `core` +- `Vec`, `String`, `vec![]`, and `format!` from `alloc` - project-local `print!` and `println!` macros -- direct platform stdout and heap calls +- direct platform stdout, heap calls, and busy-wait sleep The project contains: - `src/lib.rs`: the `#![no_std]` common library API - `src/io.rs`: the `core::fmt::Write` stdout backend -- `src/runtime.rs`: allocator, async polling, and platform entry helpers +- `src/runtime.rs`: allocator, async polling, monotonic sleep, and platform + entry helpers - `macros/src/lib.rs`: host-side `#[rslibc_macros::main]` attribute macro - `runtime/main.rs`: minimal example entry point - `CMakeLists.txt`: direct `rustc` library, macro, and executable commands @@ -36,8 +37,8 @@ The project contains: - Keep OS integration in small FFI modules. Do not pull in a general-purpose runtime or C I/O layer for basic output. - `rslibc_macros::main` can wrap `async fn main()` with the small `block_on` - executor. It is only a polling loop and does not provide Tokio-style I/O, - timers, or a reactor. + executor. It is only a polling loop and does not provide Tokio-style I/O + or a reactor; delays use the busy-wait `rslibc::runtime::sleep`. - Keep unsafe code inside small safe wrappers. Application code should be able to use calls such as: @@ -52,6 +53,8 @@ The project contains: - Windows stdout calls `GetStdHandle` and `WriteFile` through `kernel32`. - Windows allocation calls `HeapAlloc` and `HeapFree` through the system heap. +- Sleep uses a monotonic clock: `clock_gettime` on Unix and + `QueryPerformanceCounter` on Windows. - The Windows runtime dynamically links only the platform support needed by `alloc` and the compiler runtime. Do not statically link the full C runtime. @@ -116,7 +119,8 @@ cmake --build build ``` On Windows, use `Remove-Item -Recurse -Force build` and run -`build\\rslibc_example.exe` instead. Confirm that stdout contains `Hello`. +`build\\rslibc_example.exe` instead. Confirm that stdout contains +`100% done`. When size matters, inspect the Windows executable after a release build. Avoid adding formatting, allocation, or OS dependencies unless they are diff --git a/README.md b/README.md index d85900d..c48204b 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,8 @@ library rlib, and a minimal example executable. - `Result` and formatting from `core` - `Vec` and `vec![]` from `alloc` -- project-local `print!` / `println!` macros +- project-local `print!` / `println!` macros, plus `String` and `format!` +- busy-wait `sleep` on a monotonic clock - direct platform stdout with no C I/O layer - OS heap allocator (`malloc`/`free`, `HeapAlloc`/`HeapFree`) - `panic=abort` with a minimal panic handler @@ -41,8 +42,9 @@ Run the example: .\build\rslibc_example.exe ``` -The release example prints `Hello, World!`. The Windows executable is -about 4 KiB and depends only on `KERNEL32.dll` and `VCRUNTIME140.dll`. +The release example runs an animated loading loop (progress bar plus +spinner) and then prints a short log. The Windows executable is about +27 KiB and depends only on `KERNEL32.dll` and `VCRUNTIME140.dll`. Individual targets: diff --git a/runtime/main.rs b/runtime/main.rs index d979fe5..b6388bd 100644 --- a/runtime/main.rs +++ b/runtime/main.rs @@ -1,11 +1,55 @@ #![no_std] #![no_main] -fn print(message: &str) { - let _ = rslibc::io::write_str(message); +use core::time::Duration; + +use rslibc::{format, print, println, vec, String, Vec}; + +const FRAMES: [&str; 4] = ["|", "/", "-", "\\"]; +const WIDTH: usize = 20; +const STEPS: usize = 10; + +fn bar(done: usize) -> String { + let mut out = String::new(); + for i in 0..WIDTH { + out.push(if i < done { '#' } else { '-' }); + } + out +} + +fn load(step: usize) -> rslibc::Result { + // Pretend to do work and return a checksum-like value. + let mut total = 0u32; + for value in vec![1, 2, 3, 4, 5] { + total = total.wrapping_add(value * step as u32); + } + Ok(total) } #[rslibc_macros::main] async fn main() { - print("Hello\n"); + println!("rslibc loading example"); + let mut log: Vec = Vec::new(); + + for step in 0..STEPS { + match load(step) { + Ok(value) => { + let frame = FRAMES[step % FRAMES.len()]; + let percent = 100 * (step + 1) / STEPS; + print!( + "\r [{}] {:>3}% {}", + bar((step + 1) * WIDTH / STEPS), + percent, + frame + ); + log.push(format!("step {step} -> {value}")); + } + Err(message) => println!("error: {message}"), + } + + rslibc::runtime::sleep(Duration::from_millis(60)); + } + + println!("\r [{}] 100% done", bar(WIDTH)); + println!("log = {log:?}"); } diff --git a/src/lib.rs b/src/lib.rs index 5dd20ac..4570b9d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,9 +5,12 @@ extern crate alloc; pub mod io; pub mod runtime; +pub use alloc::format; +pub use alloc::string::String; pub use alloc::vec; pub use alloc::vec::Vec; pub use core::result::Result; +pub use core::time::Duration; #[macro_export] macro_rules! print { diff --git a/src/runtime.rs b/src/runtime.rs index 7c2a54c..8f53187 100644 --- a/src/runtime.rs +++ b/src/runtime.rs @@ -2,11 +2,26 @@ use core::alloc::{GlobalAlloc, Layout}; use core::future::Future; use core::pin::Pin; use core::task::{Context, Poll, RawWaker, RawWakerVTable, Waker}; +use core::time::Duration; + +#[cfg(unix)] +#[repr(C)] +struct Timespec { + tv_sec: i64, + tv_nsec: i64, +} + +// CLOCK_MONOTONIC for the supported Unix targets. +#[cfg(all(unix, target_os = "macos"))] +const CLOCK_MONOTONIC: i32 = 4; +#[cfg(all(unix, not(target_os = "macos")))] +const CLOCK_MONOTONIC: i32 = 1; #[cfg(unix)] unsafe extern "C" { fn malloc(size: usize) -> *mut u8; fn free(pointer: *mut u8); + fn clock_gettime(clock_id: i32, ts: *mut Timespec) -> i32; } #[cfg(windows)] @@ -15,6 +30,8 @@ unsafe extern "system" { fn HeapAlloc(heap: *mut u8, flags: u32, bytes: usize) -> *mut u8; fn HeapFree(heap: *mut u8, flags: u32, pointer: *mut u8) -> i32; fn ExitProcess(status: u32) -> !; + fn QueryPerformanceCounter(counter: *mut i64) -> i32; + fn QueryPerformanceFrequency(frequency: *mut i64) -> i32; } pub struct SystemAllocator; @@ -71,6 +88,40 @@ pub fn exit(status: u32) -> ! { unsafe { ExitProcess(status) } } +fn now() -> Duration { + #[cfg(unix)] + { + let mut ts = Timespec { tv_sec: 0, tv_nsec: 0 }; + if unsafe { clock_gettime(CLOCK_MONOTONIC, &mut ts) } == 0 && ts.tv_sec >= 0 { + return Duration::new(ts.tv_sec as u64, ts.tv_nsec as u32); + } + Duration::ZERO + } + + #[cfg(windows)] + { + let mut counter: i64 = 0; + let mut frequency: i64 = 0; + if unsafe { QueryPerformanceCounter(&mut counter) } != 0 + && unsafe { QueryPerformanceFrequency(&mut frequency) } != 0 + && frequency > 0 + { + let nanos = (counter as u128 * 1_000_000_000u128) / frequency as u128; + return Duration::from_nanos(nanos as u64); + } + Duration::ZERO + } +} + +// Busy-wait sleep on a monotonic clock. The executor has no reactor yet, +// so the platform cannot block us; spinning is the only correct option. +pub fn sleep(duration: Duration) { + let deadline = now() + duration; + while now() < deadline { + core::hint::spin_loop(); + } +} + fn clone_waker(_: *const ()) -> RawWaker { RawWaker::new(core::ptr::null(), &WAKER_VTABLE) }