Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BREAKING CHANGE: defaults to v2 format #16

Merged
merged 1 commit into from
Jul 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 22 additions & 13 deletions lib/doc.ex
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ defmodule Yex.Doc do
end
end

def monitor_update(%__MODULE__{} = doc) do
monitor_update_v2(doc)
end

def monitor_update_v1(%__MODULE__{} = doc) do
case Yex.Nif.doc_monitor_update_v1(doc, self()) do
{:ok, ref} ->
Expand All @@ -81,23 +85,28 @@ defmodule Yex.Doc do
end
end

# def monitor_update_v2(%__MODULE__{} = doc) do
# case Yex.Nif.doc_monitor_update_v2(doc, self()) do
# {:ok, ref} ->
# # Subscription should not be automatically released by gc, so put it in the process dictionary
# Process.put(__MODULE__.Subscriptions, [ref | Process.get(__MODULE__.Subscriptions, [])])
# {:ok, ref}
# error ->
# error
# end
# end
def monitor_update_v2(%__MODULE__{} = doc) do
case Yex.Nif.doc_monitor_update_v2(doc, self()) do
{:ok, ref} ->
# Subscription should not be automatically released by gc, so put it in the process dictionary
Process.put(__MODULE__.Subscriptions, [ref | Process.get(__MODULE__.Subscriptions, [])])
{:ok, ref}

error ->
error
end
end

def demonitor_update(sub) do
demonitor_update_v2(sub)
end

def demonitor_update_v1(sub) do
Process.put(__MODULE__.Subscriptions, Process.get() |> Enum.reject(&(&1 == sub)))
Yex.Nif.sub_unsubscribe(sub)
end

# def demonitor_update_v2(sub) do
# Yex.Nif.sub_unsubscribe(sub)
# end
def demonitor_update_v2(sub) do
Yex.Nif.sub_unsubscribe(sub)
end
end
10 changes: 7 additions & 3 deletions lib/nif.ex
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,11 @@ defmodule Yex.Nif do
def map_to_map(_map), do: :erlang.nif_error(:nif_not_loaded)
def map_to_json(_map), do: :erlang.nif_error(:nif_not_loaded)

def encode_state_vector(_doc), do: :erlang.nif_error(:nif_not_loaded)
def encode_state_as_update(_doc, _diff), do: :erlang.nif_error(:nif_not_loaded)
def apply_update(_doc, _update), do: :erlang.nif_error(:nif_not_loaded)
def encode_state_vector_v1(_doc), do: :erlang.nif_error(:nif_not_loaded)
def encode_state_as_update_v1(_doc, _diff), do: :erlang.nif_error(:nif_not_loaded)
def apply_update_v1(_doc, _update), do: :erlang.nif_error(:nif_not_loaded)

def encode_state_vector_v2(_doc), do: :erlang.nif_error(:nif_not_loaded)
def encode_state_as_update_v2(_doc, _diff), do: :erlang.nif_error(:nif_not_loaded)
def apply_update_v2(_doc, _update), do: :erlang.nif_error(:nif_not_loaded)
end
36 changes: 29 additions & 7 deletions lib/y_ex.ex
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,11 @@ defmodule Yex do

## Examples
iex> doc = Yex.Doc.new()
iex> Yex.encode_state_vector(doc)
{:ok, <<0>>}
iex> {:ok, _binary} = Yex.encode_state_vector(doc)
"""
@spec encode_state_vector(Yex.Doc.t()) :: {:ok, binary()} | {:error, term()}
def encode_state_vector(%Yex.Doc{} = doc) do
Yex.Nif.encode_state_vector(doc)
encode_state_vector_v2(doc)
end

@spec encode_state_vector!(Yex.Doc.t()) :: {:ok, binary()} | {:error, term()}
Expand All @@ -24,17 +23,24 @@ defmodule Yex do
end
end

def encode_state_vector_v1(%Yex.Doc{} = doc) do
Yex.Nif.encode_state_vector_v1(doc)
end

def encode_state_vector_v2(%Yex.Doc{} = doc) do
Yex.Nif.encode_state_vector_v2(doc)
end

@doc """
Encode the document state as a single update message that can be applied on the remote document. Optionally, specify the target state vector to only write the missing differences to the update message.

