From 45864e19f6336b69d69813f4840fb373078d457c Mon Sep 17 00:00:00 2001 From: alemi Date: Sun, 6 Oct 2024 10:18:58 +0200 Subject: [PATCH] feat: added Delta object to ffis --- dist/java/src/mp/code/data/Delta.java | 37 +++++++++++++++++++++++++ dist/lua/annotations.lua | 39 ++++++++++++++++----------- dist/py/src/codemp/codemp.pyi | 8 ++++++ src/api/change.rs | 16 ----------- src/buffer/controller.rs | 29 ++++++++++++++------ src/buffer/worker.rs | 5 ++-- src/ffi/java/buffer.rs | 7 +++-- src/ffi/java/mod.rs | 2 ++ src/ffi/js/buffer.rs | 7 +++-- src/ffi/lua/buffer.rs | 10 +++---- src/ffi/lua/ext/callback.rs | 7 +++-- src/ffi/mod.rs | 2 ++ 12 files changed, 107 insertions(+), 62 deletions(-) create mode 100644 dist/java/src/mp/code/data/Delta.java diff --git a/dist/java/src/mp/code/data/Delta.java b/dist/java/src/mp/code/data/Delta.java new file mode 100644 index 0000000..e04af1c --- /dev/null +++ b/dist/java/src/mp/code/data/Delta.java @@ -0,0 +1,37 @@ +package mp.code.data; + +import lombok.Getter; +import mp.code.data.Config; +import mp.code.data.User; +import mp.code.exceptions.ConnectionException; +import mp.code.exceptions.ConnectionRemoteException; + +import java.util.Optional; + +@Getter +public final class Delta { + private final long ptr; + + Delta(long ptr) { + this.ptr = ptr; + Extensions.CLEANER.register(this, () -> free(ptr)); + } + + private static native TextChange get_text_change(long self); + + public mp.code.data.TextChange getTextChange() { + return get_text_change(this.ptr); + } + + private static native void ack_native(long self, boolean success) throws ConnectionException; + + public void ack(boolean success) throws ConnectionException { + return ack_native(this.ptr, success); + } + + private static native void free(long self); + + static { + NativeUtils.loadLibraryIfNeeded(); + } +} diff --git a/dist/lua/annotations.lua b/dist/lua/annotations.lua index 0e573fa..895e324 100644 --- a/dist/lua/annotations.lua +++ b/dist/lua/annotations.lua @@ -135,28 +135,28 @@ function MaybeCursorPromise:cancel() end function MaybeCursorPromise:and_then(cb) end ----@class (exact) TextChangePromise : Promise -local TextChangePromise = {} +---@class (exact) DeltaPromise : Promise +local DeltaPromise = {} --- block until promise is ready and return value ---- @return TextChange -function TextChangePromise:await() end +--- @return Delta +function DeltaPromise:await() end --- cancel promise execution -function TextChangePromise:cancel() end ----@param cb fun(x: TextChange) callback to invoke +function DeltaPromise:cancel() end +---@param cb fun(x: Delta) callback to invoke ---invoke callback asynchronously as soon as promise is ready -function TextChangePromise:and_then(cb) end +function DeltaPromise:and_then(cb) end ----@class (exact) MaybeTextChangePromise : Promise -local MaybeTextChangePromise = {} +---@class (exact) MaybeDeltaPromise : Promise +local MaybeDeltaPromise = {} --- block until promise is ready and return value ---- @return TextChange | nil -function MaybeTextChangePromise:await() end +--- @return Delta | nil +function MaybeDeltaPromise:await() end --- cancel promise execution -function MaybeTextChangePromise:cancel() end ----@param cb fun(x: TextChange | nil) callback to invoke +function MaybeDeltaPromise:cancel() end +---@param cb fun(x: Delta | nil) callback to invoke ---invoke callback asynchronously as soon as promise is ready -function MaybeTextChangePromise:and_then(cb) end +function MaybeDeltaPromise:and_then(cb) end -- [[ END ASYNC STUFF ]] @@ -325,6 +325,13 @@ local BufferController = {} ---@field hash integer? optional hash of text buffer after this change, for sync checks local TextChange = {} +---@class (exact) Delta +---@field change TextChange text change for this delta +local Delta = {} + +---notify controller that this change has been correctly applied +function Delta:ack() end + ---@param other string text to apply change to ---apply this text change to a string, returning the result function TextChange:apply(other) end @@ -336,13 +343,13 @@ function TextChange:apply(other) end ---update buffer with a text change; note that to delete content should be empty but not span, while to insert span should be empty but not content (can insert and delete at the same time) function BufferController:send(change) end ----@return MaybeTextChangePromise +---@return MaybeDeltaPromise ---@async ---@nodiscard ---try to receive text changes, returning nil if none is available function BufferController:try_recv() end ----@return TextChangePromise +---@return DeltaPromise ---@async ---@nodiscard ---block until next text change and return it diff --git a/dist/py/src/codemp/codemp.pyi b/dist/py/src/codemp/codemp.pyi index 6c088c5..ead8f3c 100644 --- a/dist/py/src/codemp/codemp.pyi +++ b/dist/py/src/codemp/codemp.pyi @@ -92,6 +92,14 @@ class TextChange: def is_empty(self) -> bool: ... def apply(self, txt: str) -> str: ... +class Delta: + """ + A single editor delta event, wrapping a TextChange and the corresponding ACK channel + """ + change: TextChange + + def ack(self,) -> str: ... + class BufferController: """ diff --git a/src/api/change.rs b/src/api/change.rs index f41e342..5a2ebfe 100644 --- a/src/api/change.rs +++ b/src/api/change.rs @@ -45,22 +45,6 @@ pub struct TextChange { pub hash: Option, } -/// This wrapper around a [`TextChange`] contains a handle to Acknowledge correct change -/// application -#[derive(Debug)] -pub struct Delta { - /// The change received - pub change: TextChange, - /// The ack handle, must be called after correctly applying this change - pub ack: T, -} - -/// A token which can be used to acknowledge changes -pub trait Acknowledgeable { - /// Send Acknowledgement. This action is idempotent - fn send(&mut self); -} - impl TextChange { /// Returns the [`std::ops::Range`] representing this change's span. pub fn span(&self) -> std::ops::Range { diff --git a/src/buffer/controller.rs b/src/buffer/controller.rs index 5ff432c..4c55297 100644 --- a/src/buffer/controller.rs +++ b/src/buffer/controller.rs @@ -6,7 +6,6 @@ use std::sync::Arc; use diamond_types::LocalVersion; use tokio::sync::{mpsc, oneshot, watch}; -use crate::api::change::{Acknowledgeable, Delta}; use crate::api::controller::{AsyncReceiver, AsyncSender, Controller, ControllerCallback}; use crate::api::TextChange; use crate::errors::ControllerResult; @@ -14,16 +13,30 @@ use crate::ext::IgnorableError; use super::worker::DeltaRequest; +/// This wrapper around a [`TextChange`] contains a handle to Acknowledge correct change +/// application +#[derive(Debug)] +#[cfg_attr(any(feature = "py", feature = "py-noabi"), pyo3::pyclass)] +#[cfg_attr(feature = "js", napi_derive::napi)] +pub struct Delta { + /// The change received + pub change: TextChange, + /// The ack handle, must be called after correctly applying this change + pub(crate) ack: BufferAck, +} + #[derive(Clone, Debug)] pub(crate) struct BufferAck { pub(crate) tx: mpsc::UnboundedSender, pub(crate) version: LocalVersion, } -impl Acknowledgeable for BufferAck { - fn send(&mut self) { - self.tx - .send(self.version.clone()) +#[cfg_attr(any(feature = "py", feature = "py-noabi"), pyo3::pymethods)] +#[cfg_attr(feature = "js", napi_derive::napi)] +impl Delta { + pub fn ack(&mut self) { + self.ack.tx + .send(self.ack.version.clone()) .unwrap_or_warn("no worker to receive sent ack"); } } @@ -65,7 +78,7 @@ pub(crate) struct BufferControllerInner { } #[cfg_attr(feature = "async-trait", async_trait::async_trait)] -impl Controller> for BufferController {} +impl Controller for BufferController {} impl AsyncSender for BufferController { fn send(&self, op: TextChange) -> ControllerResult<()> { @@ -75,7 +88,7 @@ impl AsyncSender for BufferController { } #[cfg_attr(feature = "async-trait", async_trait::async_trait)] -impl AsyncReceiver> for BufferController { +impl AsyncReceiver for BufferController { async fn poll(&self) -> ControllerResult<()> { if *self.0.local_version.borrow() != *self.0.latest_version.borrow() { return Ok(()); @@ -87,7 +100,7 @@ impl AsyncReceiver> for BufferController { Ok(()) } - async fn try_recv(&self) -> ControllerResult>> { + async fn try_recv(&self) -> ControllerResult> { let last_update = self.0.local_version.borrow().clone(); let latest_version = self.0.latest_version.borrow().clone(); diff --git a/src/buffer/worker.rs b/src/buffer/worker.rs index 01826b8..610abf1 100644 --- a/src/buffer/worker.rs +++ b/src/buffer/worker.rs @@ -6,16 +6,15 @@ use tokio::sync::{mpsc, oneshot, watch}; use tonic::Streaming; use uuid::Uuid; -use crate::api::change::Delta; use crate::api::controller::ControllerCallback; use crate::api::TextChange; use crate::ext::IgnorableError; use codemp_proto::buffer::{BufferEvent, Operation}; -use super::controller::{BufferAck, BufferController, BufferControllerInner}; +use super::controller::{BufferAck, BufferController, BufferControllerInner, Delta}; -pub(crate) type DeltaOp = Option>; +pub(crate) type DeltaOp = Option; pub(crate) type DeltaRequest = (LocalVersion, oneshot::Sender); struct BufferWorker { diff --git a/src/ffi/java/buffer.rs b/src/ffi/java/buffer.rs index 1a5e2d8..c700fde 100644 --- a/src/ffi/java/buffer.rs +++ b/src/ffi/java/buffer.rs @@ -3,11 +3,10 @@ use jni_toolbox::jni; use crate::{ api::{ - change::Delta, controller::{AsyncReceiver, AsyncSender}, TextChange, }, - buffer::controller::BufferAck, + buffer::controller::Delta, errors::ControllerError, }; @@ -29,13 +28,13 @@ fn get_content(controller: &mut crate::buffer::Controller) -> Result Result>, ControllerError> { +) -> Result, ControllerError> { super::tokio().block_on(controller.try_recv()) } /// Block until it receives a [TextChange]. #[jni(package = "mp.code", class = "BufferController")] -fn recv(controller: &mut crate::buffer::Controller) -> Result, ControllerError> { +fn recv(controller: &mut crate::buffer::Controller) -> Result { super::tokio().block_on(controller.recv()) } diff --git a/src/ffi/java/mod.rs b/src/ffi/java/mod.rs index cad7768..0fb0980 100644 --- a/src/ffi/java/mod.rs +++ b/src/ffi/java/mod.rs @@ -141,6 +141,7 @@ into_java_ptr_class!(crate::Client, "mp/code/Client"); into_java_ptr_class!(crate::Workspace, "mp/code/Workspace"); into_java_ptr_class!(crate::cursor::Controller, "mp/code/CursorController"); into_java_ptr_class!(crate::buffer::Controller, "mp/code/BufferController"); +into_java_ptr_class!(crate::buffer::controller::Delta, "mp/code/data/Delta"); impl<'j> jni_toolbox::IntoJavaObject<'j> for crate::api::User { const CLASS: &'static str = "mp/code/data/User"; @@ -275,6 +276,7 @@ from_java_ptr!(crate::Client); from_java_ptr!(crate::Workspace); from_java_ptr!(crate::cursor::Controller); from_java_ptr!(crate::buffer::Controller); +from_java_ptr!(crate::buffer::controller::Delta); impl<'j> jni_toolbox::FromJava<'j> for crate::api::Config { type From = jni::objects::JObject<'j>; diff --git a/src/ffi/js/buffer.rs b/src/ffi/js/buffer.rs index e3a9f48..ea28eb0 100644 --- a/src/ffi/js/buffer.rs +++ b/src/ffi/js/buffer.rs @@ -1,7 +1,6 @@ -use crate::api::change::Delta; use crate::api::controller::{AsyncReceiver, AsyncSender}; use crate::api::TextChange; -use crate::buffer::controller::BufferController; +use crate::buffer::controller::{Delta, BufferController}; use napi::threadsafe_function::{ ErrorStrategy::Fatal, ThreadSafeCallContext, ThreadsafeFunction, ThreadsafeFunctionCallMode, }; @@ -54,13 +53,13 @@ impl BufferController { #[napi(js_name = "try_recv")] pub async fn js_try_recv( &self, - ) -> napi::Result>> { + ) -> napi::Result> { Ok(self.try_recv().await?) } /// Wait for next buffer event and return it #[napi(js_name = "recv")] - pub async fn js_recv(&self) -> napi::Result> { + pub async fn js_recv(&self) -> napi::Result { Ok(self.recv().await?) } diff --git a/src/ffi/lua/buffer.rs b/src/ffi/lua/buffer.rs index 945fa83..c7d4ec7 100644 --- a/src/ffi/lua/buffer.rs +++ b/src/ffi/lua/buffer.rs @@ -1,5 +1,4 @@ -use crate::api::change::{Acknowledgeable, Delta}; -use crate::buffer::controller::BufferAck; +use crate::buffer::controller::Delta; use crate::prelude::*; use mlua::prelude::*; use mlua_codemp_patch as mlua; @@ -62,15 +61,12 @@ impl LuaUserData for CodempTextChange { } } -impl LuaUserData for Delta { +impl LuaUserData for Delta { fn add_fields>(fields: &mut F) { fields.add_field_method_get("change", |_, this| Ok(this.change.clone())); - fields.add_field_method_get("ack", |_, this| Ok(this.ack.clone())); } -} -impl LuaUserData for BufferAck { fn add_methods>(methods: &mut M) { - methods.add_method_mut("send", |_, this, ()| Ok(this.send())); + methods.add_method_mut("ack", |_, this, ()| Ok(this.ack())); } } diff --git a/src/ffi/lua/ext/callback.rs b/src/ffi/lua/ext/callback.rs index d4baa91..b67757d 100644 --- a/src/ffi/lua/ext/callback.rs +++ b/src/ffi/lua/ext/callback.rs @@ -1,5 +1,4 @@ -use crate::api::change::Delta; -use crate::buffer::controller::BufferAck; +use crate::buffer::controller::Delta; use crate::ext::IgnorableError; use crate::prelude::*; use mlua::prelude::*; @@ -113,6 +112,6 @@ callback_args! { MaybeCursor: Option, TextChange: CodempTextChange, MaybeTextChange: Option, - Delta: Delta, - MaybeDelta: Option>, + Delta: Delta, + MaybeDelta: Option, } diff --git a/src/ffi/mod.rs b/src/ffi/mod.rs index fa03449..bc92147 100644 --- a/src/ffi/mod.rs +++ b/src/ffi/mod.rs @@ -43,6 +43,8 @@ //! `JNIException`s are however unchecked: there is nothing you can do to recover from them, as they usually represent a severe error in the glue code. If they arise, it's probably a bug. //! +#![allow(clippy::unit_arg)] + /// java bindings, built with [jni] #[cfg(feature = "java")] pub mod java;