feat(java): callback API, buffer send

Co-authored-by: alemi <me@alemi.dev>
This commit is contained in:
zaaarf 2024-09-15 01:56:51 +02:00
parent a59d2c4648
commit 963f2b698c
No known key found for this signature in database
GPG key ID: 102E445F4C3F829B
4 changed files with 157 additions and 19 deletions

View file

@ -1,6 +1,6 @@
use jni::{objects::{JClass, JObject, JValueGen}, sys::{jlong, jobject, jstring}, JNIEnv}; use jni::{objects::{JClass, JObject, JValueGen}, sys::{jlong, jobject, jstring}, JNIEnv};
use crate::api::Controller; use crate::{api::Controller, ffi::java::handle_callback};
use super::{JExceptable, RT}; use super::{JExceptable, RT};
@ -87,3 +87,70 @@ fn recv_jni(env: &mut JNIEnv, change: Option<crate::api::TextChange>) -> jobject
} }
}.as_raw() }.as_raw()
} }
#[no_mangle]
pub extern "system" fn Java_mp_code_BufferController_callback<'local>(
mut env: JNIEnv,
_class: JClass<'local>,
self_ptr: jlong,
cb: JObject<'local>,
) {
handle_callback!("mp/code/BufferController", env, self_ptr, cb, crate::buffer::Controller);
}
/// Receive from Java, converts and sends a [crate::api::TextChange].
#[no_mangle]
pub extern "system" fn Java_mp_code_BufferController_send<'local>(
mut env: JNIEnv,
_class: JClass<'local>,
self_ptr: jlong,
input: JObject<'local>,
) {
let Ok(start) = env.get_field(&input, "start", "I")
.and_then(|sr| sr.i())
.jexcept(&mut env)
.try_into()
else {
env.throw_new("java/lang/IllegalArgumentException", "Start index cannot be negative!")
.expect("Failed to throw exception!");
return;
};
let Ok(end) = env.get_field(&input, "end", "I")
.and_then(|er| er.i())
.jexcept(&mut env)
.try_into()
else {
env.throw_new("java/lang/IllegalArgumentException", "End index cannot be negative!")
.expect("Failed to throw exception!");
return;
};
let content = env.get_field(&input, "content", "Ljava/lang/String;")
.and_then(|b| b.l())
.map(|b| b.into())
.jexcept(&mut env);
let content = env.get_string(&content)
.map(|b| b.into())
.jexcept(&mut env);
let hash = env.get_field(&input, "hash", "Ljava/util/OptionalLong")
.and_then(|hash| hash.l())
.and_then(|hash| {
if env.call_method(&hash, "isPresent", "()Z", &[]).and_then(|r| r.z()).jexcept(&mut env) {
env.call_method(&hash, "getAsLong", "()J", &[])
.and_then(|r| r.j())
.map(Some)
} else {
Ok(None)
}
}).jexcept(&mut env);
let controller = unsafe { Box::leak(Box::from_raw(self_ptr as *mut crate::buffer::Controller)) };
RT.block_on(controller.send(crate::api::TextChange {
start,
end,
content,
hash,
})).jexcept(&mut env);
}

View file

