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

feat: implement observe/observe_deep #76

Merged
merged 2 commits into from
Dec 5, 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
6 changes: 6 additions & 0 deletions lib/nif.ex
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,12 @@ defmodule Yex.Nif do

def xml_text_parent(_xml_text, _cur_txn), do: :erlang.nif_error(:nif_not_loaded)

def shared_type_observe(_map, _cur_txn, _pid, _ref, _metadata),
do: :erlang.nif_error(:nif_not_loaded)

def shared_type_observe_deep(_map, _cur_txn, _pid, _ref, _metadata),
do: :erlang.nif_error(:nif_not_loaded)

def encode_state_vector_v1(_doc, _cur_txn), do: :erlang.nif_error(:nif_not_loaded)
def encode_state_as_update_v1(_doc, _cur_txn, _diff), do: :erlang.nif_error(:nif_not_loaded)
def apply_update_v1(_doc, _cur_txn, _update), do: :erlang.nif_error(:nif_not_loaded)
Expand Down
5 changes: 1 addition & 4 deletions lib/protocols/awareness.ex
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
defmodule Yex.Awareness do
@moduledoc """
Awareness is an optional feature that works well together with Yjs.
## Examples
iex> doc = Yex.Doc.new()
iex> {:ok, _awareness} = Yex.Awareness.new(doc)
"""

