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
---@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

View file

@ -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:
"""

View file

@ -45,22 +45,6 @@ pub struct TextChange {
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 {
/// Returns the [`std::ops::Range`] representing this change's span.
pub fn span(&self) -> std::ops::Range<usize> {

View file

@ -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<LocalVersion>,
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<TextChange, Delta<BufferAck>> for BufferController {}
impl Controller<TextChange, Delta> for BufferController {}
impl AsyncSender<TextChange> for BufferController {
fn send(&self, op: TextChange) -> ControllerResult<()> {
@ -75,7 +88,7 @@ impl AsyncSender<TextChange> for BufferController {
}
#[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<()> {
if *self.0.local_version.borrow() != *self.0.latest_version.borrow() {
return Ok(());
@ -87,7 +100,7 @@ impl AsyncReceiver<Delta<BufferAck>> for BufferController {
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 latest_version = self.0.latest_version.borrow().clone();

View file

@ -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<Delta<BufferAck>>;
pub(crate) type DeltaOp = Option<Delta>;
pub(crate) type DeltaRequest = (LocalVersion, oneshot::Sender<DeltaOp>);
struct BufferWorker {

View file

@ -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<String, Con
#[jni(package = "mp.code", class = "BufferController")]
fn try_recv(
controller: &mut crate::buffer::Controller,
) -> Result<Option<Delta<BufferAck>>, ControllerError> {
) -> Result<Option<Delta>, 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<Delta<BufferAck>, ControllerError> {
fn recv(controller: &mut crate::buffer::Controller) -> Result<Delta, ControllerError> {
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::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>;

View file

@ -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<Option<Delta<crate::buffer::controller::BufferAck>>> {
) -> napi::Result<Option<Delta>> {
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<Delta<crate::buffer::controller::BufferAck>> {
pub async fn js_recv(&self) -> napi::Result<Delta> {
Ok(self.recv().await?)
}

View file

@ -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<BufferAck> {
impl LuaUserData for Delta {
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("ack", |_, this| Ok(this.ack.clone()));
}
}
impl LuaUserData for BufferAck {
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::BufferAck;
use crate::buffer::controller::Delta;
use crate::ext::IgnorableError;
use crate::prelude::*;
use mlua::prelude::*;
@ -113,6 +112,6 @@ callback_args! {
MaybeCursor: Option<CodempCursor>,
TextChange: CodempTextChange,
MaybeTextChange: Option<CodempTextChange>,
Delta: Delta<BufferAck>,
MaybeDelta: Option<Delta<BufferAck>>,
Delta: Delta,
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.
//!
#![allow(clippy::unit_arg)]
/// java bindings, built with [jni]
#[cfg(feature = "java")]
pub mod java;