@ -1,7 +1,7 @@
use jni::{objects::{JClass, JObject, JString, JValueGen}, sys::{jlong, jobject}, JNIEnv}; use jni::{objects::{JClass, JObject, JString, JValueGen}, sys::{jlong, jobject}, JNIEnv};
use crate::api::Controller; use crate::api::Controller;
use super::{JExceptable, RT}; use super::{handle_callback, JExceptable, RT};
/// Try to fetch a [crate::api::Cursor], or returns null if there's nothing. /// Try to fetch a [crate::api::Cursor], or returns null if there's nothing.
#[no_mangle] #[no_mangle]
@ -56,6 +56,16 @@ fn jni_recv(env: &mut JNIEnv, cursor: Option<crate::api::Cursor>) -> jobject {
}.as_raw() }.as_raw()
} }
#[no_mangle]
pub extern "system" fn Java_mp_code_CursorController_callback<'local>(
mut env: JNIEnv,
_class: JClass<'local>,
self_ptr: jlong,
cb: JObject<'local>,
) {
handle_callback!("mp/code/CursorController", env, self_ptr, cb, crate::cursor::Controller);
}
/// Receive from Java, converts and sends a [crate::api::Cursor]. /// Receive from Java, converts and sends a [crate::api::Cursor].
#[no_mangle] #[no_mangle]
pub extern "system" fn Java_mp_code_CursorController_send<'local>( pub extern "system" fn Java_mp_code_CursorController_send<'local>(

View file

@ -105,7 +105,7 @@ impl<T> JExceptable<T> for Result<T, uuid::Error> where T: Default {
/// This is essentially the same as [TryInto], but that can't be emplemented on non-local types. /// This is essentially the same as [TryInto], but that can't be emplemented on non-local types.
pub(crate) trait JObjectify<'local> { pub(crate) trait JObjectify<'local> {
/// The error type, likely to be [jni::errors::Error]. /// The error type, likely to be [jni::errors::Error].
type Error; type Error: std::fmt::Debug;
/// Attempt to convert the given object to a [jni::objects::JObject]. /// Attempt to convert the given object to a [jni::objects::JObject].
fn jobjectify(self, env: &mut jni::JNIEnv<'local>) -> Result<jni::objects::JObject<'local>, Self::Error>; fn jobjectify(self, env: &mut jni::JNIEnv<'local>) -> Result<jni::objects::JObject<'local>, Self::Error>;
@ -113,7 +113,7 @@ pub(crate) trait JObjectify<'local> {
impl<'local> JObjectify<'local> for uuid::Uuid { impl<'local> JObjectify<'local> for uuid::Uuid {
type Error = jni::errors::Error; type Error = jni::errors::Error;
fn jobjectify(self, env: &mut jni::JNIEnv<'local>) -> Result<jni::objects::JObject<'local>, jni::errors::Error> { fn jobjectify(self, env: &mut jni::JNIEnv<'local>) -> Result<jni::objects::JObject<'local>, Self::Error> {
env.find_class("java/util/UUID").and_then(|class| { env.find_class("java/util/UUID").and_then(|class| {
let (msb, lsb) = self.as_u64_pair(); let (msb, lsb) = self.as_u64_pair();
let msb = i64::from_ne_bytes(msb.to_ne_bytes()); let msb = i64::from_ne_bytes(msb.to_ne_bytes());
@ -122,3 +122,71 @@ impl<'local> JObjectify<'local> for uuid::Uuid {
}) })
} }
} }
impl<'local> JObjectify<'local> for crate::cursor::Controller {
type Error = jni::errors::Error;
fn jobjectify(self, env: &mut jni::JNIEnv<'local>) -> Result<jni::objects::JObject<'local>, Self::Error> {
let class = env.find_class("mp/code/CursorController")?;
env.new_object(
class,
"(J)V",
&[
jni::objects::JValueGen::Long(Box::into_raw(Box::new(&self)) as jni::sys::jlong)
]
)
}
}
impl<'local> JObjectify<'local> for crate::buffer::Controller {
type Error = jni::errors::Error;
fn jobjectify(self, env: &mut jni::JNIEnv<'local>) -> Result<jni::objects::JObject<'local>, Self::Error> {
let class = env.find_class("mp/code/BufferController")?;
env.new_object(
class,
"(J)V",
&[
jni::objects::JValueGen::Long(Box::into_raw(Box::new(&self)) as jni::sys::jlong)
]
)
}
}
macro_rules! handle_callback {
($jtype:literal, $env:ident, $self_ptr:ident, $cb:ident, $t:ty) => {
let controller = unsafe { Box::leak(Box::from_raw($self_ptr as *mut $t)) };
let Ok(jvm) = $env.get_java_vm() else {
$env.throw_new("mp/code/exceptions/JNIException", "Failed to get JVM reference!")
.expect("Failed to throw exception!");
return;
};
let Ok(cb_ref) = $env.new_global_ref($cb) else {
$env.throw_new("mp/code/exceptions/JNIException", "Failed to pin callback reference!")
.expect("Failed to throw exception!");
return;
};
controller.callback(move |controller: $t| {
use std::ops::DerefMut;
use crate::ffi::java::JObjectify;
let mut guard = jvm.attach_current_thread().unwrap();
let jcontroller = match controller.jobjectify(guard.deref_mut()) {
Err(e) => return tracing::error!("could not convert callback argument: {e:?}"),
Ok(x) => x,
};
let sig = format!("(L{};)V", $jtype);
if let Err(e) = guard.call_method(&cb_ref,
"invoke",
&sig,
&[jni::objects::JValueGen::Object(&jcontroller)]
) {
tracing::error!("error invoking callback: {e:?}");
}
});
};
}
pub(crate) use handle_callback;

View file

@ -22,9 +22,7 @@ pub extern "system" fn Java_mp_code_Workspace_get_1cursor<'local>(
self_ptr: jlong self_ptr: jlong
) -> jobject { ) -> jobject {
let workspace = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Workspace)) }; let workspace = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Workspace)) };
env.find_class("mp/code/CursorController").and_then(|class| workspace.cursor().jobjectify(&mut env).jexcept(&mut env).as_raw()
env.new_object(class, "(J)V", &[JValueGen::Long(Box::into_raw(Box::new(workspace.cursor())) as jlong)])
).jexcept(&mut env).as_raw()
} }
/// Get a buffer controller by name and returns a pointer to it. /// Get a buffer controller by name and returns a pointer to it.
@ -39,12 +37,10 @@ pub extern "system" fn Java_mp_code_Workspace_get_1buffer<'local>(
let path = unsafe { env.get_string_unchecked(&input) } let path = unsafe { env.get_string_unchecked(&input) }
.map(|path| path.to_string_lossy().to_string()) .map(|path| path.to_string_lossy().to_string())
.jexcept(&mut env); .jexcept(&mut env);
workspace.buffer_by_name(&path)
workspace.buffer_by_name(&path).map(|buf| { .map(|buf| buf.jobjectify(&mut env).jexcept(&mut env))
env.find_class("mp/code/BufferController").and_then(|class| .unwrap_or_default()
env.new_object(class, "(J)V", &[JValueGen::Long(Box::into_raw(Box::new(buf)) as jlong)]) .as_raw()
).jexcept(&mut env)
}).unwrap_or_default().as_raw()
} }
/// Create a new buffer. /// Create a new buffer.
@ -108,12 +104,9 @@ pub extern "system" fn Java_mp_code_Workspace_attach_1to_1buffer<'local>(
.map(|path| path.to_string_lossy().to_string()) .map(|path| path.to_string_lossy().to_string())
.jexcept(&mut env); .jexcept(&mut env);
RT.block_on(workspace.attach(&path)) RT.block_on(workspace.attach(&path))
.map(|buffer| Box::into_raw(Box::new(buffer)) as jlong) .map(|buffer| buffer.jobjectify(&mut env).jexcept(&mut env))
.map(|ptr| { .jexcept(&mut env)
env.find_class("mp/code/BufferController") .as_raw()
.and_then(|class| env.new_object(class, "(J)V", &[JValueGen::Long(ptr)]))
.jexcept(&mut env)
}).jexcept(&mut env).as_raw()
} }
/// Detach from a buffer. /// Detach from a buffer.