## Examples
iex> doc = Yex.Doc.new()
iex> Yex.encode_state_as_update(doc)
{:ok, <<0, 0>>}
iex> {:ok, _binary} = Yex.encode_state_as_update(doc)
"""
@spec encode_state_as_update(Yex.Doc.t(), binary()) :: {:ok, binary()} | {:error, term()}
def encode_state_as_update(%Yex.Doc{} = doc, encoded_state_vector \\ nil) do
Yex.Nif.encode_state_as_update(doc, encoded_state_vector)
encode_state_as_update_v2(doc, encoded_state_vector)
end

@spec encode_state_as_update!(Yex.Doc.t(), binary()) :: binary()
Expand All @@ -45,6 +51,14 @@ defmodule Yex do
end
end

def encode_state_as_update_v1(%Yex.Doc{} = doc, encoded_state_vector \\ nil) do
Yex.Nif.encode_state_as_update_v1(doc, encoded_state_vector)
end

def encode_state_as_update_v2(%Yex.Doc{} = doc, encoded_state_vector \\ nil) do
Yex.Nif.encode_state_as_update_v2(doc, encoded_state_vector)
end

@doc """
Apply a document update on the shared document.

Expand All @@ -60,7 +74,15 @@ defmodule Yex do
"""
@spec apply_update(Yex.Doc.t(), binary()) :: :ok
def apply_update(%Yex.Doc{} = doc, update) do
Yex.Nif.apply_update(doc, update) |> unwrap_ok_tuple()
apply_update_v2(doc, update)
end

def apply_update_v1(%Yex.Doc{} = doc, update) do
Yex.Nif.apply_update_v1(doc, update) |> unwrap_ok_tuple()
end

def apply_update_v2(%Yex.Doc{} = doc, update) do
Yex.Nif.apply_update_v2(doc, update) |> unwrap_ok_tuple()
end

defp unwrap_ok_tuple({:ok, {}}), do: :ok
Expand Down
2 changes: 1 addition & 1 deletion mix.exs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
defmodule Yex.MixProject do
use Mix.Project

@version "0.0.5"
@version "0.1.0"
@repo "https://github.com/satoren/y_ex"

@description """
Expand Down
67 changes: 55 additions & 12 deletions native/yex/src/doc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ use crate::subscription::SubscriptionResource;
use crate::wrap::encode_binary_slice_to_term;
use crate::{atoms, ENV};
use rustler::{LocalPid, NifStruct, NifUnitEnum, ResourceArc};
use yrs::updates::decoder::{Decode, DecoderV1};
use yrs::updates::encoder::{Encode, Encoder, EncoderV1};
use yrs::updates::decoder::Decode;
use yrs::updates::encoder::Encode;
use yrs::*;

use crate::{wrap::NifWrap, NifArray, NifError, NifMap, NifText};
Expand Down Expand Up @@ -152,7 +152,7 @@ impl NifDoc {
*self.reference.current_transaction.borrow_mut() = None;
}

