added new_async behind loop feature flag

This commit is contained in:
noib3 2022-08-15 21:10:03 +02:00
parent 912385e776
commit abf70cbf2f
No known key found for this signature in database
GPG key ID: 7AF92216C504A017
17 changed files with 205 additions and 204 deletions

View file

@ -1,13 +0,0 @@
[package]
name = "async-apples"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
async-std = { version = "1.12", features = ["unstable"] }
futures-lite = "1.12"
nvim-oxi = { path = "../../nvim-oxi" }
rand = "0.8"

View file

@ -1,70 +0,0 @@
# async-apples
Wait a random number of seconds between 4 and 9. Every second sleep a
task and print an update message. If we end before 7 seconds we print how many
apples we have. If not we cancel the task, stop waiting and print a message
saying we're done. Do all of this without blocking the thread.
This crate does the equivalent of the following Lua code
```lua
--[[
async_apples.lua
You can test this inside Neovim by creating a new file with the following code
and calling `:luafile %`.
--]]
local uv = vim.loop
local MAX_WAIT = 7
local UPDATE_MSGS = {
"Started counting apples!",
"Counted a few",
"Still counting",
"Not quite done yet",
"Umhh, this might take a while",
"Not sure if I'll finish in time",
"Almost done",
}
local WAIT_TIME = math.random(4, 9)
local i = 0
local apple_counter = uv.new_timer()
apple_counter:start(0, 1000, function()
if i ~= WAIT_TIME then
print(UPDATE_MSGS[i + 1])
i = i + 1
return
end
local apples = math.random(0, 100)
print(("Done in %ss! You have %s apples!"):format(WAIT_TIME, apples))
apple_counter:stop()
apple_counter:close()
end)
local stopped = false
local controller = uv.new_timer()
controller:start(0, 1000, function()
if apple_counter:is_active() then
if i <= MAX_WAIT then
return
end
apple_counter:stop()
apple_counter:close()
stopped = true
end
if stopped then
print("I've had enough of these damn apples!")
end
controller:stop()
controller:close()
end)
```

View file

@ -1,69 +0,0 @@
use std::cell::RefCell;
use std::rc::Rc;
use std::time::Duration;
use async_std::task;
use nvim_oxi::{self as nvim, print};
use rand::{thread_rng, Rng};
const MAX_WAIT: usize = 7;
const UPDATE_MSGS: &[&str] = &[
"Started counting apples!",
"Counted a few",
"Still counting",
"Not quite done yet",
"Umhh, this might take a while",
"Not sure if I'll finish in time",
"Almost done",
];
#[nvim::module]
fn async_apples() -> nvim::Result<()> {
let wait_time: usize = thread_rng().gen_range(4..10);
let i = Rc::new(RefCell::new(0));
let done = Rc::new(RefCell::new(false));
let i_cl = Rc::clone(&i);
let done_cl = Rc::clone(&done);
let task = task::spawn_local(async move {
let i = i_cl;
while *i.try_borrow().unwrap() != wait_time {
let mut j = i.try_borrow_mut().unwrap();
print!("{}", UPDATE_MSGS[*j]);
task::sleep(Duration::from_secs(1)).await;
*j += 1;
}
let apples = thread_rng().gen_range(0..101);
print!("Done! You have {apples} apples!");
let mut done = done_cl.try_borrow_mut().unwrap();
*done = true;
});
let _ = task::spawn_local(async move {
let mut stopped = false;
while !*done.try_borrow().unwrap() {
if *i.try_borrow().unwrap() == MAX_WAIT {
task.cancel().await;
stopped = true;
break;
}
task::sleep(Duration::from_secs(1)).await;
}
if stopped {
print!("I've had enough of these damn apples!");
}
});
// Great..except we have no executor to run these tasks on w/o blocking the
// thread :/
//
// TODO: create something like a new `Task` object that takes an async
// `FnMut(()) -> impl Future<Output = R> + 'static` and drives it to
// completion using libuv.
Ok(())
}

11
examples/async/Cargo.toml Normal file
View file

@ -0,0 +1,11 @@
[package]
name = "async"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
nvim-oxi = { path = "../../nvim-oxi", features = ["loop"] }
tokio = { version = "1.0", features = ["full"] }

34
examples/async/src/lib.rs Normal file
View file

@ -0,0 +1,34 @@
use std::thread;
use std::time::Duration;
use nvim_oxi as oxi;
use nvim_oxi::r#loop::{self, AsyncHandle};
use tokio::sync::mpsc::{self, UnboundedSender};
use tokio::time;
#[oxi::module]
fn async_print() -> oxi::Result<()> {
let (sender, mut recv) = mpsc::unbounded_channel::<i32>();
let handle = r#loop::new_async(move || {
let i = recv.blocking_recv().unwrap();
oxi::print!("Bonsoir {i}!");
Ok(())
})?;
let _ = thread::spawn(move || hello(handle, sender));
Ok(())
}
#[tokio::main(flavor = "current_thread")]
async fn hello(mut handle: AsyncHandle, sender: UnboundedSender<i32>) {
let mut i = -1;
loop {
i += 1;
sender.send(i).unwrap();
let _ = handle.send();
time::sleep(Duration::from_secs(1)).await;
}
}

