mirror of
https://github.com/noib3/nvim-oxi.git
synced 2025-08-04 19:08:31 +00:00
✨ added new_async
behind loop
feature flag
This commit is contained in:
parent
912385e776
commit
abf70cbf2f
17 changed files with 205 additions and 204 deletions
|
@ -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"
|
|
@ -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)
|
||||
```
|
|
@ -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
11
examples/async/Cargo.toml
Normal 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
34
examples/async/src/lib.rs
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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")))]
|
||||
|
|
|
@ -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!()
|
||||
}
|
||||
}
|
90
nvim-oxi/src/loop/async.rs
Normal file
90
nvim-oxi/src/loop/async.rs
Normal 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 }),
|
||||
}
|
||||
}
|
12
nvim-oxi/src/loop/error.rs
Normal file
12
nvim-oxi/src/loop/error.rs
Normal 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
26
nvim-oxi/src/loop/loop.rs
Normal 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
7
nvim-oxi/src/loop/mod.rs
Normal 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};
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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)]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue