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

UndoManager #40

Merged
merged 11 commits into from
Mar 5, 2024
66 changes: 66 additions & 0 deletions Sources/YSwift/Protocols.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import Yniffi

/// A type that contains a reference to Yrs shared collection type.
public protocol YCollection {
func sharedHandle() -> YrsSharedRef;
}


public protocol OriginProtocol {
Horusiath marked this conversation as resolved.
Show resolved Hide resolved
//TODO: this should be fine for any type representable and comparable as [UInt8]
func asOrigin() -> Origin;
}

extension String: OriginProtocol {
public func asOrigin() -> Origin {

}
}

extension UInt8: OriginProtocol {
public func asOrigin() -> Origin {

}
}

extension UInt16: OriginProtocol {
public func asOrigin() -> Origin {
<#code#>
}
}

extension UInt32: OriginProtocol {
public func asOrigin() -> Origin {
<#code#>
}
}

extension UInt64: OriginProtocol {
public func asOrigin() -> Origin {
<#code#>
}
}

extension Int8: OriginProtocol {
public func asOrigin() -> Origin {
<#code#>
}
}

extension Int16: OriginProtocol {
public func asOrigin() -> Origin {
<#code#>
}
}

extension Int32: OriginProtocol {
public func asOrigin() -> Origin {
<#code#>
}
}

