Skip to content

Commit 91a8101

Browse files
committed
doc: add readme
1 parent 10d2336 commit 91a8101

File tree

8 files changed

+200
-27
lines changed

8 files changed

+200
-27
lines changed

.vscode/settings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
"cSpell.words": ["oplog", "uniffi", "xcframework"]
2+
"cSpell.words": ["oplog", "uniffi", "xcframework", "Zhao"]
33
}

README.md

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,17 @@
11
# Loro-swift
22

3+
<p align="center">
4+
<a aria-label="X" href="https://x.com/loro_dev" target="_blank">
5+
<img alt="" src="https://img.shields.io/badge/Twitter-black?style=for-the-badge&logo=Twitter">
6+
</a>
7+
<a aria-label="Discord-Link" href="https://discord.gg/tUsBSVfqzf" target="_blank">
8+
<img alt="" src="https://img.shields.io/badge/Discord-black?style=for-the-badge&logo=discord">
9+
</a>
10+
</p>
11+
312
This repository contains experimental Swift bindings for [Loro CRDT](https://github.com/loro-dev/loro).
413

5-
If you have any suggestions for API, please feel free to create an issue.
14+
If you have any suggestions for API, please feel free to create an issue or join our [Discord](https://discord.gg/tUsBSVfqzf) community.
615

716
## Usage
817

@@ -14,7 +23,7 @@ let package = Package(
1423
products: [......],
1524
dependencies:[
1625
...,
17-
.package(url: "https://github.com/loro-dev/loro-swift.git")
26+
.package(url: "https://github.com/loro-dev/loro-swift.git", from: 0.16.2)
1827
],
1928
targets:[
2029
.executableTarget(
@@ -26,6 +35,47 @@ let package = Package(
2635

2736
```
2837

38+
## Examples
39+
40+
```swift
41+
import Loro
42+
43+
// create a Loro document
44+
let doc = LoroDoc()
45+
46+
// create Root Container by getText, getList, getMap, getTree, getMovableList, getCounter
47+
let text = doc.getText(id: "text")
48+
49+
try! text.insert(pos: 0, s: "abc")
50+
try! text.delete(pos: 0, len: 1)
51+
let s = text.toString()
52+
// XCTAssertEqual(s, "bc")
53+
54+
// subscribe the event
55+
let subId = doc.subscribeRoot{ diffEvent in
56+
print(diffEvent)
57+
}
58+
// unsubscribe
59+
doc.unsubscribe(subId: id)
60+
61+
// export updates or snapshot
62+
63+
let doc2 = LoroDoc()
64+
let snapshot = doc.exportSnapshot()
65+
let updates = doc.exportFrom(vv: VersionVector())
66+
67+
// import updates or snapshot
68+
try! doc2.import(snapshot)
69+
try! doc2.import(updates)
70+
// import batch of updates or snapshot
71+
try! doc2.importBatch(bytes: [snapshot, updates])
72+
73+
// checkout to any version
74+
let startFrontiers = doc.oplogFrontiers()
75+
try! doc.checkout(frontiers: startFrontiers)
76+
doc.checkoutToLatest()
77+
```
78+
2979
## Develop
3080

3181
If you wanna build and develop this project, you need first run this script:

Sources/Loro/Container.swift

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,6 @@ extension LoroCounter: ContainerLike{}
9595
extension LoroUnknown: ContainerLike{}
9696

9797

98-
99-
10098
extension LoroList: ContainerLike{
10199
public func pushContainer(child: ContainerLike) throws -> ContainerLike{
102100
let idx = self.len()

Sources/Loro/Event.swift

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
2+
3+
class ClosureSubscriber: Subscriber {
4+
private let closure: (DiffEvent) -> Void
5+
6+
public init(closure: @escaping (DiffEvent) -> Void) {
7+
self.closure = closure
8+
}
9+
10+
public func onDiff(diff: DiffEvent) {
11+
closure(diff)
12+
}
13+
}
14+
15+
extension LoroDoc{
16+
/** Subscribe all the events.
17+
*
18+
* The callback will be invoked when any part of the [loro_internal::DocState] is changed.
19+
* Returns a subscription id that can be used to unsubscribe.
20+
*/
21+
public func subscribeRoot(callback: @escaping (DiffEvent)->Void) -> SubId {
22+
let closureSubscriber = ClosureSubscriber(closure: callback)
23+
return self.subscribeRoot(subscriber: closureSubscriber)
24+
}
25+
26+
/** Subscribe the events of a container.
27+
*
28+
* The callback will be invoked when the container is changed.
29+
* Returns a subscription id that can be used to unsubscribe.
30+
*/
31+
public func subscribe(containerId: ContainerId, callback: @escaping (DiffEvent)->Void) -> SubId {
32+
let closureSubscriber = ClosureSubscriber(closure: callback)
33+
return self.subscribe(containerId: containerId, subscriber: closureSubscriber)
34+
}
35+
}

Sources/Loro/LoroFFI.swift

Lines changed: 57 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4208,14 +4208,14 @@ public func FfiConverterTypeLoroValueLike_lower(_ value: LoroValueLike) -> Unsaf
42084208

42094209

42104210

4211-
public protocol SubscriberProtocol : AnyObject {
4211+
public protocol Subscriber : AnyObject {
42124212

42134213
func onDiff(diff: DiffEvent)
42144214

42154215
}
42164216

4217-
open class Subscriber:
4218-
SubscriberProtocol {
4217+
open class SubscriberImpl:
4218+
Subscriber {
42194219
fileprivate let pointer: UnsafeMutableRawPointer!
42204220

42214221
/// Used to instantiate a [FFIObject] without an actual pointer, for fakes in tests, mostly.
@@ -4265,17 +4265,65 @@ open func onDiff(diff: DiffEvent) {try! rustCall() {
42654265

42664266
}
42674267

4268+
4269+
// Put the implementation in a struct so we don't pollute the top-level namespace
4270+
fileprivate struct UniffiCallbackInterfaceSubscriber {
4271+
4272+
// Create the VTable using a series of closures.
4273+
// Swift automatically converts these into C callback functions.
4274+
static var vtable: UniffiVTableCallbackInterfaceSubscriber = UniffiVTableCallbackInterfaceSubscriber(
4275+
onDiff: { (
4276+
uniffiHandle: UInt64,
4277+
diff: RustBuffer,
4278+
uniffiOutReturn: UnsafeMutableRawPointer,
4279+
uniffiCallStatus: UnsafeMutablePointer<RustCallStatus>
4280+
) in
4281+
let makeCall = {
4282+
() throws -> () in
4283+
guard let uniffiObj = try? FfiConverterTypeSubscriber.handleMap.get(handle: uniffiHandle) else {
4284+
throw UniffiInternalError.unexpectedStaleHandle
4285+
}
4286+
return uniffiObj.onDiff(
4287+
diff: try FfiConverterTypeDiffEvent.lift(diff)
4288+
)
4289+
}
4290+
4291+
4292+
let writeReturn = { () }
4293+
uniffiTraitInterfaceCall(
4294+
callStatus: uniffiCallStatus,
4295+
makeCall: makeCall,
4296+
writeReturn: writeReturn
4297+
)
4298+
},
4299+
uniffiFree: { (uniffiHandle: UInt64) -> () in
4300+
let result = try? FfiConverterTypeSubscriber.handleMap.remove(handle: uniffiHandle)
4301+
if result == nil {
4302+
print("Uniffi callback interface Subscriber: handle missing in uniffiFree")
4303+
}
4304+
}
4305+
)
4306+
}
4307+
4308+
private func uniffiCallbackInitSubscriber() {
4309+
uniffi_loro_fn_init_callback_vtable_subscriber(&UniffiCallbackInterfaceSubscriber.vtable)
4310+
}
4311+
42684312
public struct FfiConverterTypeSubscriber: FfiConverter {
4313+
fileprivate static var handleMap = UniffiHandleMap<Subscriber>()
42694314

42704315
typealias FfiType = UnsafeMutableRawPointer
42714316
typealias SwiftType = Subscriber
42724317

42734318
public static func lift(_ pointer: UnsafeMutableRawPointer) throws -> Subscriber {
4274-
return Subscriber(unsafeFromRawPointer: pointer)
4319+
return SubscriberImpl(unsafeFromRawPointer: pointer)
42754320
}
42764321

42774322
public static func lower(_ value: Subscriber) -> UnsafeMutableRawPointer {
4278-
return value.uniffiClonePointer()
4323+
guard let ptr = UnsafeMutableRawPointer(bitPattern: UInt(truncatingIfNeeded: handleMap.insert(obj: value))) else {
4324+
fatalError("Cast to UnsafeMutableRawPointer failed")
4325+
}
4326+
return ptr
42794327
}
42804328

42814329
public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Subscriber {
@@ -6830,10 +6878,10 @@ private var initializationResult: InitializationResult = {
68306878
if (uniffi_loro_checksum_method_lorodoc_state_vv() != 1627) {
68316879
return InitializationResult.apiChecksumMismatch
68326880
}
6833-
if (uniffi_loro_checksum_method_lorodoc_subscribe() != 56858) {
6881+
if (uniffi_loro_checksum_method_lorodoc_subscribe() != 28252) {
68346882
return InitializationResult.apiChecksumMismatch
68356883
}
6836-
if (uniffi_loro_checksum_method_lorodoc_subscribe_root() != 41210) {
6884+
if (uniffi_loro_checksum_method_lorodoc_subscribe_root() != 7800) {
68376885
return InitializationResult.apiChecksumMismatch
68386886
}
68396887
if (uniffi_loro_checksum_method_lorodoc_unsubscribe() != 32901) {
@@ -7124,7 +7172,7 @@ private var initializationResult: InitializationResult = {
71247172
if (uniffi_loro_checksum_method_lorovaluelike_as_loro_value() != 23668) {
71257173
return InitializationResult.apiChecksumMismatch
71267174
}
7127-
if (uniffi_loro_checksum_method_subscriber_on_diff() != 56592) {
7175+
if (uniffi_loro_checksum_method_subscriber_on_diff() != 462) {
71287176
return InitializationResult.apiChecksumMismatch
71297177
}
71307178
if (uniffi_loro_checksum_method_valueorcontainer_as_container() != 61163) {
@@ -7178,6 +7226,7 @@ private var initializationResult: InitializationResult = {
71787226

71797227
uniffiCallbackInitContainerIdLike()
71807228
uniffiCallbackInitLoroValueLike()
7229+
uniffiCallbackInitSubscriber()
71817230
return InitializationResult.ok
71827231
}()
71837232

Tests/LoroTests/LoroTests.swift

Lines changed: 49 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,55 @@ import XCTest
22
@testable import Loro
33

44
final class LoroTests: XCTestCase {
5-
func testExample() throws {
6-
// XCTest Documentation
7-
// https://developer.apple.com/documentation/xctest
8-
9-
// Defining Test Cases and Test Methods
10-
// https://developer.apple.com/documentation/xctest/defining_test_cases_and_test_methods
5+
func testEvent(){
6+
let doc = LoroDoc()
7+
var num = 0
8+
let id = doc.subscribeRoot{ diffEvent in
9+
num += 1
10+
}
11+
let list = doc.getList(id: "list")
12+
try! list.insert(pos: 0, v: 123)
13+
doc.commit()
14+
XCTAssertEqual(num, 1)
15+
doc.unsubscribe(subId: id)
16+
}
17+
18+
func testText(){
19+
let doc = LoroDoc()
20+
let text = doc.getText(id: "text")
21+
try! text.insert(pos: 0, s: "abc")
22+
try! text.delete(pos: 0, len: 1)
23+
let s = text.toString()
24+
XCTAssertEqual(s, "bc")
25+
}
26+
27+
func testSync(){
28+
let doc = LoroDoc()
29+
try! doc.setPeerId(peer: 0)
30+
let text = doc.getText(id: "text")
31+
try! text.insert(pos: 0, s: "abc")
32+
try! text.delete(pos: 0, len: 1)
33+
let s = text.toString()
34+
XCTAssertEqual(s, "bc")
35+
36+
let doc2 = LoroDoc()
37+
try! doc2.setPeerId(peer: 1)
38+
let text2 = doc2.getText(id: "text")
39+
try! text2.insert(pos: 0, s:"123")
40+
try! doc2.import(bytes: doc.exportSnapshot())
41+
try! doc2.importBatch(bytes: [doc.exportSnapshot(), doc.exportFrom(vv: VersionVector())])
42+
XCTAssertEqual(text2.toString(), "bc123")
43+
}
44+
45+
func testCheckout(){
1146
let doc = LoroDoc()
12-
try! doc.setPeerId(peer: 12)
13-
assert(doc.peerId()==12)
47+
try! doc.setPeerId(peer: 0)
48+
let text = doc.getText(id: "text")
49+
try! text.insert(pos: 0, s: "abc")
50+
try! text.delete(pos: 0, len: 1)
51+
52+
let startFrontiers = doc.oplogFrontiers()
53+
try! doc.checkout(frontiers: startFrontiers)
54+
doc.checkoutToLatest()
1455
}
1556
}

loro-rs/src/loro.udl

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ interface ContainerIdLike{
2222
ContainerID as_container_id(ContainerType ty);
2323
};
2424

25+
[Trait, WithForeign]
26+
interface Subscriber{
27+
void on_diff(DiffEvent diff);
28+
};
29+
2530
// ============= LORO DOC =============
2631

2732
interface LoroDoc{
@@ -725,11 +730,6 @@ interface Frontiers{
725730

726731
// ============= EVENTS =============
727732

728-
[Trait]
729-
interface Subscriber{
730-
void on_diff(DiffEvent diff);
731-
};
732-
733733
dictionary DiffEvent{
734734
/// How the event is triggered.
735735
EventTriggerKind triggered_by;

scripts/build_swift_ffi.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ $cargo_build --target wasm32-wasi --locked --release
101101
mkdir -p "${BUILD_FOLDER}/includes"
102102
cp "${SWIFT_FOLDER}/loroFFI.h" "${BUILD_FOLDER}/includes"
103103
cp "${SWIFT_FOLDER}/loroFFI.modulemap" "${BUILD_FOLDER}/includes/module.modulemap"
104-
cp "${SWIFT_FOLDER}/loro.swift" "${THIS_SCRIPT_DIR}/../Sources/Loro/LoroFFI.swift"
104+
cp -f "${SWIFT_FOLDER}/loro.swift" "${THIS_SCRIPT_DIR}/../Sources/Loro/LoroFFI.swift"
105105

106106
echo "▸ Lipo (merge) x86 and arm simulator static libraries into a fat static binary"
107107
mkdir -p "${BUILD_FOLDER}/ios-simulator/release"

0 commit comments

Comments
 (0)