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<String>. Co-Authored-By: qwen (lmstudio/qwen3.8-27b@q3_k_xl): wrote example
This commit is contained in:
@@ -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
|
CMake invokes `rustc` directly. The library provides a low-footprint subset
|
||||||
of standard-library-like functionality:
|
of standard-library-like functionality:
|
||||||
|
|
||||||
- `Result` and formatting from `core`
|
- `Result`, `Duration`, and formatting from `core`
|
||||||
- `Vec` and `vec![]` from `alloc`
|
- `Vec`, `String`, `vec![]`, and `format!` from `alloc`
|
||||||
- project-local `print!` and `println!` macros
|
- project-local `print!` and `println!` macros
|
||||||
- direct platform stdout and heap calls
|
- direct platform stdout, heap calls, and busy-wait sleep
|
||||||
|
|
||||||
The project contains:
|
The project contains:
|
||||||
|
|
||||||
- `src/lib.rs`: the `#![no_std]` common library API
|
- `src/lib.rs`: the `#![no_std]` common library API
|
||||||
- `src/io.rs`: the `core::fmt::Write` stdout backend
|
- `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
|
- `macros/src/lib.rs`: host-side `#[rslibc_macros::main]` attribute macro
|
||||||
- `runtime/main.rs`: minimal example entry point
|
- `runtime/main.rs`: minimal example entry point
|
||||||
- `CMakeLists.txt`: direct `rustc` library, macro, and executable commands
|
- `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
|
- Keep OS integration in small FFI modules. Do not pull in a general-purpose
|
||||||
runtime or C I/O layer for basic output.
|
runtime or C I/O layer for basic output.
|
||||||
- `rslibc_macros::main` can wrap `async fn main()` with the small `block_on`
|
- `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,
|
executor. It is only a polling loop and does not provide Tokio-style I/O
|
||||||
timers, or a reactor.
|
or a reactor; delays use the busy-wait `rslibc::runtime::sleep`.
|
||||||
- Keep unsafe code inside small safe wrappers. Application code should be
|
- Keep unsafe code inside small safe wrappers. Application code should be
|
||||||
able to use calls such as:
|
able to use calls such as:
|
||||||
|
|
||||||
@@ -52,6 +53,8 @@ The project contains:
|
|||||||
- Windows stdout calls `GetStdHandle` and `WriteFile` through `kernel32`.
|
- Windows stdout calls `GetStdHandle` and `WriteFile` through `kernel32`.
|
||||||
- Windows allocation calls `HeapAlloc` and `HeapFree` through the system
|
- Windows allocation calls `HeapAlloc` and `HeapFree` through the system
|
||||||
heap.
|
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
|
- The Windows runtime dynamically links only the platform support needed by
|
||||||
`alloc` and the compiler runtime. Do not statically link the full C
|
`alloc` and the compiler runtime. Do not statically link the full C
|
||||||
runtime.
|
runtime.
|
||||||
@@ -116,7 +119,8 @@ cmake --build build
|
|||||||
```
|
```
|
||||||
|
|
||||||
On Windows, use `Remove-Item -Recurse -Force build` and run
|
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.
|
When size matters, inspect the Windows executable after a release build.
|
||||||
Avoid adding formatting, allocation, or OS dependencies unless they are
|
Avoid adding formatting, allocation, or OS dependencies unless they are
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ library rlib, and a minimal example executable.
|
|||||||
|
|
||||||
- `Result` and formatting from `core`
|
- `Result` and formatting from `core`
|
||||||
- `Vec` and `vec![]` from `alloc`
|
- `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
|
- direct platform stdout with no C I/O layer
|
||||||
- OS heap allocator (`malloc`/`free`, `HeapAlloc`/`HeapFree`)
|
- OS heap allocator (`malloc`/`free`, `HeapAlloc`/`HeapFree`)
|
||||||
- `panic=abort` with a minimal panic handler
|
- `panic=abort` with a minimal panic handler
|
||||||
@@ -41,8 +42,9 @@ Run the example:
|
|||||||
.\build\rslibc_example.exe
|
.\build\rslibc_example.exe
|
||||||
```
|
```
|
||||||
|
|
||||||
The release example prints `Hello, World!`. The Windows executable is
|
The release example runs an animated loading loop (progress bar plus
|
||||||
about 4 KiB and depends only on `KERNEL32.dll` and `VCRUNTIME140.dll`.
|
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:
|
Individual targets:
|
||||||
|
|
||||||
|
|||||||
+47
-3
@@ -1,11 +1,55 @@
|
|||||||
#![no_std]
|
#![no_std]
|
||||||
#![no_main]
|
#![no_main]
|
||||||
|
|
||||||
fn print(message: &str) {
|
use core::time::Duration;
|
||||||
let _ = rslibc::io::write_str(message);
|
|
||||||
|
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<u32, &'static str> {
|
||||||
|
// 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]
|
#[rslibc_macros::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
print("Hello\n");
|
println!("rslibc loading example");
|
||||||
|
let mut log: Vec<String> = 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:?}");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,9 +5,12 @@ extern crate alloc;
|
|||||||
pub mod io;
|
pub mod io;
|
||||||
pub mod runtime;
|
pub mod runtime;
|
||||||
|
|
||||||
|
pub use alloc::format;
|
||||||
|
pub use alloc::string::String;
|
||||||
pub use alloc::vec;
|
pub use alloc::vec;
|
||||||
pub use alloc::vec::Vec;
|
pub use alloc::vec::Vec;
|
||||||
pub use core::result::Result;
|
pub use core::result::Result;
|
||||||
|
pub use core::time::Duration;
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! print {
|
macro_rules! print {
|
||||||
|
|||||||
@@ -2,11 +2,26 @@ use core::alloc::{GlobalAlloc, Layout};
|
|||||||
use core::future::Future;
|
use core::future::Future;
|
||||||
use core::pin::Pin;
|
use core::pin::Pin;
|
||||||
use core::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
|
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)]
|
#[cfg(unix)]
|
||||||
unsafe extern "C" {
|
unsafe extern "C" {
|
||||||
fn malloc(size: usize) -> *mut u8;
|
fn malloc(size: usize) -> *mut u8;
|
||||||
fn free(pointer: *mut u8);
|
fn free(pointer: *mut u8);
|
||||||
|
fn clock_gettime(clock_id: i32, ts: *mut Timespec) -> i32;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
@@ -15,6 +30,8 @@ unsafe extern "system" {
|
|||||||
fn HeapAlloc(heap: *mut u8, flags: u32, bytes: usize) -> *mut u8;
|
fn HeapAlloc(heap: *mut u8, flags: u32, bytes: usize) -> *mut u8;
|
||||||
fn HeapFree(heap: *mut u8, flags: u32, pointer: *mut u8) -> i32;
|
fn HeapFree(heap: *mut u8, flags: u32, pointer: *mut u8) -> i32;
|
||||||
fn ExitProcess(status: u32) -> !;
|
fn ExitProcess(status: u32) -> !;
|
||||||
|
fn QueryPerformanceCounter(counter: *mut i64) -> i32;
|
||||||
|
fn QueryPerformanceFrequency(frequency: *mut i64) -> i32;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SystemAllocator;
|
pub struct SystemAllocator;
|
||||||
@@ -71,6 +88,40 @@ pub fn exit(status: u32) -> ! {
|
|||||||
unsafe { ExitProcess(status) }
|
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 {
|
fn clone_waker(_: *const ()) -> RawWaker {
|
||||||
RawWaker::new(core::ptr::null(), &WAKER_VTABLE)
|
RawWaker::new(core::ptr::null(), &WAKER_VTABLE)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user