extension Int64: OriginProtocol {
public func asOrigin() -> Origin {
<#code#>
}
}
3 changes: 2 additions & 1 deletion Sources/YSwift/Transactable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ extension Transactable {
if let transaction = transaction {
return changes(transaction)
} else {
return document.transactSync(changes)
let origin: String? = .none
return document.transactSync(origin: origin, changes)
}
}
}
7 changes: 6 additions & 1 deletion Sources/YSwift/YArray.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import Yniffi
/// Store, order, and retrieve any single `Codable` type within a `YArray`.
///
/// Create a new `YArray` instance using ``YSwift/YDocument/getOrCreateArray(named:)`` from a ``YDocument``.
public final class YArray<T: Codable>: Transactable {
public final class YArray<T: Codable>: Transactable, YCollection {
private let _array: YrsArray
let document: YDocument

Expand Down Expand Up @@ -154,6 +154,11 @@ public final class YArray<T: Codable>: Transactable {
public func unobserve(_ subscriptionId: UInt32) {
_array.unobserve(subscriptionId: subscriptionId)
}

public func sharedHandle() -> YrsSharedRef {
//TODO: on uniffi side all shared collections implement YrsSharedRef,
// so essentially this should be `_array as YrsSharedRef`
}
}

extension YArray: Sequence {
Expand Down
17 changes: 11 additions & 6 deletions Sources/YSwift/YDocument.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,14 @@ public final class YDocument {
/// Creates a synchronous transaction and provides that transaction to a trailing closure, within which you make changes to shared data types.
/// - Parameter changes: The closure in which you make changes to the document.
/// - Returns: The value that you return from the closure.
public func transactSync<T>(_ changes: @escaping (YrsTransaction) -> T) -> T {
public func transactSync<T, O>(origin: O?, _ changes: @escaping (YrsTransaction) -> T) -> T where O: OriginProtocol {
// Avoiding deadlocks & thread explosion. We do not allow re-entrancy in Transaction methods.
// It is a programmer's error to invoke synchronous transact from within transaction.
// Better approach would be to leverage something like `DispatchSpecificKey` in Watchdog style implementation
// Reference: https://github.com/groue/GRDB.swift/blob/master/GRDB/Core/SchedulingWatchdog.swift
dispatchPrecondition(condition: .notOnQueue(transactionQueue))
return transactionQueue.sync {
let transaction = document.transact()
let transaction = document.transact(origin: origin?.asOrigin())
defer {
transaction.free()
}
Expand All @@ -48,9 +48,9 @@ public final class YDocument {
/// Creates an asynchronous transaction and provides that transaction to a trailing closure, within which you make changes to shared data types.
/// - Parameter changes: The closure in which you make changes to the document.
/// - Returns: The value that you return from the closure.
public func transact<T>(_ changes: @escaping (YrsTransaction) -> T) async -> T {
public func transact<T, O>(origin: O?, _ changes: @escaping (YrsTransaction) -> T) async -> T where O: OriginProtocol {
await withCheckedContinuation { continuation in
transactAsync(changes) { result in
transactAsync(origin, changes) { result in
continuation.resume(returning: result)
}
}
Expand All @@ -59,10 +59,10 @@ public final class YDocument {
/// Creates an asynchronous transaction and provides that transaction to a trailing closure, within which you make changes to shared data types.
/// - Parameter changes: The closure in which you make changes to the document.
/// - Parameter completion: A completion handler that is called with the value returned from the closure in which you made changes.
public func transactAsync<T>(_ changes: @escaping (YrsTransaction) -> T, completion: @escaping (T) -> Void) {
public func transactAsync<T, O>(_ origin: O?, _ changes: @escaping (YrsTransaction) -> T, completion: @escaping (T) -> Void) where O: OriginProtocol {
transactionQueue.async { [weak self] in
guard let self = self else { return }
let transaction = self.document.transact()
let transaction = self.document.transact(origin: origin?.asOrigin())
defer {
transaction.free()
}
Expand Down Expand Up @@ -93,4 +93,9 @@ public final class YDocument {
public func getOrCreateMap<T: Codable>(named: String) -> YMap<T> {
YMap(map: document.getMap(name: named), document: self)
}

public func undoManager(trackedRefs: [YCollection]) -> YUndoManager {
let mapped = trackedRefs.map({$0.sharedHandle()})
return YUndoManager(manager: self.document.undoManager(trackedRefs: mapped))
}
}
7 changes: 6 additions & 1 deletion Sources/YSwift/YMap.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import Yniffi
/// Store, order, and retrieve any single `Codable` type within a `YMap` keyed with a `String`.
///
/// Create a new `YMap` instance using ``YSwift/YDocument/getOrCreateMap(named:)`` from a ``YDocument``.
public final class YMap<T: Codable>: Transactable {
public final class YMap<T: Codable>: Transactable, YCollection {
private let _map: YrsMap
let document: YDocument

Expand Down Expand Up @@ -183,6 +183,11 @@ public final class YMap<T: Codable>: Transactable {
}
return replicatedMap
}

public func sharedHandle() -> YrsSharedRef {
//TODO: on uniffi side all shared collections implement YrsSharedRef,
// so essentially this should be `_map as YrsSharedRef`
}
}

extension YMap: Sequence {
Expand Down
7 changes: 6 additions & 1 deletion Sources/YSwift/YText.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import Yniffi
/// A type that provides a text-oriented shared data type.
///
/// Create a new `YText` instance using ``YSwift/YDocument/getOrCreateText(named:)`` from a ``YDocument``.
public final class YText: Transactable {
public final class YText: Transactable, YCollection {
private let _text: YrsText
let document: YDocument

Expand Down Expand Up @@ -163,6 +163,11 @@ public final class YText: Transactable {
public func unobserve(_ subscriptionId: UInt32) {
_text.unobserve(subscriptionId: subscriptionId)
}

public func sharedHandle() -> YrsSharedRef {
//TODO: on uniffi side all shared collections implement YrsSharedRef,
// so essentially this should be `_text as YrsSharedRef`
}
}

extension YText: Equatable {
Expand Down
34 changes: 34 additions & 0 deletions Sources/YSwift/YUndoManager.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import Foundation
import Yniffi

public final class YUndoManager {
private let _manager: YrsUndoManager

init(manager: YrsUndoManager) {
_manager = manager
}

public func addOrigin<O>(_ origin: O) where O: OriginProtocol {
_manager.addOrigin(origin: origin.asOrigin())
}

public func removeOrigin<O>(_ origin: O) where O: OriginProtocol {
_manager.removeOrigin(origin: origin.asOrigin())
}

public func undo() throws -> Bool {
return _manager.undo()
}

public func redo() throws -> Bool {
return _manager.redo()
}

public func wrap() {
_manager.wrapChanges()
}

public func clear() throws {
_manager.clear()
}
}
1 change: 1 addition & 0 deletions lib/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ all_symbols.txt
swift/scaffold/Documentation.docc/.docc-build
docs/
.vscode/
.idea/
13 changes: 12 additions & 1 deletion lib/src/array.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,25 @@
use crate::transaction::YrsTransaction;
use crate::{change::YrsChange, error::CodingError};
use crate::{change::YrsChange, error::CodingError, YrsSharedRef};
use std::cell::RefCell;
use std::fmt::Debug;
use yrs::{types::Value, Any, Array, ArrayRef, Observable};
use yrs::types::Branch;

pub(crate) struct YrsArray(RefCell<ArrayRef>);

unsafe impl Send for YrsArray {}
unsafe impl Sync for YrsArray {}

impl YrsSharedRef for YrsArray {}

impl AsRef<Branch> for YrsArray {
fn as_ref(&self) -> &Branch {
//FIXME: after yrs v0.18 use logical references
let branch = &*self.0.borrow();
unsafe { std::mem::transmute(branch.as_ref()) }
}
}

impl From<ArrayRef> for YrsArray {
fn from(value: ArrayRef) -> Self {
YrsArray(RefCell::from(value))
Expand Down
50 changes: 47 additions & 3 deletions lib/src/doc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ use crate::text::YrsText;
use crate::transaction::YrsTransaction;
use std::sync::Arc;
use std::{borrow::Borrow, cell::RefCell};
use yrs::{updates::decoder::Decode, ArrayRef, Doc, OffsetKind, Options, StateVector, Transact};
use yrs::{updates::decoder::Decode, ArrayRef, Doc, OffsetKind, Options, StateVector, Transact, Origin};
use yrs::{MapRef, ReadTxn};
use crate::{UniffiCustomTypeConverter, YrsSharedRef};
use crate::undo::YrsUndoManager;

pub(crate) struct YrsDoc(RefCell<Doc>);

Expand Down Expand Up @@ -50,9 +52,51 @@ impl YrsDoc {
Arc::from(YrsMap::from(map_ref))
}

pub(crate) fn transact<'doc>(&self) -> Arc<YrsTransaction> {
pub(crate) fn transact<'doc>(&self, origin: Option<YrsOrigin>) -> Arc<YrsTransaction> {
let tx = self.0.borrow();
let tx = tx.transact_mut();
let tx = if let Some(origin) = origin {
tx.transact_mut_with(origin)
} else {
tx.transact_mut()
};
Arc::from(YrsTransaction::from(tx))
}

pub(crate) fn undo_manager(&self, tracked_refs: Vec<Arc<dyn YrsSharedRef>>) -> Arc<YrsUndoManager> {
let doc = &*self.0.borrow();
let mut i = tracked_refs.into_iter();
let first = i.next().unwrap();
let mut undo_manager = yrs::UndoManager::new::<&dyn YrsSharedRef>(doc, &first.as_ref());
while let Some(n) = i.next() {
undo_manager.expand_scope(&n.as_ref());
}
Arc::new(YrsUndoManager::from(undo_manager))
}
}

#[derive(Clone)]
pub(crate) struct YrsOrigin(Arc<[u8]>);

impl From<Origin> for YrsOrigin {
fn from(value: Origin) -> Self {
YrsOrigin(Arc::from(value.as_ref()))
}
}

impl Into<Origin> for YrsOrigin {
fn into(self) -> Origin {
Origin::from(self.0.as_ref())
}
}

impl UniffiCustomTypeConverter for YrsOrigin {
type Builtin = Vec<u8>;

fn into_custom(val: Self::Builtin) -> uniffi::Result<Self> where Self: Sized {
Ok(YrsOrigin(val.into()))
}

fn from_custom(obj: Self) -> Self::Builtin {
obj.0.to_vec()
}
}
6 changes: 6 additions & 0 deletions lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ mod map;
mod mapchange;
mod text;
mod transaction;
mod undo;

pub(crate) trait YrsSharedRef: Send + Sync + AsRef<yrs::types::Branch> { }

use crate::doc::YrsOrigin;
use crate::array::YrsArray;
use crate::array::YrsArrayEachDelegate;
use crate::array::YrsArrayObservationDelegate;
Expand All @@ -25,5 +29,7 @@ use crate::mapchange::YrsMapChange;
use crate::text::YrsText;
use crate::text::YrsTextObservationDelegate;
use crate::transaction::YrsTransaction;
use crate::undo::YrsUndoManager;
use crate::undo::YrsUndoError;

uniffi::include_scaffolding!("yniffi");
Loading
Loading