feat: added Delta object to ffis

This commit is contained in:
əlemi 2024-10-06 10:18:58 +02:00 committed by alemi.dev
parent a318e3bc28
commit 45864e19f6
12 changed files with 107 additions and 62 deletions

37
dist/java/src/mp/code/data/Delta.java vendored Normal file
View file

@ -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();
}
}

View file

@ -135,28 +135,28 @@ function MaybeCursorPromise:cancel() end
function MaybeCursorPromise:and_then(cb) end function MaybeCursorPromise:and_then(cb) end
---@class (exact) TextChangePromise : Promise ---@class (exact) DeltaPromise : Promise
local TextChangePromise = {} local DeltaPromise = {}
--- block until promise is ready and return value --- block until promise is ready and return value
--- @return TextChange --- @return Delta
function TextChangePromise:await() end function DeltaPromise:await() end
--- cancel promise execution --- cancel promise execution
function TextChangePromise:cancel() end function DeltaPromise:cancel() end
---@param cb fun(x: TextChange) callback to invoke ---@param cb fun(x: Delta) callback to invoke
---invoke callback asynchronously as soon as promise is ready ---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 ---@class (exact) MaybeDeltaPromise : Promise
local MaybeTextChangePromise = {} local MaybeDeltaPromise = {}
--- block until promise is ready and return value --- block until promise is ready and return value
--- @return TextChange | nil --- @return Delta | nil
function MaybeTextChangePromise:await() end function MaybeDeltaPromise:await() end
--- cancel promise execution --- cancel promise execution
function MaybeTextChangePromise:cancel() end function MaybeDeltaPromise:cancel() end
---@param cb fun(x: TextChange | nil) callback to invoke ---@param cb fun(x: Delta | nil) callback to invoke
---invoke callback asynchronously as soon as promise is ready ---invoke callback asynchronously as soon as promise is ready
function MaybeTextChangePromise:and_then(cb) end function MaybeDeltaPromise:and_then(cb) end
-- [[ END ASYNC STUFF ]] -- [[ END ASYNC STUFF ]]
@ -325,6 +325,13 @@ local BufferController = {}
---@field hash integer? optional hash of text buffer after this change, for sync checks ---@field hash integer? optional hash of text buffer after this change, for sync checks
local TextChange = {} 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 ---@param other string text to apply change to
---apply this text change to a string, returning the result ---apply this text change to a string, returning the result
function TextChange:apply(other) end 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) ---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 function BufferController:send(change) end
---@return MaybeTextChangePromise ---@return MaybeDeltaPromise
---@async ---@async
---@nodiscard ---@nodiscard
---try to receive text changes, returning nil if none is available ---try to receive text changes, returning nil if none is available
function BufferController:try_recv() end function BufferController:try_recv() end
---@return TextChangePromise ---@return DeltaPromise
---@async ---@async
---@nodiscard ---@nodiscard
---block until next text change and return it ---block until next text change and return it

View file

@ -92,6 +92,14 @@ class TextChange:
def is_empty(self) -> bool: ... def is_empty(self) -> bool: ...
def apply(self, txt: str) -> str: ... 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: class BufferController:
""" """

View file

