luminal_rt 0.4.1

A DLL-boundary safe async runtime with tokio-compatible API
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
# Luminal no_std Guide

This guide covers using Luminal in `no_std` environments, such as embedded systems and other constrained platforms.

## Overview

Luminal provides full async runtime support for `no_std` environments with the following characteristics:

- ✅ Single-threaded execution model (no threading support)
- ✅ Requires `alloc` for heap allocation
- ✅ Uses heapless data structures with bounded queues
- ✅ Explicit runtime context (no global functions)
- ✅ DLL boundary safe (no thread-local storage)
- ✅ Minimal resource usage
- ✅ Same async/await API as std version

## Installation

Add Luminal to your `Cargo.toml` with `default-features = false`:

```toml
[dependencies]
luminal = { version = "0.3.0", default-features = false }
```

## Requirements

### Rust Setup

Your `no_std` project needs:

```rust
#![no_std]
extern crate alloc;
```

### Dependencies

The `no_std` version of Luminal uses:
- `heapless` for bounded data structures
- `parking_lot` for synchronization primitives (no_std compatible)
- `crossbeam-deque` for work stealing (no threading in no_std mode)

## Basic Usage

### Creating and Using a Runtime

```rust
#![no_std]
extern crate alloc;

use alloc::vec::Vec;
use luminal::Runtime;

fn main() -> Result<(), luminal::RuntimeError> {
    // Create a new runtime
    let rt = Runtime::new()?;

    // Spawn a task
    let handle = rt.spawn(async {
        let mut results = Vec::new();
        for i in 0..10 {
            results.push(i * 2);
        }
        results.iter().sum::<i32>()
    });

    // Block until the task completes
    let result = rt.block_on(handle);
    assert_eq!(result, 90);

    Ok(())
}
```

### Async Functions

```rust
#![no_std]
extern crate alloc;

use alloc::{vec::Vec, format};
use luminal::Runtime;

async fn process_data(data: Vec<u32>) -> Vec<u32> {
    // Simulate async processing
    data.iter().map(|x| x * 2).collect()
}

async fn complex_task() -> u32 {
    let data = alloc::vec![1, 2, 3, 4, 5];
    let processed = process_data(data).await;
    processed.iter().sum()
}

fn main() {
    let rt = Runtime::new().unwrap();
    let result = rt.block_on(complex_task());
    assert_eq!(result, 30); // (1+2+3+4+5) * 2 = 30
}
```

## Configuration and Limits

### Bounded Queues

The no_std version uses bounded queues with fixed capacities:

- **Task Queue**: 1024 tasks maximum
- **Result Queue**: 16 results per JoinHandle

These limits are compile-time constants to ensure deterministic memory usage.

### Memory Usage

The runtime uses a fixed amount of memory:
- Task queue: ~1MB (1024 tasks * ~1KB per task)
- Minimal overhead for synchronization
- No dynamic thread creation

## Architecture Differences

### std vs no_std

| Feature | std | no_std |
|---------|-----|---------|
| Threading | Multi-threaded work stealing | Single-threaded |
| Global Functions | `spawn()`, `block_on()` | None |
| Queue Type | Unbounded channels | Bounded heapless queues |
| Memory Model | Dynamic allocation | Fixed-size allocation |
| Synchronization | Standard library primitives | parking_lot no_std |

### Execution Model

In no_std mode, Luminal uses a simplified execution model:

1. **Single Event Loop**: All tasks run on the calling thread
2. **Cooperative Scheduling**: Tasks yield control at `.await` points
3. **Bounded Queues**: Prevents unbounded memory growth
4. **Busy Waiting**: Simple polling loop (no thread sleeping)

## Error Handling

```rust
#![no_std]
extern crate alloc;

use luminal::{Runtime, RuntimeError};

fn main() -> Result<(), RuntimeError> {
    let rt = Runtime::new()?;

    let handle = rt.spawn(async {
        // This task might fail
        if some_condition() {
            panic!("Task failed!");
        }
        42
    });

    // Handle potential task panics
    match rt.block_on(handle) {
        result => {
            // Task completed successfully
            println!("Result: {}", result);
            Ok(())
        }
    }
}

fn some_condition() -> bool { false }
```

## Best Practices

### 1. Minimize Task Queue Usage

Since the task queue is bounded, avoid creating too many concurrent tasks:

```rust
// Good: Process items in batches
async fn process_batch(items: &[u32]) -> Vec<u32> {
    items.iter().map(|x| x * 2).collect()
}

// Avoid: Creating many individual tasks
// for item in large_vec {
//     rt.spawn(async move { process_item(item) });
// }
```

### 2. Use Explicit Runtime References

Always pass runtime references explicitly:

```rust
async fn worker_task(rt: &Runtime, data: u32) -> u32 {
    let subtask = rt.spawn(async move { data * 2 });
    rt.block_on(subtask)
}
```

### 3. Handle Queue Capacity

Be aware of bounded queue limitations:

