oficina / docs Public

Clone
git clone https://amadan.net/oficina/docs

Plain git — no account needed to clone.

Download

Download this file

1import DocsCRDT
2import Foundation
3
4/// Handles never leave the actor. Every untrusted decode executes in bounded WASM.
5actor CRDTReplica {
6 private final class Sandbox {
7 let host: UnsafeMutableRawPointer
8 init() throws { guard let host = docs_host_new() else { throw Failure.unavailable }; self.host = host }
9 deinit { docs_host_free(host) }
10 func invoke(_ data: Data) throws -> Data {
11 let maximum = 8 * 1024 * 1024 + 1
12 guard !data.isEmpty, data.count <= maximum else { throw Failure.unavailable }
13 var output = Data(count: maximum)
14 let count = output.withUnsafeMutableBytes { target in data.withUnsafeBytes { source in
15 docs_host_call(host, source.bindMemory(to: UInt8.self).baseAddress!, data.count,
16 target.bindMemory(to: UInt8.self).baseAddress!, maximum)
17 } }
18 guard count >= 0, count <= maximum else { throw Failure.rejected }
19 output.count = Int(count); return output
20 }
21 func invoke(_ command: [String: JSONValue]) throws -> Data { try invoke(JSONEncoder().encode(command)) }
22 func projection() throws -> CRDTProjection {
23 try JSONDecoder().decode(CRDTProjection.self, from: invoke(["op": .string("project")]))
24 }
25 }
26 private var authority: Sandbox?
27 private var merger: Sandbox?
28 private var opened = false
29 private var sources: [UInt64: Sandbox] = [:]
30 private var sourceSnapshots: [UInt64: Data] = [:]
31 private var nextGeneration: UInt64 = 1
32 // IDs remain reserved for this session even when a source is retired or traps.
33 private var reserved: Set<UInt64> = []
34 private var lastGood = Data()
35 private var commandsApplied = 0
36 private var durableSequence: Int64 = 0
37 private let guid: String
38 enum Failure: Error { case unavailable, rejected, invalidProjection, generationLimit }
39 init(guid: String) { self.guid = guid }
40 struct PersistentState: Sendable {
41 let state: Data
42 let sequence: Int64
43 let commandsApplied: Int
44 let guid: String
45 }
46 func persistentState() throws -> PersistentState {
47 _ = try currentAuthority()
48 return PersistentState(state: lastGood, sequence: durableSequence, commandsApplied: commandsApplied, guid: guid)
49 }
50 func setSequence(_ sequence: Int64) { durableSequence = max(durableSequence, sequence) }
51 func close() { opened = false; authority = nil; merger = nil; sources.removeAll(); sourceSnapshots.removeAll(); lastGood = Data() }
52 private func fresh(update: Data) throws -> (UInt64, Sandbox) {
53 let sandbox = try Sandbox()
54 var client = UInt64(UInt32.random(in: 1...UInt32.max))
55 while reserved.contains(client) { client = UInt64(UInt32.random(in: 1...UInt32.max)) }
56 reserved.insert(client)
57 _ = try sandbox.invoke(["op": .string("open"), "guid": .string(guid), "client": .number(Double(client))])
58 if !update.isEmpty { _ = try sandbox.invoke(Data([255]) + update) }
59 let used = try JSONDecoder().decode([UInt64].self, from: sandbox.invoke(["op": .string("clients")]))
60 reserved.formUnion(used)
61 // Imported state may already contain the proposed ID. Never write it.
62 if used.contains(client) { return try fresh(update: update) }
63 return (client, sandbox)
64 }
65 func open(update: Data) throws -> CRDTProjection {
66 sources.removeAll(); sourceSnapshots.removeAll()
67 commandsApplied = 0; durableSequence = 0
68 authority = try fresh(update: update).1
69 opened = true
70 lastGood = try authority!.invoke(["op": .string("snapshot")])
71 return try projection()
72 }
73 private func currentAuthority() throws -> Sandbox {
74 guard opened else { throw Failure.unavailable }
75 if authority == nil { authority = try fresh(update: lastGood).1 }
76 guard let authority else { throw Failure.unavailable }
77 return authority
78 }
79 func projection() throws -> CRDTProjection {
80 let authority = try currentAuthority()
81 return try authority.projection()
82 }
83 /// Bound retained WASM memories. The caller defers publication until render
84 /// acknowledgement and queued-callback drainage allow old sources to retire.
85 func presentation(source generation: UInt64, selection: NSRange, allowReuse: Bool = false) throws -> (UInt64, CRDTProjection, NSRange)? {
86 let authority = try currentAuthority()
87 // Only the caller knows that this source has actually been rendered and
88 // has no pending candidate. Byte-identical state is additionally required:
89 // peers, undo, and divergent queued edits must still publish a fresh source.
90 // Never reconstruct or transfer a client ID to enable this fast path.
91 let reuse = allowReuse && sources[generation] != nil && sourceSnapshots[generation] == lastGood
92 guard reuse || sources.count < 3 else { return nil }
93 var resolved = selection
94 if let displayed = sources[generation] {
95 func endpoint(_ position: Int) throws -> Int {
96 let encoded = try displayed.invoke(["op": .string("anchor"), "at": .number(Double(position))])
97 let anchor = try JSONDecoder().decode(JSONValue.self, from: encoded)
98 return try JSONDecoder().decode(Int.self, from: authority.invoke(["op": .string("resolve"), "anchor": anchor]))
99 }
100 do {
101 let start = try endpoint(selection.location), end = try endpoint(NSMaxRange(selection))
102 resolved = NSRange(location: min(start, end), length: abs(end - start))
103 } catch {
104 if let snapshot = sourceSnapshots[generation] { sources[generation] = try? fresh(update: snapshot).1 }
105 else { sources.removeValue(forKey: generation) }
106 recover()
107 throw error
108 }
109 }
110 if reuse { return (generation, try authority.projection(), resolved) }
111 let (_, source) = try fresh(update: lastGood)
112 let generation = nextGeneration; nextGeneration += 1
113 sources[generation] = source; sourceSnapshots[generation] = lastGood
114 return (generation, try authority.projection(), resolved)
115 }
116 func retainGenerations(_ ids: Set<UInt64>) {
117 sources = sources.filter { ids.contains($0.key) }
118 sourceSnapshots = sourceSnapshots.filter { ids.contains($0.key) }
119 }
120 private func recover() {
121 // No invalid interpreter is reused. A new authority ID cannot reuse
122 // clocks from a partially executed command. UI reports undo loss.
123 authority = try? fresh(update: lastGood).1
124 }
125 func mergeUpdates(_ updates: [Data]) throws -> Data {
126 guard let first = updates.first else { throw Failure.rejected }
127 if updates.count == 1 { return first }
128 guard opened else { throw Failure.unavailable }
129 if merger == nil { merger = try Sandbox() }
130 var envelope = Data([253])
131 for update in updates { envelope.append(update) }
132 do { return try merger!.invoke(envelope) }
133 catch { merger = nil; throw error }
134 }
135 func apply(_ update: Data) throws -> CRDTProjection {
136 let authority = try currentAuthority()
137 do {
138 _ = try authority.invoke(Data([255]) + update)
139 let projected = try authority.projection()
140 lastGood = try authority.invoke(["op": .string("snapshot")])
141 return projected
142 } catch { recover(); throw error }
143 }
144 func command(_ command: [String: JSONValue]) throws -> (Data, CRDTProjection) {
145 let authority = try currentAuthority()
146 let generation: UInt64? = if case .number(let value) = command["generation"] { UInt64(exactly: value) } else { nil }
147 let isHistory = command["op"] == .string("undo") || command["op"] == .string("redo")
148 let writer: Sandbox
149 if isHistory || generation == nil { writer = authority }
150 else {
151 let generation = generation!
152 if sources[generation] == nil, let snapshot = sourceSnapshots[generation] { sources[generation] = try fresh(update: snapshot).1 }
153 guard let source = sources[generation] else { throw Failure.unavailable }
154 writer = source
155 }
156 let sourceBefore = writer === authority ? lastGood : (generation.flatMap { sourceSnapshots[$0] } ?? lastGood)
157 var authorityTouched = writer === authority
158 do {
159 let update = try writer.invoke(command)
160 let sourceAfter = try writer.invoke(["op": .string("snapshot")])
161 if writer !== authority { authorityTouched = true; _ = try authority.invoke(Data([254]) + update) }
162 let projected = try authority.projection()
163 lastGood = try authority.invoke(["op": .string("snapshot")])
164 if writer !== authority, let generation { sourceSnapshots[generation] = sourceAfter }
165 commandsApplied += 1
166 return (update, projected)
167 } catch {
168 if writer !== authority, let generation { sources[generation] = try? fresh(update: sourceBefore).1 }
169 if authorityTouched { recover() }
170 throw error
171 }
172 }
173}
174