pub fn encode_state_vector(&self) -> Result<Vec<u8>, NifError> {
pub fn encode_state_vector_v1(&self) -> Result<Vec<u8>, NifError> {
if let Some(txn) = self.reference.current_transaction.borrow_mut().as_mut() {
Ok(txn.state_vector().encode_v1())
} else {
Expand All @@ -161,10 +161,55 @@ impl NifDoc {
Ok(txn.state_vector().encode_v1())
}
}
pub fn encode_state_as_update(&self, state_vector: Option<&[u8]>) -> Result<Vec<u8>, NifError> {
let mut encoder = EncoderV1::new();
pub fn encode_state_as_update_v1(
&self,
state_vector: Option<&[u8]>,
) -> Result<Vec<u8>, NifError> {
let sv = if let Some(vector) = state_vector {
StateVector::decode_v1(vector).map_err(|e| NifError {
reason: atoms::encoding_exception(),
message: e.to_string(),
})?
} else {
StateVector::default()
};

if let Some(txn) = self.reference.current_transaction.borrow_mut().as_mut() {
Ok(txn.encode_diff_v1(&sv))
} else {
let txn = self.reference.doc.transact();

Ok(txn.encode_diff_v1(&sv))
}
}
pub fn apply_update_v1(&self, update: &[u8]) -> Result<(), NifError> {
let update = Update::decode_v1(update).map_err(|e| NifError {
reason: atoms::encoding_exception(),
message: e.to_string(),
})?;
if let Some(txn) = self.reference.current_transaction.borrow_mut().as_mut() {
txn.apply_update(update);
} else {
let mut txn = self.reference.doc.transact_mut();
txn.apply_update(update);
}
Ok(())
}
pub fn encode_state_vector_v2(&self) -> Result<Vec<u8>, NifError> {
if let Some(txn) = self.reference.current_transaction.borrow_mut().as_mut() {
Ok(txn.state_vector().encode_v2())
} else {
let txn = self.reference.doc.transact();

Ok(txn.state_vector().encode_v2())
}
}
pub fn encode_state_as_update_v2(
&self,
state_vector: Option<&[u8]>,
) -> Result<Vec<u8>, NifError> {
let sv = if let Some(vector) = state_vector {
StateVector::decode_v1(vector.to_vec().as_slice()).map_err(|e| NifError {
StateVector::decode_v2(vector).map_err(|e| NifError {
reason: atoms::encoding_exception(),
message: e.to_string(),
})?
Expand All @@ -173,17 +218,15 @@ impl NifDoc {
};

if let Some(txn) = self.reference.current_transaction.borrow_mut().as_mut() {
txn.encode_diff(&sv, &mut encoder)
Ok(txn.encode_diff_v2(&sv))
} else {
let txn = self.reference.doc.transact();

txn.encode_diff(&sv, &mut encoder)
Ok(txn.encode_diff_v2(&sv))
}
Ok(encoder.to_vec())
}
pub fn apply_update(&self, update: &[u8]) -> Result<(), NifError> {
let mut decoder = DecoderV1::from(update);
let update = Update::decode(&mut decoder).map_err(|e| NifError {
pub fn apply_update_v2(&self, update: &[u8]) -> Result<(), NifError> {
let update = Update::decode_v2(update).map_err(|e| NifError {
reason: atoms::encoding_exception(),
message: e.to_string(),
})?;
Expand Down
36 changes: 30 additions & 6 deletions native/yex/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -239,27 +239,51 @@ fn sub_unsubscribe(env: Env<'_>, sub: ResourceArc<SubscriptionResource>) -> Resu
}

#[rustler::nif]
fn encode_state_vector(env: Env<'_>, doc: NifDoc) -> Result<Term<'_>, NifError> {
fn encode_state_vector_v1(env: Env<'_>, doc: NifDoc) -> Result<Term<'_>, NifError> {
ENV.set(&mut env.clone(), || {
doc.encode_state_vector()
doc.encode_state_vector_v1()
.map(|vec| encode_binary_slice_to_term(env, vec.as_slice()))
})
}

#[rustler::nif]
fn encode_state_as_update<'a>(
fn encode_state_as_update_v1<'a>(
env: Env<'a>,
doc: NifDoc,
state_vector: Option<Binary>,
) -> Result<Term<'a>, NifError> {
ENV.set(&mut env.clone(), || {
doc.encode_state_as_update(state_vector.map(|b| b.as_slice()))
doc.encode_state_as_update_v1(state_vector.map(|b| b.as_slice()))
.map(|vec| encode_binary_slice_to_term(env, vec.as_slice()))
})
}
#[rustler::nif]
fn apply_update(env: Env<'_>, doc: NifDoc, update: Binary) -> Result<(), NifError> {
ENV.set(&mut env.clone(), || doc.apply_update(update.as_slice()))
fn apply_update_v1(env: Env<'_>, doc: NifDoc, update: Binary) -> Result<(), NifError> {
ENV.set(&mut env.clone(), || doc.apply_update_v1(update.as_slice()))
}

#[rustler::nif]
fn encode_state_vector_v2(env: Env<'_>, doc: NifDoc) -> Result<Term<'_>, NifError> {
ENV.set(&mut env.clone(), || {
doc.encode_state_vector_v2()
.map(|vec| encode_binary_slice_to_term(env, vec.as_slice()))
})
}

#[rustler::nif]
fn encode_state_as_update_v2<'a>(
env: Env<'a>,
doc: NifDoc,
state_vector: Option<Binary>,
) -> Result<Term<'a>, NifError> {
ENV.set(&mut env.clone(), || {
doc.encode_state_as_update_v2(state_vector.map(|b| b.as_slice()))
.map(|vec| encode_binary_slice_to_term(env, vec.as_slice()))
})
}
#[rustler::nif]
fn apply_update_v2(env: Env<'_>, doc: NifDoc, update: Binary) -> Result<(), NifError> {
ENV.set(&mut env.clone(), || doc.apply_update_v2(update.as_slice()))
}

rustler::init!("Elixir.Yex.Nif");
22 changes: 11 additions & 11 deletions test/doc_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -47,21 +47,21 @@ defmodule Yex.DocTest do
assert Text.to_string(text2) == "Hello"
end

test "monitor_update_v1" do
test "monitor_update" do
doc = Doc.new()
{:ok, monitor_ref} = Doc.monitor_update_v1(doc)
{:ok, monitor_ref} = Doc.monitor_update(doc)

text1 = Doc.get_text(doc, "text")
Text.insert(text1, 0, "HelloWorld")

assert Text.to_string(text1) == "HelloWorld"
assert_receive {:update_v1, _update, nil, ^doc}
Doc.demonitor_update_v1(monitor_ref)
assert_receive {:update_v2, _update, nil, ^doc}
Doc.demonitor_update(monitor_ref)
end

test "monitor_update_v1 with transaction" do
test "monitor_update with transaction" do
doc = Doc.new()
{:ok, monitor_ref} = Doc.monitor_update_v1(doc)
{:ok, monitor_ref} = Doc.monitor_update(doc)

text1 = Doc.get_text(doc, "text")

Expand All @@ -71,25 +71,25 @@ defmodule Yex.DocTest do
end)

assert Text.to_string(text1) == "HelloWorld"
assert_receive {:update_v1, _update, nil, ^doc}
Doc.demonitor_update_v1(monitor_ref)
assert_receive {:update_v2, _update, nil, ^doc}
Doc.demonitor_update(monitor_ref)
end

test "apply_update from update event" do
doc = Doc.new()
{:ok, monitor_ref} = Doc.monitor_update_v1(doc)
{:ok, monitor_ref} = Doc.monitor_update(doc)

text1 = Doc.get_text(doc, "text")
Text.insert(text1, 0, "HelloWorld")

assert Text.to_string(text1) == "HelloWorld"
assert_receive {:update_v1, update, nil, ^doc}
assert_receive {:update_v2, update, nil, ^doc}

doc2 = Doc.new()
:ok = Yex.apply_update(doc2, update)
text2 = Doc.get_text(doc2, "text")
assert Text.to_string(text2) == "HelloWorld"

Doc.demonitor_update_v1(monitor_ref)
Doc.demonitor_update(monitor_ref)
end
end
Loading