-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Cursors can be used to obtain a reference to a position in a sequence or text object which is stable across concurrent changes.
- Loading branch information
Showing
10 changed files
with
423 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package org.automerge; | ||
|
||
public class Cursor { | ||
private byte[] raw; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
package org.automerge; | ||
|
||
import org.junit.jupiter.api.Assertions; | ||
import org.junit.jupiter.api.BeforeEach; | ||
import org.junit.jupiter.api.Test; | ||
|
||
public final class TestCursor { | ||
private Document doc; | ||
private ObjectId text; | ||
|
||
@BeforeEach | ||
public void setup() { | ||
doc = new Document(); | ||
try (Transaction tx = doc.startTransaction()) { | ||
text = tx.set(ObjectId.ROOT, "text", ObjectType.TEXT); | ||
tx.spliceText(text, 0, 0, "hello world"); | ||
tx.commit(); | ||
} | ||
} | ||
|
||
@Test | ||
public void testCursorInDoc() { | ||
Cursor cursor = doc.makeCursor(text, 3); | ||
Assertions.assertEquals(doc.lookupCursorIndex(text, cursor), 3); | ||
|
||
ChangeHash[] heads = doc.getHeads(); | ||
|
||
try (Transaction tx = doc.startTransaction()) { | ||
tx.spliceText(text, 3, 0, "!"); | ||
tx.commit(); | ||
} | ||
|
||
Assertions.assertEquals(doc.lookupCursorIndex(text, cursor), 4); | ||
Assertions.assertEquals(doc.lookupCursorIndex(text, cursor, heads), 3); | ||
|
||
Cursor oldCursor = doc.makeCursor(text, 3, heads); | ||
Assertions.assertEquals(doc.lookupCursorIndex(text, oldCursor), 4); | ||
Assertions.assertEquals(doc.lookupCursorIndex(text, oldCursor, heads), 3); | ||
|
||
} | ||
|
||
@Test | ||
public void testCursorInTx() { | ||
ChangeHash[] heads = doc.getHeads(); | ||
Cursor cursor; | ||
try (Transaction tx = doc.startTransaction()) { | ||
cursor = tx.makeCursor(text, 3); | ||
Assertions.assertEquals(tx.lookupCursorIndex(text, cursor), 3); | ||
tx.spliceText(text, 3, 0, "!"); | ||
Assertions.assertEquals(tx.lookupCursorIndex(text, cursor), 4); | ||
tx.commit(); | ||
} | ||
|
||
try (Transaction tx = doc.startTransaction()) { | ||
Cursor oldCursor = tx.makeCursor(text, 3, heads); | ||
Assertions.assertEquals(tx.lookupCursorIndex(text, oldCursor), 4); | ||
Assertions.assertEquals(tx.lookupCursorIndex(text, oldCursor, heads), 3); | ||
tx.commit(); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
use automerge_jni_macros::jni_fn; | ||
use jni::{ | ||
objects::{JObject, JString}, | ||
sys::{jbyteArray, jobject}, | ||
JNIEnv, | ||
}; | ||
|
||
use crate::AUTOMERGE_EXCEPTION; | ||
|
||
#[derive(Debug)] | ||
pub struct Cursor(automerge::Cursor); | ||
|
||
impl AsRef<automerge::Cursor> for Cursor { | ||
fn as_ref(&self) -> &automerge::Cursor { | ||
&self.0 | ||
} | ||
} | ||
|
||
impl From<automerge::Cursor> for Cursor { | ||
fn from(i: automerge::Cursor) -> Self { | ||
Self(i) | ||
} | ||
} | ||
|
||
const CLASSNAME: &str = am_classname!("Cursor"); | ||
|
||
impl Cursor { | ||
pub(crate) fn into_raw(self, env: &JNIEnv) -> Result<jobject, jni::errors::Error> { | ||
Ok(self.into_jobject(env)?.into_raw()) | ||
} | ||
|
||
pub(crate) fn into_jobject<'a>( | ||
self, | ||
env: &JNIEnv<'a>, | ||
) -> Result<JObject<'a>, jni::errors::Error> { | ||
let raw_obj = env.alloc_object(CLASSNAME)?; | ||
let bytes = self.0.to_bytes(); | ||
let jbytes = env.byte_array_from_slice(&bytes)?; | ||
env.set_field( | ||
raw_obj, | ||
"raw", | ||
"[B", | ||
unsafe { JObject::from_raw(jbytes) }.into(), | ||
)?; | ||
Ok(raw_obj) | ||
} | ||
|
||
pub(crate) unsafe fn from_raw(env: &JNIEnv<'_>, raw: jobject) -> Result<Self, errors::FromRaw> { | ||
let obj = JObject::from_raw(raw); | ||
let bytes_jobject = env | ||
.get_field(obj, "raw", "[B") | ||
.map_err(errors::FromRaw::GetRaw)? | ||
.l() | ||
.map_err(errors::FromRaw::RawPointerNotObject)? | ||
.into_raw() as jbyteArray; | ||
let arr = env | ||
.get_byte_array_elements(bytes_jobject, jni::objects::ReleaseMode::NoCopyBack) | ||
.map_err(errors::FromRaw::GetByteArray)?; | ||
let bytes = | ||
std::slice::from_raw_parts(arr.as_ptr() as *const u8, arr.size().unwrap() as usize); | ||
let cursor: automerge::Cursor = | ||
bytes.try_into().map_err(errors::FromRaw::Invalid)?; | ||
Ok(Self(cursor)) | ||
} | ||
} | ||
|
||
#[no_mangle] | ||
#[jni_fn] | ||
pub unsafe extern "C" fn cursorToString( | ||
env: jni::JNIEnv, | ||
_class: jni::objects::JClass, | ||
obj: jni::sys::jobject, | ||
) -> jni::sys::jobject { | ||
let cursor = Cursor::from_raw(&env, obj).unwrap(); | ||
let s = cursor.as_ref().to_string(); | ||
let jstr = env.new_string(s).unwrap(); | ||
jstr.into_raw() | ||
} | ||
|
||
#[no_mangle] | ||
#[jni_fn] | ||
pub unsafe extern "C" fn cursorFromString( | ||
env: jni::JNIEnv, | ||
_class: jni::objects::JClass, | ||
s: jni::sys::jstring, | ||
) -> jobject { | ||
let s = env.get_string(JString::from_raw(s)).unwrap(); | ||
let Ok(s) = s.to_str() else { | ||
env.throw_new(AUTOMERGE_EXCEPTION, "invalid cursor string") | ||
.unwrap(); | ||
return JObject::null().into_raw(); | ||
}; | ||
let Ok(cursor) = automerge::Cursor::try_from(s) else { | ||
env.throw_new(AUTOMERGE_EXCEPTION, "invalid cursor string") | ||
.unwrap(); | ||
return JObject::null().into_raw(); | ||
}; | ||
Cursor::from(cursor).into_raw(&env).unwrap() | ||
} | ||
|
||
pub mod errors { | ||
use super::CLASSNAME; | ||
|
||
#[derive(Debug, thiserror::Error)] | ||
pub(crate) enum FromRaw { | ||
#[error("unable to get the 'raw' field: {0} for class {}", CLASSNAME)] | ||
GetRaw(jni::errors::Error), | ||
#[error("could not convert the 'raw' pointer to an object: {0}")] | ||
RawPointerNotObject(jni::errors::Error), | ||
#[error("error getting byte array from object: {0}")] | ||
GetByteArray(jni::errors::Error), | ||
#[error("invalid ID")] | ||
Invalid(automerge::AutomergeError), | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,6 +8,7 @@ macro_rules! am_classname { | |
} | ||
|
||
mod conflicts; | ||
mod cursor; | ||
mod document; | ||
mod expand_mark; | ||
mod interop; | ||
|
Oops, something went wrong.
2f2a5cf
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very nice!
A thought occurs: If I need to communicate a cursor to a peer, is there a way to serialize it? Maybe
.toString
might suffice, especially if this gives interop with JS.2f2a5cf
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah yeah I'll add both
toString
andtoBytes
(or similar). Thebytes
which theCursor
wraps are actually already serialized in a forward compatible form so would be preferable to use if interop is not a concern.2f2a5cf
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm up for anything that the JS counterpart can consume, so I'm not sure bytes will be immediately useful, but it can't hurt.
Speaking of which
from
bytes/string might be nice for symmetry.