```rust
let rt = Runtime::new().unwrap();

// Check queue stats
let (queue_len, processed) = rt.stats();
if queue_len > 900 {  // Near capacity
    // Wait for tasks to complete or handle overflow
}
```

## Platform-Specific Considerations

### Embedded Targets

For embedded platforms like ARM Cortex-M:

```toml
# Cargo.toml
[target.'cfg(target_arch = "arm")'.dependencies]
luminal = { version = "0.3.0", default-features = false }

# Optional: Reduce memory usage
[features]
reduced-memory = []
```

### WASM

For WebAssembly targets:

```toml
# Cargo.toml
[target.'cfg(target_arch = "wasm32")'.dependencies]
luminal = { version = "0.3.0", default-features = false }
```

## Debugging and Profiling

### Runtime Statistics

Monitor runtime performance:

```rust
let rt = Runtime::new().unwrap();

// ... spawn tasks ...

let (queue_len, tasks_processed) = rt.stats();
println!("Queue length: {}, Tasks processed: {}", queue_len, tasks_processed);
```

### Memory Usage

Track memory usage in constrained environments:

```rust
#![no_std]
extern crate alloc;

use alloc::alloc::{GlobalAlloc, Layout};
use luminal::Runtime;

// Custom allocator for tracking
struct TrackingAllocator;

unsafe impl GlobalAlloc for TrackingAllocator {
    unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
        // Track allocations
        std::alloc::System.alloc(layout)
    }

    unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
        std::alloc::System.dealloc(ptr, layout)
    }
}

#[global_allocator]
static GLOBAL: TrackingAllocator = TrackingAllocator;
```

## Examples

### LED Blinking (Embedded)

```rust
#![no_std]
#![no_main]

extern crate alloc;
use alloc::vec::Vec;
use luminal::Runtime;

// Embedded HAL imports would go here
// use embedded_hal::digital::v2::OutputPin;

#[no_mangle]
pub extern "C" fn main() -> ! {
    let rt = Runtime::new().unwrap();

    let blink_task = rt.spawn(async {
        let mut counter = 0u32;
        loop {
            // Toggle LED (pseudocode)
            // led.toggle();
            counter += 1;

            // Yield to other tasks
            async_yield().await;

            if counter > 1000 {
                break;
            }
        }
        counter
    });

    let _result = rt.block_on(blink_task);

    // Embedded systems typically run forever
    loop {}
}

async fn async_yield() {
    // Minimal yield implementation
}

#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
    loop {}
}
```

### Sensor Data Processing

```rust
#![no_std]
extern crate alloc;

use alloc::vec::Vec;
use luminal::Runtime;

struct SensorReading {
    temperature: f32,
    humidity: f32,
}

async fn read_sensor() -> SensorReading {
    // Simulate sensor reading
    SensorReading {
        temperature: 25.0,
        humidity: 60.0,
    }
}

async fn process_readings(count: usize) -> f32 {
    let mut readings = Vec::new();

    for _ in 0..count {
        let reading = read_sensor().await;
        readings.push(reading.temperature);
    }

    readings.iter().sum::<f32>() / readings.len() as f32
}

fn main() {
    let rt = Runtime::new().unwrap();

    let avg_temp = rt.block_on(process_readings(10));

    // Use average temperature for control logic
    if avg_temp > 30.0 {
        // Turn on cooling
    }
}
```

## Migration from std

When migrating from std to no_std:

1. **Remove global functions**: Replace `spawn()` and `block_on()` with runtime methods
2. **Add bounds checking**: Handle queue capacity limits
3. **Replace std types**: Use `alloc` equivalents (`Vec`, `String`, etc.)
4. **Single-threaded mindset**: Design for cooperative multitasking

### Before (std):
```rust
use luminal::{spawn, block_on};

fn main() {
    let handle = spawn(async { 42 });
    let result = block_on(handle);
}
```

### After (no_std):
```rust
#![no_std]
extern crate alloc;

use luminal::Runtime;

fn main() {
    let rt = Runtime::new().unwrap();
    let handle = rt.spawn(async { 42 });
    let result = rt.block_on(handle);
}
```

## Troubleshooting

### Common Issues

**Queue Full Errors**: Reduce concurrent tasks or increase processing speed
**Memory Exhaustion**: Monitor allocations and use streaming processing
**Blocking Operations**: Ensure all operations are truly async

### Performance Tips

1. **Batch Processing**: Group operations to reduce task overhead
2. **Bounded Resources**: Use fixed-size collections where possible
3. **Cooperative Yields**: Add manual yield points in long-running tasks
4. **Memory Reuse**: Reuse allocations instead of creating new ones

## Contributing

When contributing to no_std support:

1. Test on actual embedded targets
2. Measure memory usage
3. Ensure deterministic behavior
4. Document resource requirements

## Further Reading

- [Rust Embedded Book]https://doc.rust-lang.org/embedded-book/
- [heapless documentation]https://docs.rs/heapless/
- [parking_lot no_std guide]https://docs.rs/parking_lot/
- [Async programming in embedded Rust]https://book.embassy.dev/