View file

@ -16,7 +16,6 @@ all-features = true
rustdoc-args = ["--cfg", "docsrs"]
[features]
libuv-rs = ["loop", "dep:libuv"]
loop = ["dep:libuv-sys2"]
mlua = ["dep:mlua"]
nightly = ["nvim-types/nightly"]
@ -26,7 +25,6 @@ test = ["dep:oxi-test"]
derive_builder = "0.11"
libc = "0.2"
libuv-sys2 = { version = "1.43", optional = true }
libuv = { version = "2.4", optional = true }
mlua = { version = "0.8", optional = true }
nvim-types = { version = "0.1", path = "../nvim-types", features = ["serde"] }
once_cell = "1.12"

View file

@ -1,12 +1,13 @@
use std::fmt;
use serde::{de, ser};
use thiserror::Error as ThisError;
/// Alias for a `Result` with error type [`nvim_oxi::Error`](Error).
pub type Result<T> = std::result::Result<T, Error>;
/// Error returned by `nvim-oxi` functions.
#[derive(thiserror::Error, Debug)]
#[derive(Debug, ThisError)]
#[cfg_attr(not(feature = "mlua"), derive(Eq, PartialEq))]
pub enum Error {
#[error(transparent)]
@ -42,6 +43,10 @@ pub enum Error {
#[error("{0}")]
Other(String),
#[cfg(feature = "loop")]
#[error(transparent)]
LoopError(#[from] crate::r#loop::Error),
#[cfg(feature = "mlua")]
#[error(transparent)]
MluaError(#[from] mlua::Error),

View file

@ -40,7 +40,7 @@ pub mod types {
// Public modules behind feature flags.
#[cfg(feature = "loop")]
#[cfg_attr(docsrs, doc(cfg(feature = "loop")))]
pub mod libuv;
pub mod r#loop;
#[cfg(feature = "mlua")]
#[cfg_attr(docsrs, doc(cfg(feature = "mlua")))]

View file

@ -1,40 +0,0 @@
#[cfg(feature = "libuv-rs")]
use libuv::r#loop::Loop as LibuvRsLoop;
use libuv_sys2::uv_loop_t;
// https://github.com/neovim/neovim/blob/master/src/nvim/event/loop.h#L44
#[repr(C)]
struct Loop {
uv: uv_loop_t,
}
extern "C" {
// https://github.com/neovim/neovim/blob/master/src/nvim/main.c#L107
#[link_name = "main_loop"]
static MAIN_LOOP: Loop;
}
thread_local! {
/// Handle to the main [libuv event loop] used by Neovim to schedule its
/// asynchronous tasks.
///
/// [libuv event loop]: http://docs.libuv.org/en/v1.x/loop.html
pub static LOOP: uv_loop_t = unsafe { MAIN_LOOP.uv };
/// TODO: docs
#[cfg(feature = "libuv-rs")]
#[cfg_attr(docsrs, doc(cfg(feature = "libuv-rs")))]
pub static LIBUV_RS_LOOP: LibuvRsLoop = LibuvRsLoop::from_ptr(&LOOP as *const _ as *mut _);
}
#[cfg(feature = "libuv-rs")]
trait FromPtr {
fn from_ptr(ptr: *mut uv_loop_t) -> LibuvRsLoop;
}
#[cfg(feature = "libuv-rs")]
impl FromPtr for LibuvRsLoop {
fn from_ptr(ptr: *mut uv_loop_t) -> LibuvRsLoop {
todo!()
}
}

View file

@ -0,0 +1,90 @@
use std::alloc::{self, Layout};
use std::cmp::Ordering;
use libuv_sys2::{
uv_async_init,
uv_async_send,
uv_async_t,
uv_close,
uv_handle_get_data,
uv_handle_set_data,
};
#[allow(unused_imports)]
use crate::lua;
#[derive(Clone)]
pub struct AsyncHandle {
handle: *mut uv_async_t,
}
impl AsyncHandle {
#[inline(always)]
pub fn send(&mut self) -> super::Result<()> {
let ret = unsafe { uv_async_send(self.handle) };
match ret.cmp(&0) {
Ordering::Less => Err(super::Error::CouldntTriggerAsyncHandle),
_ => Ok(()),
}
}
}
unsafe impl Send for AsyncHandle {}
unsafe impl Sync for AsyncHandle {}
// unsafe extern "C" fn close_cb(handle: *mut uv_handle_t) {}
impl Drop for AsyncHandle {
fn drop(&mut self) {
unsafe { uv_close(self.handle as _, None) };
if !self.handle.is_null() {
let layout = Layout::new::<uv_async_t>();
unsafe { std::alloc::dealloc(self.handle as _, layout) };
}
}
}
type Cb = Box<dyn FnMut() -> crate::Result<()> + 'static>;
unsafe extern "C" fn async_cb(handle: *mut uv_async_t) {
let cb_ptr = uv_handle_get_data(handle as _) as *mut Cb;
// TODO: use let chains once they are stable (1.65?).
if !cb_ptr.is_null() {
if let Err(_err) = (&mut *cb_ptr)() {
// TODO: how to handle errors?
//
// lua::with_state(|lstate| lua::handle_error(lstate, err.into()))
}
}
}
pub fn new_async<F>(fun: F) -> super::Result<AsyncHandle>
where
F: FnMut() -> crate::Result<()> + 'static,
{
let layout = Layout::new::<uv_async_t>();
let handle = unsafe { alloc::alloc(layout) as *mut uv_async_t };
let cb_ptr = Box::into_raw(Box::new(Box::new(fun) as Cb));
unsafe { uv_handle_set_data(handle as _, cb_ptr as _) };
let ret = unsafe {
super::with_loop(|main_loop| {
uv_async_init(main_loop, handle, Some(async_cb as _))
})
};
match ret.cmp(&0) {
// A negative return value indicates an error.
Ordering::Less => {
unsafe { alloc::dealloc(handle as _, layout) };
Err(super::Error::CouldntCreateAsyncHandle)
},
_ => Ok(AsyncHandle { handle }),
}
}

View file

@ -0,0 +1,12 @@
use thiserror::Error as ThisError;
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, Eq, PartialEq, ThisError)]
pub enum Error {
#[error("Couldn't create async handle")]
CouldntCreateAsyncHandle,
#[error("Couldn't trigger async handle")]
CouldntTriggerAsyncHandle,
}

26
nvim-oxi/src/loop/loop.rs Normal file
View file

@ -0,0 +1,26 @@
use libuv_sys2::uv_loop_t;
use once_cell::unsync::OnceCell;
use crate::lua;
extern "C" {
// https://github.com/luvit/luv/blob/master/src/luv.c#L751
fn luv_loop(L: *mut lua::lua_State) -> *mut uv_loop_t;
}
thread_local! {
static LOOP: OnceCell<*mut uv_loop_t> = OnceCell::new();
}
#[inline]
pub(crate) unsafe fn init_loop(lstate: *mut lua::lua_State) {
LOOP.with(|main_loop| main_loop.set(luv_loop(lstate)).unwrap_unchecked());
}
#[inline]
pub(crate) unsafe fn with_loop<F, R>(fun: F) -> R
where
F: FnOnce(*mut uv_loop_t) -> R,
{
LOOP.with(move |main_loop| fun(*(main_loop.get().unwrap_unchecked())))
}

7
nvim-oxi/src/loop/mod.rs Normal file
View file

@ -0,0 +1,7 @@
mod r#async;
mod error;
mod r#loop;
pub use error::{Error, Result};
pub use r#async::{new_async, AsyncHandle};
pub(crate) use r#loop::{init_loop, with_loop};

View file

@ -4,7 +4,7 @@ use std::marker::PhantomData;
use std::result::Result as StdResult;
use std::{fmt, mem, ptr};
use libc::{c_char, c_int};
use libc::c_int;
use nvim_types::{LuaRef, Object, ObjectKind};
use serde::{de, ser};
@ -97,7 +97,7 @@ impl<A, R> Function<A, R> {
&**upv
};
fun(lstate).unwrap_or_else(|err| handle_error(lstate, err))
fun(lstate).unwrap_or_else(|err| super::handle_error(lstate, err))
}
let r#ref = super::with_state(move |lstate| unsafe {
@ -187,9 +187,3 @@ impl<A, R> Function<A, R> {
})
}
}
unsafe fn handle_error(lstate: *mut lua_State, err: crate::Error) -> ! {
let msg = err.to_string();
lua_pushlstring(lstate, msg.as_ptr() as *const c_char, msg.len());
lua_error(lstate);
}

View file

@ -38,6 +38,10 @@ where
R: super::LuaPushable,
{
self::init_state(lstate);
#[cfg(feature = "loop")]
crate::r#loop::init_loop(lstate);
body().unwrap().push(lstate).unwrap()
}
@ -103,3 +107,12 @@ pub(crate) unsafe fn debug_type(
) -> impl fmt::Display {
CStr::from_ptr(luaL_typename(lstate, n)).to_string_lossy()
}
pub(crate) unsafe fn handle_error(
lstate: *mut lua_State,
err: crate::Error,
) -> ! {
let msg = err.to_string();
lua_pushlstring(lstate, msg.as_ptr() as *const _, msg.len());
lua_error(lstate);
}

View file

@ -13,6 +13,9 @@ pub struct Error {
msg: *mut c_char,
}
unsafe impl Send for Error {}
unsafe impl Sync for Error {}
// https://github.com/neovim/neovim/blob/master/src/nvim/api/private/defs.h#L26
#[allow(dead_code, non_camel_case_types)]
#[derive(Eq, PartialEq)]