async keyboard
This commit is contained in:
parent
cdad99a352
commit
d609af0b6e
94
Cargo.lock
generated
94
Cargo.lock
generated
@ -2,6 +2,12 @@
|
|||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 3
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "autocfg"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bit_field"
|
name = "bit_field"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
@ -20,6 +26,73 @@ version = "0.9.18"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7a3c1ceed1cd9e61c7998100cc18c13d413aa40d018992b871ab8e7435ce6372"
|
checksum = "7a3c1ceed1cd9e61c7998100cc18c13d413aa40d018992b871ab8e7435ce6372"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "0.1.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "conquer-once"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "96eb12fb69466716fbae9009d389e6a30830ae8975e170eff2d2cff579f9efa3"
|
||||||
|
dependencies = [
|
||||||
|
"conquer-util",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "conquer-util"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "654fb2472cc369d311c547103a1fa81d467bef370ae7a0680f65939895b1182a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossbeam-queue"
|
||||||
|
version = "0.2.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "774ba60a54c213d409d5353bda12d49cd68d14e45036a285234c8d6f91f92570"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"crossbeam-utils",
|
||||||
|
"maybe-uninit",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossbeam-utils"
|
||||||
|
version = "0.7.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"cfg-if",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-core"
|
||||||
|
version = "0.3.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0402f765d8a89a26043b889b26ce3c4679d268fa6bb22cd7c6aad98340e179d1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-task"
|
||||||
|
version = "0.3.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8a16bef9fc1a4dddb5bee51c989e3fbba26569cbb0e31f5b303c184e3dd33dae"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-util"
|
||||||
|
version = "0.3.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "feb5c238d27e2bf94ffdfd27b2c29e3df4a68c4193bb6427384259e2bf191967"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"futures-core",
|
||||||
|
"futures-task",
|
||||||
|
"pin-project-lite",
|
||||||
|
"pin-utils",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lazy_static"
|
name = "lazy_static"
|
||||||
version = "1.4.0"
|
version = "1.4.0"
|
||||||
@ -47,6 +120,12 @@ dependencies = [
|
|||||||
"scopeguard",
|
"scopeguard",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "maybe-uninit"
|
||||||
|
version = "2.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pc-keyboard"
|
name = "pc-keyboard"
|
||||||
version = "0.5.1"
|
version = "0.5.1"
|
||||||
@ -62,11 +141,26 @@ dependencies = [
|
|||||||
"x86_64",
|
"x86_64",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pin-project-lite"
|
||||||
|
version = "0.2.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pin-utils"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rust_os"
|
name = "rust_os"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bootloader",
|
"bootloader",
|
||||||
|
"conquer-once",
|
||||||
|
"crossbeam-queue",
|
||||||
|
"futures-util",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"linked_list_allocator",
|
"linked_list_allocator",
|
||||||
"pc-keyboard",
|
"pc-keyboard",
|
||||||
|
|||||||
14
Cargo.toml
14
Cargo.toml
@ -13,6 +13,20 @@ pic8259 = "0.10.1"
|
|||||||
pc-keyboard = "0.5.0"
|
pc-keyboard = "0.5.0"
|
||||||
linked_list_allocator = "0.9.0"
|
linked_list_allocator = "0.9.0"
|
||||||
|
|
||||||
|
[dependencies.crossbeam-queue]
|
||||||
|
version = "0.2.1"
|
||||||
|
default-features = false
|
||||||
|
features = ["alloc"]
|
||||||
|
|
||||||
|
[dependencies.futures-util]
|
||||||
|
version = "0.3.4"
|
||||||
|
default-features = false
|
||||||
|
features = ["alloc"]
|
||||||
|
|
||||||
|
[dependencies.conquer-once]
|
||||||
|
version = "0.2.0"
|
||||||
|
default-features = false
|
||||||
|
|
||||||
[dependencies.lazy_static]
|
[dependencies.lazy_static]
|
||||||
version = "1.0"
|
version = "1.0"
|
||||||
features = ["spin_no_std"]
|
features = ["spin_no_std"]
|
||||||
|
|||||||
@ -66,31 +66,13 @@ extern "x86-interrupt" fn breakpoint_handler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
extern "x86-interrupt" fn keyboard_interrupt_handler(
|
extern "x86-interrupt" fn keyboard_interrupt_handler(
|
||||||
_stack_frame: InterruptStackFrame)
|
_stack_frame: InterruptStackFrame
|
||||||
{
|
) {
|
||||||
use pc_keyboard::{layouts, DecodedKey, HandleControl, Keyboard, ScancodeSet1};
|
|
||||||
use spin::Mutex;
|
|
||||||
use x86_64::instructions::port::Port;
|
use x86_64::instructions::port::Port;
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
static ref KEYBOARD: Mutex<Keyboard<layouts::Us104Key, ScancodeSet1>> =
|
|
||||||
Mutex::new(Keyboard::new(layouts::Us104Key, ScancodeSet1,
|
|
||||||
HandleControl::Ignore)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut keyboard = KEYBOARD.lock();
|
|
||||||
let mut port = Port::new(0x60);
|
let mut port = Port::new(0x60);
|
||||||
|
|
||||||
let scancode: u8 = unsafe { port.read() };
|
let scancode: u8 = unsafe { port.read() };
|
||||||
if let Ok(Some(key_event)) = keyboard.add_byte(scancode) {
|
crate::task::keyboard::add_scancode(scancode);
|
||||||
if let Some(key) = keyboard.process_keyevent(key_event) {
|
|
||||||
match key {
|
|
||||||
DecodedKey::Unicode(character) => print!("{}", character),
|
|
||||||
DecodedKey::RawKey(key) => print!("{:?}", key),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
PICS.lock()
|
PICS.lock()
|
||||||
|
|||||||
30
src/main.rs
30
src/main.rs
@ -6,18 +6,17 @@
|
|||||||
|
|
||||||
extern crate alloc;
|
extern crate alloc;
|
||||||
|
|
||||||
use alloc::{boxed::Box, vec, vec::Vec, rc::Rc};
|
|
||||||
use core::panic::PanicInfo;
|
|
||||||
use rust_os::println;
|
use rust_os::println;
|
||||||
use rust_os::task::{Task, simple_executor::SimpleExecutor};
|
use rust_os::task::{executor::Executor, keyboard, Task};
|
||||||
use bootloader::{BootInfo, entry_point};
|
use bootloader::{entry_point, BootInfo};
|
||||||
use x86_64::VirtAddr;
|
use core::panic::PanicInfo;
|
||||||
|
|
||||||
entry_point!(kernel_main);
|
entry_point!(kernel_main);
|
||||||
|
|
||||||
fn kernel_main(boot_info: &'static BootInfo) -> ! {
|
fn kernel_main(boot_info: &'static BootInfo) -> ! {
|
||||||
use rust_os::allocator;
|
use rust_os::allocator;
|
||||||
use rust_os::memory::{self, BootInfoFrameAllocator};
|
use rust_os::memory::{self, BootInfoFrameAllocator};
|
||||||
|
use x86_64::VirtAddr;
|
||||||
|
|
||||||
println!("Hello World{}", "!");
|
println!("Hello World{}", "!");
|
||||||
rust_os::init();
|
rust_os::init();
|
||||||
@ -31,8 +30,9 @@ fn kernel_main(boot_info: &'static BootInfo) -> ! {
|
|||||||
allocator::init_heap(&mut mapper, &mut frame_allocator)
|
allocator::init_heap(&mut mapper, &mut frame_allocator)
|
||||||
.expect("heap initialization failed");
|
.expect("heap initialization failed");
|
||||||
|
|
||||||
let mut executor = SimpleExecutor::new();
|
let mut executor = Executor::new();
|
||||||
executor.spawn(Task::new(example_task()));
|
executor.spawn(Task::new(example_task()));
|
||||||
|
executor.spawn(Task::new(keyboard::print_keypresses()));
|
||||||
executor.run();
|
executor.run();
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@ -42,15 +42,6 @@ fn kernel_main(boot_info: &'static BootInfo) -> ! {
|
|||||||
rust_os::hlt_loop();
|
rust_os::hlt_loop();
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn async_number() -> u32 {
|
|
||||||
42
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn example_task() {
|
|
||||||
let number = async_number().await;
|
|
||||||
println!("async number: {}", number);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(test))]
|
#[cfg(not(test))]
|
||||||
#[panic_handler]
|
#[panic_handler]
|
||||||
fn panic(info: &PanicInfo) -> ! {
|
fn panic(info: &PanicInfo) -> ! {
|
||||||
@ -63,3 +54,12 @@ fn panic(info: &PanicInfo) -> ! {
|
|||||||
fn panic(info: &PanicInfo) -> ! {
|
fn panic(info: &PanicInfo) -> ! {
|
||||||
rust_os::test_panic_handler(info)
|
rust_os::test_panic_handler(info)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn async_number() -> u32 {
|
||||||
|
42
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn example_task() {
|
||||||
|
let number = async_number().await;
|
||||||
|
println!("async number: {}", number);
|
||||||
|
}
|
||||||
104
src/task/executor.rs
Normal file
104
src/task/executor.rs
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
use super::{Task, TaskId};
|
||||||
|
use alloc::{collections::BTreeMap, sync::Arc};
|
||||||
|
use core::task::Waker;
|
||||||
|
use crossbeam_queue::ArrayQueue;
|
||||||
|
use core::task::{Context, Poll};
|
||||||
|
use alloc::task::Wake;
|
||||||
|
|
||||||
|
pub struct Executor {
|
||||||
|
tasks: BTreeMap<TaskId, Task>,
|
||||||
|
task_queue: Arc<ArrayQueue<TaskId>>,
|
||||||
|
waker_cache: BTreeMap<TaskId, Waker>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TaskWaker {
|
||||||
|
task_id: TaskId,
|
||||||
|
task_queue: Arc<ArrayQueue<TaskId>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Executor {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Executor {
|
||||||
|
tasks: BTreeMap::new(),
|
||||||
|
task_queue: Arc::new(ArrayQueue::new(100)),
|
||||||
|
waker_cache: BTreeMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn spawn(&mut self, task: Task) {
|
||||||
|
let task_id = task.id;
|
||||||
|
if self.tasks.insert(task.id, task).is_some() {
|
||||||
|
panic!("task with same ID already in tasks");
|
||||||
|
}
|
||||||
|
self.task_queue.push(task_id).expect("queue full");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_ready_tasks(&mut self) {
|
||||||
|
// destructure `self` to avoid borrow checker errors
|
||||||
|
let Self {
|
||||||
|
tasks,
|
||||||
|
task_queue,
|
||||||
|
waker_cache,
|
||||||
|
} = self;
|
||||||
|
|
||||||
|
while let Ok(task_id) = task_queue.pop() {
|
||||||
|
let task = match tasks.get_mut(&task_id) {
|
||||||
|
Some(task) => task,
|
||||||
|
None => continue, // task no longer exists
|
||||||
|
};
|
||||||
|
let waker = waker_cache
|
||||||
|
.entry(task_id)
|
||||||
|
.or_insert_with(|| TaskWaker::new(task_id, task_queue.clone()));
|
||||||
|
let mut context = Context::from_waker(waker);
|
||||||
|
match task.poll(&mut context) {
|
||||||
|
Poll::Ready(()) => {
|
||||||
|
// task done -> remove it and its cached waker
|
||||||
|
tasks.remove(&task_id);
|
||||||
|
waker_cache.remove(&task_id);
|
||||||
|
}
|
||||||
|
Poll::Pending => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run(&mut self) -> ! {
|
||||||
|
loop {
|
||||||
|
self.run_ready_tasks();
|
||||||
|
self.sleep_if_idle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sleep_if_idle(&self) {
|
||||||
|
use x86_64::instructions::interrupts::{self, enable_and_hlt};
|
||||||
|
|
||||||
|
interrupts::disable();
|
||||||
|
if self.task_queue.is_empty() {
|
||||||
|
enable_and_hlt();
|
||||||
|
} else {
|
||||||
|
interrupts::enable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TaskWaker {
|
||||||
|
fn wake_task(&self) {
|
||||||
|
self.task_queue.push(self.task_id).expect("task_queue full");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new(task_id: TaskId, task_queue: Arc<ArrayQueue<TaskId>>) -> Waker {
|
||||||
|
Waker::from(Arc::new(TaskWaker {
|
||||||
|
task_id,
|
||||||
|
task_queue,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Wake for TaskWaker {
|
||||||
|
fn wake(self: Arc<Self>) {
|
||||||
|
self.wake_task();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wake_by_ref(self: &Arc<Self>) {
|
||||||
|
self.wake_task();
|
||||||
|
}
|
||||||
|
}
|
||||||
77
src/task/keyboard.rs
Normal file
77
src/task/keyboard.rs
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
use conquer_once::spin::OnceCell;
|
||||||
|
use crossbeam_queue::ArrayQueue;
|
||||||
|
use crate::println;
|
||||||
|
use core::{pin::Pin, task::{Poll, Context}};
|
||||||
|
use futures_util::stream::Stream;
|
||||||
|
use futures_util::task::AtomicWaker;
|
||||||
|
use futures_util::stream::StreamExt;
|
||||||
|
use pc_keyboard::{layouts, DecodedKey, HandleControl, Keyboard, ScancodeSet1};
|
||||||
|
use crate::print;
|
||||||
|
|
||||||
|
static WAKER: AtomicWaker = AtomicWaker::new();
|
||||||
|
static SCANCODE_QUEUE: OnceCell<ArrayQueue<u8>> = OnceCell::uninit();
|
||||||
|
|
||||||
|
pub async fn print_keypresses() {
|
||||||
|
let mut scancodes = ScancodeStream::new();
|
||||||
|
let mut keyboard = Keyboard::new(layouts::Us104Key, ScancodeSet1,
|
||||||
|
HandleControl::Ignore);
|
||||||
|
|
||||||
|
while let Some(scancode) = scancodes.next().await {
|
||||||
|
if let Ok(Some(key_event)) = keyboard.add_byte(scancode) {
|
||||||
|
if let Some(key) = keyboard.process_keyevent(key_event) {
|
||||||
|
match key {
|
||||||
|
DecodedKey::Unicode(character) => print!("{}", character),
|
||||||
|
DecodedKey::RawKey(key) => print!("{:?}", key),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn add_scancode(scancode: u8) {
|
||||||
|
if let Ok(queue) = SCANCODE_QUEUE.try_get() {
|
||||||
|
if let Err(_) = queue.push(scancode) {
|
||||||
|
println!("WARNING: scancode queue full; dropping keyboard input");
|
||||||
|
} else {
|
||||||
|
WAKER.wake(); // new
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
println!("WARNING: scancode queue uninitialized");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ScancodeStream {
|
||||||
|
_private: (),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ScancodeStream {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
SCANCODE_QUEUE.try_init_once(|| ArrayQueue::new(100))
|
||||||
|
.expect("ScancodeStream::new should only be called once");
|
||||||
|
ScancodeStream { _private: () }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Stream for ScancodeStream {
|
||||||
|
type Item = u8;
|
||||||
|
|
||||||
|
fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<u8>> {
|
||||||
|
let queue = SCANCODE_QUEUE
|
||||||
|
.try_get()
|
||||||
|
.expect("scancode queue not initialized");
|
||||||
|
|
||||||
|
// fast path
|
||||||
|
if let Ok(scancode) = queue.pop() {
|
||||||
|
return Poll::Ready(Some(scancode));
|
||||||
|
}
|
||||||
|
|
||||||
|
WAKER.register(&cx.waker());
|
||||||
|
match queue.pop() {
|
||||||
|
Ok(scancode) => {
|
||||||
|
WAKER.take();
|
||||||
|
Poll::Ready(Some(scancode))
|
||||||
|
}
|
||||||
|
Err(crossbeam_queue::PopError) => Poll::Pending,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,16 +1,21 @@
|
|||||||
use core::{future::Future, pin::Pin};
|
use core::{future::Future, pin::Pin};
|
||||||
use alloc::boxed::Box;
|
use alloc::boxed::Box;
|
||||||
use core::task::{Context, Poll};
|
use core::task::{Context, Poll};
|
||||||
|
use core::sync::atomic::{AtomicU64, Ordering};
|
||||||
|
|
||||||
pub mod simple_executor;
|
pub mod simple_executor;
|
||||||
|
pub mod keyboard;
|
||||||
|
pub mod executor;
|
||||||
|
|
||||||
pub struct Task {
|
pub struct Task {
|
||||||
|
id: TaskId,
|
||||||
future: Pin<Box<dyn Future<Output = ()>>>,
|
future: Pin<Box<dyn Future<Output = ()>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Task {
|
impl Task {
|
||||||
pub fn new(future: impl Future<Output = ()> + 'static) -> Task {
|
pub fn new(future: impl Future<Output = ()> + 'static) -> Task {
|
||||||
Task {
|
Task {
|
||||||
|
id: TaskId::new(),
|
||||||
future: Box::pin(future),
|
future: Box::pin(future),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -19,3 +24,13 @@ impl Task {
|
|||||||
self.future.as_mut().poll(context)
|
self.future.as_mut().poll(context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
struct TaskId(u64);
|
||||||
|
|
||||||
|
impl TaskId {
|
||||||
|
fn new() -> Self {
|
||||||
|
static NEXT_ID: AtomicU64 = AtomicU64::new(0);
|
||||||
|
TaskId(NEXT_ID.fetch_add(1, Ordering::Relaxed))
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user