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

Milestone #1 #6

Closed
dmonad opened this issue Apr 21, 2021 · 1 comment
Closed

Milestone #1 #6

dmonad opened this issue Apr 21, 2021 · 1 comment

Comments

@dmonad
Copy link
Contributor

dmonad commented Apr 21, 2021

Regarding M1:

Y.applyUpdate(Y.Doc, update:Uint8Array, [transactionOrigin:any])

Ideally I'd try to deserialize update in first step into Rust object, then apply that rust object as an update.

let updates: Vec<Update> = Vec::decode(&mut decoder);
doc.apply_updates(updates);

But I don't know how feasible is doing that upfront. If it's not then proposed API:

impl Doc {
  /// D is implemented by DecoderV1 or DecoderV2, eg.:
  /// ```rust
  /// let mut decoder = DecoverV1::new(binary);
  /// let mut doc = Doc::new();
  /// doc.apply_update(&mut decoder)?;
  /// ```
  pub fn apply_update<D: Decoder>(&mut self, decoder: &mut D) -> Result<()>;
}

Rationale: in Rust there's no sense in making static methods that operate over specific local variables eg. doc.apply_update(decoder) is the same thing as Doc::apply_update(doc, decoder). I also proposed using generics, as Rust will recognize them at call site and devirtualize. Downside of that approach is that generics cannot be exposed in WASM and C FFI. For that, we could work around different decoder version with:

pub enum Decoder { 
  V1(DecoderV1), 
  V2(DecoderV2) 
}

impl Doc {
  pub fn apply_update(&mut self, &mut decoder: Decoder) -> Result<()>;
}

I don't know about all possible use cases of transactionOrigin, so I didn't include it in the API.

Y.encodeStateAsUpdate(Y.Doc, [encodedTargetStateVector:Uint8Array]): Uint8Array

Would it be plausible to separate principles like update generation from serialization/deserialization?

impl Doc {
  pub fn get_updates_since(&self, version: &StateVector) -> Vec<Update>;
}

// Update implements Encode
pub struct Update {
  client_blocks: Vec<Block>,
  delete_set: DeleteSet,
}

let version = StateVector::decode(&mut decoderV1);
let doc = Doc::new();
let updates = doc.get_updates_since(version);
let mut output = Vec::new();
let mut encoder = EncoderV1::new(&mut output);
updates.encode(encoder);

Y.encodeStateVector(Y.Doc): Uint8Array

Similar as above?

impl Doc {
  pub fn state_vector(&self) -> &StateVector;
}

let doc = Doc::new();
let version = doc.state_vector();
let mut output = Vec::new();
let mut encoder = EncoderV1::new(&mut output);
version.encode(encoder);

I admit it's way easier to test them this way.

ydoc.on('update', eventHandler: function(update: Uint8Array, origin: any, doc: Y.Doc))

I must admit that I have the most problems thinking about this one. Rust is well known of using safe approach to pub/sub mechanics via introduction of Streams. However streams all pull-based, while Observables/EventHandlers represent push-based paradigm. However given C FFI and WASM support, we probably should just go with callback support. Therefore proposed API:

impl Doc {
  pub fn on_update<F: : Fn(&[u8], &Doc)->()>(&mut self, fn: F) -> Subscription
}

Subscription here implements drop trait → droping subscription object works as unsubscribe. Since I'm not sure what origin could, be I left it unrepresented.

Y.logUpdate(Uint8Array)

Originally posted by @Horusiath in #2 (comment)

@dmonad dmonad mentioned this issue Apr 21, 2021
1 task
@dmonad
Copy link
Contributor Author

dmonad commented Apr 21, 2021

I think the updates and the state vector should be computed from the transactions.

Additionally, we might want to implement something like the on_subscribe event as you described. It could fire when the Transaction is dropped. But let's only do that when we have an actual use-case for it. I imagine that a native application that wants to use Yrs would like to implement something on-top of the transaction system. E.g. Whenever something changes in a transaction, we apply the changes to the view, then we compute the update. While Yjs is a fully fledged framework that provides a full life-cycle for handling these events, I'd like that Yrs is more like a library that you can use to implement such an event-system. There are many opinions about event systems (especially when we export an FFI for Yrs). So the framework that uses Yrs should handle the events instead.

I don't know about all possible use cases of transactionOrigin, so I didn't include it in the API.

transactionOrigin is part of the opinionated framework. The described idea works great in languages that don't allow multithreading, like JavaScript.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants