takyon/
lib.rs

1//! A simple, single-threaded async runtime
2//! 
3//! Takyon is an async runtime for running futures and doing asynchronous IO on a single thread.
4//! It is designed to be simple, lightweight and intended for CLIs, GUI desktop apps, games,
5//! ie: any use case where a heavy, sophisticated multithreaded runtime is not required
6//! 
7//! This crate is still under development and currently only the following features are supported:
8//! - Linux support (using `io_uring`)
9//! - Spawning and joining child tasks
10//! - Sleeping
11//! - TCP and UDP network IO
12//! 
13//! The following features are planned for the future:
14//! - File IO
15//! - Windows, BSD and MacOS support
16//! - Inter-task communication such as channels, watch, notify, etc
17//! 
18//! # Examples
19//! An async TCP server:
20//! ```
21//! use takyon::net::TcpListener;
22//! 
23//! takyon::init().unwrap();
24//!
25//! takyon::run(async {
26//!     // Create a TcpListener
27//!     let listener = TcpListener::bind("127.0.0.1:5000").await.unwrap();
28//!
29//!     loop {
30//!         // Wait for incoming connections
31//!         let (stream, src_addr) = listener.accept().await.unwrap();
32//!         println!("New connection from {:?}\n", src_addr);
33//!
34//!         // Spawn task to handle connection
35//!         takyon::spawn(async move {
36//!             let mut buf = [0; 1024];
37//!
38//!             // Read data from the TcpStream
39//!             loop {
40//!                 let bytes = stream.read(&mut buf).await.unwrap();
41//!
42//!                 if bytes == 0 {
43//!                     println!("Address {:?} disconnected\n", src_addr);
44//!                     break;
45//!                 }
46//!
47//!                 println!("Read {:?} bytes from address {:?}", bytes, src_addr);
48//!                 println!("Data: {:02X?}\n", &buf[..bytes]);
49//!             }
50//!         });
51//!     }
52//! });
53//! ```
54//! 
55//! An async TCP client:
56//! ```
57//! use std::net::Shutdown;
58//! use takyon::{time::sleep_secs, net::TcpStream};
59//! 
60//! takyon::init().unwrap();
61//!
62//! takyon::run(async {
63//!     loop {
64//!         // Connect to the server
65//!         sleep_secs(1).await;
66//!         let stream = TcpStream::connect("127.0.0.1:5000").await.unwrap();
67//!
68//!         // Write some data
69//!         sleep_secs(1).await;
70//!         stream.write(&[0xAA, 0xBB, 0xCC]).await.unwrap();
71//!
72//!         // Shut down the connection
73//!         sleep_secs(1).await;
74//!         stream.shutdown(Shutdown::Both).await.unwrap();
75//!     }
76//! });
77//! ```
78
79mod error;
80mod runtime;
81mod platform;
82mod join_handle;
83
84pub mod net;
85pub mod time;
86pub mod util;
87
88pub use error::InitError;
89pub use join_handle::JoinHandle;
90
91use std::ptr;
92use std::pin::pin;
93use std::future::Future;
94use std::cell::{Cell, RefCell};
95use std::task::{Poll, Context, Waker, RawWaker, RawWakerVTable};
96
97use runtime::{Runtime, WokenTask};
98
99thread_local! {
100    // The lazy initializer panics, therefore enforcing that the runtime is initialized only using init()
101    // This works because init() uses .set() which does not run the lazy initializer
102    pub(crate) static RUNTIME: RefCell<Runtime> = panic!("takyon::init() has not been called on this thread!");
103
104    pub(crate) static RUNNING: Cell<bool> = const { Cell::new(false) };
105}
106
107static WAKER_VTABLE: RawWakerVTable = RawWakerVTable::new(|_| panic!(), |_| (), |_| (), |_| ());
108
109/// Initializes the thread-local runtime
110/// 
111/// This must be called at least once before calling [`run()`] on a thread
112pub fn init() -> Result<(), InitError> {
113    RUNTIME.set(Runtime::new()?);
114    Ok(())
115}
116
117/// Runs a future on the current thread, blocking it whenever waiting for IO
118/// 
119/// The passed future will be considered the "root task". The root task can
120/// use [`spawn()`] to spawn child tasks. The function returns the root task's
121/// result as soon as it has finished, and all pending child tasks will be dropped.
122/// 
123/// Use the child tasks' [`JoinHandle`]s if you want to wait for them to complete
124/// before returning. Remember to call [`init()`] atleast once on a thread before
125/// using [`run()`]
126/// 
127/// # Examples
128/// ```
129/// use takyon::time::sleep_secs;
130/// 
131/// // Initialize the thread-local runtime
132/// takyon::init()?;
133/// 
134/// // Run a future
135/// let result = takyon::run(async {
136///     sleep_secs(1).await;
137///     println!("1 second passed");
138/// 
139///     sleep_secs(1).await;
140///     println!("2 seconds passed");
141/// 
142///     let result = do_something().await;
143/// 
144///     result
145/// });
146/// 
147/// // Use the result returned by the future
148/// println!("{result}");
149/// ```
150pub fn run<F: Future>(root_task: F) -> F::Output {
151    RUNNING.set(true);
152
153    let waker = unsafe { Waker::from_raw(RawWaker::new(ptr::null(), &WAKER_VTABLE)) };
154    let mut cx = Context::from_waker(&waker);
155    
156    let mut root_task = pin!(root_task);
157
158    loop {
159        // Poll all woken up tasks
160        loop {
161            let task = RUNTIME.with_borrow_mut(|rt| rt.get_woken_task());
162
163            match task {
164                // Root task woken up
165                Some(WokenTask::Root) => {
166                    let poll = root_task.as_mut().poll(&mut cx);
167
168                    // Root task finished, reset runtime and return
169                    if let Poll::Ready(res) = poll {
170                        RUNTIME.with_borrow_mut(|rt| rt.reset());
171                        RUNNING.set(false);
172                        return res;
173                    }
174                },
175    
176                // Child task woken up
177                Some(WokenTask::Child(mut task)) => {
178                    let poll = task.as_mut().poll(&mut cx);
179
180                    match poll {
181                        // Child task pending, return it into task list
182                        Poll::Pending => RUNTIME.with_borrow_mut(|rt| rt.return_task(task)),
183                        
184                        // Child task finished with result
185                        Poll::Ready(res) => RUNTIME.with_borrow_mut(|rt| rt.task_finished(res))
186                    }
187                },
188
189                // No more woken tasks left
190                None => break
191            }
192        }
193
194        // Wait for IO events to wake up more tasks
195        RUNTIME.with_borrow_mut(|rt| rt.wait_for_io());
196    }
197}
198
199/// Spawns a new task and returns it's [`JoinHandle`]
200/// 
201/// The new task immediately runs concurrently with the current task without needing to
202/// `await` it. The returned [`JoinHandle`] can be used to wait for the task to finish
203/// 
204/// See the [`JoinHandle`] docs for an example of using this function
205/// 
206/// # Panics
207/// This can only be used within a [`run()`] call and will panic if used outside it
208pub fn spawn<F: Future + 'static>(task: F) -> JoinHandle<F::Output> {
209    if !RUNNING.get() {
210        panic!("takyon::spawn() called outside of a takyon::run() call!")
211    }
212
213    RUNTIME.with_borrow_mut(|rt| rt.spawn(task))
214}