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}