From 42ae4247cefd513f6767b04a5a60164929558bfd Mon Sep 17 00:00:00 2001 From: alemi Date: Tue, 17 Sep 2024 14:33:14 +0200 Subject: [PATCH] feat(lua): reintroduce and_then safely --- .github/workflows/lua.yml | 1 + dist/lua/annotations.lua | 13 ++- src/ffi/lua.rs | 180 ++++++++++++++++++++++---------------- 3 files changed, 118 insertions(+), 76 deletions(-) diff --git a/.github/workflows/lua.yml b/.github/workflows/lua.yml index 1d33d9c..0d0be3d 100644 --- a/.github/workflows/lua.yml +++ b/.github/workflows/lua.yml @@ -4,6 +4,7 @@ on: push: branches: - stable + - dev permissions: contents: read diff --git a/dist/lua/annotations.lua b/dist/lua/annotations.lua index 07029e5..b82c6e8 100644 --- a/dist/lua/annotations.lua +++ b/dist/lua/annotations.lua @@ -14,36 +14,47 @@ ---@class (exact) NilPromise : Promise ---@field await fun(self: NilPromise): nil block until promise is ready +---@field and_then fun(cb: fun(x: nil)): nil run callback after promise is complete ---@class (exact) StringPromise : Promise ---@field await fun(self: StringPromise): string block until promise is ready and return value +---@field and_then fun(cb: fun(x: string)): nil run callback after promise is complete ---@class (exact) StringArrayPromise : Promise ---@field await fun(self: StringArrayPromise): string[] block until promise is ready and return value +---@field and_then fun(cb: fun(x: string[])): nil run callback after promise is complete ---@class (exact) ClientPromise : Promise ---@field await fun(self: ClientPromise): Client block until promise is ready and return value +---@field and_then fun(cb: fun(x: Client)): nil run callback after promise is complete ---@class (exact) WorkspacePromise : Promise ---@field await fun(self: WorkspacePromise): Workspace block until promise is ready and return value +---@field and_then fun(cb: fun(x: Workspace)): nil run callback after promise is complete ---@class (exact) WorkspaceEventPromise : Promise ---@field await fun(self: WorkspaceEventPromise): WorkspaceEvent block until promise is ready and return value +---@field and_then fun(cb: fun(x: WorkspaceEvent)): nil run callback after promise is complete ---@class (exact) BufferControllerPromise : Promise ---@field await fun(self: BufferControllerPromise): BufferController block until promise is ready and return value +---@field and_then fun(cb: fun(x: BufferController)): nil run callback after promise is complete ---@class (exact) CursorPromise : Promise ---@field await fun(self: CursorPromise): Cursor block until promise is ready and return value +---@field and_then fun(cb: fun(x: Cursor)): nil run callback after promise is complete ---@class (exact) MaybeCursorPromise : Promise ---@field await fun(self: MaybeCursorPromise): Cursor? block until promise is ready and return value +---@field and_then fun(cb: fun(x: Cursor | nil)): nil run callback after promise is complete ---@class (exact) TextChangePromise : Promise ---@field await fun(self: TextChangePromise): TextChange block until promise is ready and return value +---@field and_then fun(cb: fun(x: TextChange)): nil run callback after promise is complete ---@class (exact) MaybeTextChangePromise : Promise ---@field await fun(self: MaybeTextChangePromise): TextChange? block until promise is ready and return value +---@field and_then fun(cb: fun(x: TextChange | nil)): nil run callback after promise is complete -- [[ END ASYNC STUFF ]] @@ -307,7 +318,7 @@ local Codemp = {} ---connect to codemp server, authenticate and return client function Codemp.connect(config) end ----@return function | nil +---@return function, any | nil ---@nodiscard ---check if codemp thread sent a callback to be run on main thread function Codemp.poll_callback() end diff --git a/src/ffi/lua.rs b/src/ffi/lua.rs index d1f3ba9..3fef908 100644 --- a/src/ffi/lua.rs +++ b/src/ffi/lua.rs @@ -47,13 +47,58 @@ fn lua_tuple(lua: &Lua, (a, b): (T, T)) -> LuaResult { Ok(table) } -// TODO cannot do Box ?? maybe its temporary because im using betas -#[allow(unused)] // TODO pass callback args! -//struct CallbackArg(tokio::sync::oneshot::Receiver); -struct CallbackArg; +enum CallbackArg { + Nil, + Str(String), + VecStr(Vec), + Client(CodempClient), + CursorController(CodempCursorController), + BufferController(CodempBufferController), + Workspace(CodempWorkspace), + Event(CodempEvent), + Cursor(CodempCursor), + MaybeCursor(Option), + TextChange(CodempTextChange), + MaybeTextChange(Option), +} + +impl IntoLua for CallbackArg { + // TODO this basically calls .into_lua() on all enum variants + // i wish i could do this with a Box or an impl IntoLua + // but IntoLua requires Sized so it can't be made into an object + fn into_lua(self, lua: &Lua) -> LuaResult { + match self { + CallbackArg::Nil => Ok(LuaValue::Nil), + CallbackArg::Str(x) => x.into_lua(lua), + CallbackArg::Client(x) => x.into_lua(lua), + CallbackArg::CursorController(x) => x.into_lua(lua), + CallbackArg::BufferController(x) => x.into_lua(lua), + CallbackArg::Workspace(x) => x.into_lua(lua), + CallbackArg::VecStr(x) => x.into_lua(lua), + CallbackArg::Event(x) => x.into_lua(lua), + CallbackArg::Cursor(x) => x.into_lua(lua), + CallbackArg::MaybeCursor(x) => x.into_lua(lua), + CallbackArg::TextChange(x) => x.into_lua(lua), + CallbackArg::MaybeTextChange(x) => x.into_lua(lua), + } + } +} + +impl From<()> for CallbackArg { fn from(_: ()) -> Self { CallbackArg::Nil } } +impl From for CallbackArg { fn from(value: String) -> Self { CallbackArg::Str(value) } } +impl From for CallbackArg { fn from(value: CodempClient) -> Self { CallbackArg::Client(value) } } +impl From for CallbackArg { fn from(value: CodempCursorController) -> Self { CallbackArg::CursorController(value) } } +impl From for CallbackArg { fn from(value: CodempBufferController) -> Self { CallbackArg::BufferController(value) } } +impl From for CallbackArg { fn from(value: CodempWorkspace) -> Self { CallbackArg::Workspace(value) } } +impl From> for CallbackArg { fn from(value: Vec) -> Self { CallbackArg::VecStr(value) } } +impl From for CallbackArg { fn from(value: CodempEvent) -> Self { CallbackArg::Event(value) } } +impl From for CallbackArg { fn from(value: CodempCursor) -> Self { CallbackArg::Cursor(value) } } +impl From> for CallbackArg { fn from(value: Option) -> Self { CallbackArg::MaybeCursor(value) } } +impl From for CallbackArg { fn from(value: CodempTextChange) -> Self { CallbackArg::TextChange(value) } } +impl From> for CallbackArg { fn from(value: Option) -> Self { CallbackArg::MaybeTextChange(value) } } struct CallbackChannel { - tx: tokio::sync::mpsc::UnboundedSender<(LuaFunction, CallbackArg)>, + tx: std::sync::Arc>, rx: std::sync::Mutex> } @@ -62,31 +107,31 @@ impl Default for CallbackChannel { let (tx, rx) = tokio::sync::mpsc::unbounded_channel(); let rx = std::sync::Mutex::new(rx); Self { - tx, rx, + tx: std::sync::Arc::new(tx), + rx, } } } impl CallbackChannel { - fn send(&self, cb: LuaFunction, _arg: impl IntoLuaMulti) { - // let (tx, rx) = tokio::sync::oneshot::channel(); - // tx.send(arg); - self.tx.send((cb, CallbackArg)).unwrap_or_warn("error scheduling callback") + fn send(&self, cb: LuaFunction, arg: impl Into) { + self.tx.send((cb, arg.into())) + .unwrap_or_warn("error scheduling callback") } fn recv(&self) -> Option<(LuaFunction, CallbackArg)> { match self.rx.try_lock() { + Err(e) => { + tracing::warn!("could not acquire callback channel mutex: {e}"); + None + }, Ok(mut lock) => match lock.try_recv() { - Ok(res) => Some(res), Err(TryRecvError::Empty) => None, Err(TryRecvError::Disconnected) => { tracing::error!("callback channel closed"); None }, - }, - Err(e) => { - tracing::warn!("could not acquire callback channel mutex: {e}"); - None + Ok((cb, arg)) => Some((cb, arg)), }, } } @@ -99,9 +144,9 @@ lazy_static::lazy_static! { -struct Promise(Option>>); +struct Promise(Option>>); -impl LuaUserData for Promise { +impl LuaUserData for Promise { fn add_fields>(fields: &mut F) { fields.add_field_method_get("ready", |_, this| Ok(this.0.as_ref().map_or(true, |x| x.is_finished())) @@ -118,24 +163,20 @@ impl LuaUserData for Promise { .map_err(LuaError::runtime)? }, }); - // methods.add_method_mut("and_then", |_, this, (cb,):(LuaFunction,)| match this.0.take() { - // None => Err(LuaError::runtime("Promise already awaited")), - // Some(x) => { - // tokio() - // .spawn(async move { - // match x.await { - // Err(e) => tracing::error!("could not join promise to run callback: {e}"), - // Ok(Err(e)) => tracing::error!("promise returned error: {e}"), - // Ok(Ok(res)) => { - // if let Err(e) = cb.call::<()>(res) { - // tracing::error!("error running promise callback: {e}"); - // } - // }, - // } - // }); - // Ok(()) - // }, - // }); + methods.add_method_mut("and_then", |_, this, (cb,):(LuaFunction,)| match this.0.take() { + None => Err(LuaError::runtime("Promise already awaited")), + Some(x) => { + tokio() + .spawn(async move { + match x.await { + Err(e) => tracing::error!("could not join promise to run callback: {e}"), + Ok(Err(e)) => tracing::error!("promise returned error: {e}"), + Ok(Ok(res)) => CHANNEL.send(cb, res), + } + }); + Ok(()) + }, + }); } } @@ -143,7 +184,7 @@ macro_rules! a_sync { ($($clone:ident)* => $x:expr) => { { $(let $clone = $clone.clone();)* - Ok(Promise(Some(tokio().spawn(async move { $x })))) + Ok(Promise(Some(tokio().spawn(async move { Ok(CallbackArg::from($x)) })))) } }; } @@ -206,27 +247,27 @@ impl LuaUserData for CodempClient { methods.add_meta_method(LuaMetaMethod::ToString, |_, this, ()| Ok(format!("{:?}", this))); methods.add_method("refresh", |_, this, ()| - a_sync! { this => Ok(this.refresh().await?) } + a_sync! { this => this.refresh().await? } ); methods.add_method("join_workspace", |_, this, (ws,):(String,)| - a_sync! { this => Ok(this.join_workspace(ws).await?) } + a_sync! { this => this.join_workspace(ws).await? } ); methods.add_method("create_workspace", |_, this, (ws,):(String,)| - a_sync! { this => Ok(this.create_workspace(ws).await?) } + a_sync! { this => this.create_workspace(ws).await? } ); methods.add_method("delete_workspace", |_, this, (ws,):(String,)| - a_sync! { this => Ok(this.delete_workspace(ws).await?) } + a_sync! { this => this.delete_workspace(ws).await? } ); methods.add_method("invite_to_workspace", |_, this, (ws,user):(String,String)| - a_sync! { this => Ok(this.invite_to_workspace(ws, user).await?) } + a_sync! { this => this.invite_to_workspace(ws, user).await? } ); methods.add_method("list_workspaces", |_, this, (owned,invited):(Option,Option)| - a_sync! { this => Ok(this.list_workspaces(owned.unwrap_or(true), invited.unwrap_or(true)).await?) } + a_sync! { this => this.list_workspaces(owned.unwrap_or(true), invited.unwrap_or(true)).await? } ); methods.add_method("leave_workspace", |_, this, (ws,):(String,)| @@ -242,11 +283,11 @@ impl LuaUserData for CodempWorkspace { fn add_methods>(methods: &mut M) { methods.add_meta_method(LuaMetaMethod::ToString, |_, this, ()| Ok(format!("{:?}", this))); methods.add_method("create", |_, this, (name,):(String,)| - a_sync! { this => Ok(this.create(&name).await?) } + a_sync! { this => this.create(&name).await? } ); methods.add_method("attach", |_, this, (name,):(String,)| - a_sync! { this => Ok(this.attach(&name).await?) } + a_sync! { this => this.attach(&name).await? } ); methods.add_method("detach", |_, this, (name,):(String,)| @@ -254,34 +295,22 @@ impl LuaUserData for CodempWorkspace { ); methods.add_method("delete", |_, this, (name,):(String,)| - a_sync! { this => Ok(this.delete(&name).await?) } + a_sync! { this => this.delete(&name).await? } ); methods.add_method("get_buffer", |_, this, (name,):(String,)| Ok(this.buffer_by_name(&name))); methods.add_method("event", |_, this, ()| - a_sync! { this => Ok(this.event().await?) } + a_sync! { this => this.event().await? } ); methods.add_method("fetch_buffers", |_, this, ()| - a_sync! { this => Ok(this.fetch_buffers().await?) } + a_sync! { this => this.fetch_buffers().await? } ); methods.add_method("fetch_users", |_, this, ()| - a_sync! { this => Ok(this.fetch_users().await?) } + a_sync! { this => this.fetch_users().await? } ); - // methods.add_method("callback", |_, this, (cb,):(LuaFunction,)| { - // let _this = this.clone(); - // tokio().spawn(async move { - // while let Ok(ev) = _this.event().await { - // if let Err(e) = cb.call::<()>(ev) { - // tracing::error!("error running workspace callback: {e}"); - // } - // } - // }); - // Ok(()) - // }); - methods.add_method("filetree", |_, this, (filter, strict,):(Option, Option,)| Ok(this.filetree(filter.as_deref(), strict.unwrap_or(false))) ); @@ -322,13 +351,13 @@ impl LuaUserData for CodempCursorController { methods.add_meta_method(LuaMetaMethod::ToString, |_, this, ()| Ok(format!("{:?}", this))); methods.add_method("send", |_, this, (cursor,):(CodempCursor,)| - a_sync! { this => Ok(this.send(cursor).await?) } + a_sync! { this => this.send(cursor).await? } ); methods.add_method("try_recv", |_, this, ()| - a_sync! { this => Ok(this.try_recv().await?) } + a_sync! { this => this.try_recv().await? } ); - methods.add_method("recv", |_, this, ()| a_sync! { this => Ok(this.recv().await?) }); - methods.add_method("poll", |_, this, ()| a_sync! { this => Ok(this.poll().await?) }); + methods.add_method("recv", |_, this, ()| a_sync! { this => this.recv().await? }); + methods.add_method("poll", |_, this, ()| a_sync! { this => this.poll().await? }); methods.add_method("stop", |_, this, ()| Ok(this.stop())); @@ -361,16 +390,16 @@ impl LuaUserData for CodempBufferController { methods.add_meta_method(LuaMetaMethod::ToString, |_, this, ()| Ok(format!("{:?}", this))); methods.add_method("send", |_, this, (change,): (CodempTextChange,)| - a_sync! { this => Ok(this.send(change).await?)} + a_sync! { this => this.send(change).await? } ); - methods.add_method("try_recv", |_, this, ()| a_sync! { this => Ok(this.try_recv().await?) }); - methods.add_method("recv", |_, this, ()| a_sync! { this => Ok(this.recv().await?) }); - methods.add_method("poll", |_, this, ()| a_sync! { this => Ok(this.poll().await?) }); + methods.add_method("try_recv", |_, this, ()| a_sync! { this => this.try_recv().await? }); + methods.add_method("recv", |_, this, ()| a_sync! { this => this.recv().await? }); + methods.add_method("poll", |_, this, ()| a_sync! { this => this.poll().await? }); methods.add_method("stop", |_, this, ()| Ok(this.stop())); - methods.add_method("content", |_, this, ()| a_sync! { this => Ok(this.content().await?) }); + methods.add_method("content", |_, this, ()| a_sync! { this => this.content().await? }); methods.add_method("clear_callback", |_, this, ()| { this.clear_callback(); Ok(()) }); methods.add_method("callback", |_, this, (cb,):(LuaFunction,)| { @@ -510,7 +539,7 @@ fn entrypoint(lua: &Lua) -> LuaResult { // entrypoint exports.set("connect", lua.create_function(|_, (config,):(CodempConfig,)| - a_sync! { => Ok(CodempClient::connect(config).await?) } + a_sync! { => CodempClient::connect(config).await? } )?)?; // utils @@ -520,13 +549,14 @@ fn entrypoint(lua: &Lua) -> LuaResult { // runtime exports.set("spawn_runtime_driver", lua.create_function(spawn_runtime_driver)?)?; - exports.set("poll_callback", lua.create_function(|_, ()| { + exports.set("poll_callback", lua.create_function(|lua, ()| { // TODO pass args too - if let Some((cb, _arg)) = CHANNEL.recv() { - Ok(Some(cb)) - } else { - Ok(None) + let mut val = LuaMultiValue::new(); + if let Some((cb, arg)) = CHANNEL.recv() { + val.push_back(LuaValue::Function(cb)); + val.push_back(arg.into_lua(lua)?); } + Ok(val) })?)?; // logging