@ -45,22 +45,6 @@ pub struct TextChange {
pub hash: Option<i64>, pub hash: Option<i64>,
} }
/// This wrapper around a [`TextChange`] contains a handle to Acknowledge correct change
/// application
#[derive(Debug)]
pub struct Delta<T: Acknowledgeable> {
/// 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 { impl TextChange {
/// Returns the [`std::ops::Range`] representing this change's span. /// Returns the [`std::ops::Range`] representing this change's span.
pub fn span(&self) -> std::ops::Range<usize> { pub fn span(&self) -> std::ops::Range<usize> {

View file

@ -6,7 +6,6 @@ use std::sync::Arc;
use diamond_types::LocalVersion; use diamond_types::LocalVersion;
use tokio::sync::{mpsc, oneshot, watch}; use tokio::sync::{mpsc, oneshot, watch};
use crate::api::change::{Acknowledgeable, Delta};
use crate::api::controller::{AsyncReceiver, AsyncSender, Controller, ControllerCallback}; use crate::api::controller::{AsyncReceiver, AsyncSender, Controller, ControllerCallback};
use crate::api::TextChange; use crate::api::TextChange;
use crate::errors::ControllerResult; use crate::errors::ControllerResult;
@ -14,16 +13,30 @@ use crate::ext::IgnorableError;
use super::worker::DeltaRequest; 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)] #[derive(Clone, Debug)]
pub(crate) struct BufferAck { pub(crate) struct BufferAck {
pub(crate) tx: mpsc::UnboundedSender<LocalVersion>, pub(crate) tx: mpsc::UnboundedSender<LocalVersion>,
pub(crate) version: LocalVersion, pub(crate) version: LocalVersion,
} }
impl Acknowledgeable for BufferAck { #[cfg_attr(any(feature = "py", feature = "py-noabi"), pyo3::pymethods)]
fn send(&mut self) { #[cfg_attr(feature = "js", napi_derive::napi)]
self.tx impl Delta {
.send(self.version.clone()) pub fn ack(&mut self) {
self.ack.tx
.send(self.ack.version.clone())
.unwrap_or_warn("no worker to receive sent ack"); .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)] #[cfg_attr(feature = "async-trait", async_trait::async_trait)]
impl Controller<TextChange, Delta<BufferAck>> for BufferController {} impl Controller<TextChange, Delta> for BufferController {}
impl AsyncSender<TextChange> for BufferController { impl AsyncSender<TextChange> for BufferController {
fn send(&self, op: TextChange) -> ControllerResult<()> { fn send(&self, op: TextChange) -> ControllerResult<()> {
@ -75,7 +88,7 @@ impl AsyncSender<TextChange> for BufferController {
} }
#[cfg_attr(feature = "async-trait", async_trait::async_trait)] #[cfg_attr(feature = "async-trait", async_trait::async_trait)]
impl AsyncReceiver<Delta<BufferAck>> for BufferController { impl AsyncReceiver<Delta> for BufferController {
async fn poll(&self) -> ControllerResult<()> { async fn poll(&self) -> ControllerResult<()> {
if *self.0.local_version.borrow() != *self.0.latest_version.borrow() { if *self.0.local_version.borrow() != *self.0.latest_version.borrow() {
return Ok(()); return Ok(());
@ -87,7 +100,7 @@ impl AsyncReceiver<Delta<BufferAck>> for BufferController {
Ok(()) Ok(())
} }
async fn try_recv(&self) -> ControllerResult<Option<Delta<BufferAck>>> { async fn try_recv(&self) -> ControllerResult<Option<Delta>> {
let last_update = self.0.local_version.borrow().clone(); let last_update = self.0.local_version.borrow().clone();
let latest_version = self.0.latest_version.borrow().clone(); let latest_version = self.0.latest_version.borrow().clone();

View file

@ -6,16 +6,15 @@ use tokio::sync::{mpsc, oneshot, watch};
use tonic::Streaming; use tonic::Streaming;
use uuid::Uuid; use uuid::Uuid;
use crate::api::change::Delta;
use crate::api::controller::ControllerCallback; use crate::api::controller::ControllerCallback;
use crate::api::TextChange; use crate::api::TextChange;
use crate::ext::IgnorableError; use crate::ext::IgnorableError;
use codemp_proto::buffer::{BufferEvent, Operation}; use codemp_proto::buffer::{BufferEvent, Operation};
use super::controller::{BufferAck, BufferController, BufferControllerInner}; use super::controller::{BufferAck, BufferController, BufferControllerInner, Delta};
pub(crate) type DeltaOp = Option<Delta<BufferAck>>; pub(crate) type DeltaOp = Option<Delta>;
pub(crate) type DeltaRequest = (LocalVersion, oneshot::Sender<DeltaOp>); pub(crate) type DeltaRequest = (LocalVersion, oneshot::Sender<DeltaOp>);
struct BufferWorker { struct BufferWorker {

View file

@ -3,11 +3,10 @@ use jni_toolbox::jni;
use crate::{ use crate::{
api::{ api::{
change::Delta,
controller::{AsyncReceiver, AsyncSender}, controller::{AsyncReceiver, AsyncSender},
TextChange, TextChange,
}, },
buffer::controller::BufferAck, buffer::controller::Delta,
errors::ControllerError, errors::ControllerError,
}; };
@ -29,13 +28,13 @@ fn get_content(controller: &mut crate::buffer::Controller) -> Result<String, Con
#[jni(package = "mp.code", class = "BufferController")] #[jni(package = "mp.code", class = "BufferController")]
fn try_recv( fn try_recv(
controller: &mut crate::buffer::Controller, controller: &mut crate::buffer::Controller,
) -> Result<Option<Delta<BufferAck>>, ControllerError> { ) -> Result<Option<Delta>, ControllerError> {
super::tokio().block_on(controller.try_recv()) super::tokio().block_on(controller.try_recv())
} }
/// Block until it receives a [TextChange]. /// Block until it receives a [TextChange].
#[jni(package = "mp.code", class = "BufferController")] #[jni(package = "mp.code", class = "BufferController")]
fn recv(controller: &mut crate::buffer::Controller) -> Result<Delta<BufferAck>, ControllerError> { fn recv(controller: &mut crate::buffer::Controller) -> Result<Delta, ControllerError> {
super::tokio().block_on(controller.recv()) super::tokio().block_on(controller.recv())
} }

View file

@ -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::Workspace, "mp/code/Workspace");
into_java_ptr_class!(crate::cursor::Controller, "mp/code/CursorController"); 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, "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 { impl<'j> jni_toolbox::IntoJavaObject<'j> for crate::api::User {
const CLASS: &'static str = "mp/code/data/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::Workspace);
from_java_ptr!(crate::cursor::Controller); from_java_ptr!(crate::cursor::Controller);
from_java_ptr!(crate::buffer::Controller); from_java_ptr!(crate::buffer::Controller);
from_java_ptr!(crate::buffer::controller::Delta);
impl<'j> jni_toolbox::FromJava<'j> for crate::api::Config { impl<'j> jni_toolbox::FromJava<'j> for crate::api::Config {
type From = jni::objects::JObject<'j>; type From = jni::objects::JObject<'j>;

View file

@ -1,7 +1,6 @@
use crate::api::change::Delta;
use crate::api::controller::{AsyncReceiver, AsyncSender}; use crate::api::controller::{AsyncReceiver, AsyncSender};
use crate::api::TextChange; use crate::api::TextChange;
use crate::buffer::controller::BufferController; use crate::buffer::controller::{Delta, BufferController};
use napi::threadsafe_function::{ use napi::threadsafe_function::{
ErrorStrategy::Fatal, ThreadSafeCallContext, ThreadsafeFunction, ThreadsafeFunctionCallMode, ErrorStrategy::Fatal, ThreadSafeCallContext, ThreadsafeFunction, ThreadsafeFunctionCallMode,
}; };
@ -54,13 +53,13 @@ impl BufferController {
#[napi(js_name = "try_recv")] #[napi(js_name = "try_recv")]
pub async fn js_try_recv( pub async fn js_try_recv(
&self, &self,
) -> napi::Result<Option<Delta<crate::buffer::controller::BufferAck>>> { ) -> napi::Result<Option<Delta>> {
Ok(self.try_recv().await?) Ok(self.try_recv().await?)
} }
/// Wait for next buffer event and return it /// Wait for next buffer event and return it
#[napi(js_name = "recv")] #[napi(js_name = "recv")]
pub async fn js_recv(&self) -> napi::Result<Delta<crate::buffer::controller::BufferAck>> { pub async fn js_recv(&self) -> napi::Result<Delta> {
Ok(self.recv().await?) Ok(self.recv().await?)
} }

View file

@ -1,5 +1,4 @@
use crate::api::change::{Acknowledgeable, Delta}; use crate::buffer::controller::Delta;
use crate::buffer::controller::BufferAck;
use crate::prelude::*; use crate::prelude::*;
use mlua::prelude::*; use mlua::prelude::*;
use mlua_codemp_patch as mlua; use mlua_codemp_patch as mlua;
@ -62,15 +61,12 @@ impl LuaUserData for CodempTextChange {
} }
} }
impl LuaUserData for Delta<BufferAck> { impl LuaUserData for Delta {
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) { fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
fields.add_field_method_get("change", |_, this| Ok(this.change.clone())); 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<M: LuaUserDataMethods<Self>>(methods: &mut M) { fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
methods.add_method_mut("send", |_, this, ()| Ok(this.send())); methods.add_method_mut("ack", |_, this, ()| Ok(this.ack()));
} }
} }

View file

@ -1,5 +1,4 @@
use crate::api::change::Delta; use crate::buffer::controller::Delta;
use crate::buffer::controller::BufferAck;
use crate::ext::IgnorableError; use crate::ext::IgnorableError;
use crate::prelude::*; use crate::prelude::*;
use mlua::prelude::*; use mlua::prelude::*;
@ -113,6 +112,6 @@ callback_args! {
MaybeCursor: Option<CodempCursor>, MaybeCursor: Option<CodempCursor>,
TextChange: CodempTextChange, TextChange: CodempTextChange,
MaybeTextChange: Option<CodempTextChange>, MaybeTextChange: Option<CodempTextChange>,
Delta: Delta<BufferAck>, Delta: Delta,
MaybeDelta: Option<Delta<BufferAck>>, MaybeDelta: Option<Delta>,
} }

View file

@ -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. //! `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] /// java bindings, built with [jni]
#[cfg(feature = "java")] #[cfg(feature = "java")]
pub mod java; pub mod java;