defstruct [
Expand Down
111 changes: 111 additions & 0 deletions lib/shared_type/event.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
defmodule Yex.ArrayEvent do
@moduledoc """
Event when Array type changes

@see Yex.SharedType.observe/1
@see Yex.SharedType.observe_deep/1
"""
defstruct [
:path,
:target,
:change
]

@type t :: %__MODULE__{
path: list(number() | String.t()),
target: Yex.Array.t(),
change: %{insert: list()} | %{delete: number()} | %{}
}
end

defmodule Yex.MapEvent do
@moduledoc """

Event when Map type changes

@see Yex.SharedType.observe/1
@see Yex.SharedType.observe_deep/1
"""
defstruct [
:path,
:target,
:keys
]

@type change ::
%{action: :add, new_value: term()}
| %{action: :delete, old_value: term()}
| %{action: :update, old_value: term(), new_value: term()}
@type keys :: %{String.t() => %{}}

@type t :: %__MODULE__{
path: list(number() | String.t()),
target: Yex.Map.t(),
keys: keys
}
end

defmodule Yex.TextEvent do
@moduledoc """

Event when Text type changes

@see Yex.SharedType.observe/1
@see Yex.SharedType.observe_deep/1
"""
defstruct [
:path,
:target,
:delta
]

@type t :: %__MODULE__{
path: list(number() | String.t()),
target: Yex.Text.t(),
delta: Yex.Text.delta()
}
end

defmodule Yex.XmlEvent do
@moduledoc """

Event when XMLFragment/Element type changes

@see Yex.SharedType.observe/1
@see Yex.SharedType.observe_deep/1
"""
defstruct [
:path,
:target,
:delta,
:keys
]

@type t :: %__MODULE__{
path: list(number() | String.t()),
target: Yex.Map.t(),
satoren marked this conversation as resolved.
Show resolved Hide resolved
delta: Yex.Text.delta(),
keys: %{insert: list()} | %{delete: number()} | %{}
}
end

defmodule Yex.XmlTextEvent do
@moduledoc """

Event when Text type changes

@see Yex.SharedType.observe/1
@see Yex.SharedType.observe_deep/1
"""
defstruct [
:path,
:target,
:delta
]

@type t :: %__MODULE__{
path: list(number() | String.t()),
target: Yex.XmlText.t(),
delta: Yex.Text.delta()
}
end
2 changes: 1 addition & 1 deletion lib/shared_type/map.ex
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ defmodule Yex.Map do

@doc """
get a key from the map.
## Examples
## Examples
iex> doc = Yex.Doc.new()
iex> map = Yex.Doc.get_map(doc, "map")
iex> Yex.Map.set(map, "plane", ["Hello", "World"])
Expand Down
118 changes: 118 additions & 0 deletions lib/shared_type/shared_type.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
defmodule Yex.SharedType do
@moduledoc """
The SharedType protocol defines the behavior of shared types in Yex.

"""

@doc """
Registers a change observer that will be message every time this shared type is modified.

If the shared type changes, a message is delivered to the
monitoring process in the shape of:
{:observe_event, ref, event, origin, metadata}

where:
* `ref` is a monitor reference returned by this function;
* `event` is a struct that describes the change;
* `origin` is the origin passed to the `Yex.Doc.transaction()` function.
* `metadata` is the metadata passed to the `observe` function.

## Options
* `:metadata` - provides metadata to be attached to this observe.

"""

@type t ::
%Yex.Array{}
| %Yex.Map{}
| %Yex.Text{}
| %Yex.XmlElement{}
| %Yex.XmlText{}
| %Yex.XmlFragment{}

@spec observe(t, keyword()) :: reference()
def observe(shared_type, opt \\ []) do
ref = make_ref()

sub =
Yex.Nif.shared_type_observe(
shared_type,
cur_txn(shared_type),
self(),
ref,
Keyword.get(opt, :metadata)
)
satoren marked this conversation as resolved.
Show resolved Hide resolved

Process.put(ref, sub)

ref
end

@doc """
Unobserve the shared type for changes.

"""
@spec unobserve(reference()) :: :ok
def unobserve(observe_ref) do
unsubscribe(observe_ref)
end

@doc """
Registers a change observer that will be message every time this shared type or any of its children is modified.

If the shared type changes, a message is delivered to the
monitoring process in the shape of:
{:observe_deep_event, ref, events, origin, metadata}


where:
* `ref` is a monitor reference returned by this function;
* `events` is a array of event struct that describes the change;
* `origin` is the origin passed to the `Yex.Doc.transaction()` function.
* `metadata` is the metadata passed to the `observe_deep` function.

## Options
* `:metadata` - provides metadata to be attached to this observe.

"""
@spec observe_deep(t, keyword()) :: reference()
def observe_deep(shared_type, opt \\ []) do
ref = make_ref()

sub =
Yex.Nif.shared_type_observe_deep(
shared_type,
cur_txn(shared_type),
self(),
ref,
Keyword.get(opt, :metadata)
)

Process.put(ref, sub)
ref
end

@doc """
Unobserve the shared type for changes.

"""
@spec unobserve_deep(reference()) :: :ok
def unobserve_deep(observe_ref) do
unsubscribe(observe_ref)
end

defp cur_txn(%{doc: doc_ref}) do
Process.get(doc_ref, nil)
end

defp unsubscribe(ref) do
case Process.get(ref) do
nil ->
:ok

sub ->
Process.delete(ref)
Yex.Nif.sub_unsubscribe(sub)
end
end
satoren marked this conversation as resolved.
Show resolved Hide resolved
end
5 changes: 5 additions & 0 deletions native/yex/src/array.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use yrs::*;
use crate::{
atoms,
doc::{DocResource, TransactionResource},
event::{NifArrayEvent, NifSharedTypeDeepObservable, NifSharedTypeObservable},
shared_type::{NifSharedType, SharedTypeId},
yinput::NifYInput,
youtput::NifYOut,
Expand Down Expand Up @@ -39,6 +40,10 @@ impl NifSharedType for NifArray {
}
const DELETED_ERROR: &'static str = "Array has been deleted";
}
impl NifSharedTypeDeepObservable for NifArray {}
impl NifSharedTypeObservable for NifArray {
type Event = NifArrayEvent;
}

#[rustler::nif]
fn array_insert(
Expand Down
8 changes: 7 additions & 1 deletion native/yex/src/atoms.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ rustler::atoms! {
update_v1,
update_v2,


observe_event,
observe_deep_event,

// messages types
sync,
Expand All @@ -24,6 +25,11 @@ rustler::atoms! {
awareness_update,
awareness_change,

action,
old_value,
new_value,
add,
update,
insert,
delete,
retain,
Expand Down
Loading
Loading