fixed YArray
This commit is contained in:
@@ -3,6 +3,8 @@ import * as encoding from 'lib0/encoding.js'
|
||||
import * as decoding from 'lib0/decoding.js'
|
||||
import { StructStore, getItemRange } from './StructStore.js' // eslint-disable-line
|
||||
import { Transaction } from './Transaction.js' // eslint-disable-line
|
||||
import * as error from 'lib0/error.js'
|
||||
import { ID } from './ID.js'
|
||||
|
||||
class DeleteItem {
|
||||
/**
|
||||
@@ -38,6 +40,15 @@ export class DeleteSet {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {DeleteSet} ds
|
||||
* @param {ID} id
|
||||
* @return {boolean}
|
||||
*/
|
||||
export const isDeleted = (ds, id) => {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {DeleteSet} ds
|
||||
*/
|
||||
@@ -70,7 +81,7 @@ export const sortAndMergeDeleteSet = ds => {
|
||||
export const createDeleteSetFromTransaction = transaction => {
|
||||
const ds = new DeleteSet()
|
||||
transaction.deleted.forEach(item => {
|
||||
map.setTfUndefined(ds.clients, item.id.client, () => []).push(new DeleteItem(item.id.clock, item.length))
|
||||
map.setIfUndefined(ds.clients, item.id.client, () => []).push(new DeleteItem(item.id.clock, item.length))
|
||||
})
|
||||
sortAndMergeDeleteSet(ds)
|
||||
return ds
|
||||
|
||||
23
src/utils/Snapshot.js
Normal file
23
src/utils/Snapshot.js
Normal file
@@ -0,0 +1,23 @@
|
||||
import { DeleteSet, isDeleted } from './DeleteSet'
|
||||
import { AbstractItem } from '../structs/AbstractItem.js' // eslint-disable-line
|
||||
|
||||
export class Snapshot {
|
||||
/**
|
||||
* @param {DeleteSet} ds delete store
|
||||
* @param {Map<number,number>} sm state map
|
||||
* @param {Map<number,string>} userMap
|
||||
*/
|
||||
constructor (ds, sm, userMap) {
|
||||
this.ds = new DeleteSet()
|
||||
this.sm = sm
|
||||
this.userMap = userMap
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {AbstractItem} item
|
||||
* @param {Snapshot} [snapshot]
|
||||
*/
|
||||
export const isVisible = (item, snapshot) => snapshot === undefined ? !item._deleted : (
|
||||
snapshot.sm.has(item.id.client) && (snapshot.sm.get(item.id.client) || 0) > item.id.clock && !isDeleted(snapshot.ds, item._id)
|
||||
)
|
||||
@@ -31,6 +31,19 @@ export const getStates = store =>
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* @param {StructStore} store
|
||||
* @param {number} client
|
||||
*/
|
||||
export const getState = (store, client) => {
|
||||
const structs = store.clients.get(client)
|
||||
if (structs === undefined) {
|
||||
return 0
|
||||
}
|
||||
const lastStruct = structs[structs.length - 1]
|
||||
return lastStruct.id.clock + lastStruct.length
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {StructStore} store
|
||||
*/
|
||||
@@ -51,7 +64,7 @@ export const integretyCheck = store => {
|
||||
* @param {AbstractStruct} struct
|
||||
*/
|
||||
export const addStruct = (store, struct) => {
|
||||
map.setTfUndefined(store.clients, struct.id.client, () => []).push(struct)
|
||||
map.setIfUndefined(store.clients, struct.id.client, () => []).push(struct)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -107,21 +120,6 @@ const find = (store, id) => {
|
||||
// @ts-ignore
|
||||
export const getItemType = (store, id) => find(store, id)
|
||||
|
||||
/**
|
||||
* @param {Transaction} transaction
|
||||
* @param {AbstractItem} struct
|
||||
* @param {number} diff
|
||||
*/
|
||||
const splitStruct = (transaction, struct, diff) => {
|
||||
const right = struct.splitAt(diff)
|
||||
if (transaction.added.has(struct)) {
|
||||
transaction.added.add(right)
|
||||
} else if (transaction.deleted.has(struct)) {
|
||||
transaction.deleted.add(right)
|
||||
}
|
||||
return right
|
||||
}
|
||||
|
||||
/**
|
||||
* Expects that id is actually in store. This function throws or is an infinite loop otherwise.
|
||||
* @param {StructStore} store
|
||||
@@ -139,10 +137,11 @@ export const getItemCleanStart = (store, transaction, id) => {
|
||||
const structs = store.clients.get(id.client)
|
||||
const index = findIndex(structs, id.clock)
|
||||
/**
|
||||
* @type {any}
|
||||
* @type {AbstractItem}
|
||||
*/
|
||||
let struct = structs[index]
|
||||
if (struct.id.clock < id.clock) {
|
||||
struct.splitAt()
|
||||
struct = splitStruct(transaction, struct, id.clock - struct.id.clock)
|
||||
structs.splice(index, 0, struct)
|
||||
}
|
||||
@@ -213,11 +212,11 @@ export const getItemRange = (store, transaction, client, clock, len) => {
|
||||
* @param {AbstractStruct} struct
|
||||
* @param {AbstractStruct} newStruct
|
||||
*/
|
||||
export const replace = (store, struct, newStruct) => {
|
||||
export const replaceStruct = (store, struct, newStruct) => {
|
||||
/**
|
||||
* @type {Array<AbstractStruct>}
|
||||
*/
|
||||
// @ts-ignore
|
||||
const structs = store.clients.get(struct.id.client)
|
||||
structs[findIndex(structs, struct.id)] = newStruct
|
||||
structs[findIndex(structs, struct.id.clock)] = newStruct
|
||||
}
|
||||
|
||||
@@ -8,8 +8,10 @@ import { AbstractItem } from '../structs/AbstractItem.js' // eslint-disable-line
|
||||
import { Y } from './Y.js' // eslint-disable-line
|
||||
import { YEvent } from './YEvent.js' // eslint-disable-line
|
||||
import { ItemType } from '../structs/ItemType.js' // eslint-disable-line
|
||||
import { getState } from './StructStore.js'
|
||||
import { writeStructsFromTransaction } from './structEncoding.js'
|
||||
import { createID } from './ID.js' // eslint-disable-line
|
||||
import { createDeleteSetFromTransaction, writeDeleteSet } from './DeleteSet.js'
|
||||
import { getState } from './StructStore.js'
|
||||
|
||||
/**
|
||||
* A transaction is created for every change on the Yjs model. It is possible
|
||||
@@ -54,26 +56,41 @@ export class Transaction {
|
||||
*/
|
||||
this.deleted = new Set()
|
||||
/**
|
||||
* Saves the old state set of the Yjs instance. If a state was modified,
|
||||
* the original value is saved here.
|
||||
* If a state was modified, the original value is saved here.
|
||||
* Use `stateUpdates` to compute the original state before the transaction,
|
||||
* or to compute the set of inserted operations.
|
||||
* @type {Map<Number,Number>}
|
||||
*/
|
||||
this.beforeState = new Map()
|
||||
this.stateUpdates = new Map()
|
||||
/**
|
||||
* All types that were directly modified (property added or child
|
||||
* inserted/deleted). New types are not included in this Set.
|
||||
* Maps from type to parentSubs (`item._parentSub = null` for YArray)
|
||||
* @type {Map<ItemType,Set<String|null>>}
|
||||
* @type {Map<AbstractType,Set<String|null>>}
|
||||
*/
|
||||
this.changed = new Map()
|
||||
/**
|
||||
* Stores the events for the types that observe also child elements.
|
||||
* It is mainly used by `observeDeep`.
|
||||
* @type {Map<ItemType,Array<YEvent>>}
|
||||
* @type {Map<AbstractType,Array<YEvent>>}
|
||||
*/
|
||||
this.changedParentTypes = new Map()
|
||||
this.encodedStructsLen = 0
|
||||
this.encodedStructs = encoding.createEncoder()
|
||||
/**
|
||||
* @type {encoding.Encoder|null}
|
||||
*/
|
||||
this._updateMessage = null
|
||||
}
|
||||
/**
|
||||
* @type {encoding.Encoder}
|
||||
*/
|
||||
get updateMessage () {
|
||||
if (this._updateMessage === null) {
|
||||
const encoder = encoding.createEncoder()
|
||||
writeStructsFromTransaction(encoder, this)
|
||||
writeDeleteSet(encoder, createDeleteSetFromTransaction(this))
|
||||
this._updateMessage = encoder
|
||||
}
|
||||
return this._updateMessage
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
import { DeleteStore } from './DeleteSet.js/index.js' // TODO: remove
|
||||
import { OperationStore } from './OperationStore.js'
|
||||
import { StateStore } from './StateStore.js'
|
||||
import { StructStore } from './StructStore.js'
|
||||
import * as random from 'lib0/random.js'
|
||||
import * as map from 'lib0/map.js'
|
||||
@@ -8,22 +5,10 @@ import { Observable } from 'lib0/observable.js'
|
||||
import { Transaction } from './Transaction.js'
|
||||
import { AbstractStruct, AbstractRef } from '../structs/AbstractStruct.js' // eslint-disable-line
|
||||
import { AbstractType } from '../types/AbstractType.js'
|
||||
import { YArray } from '../types/YArray.js'
|
||||
|
||||
/**
|
||||
* Anything that can be encoded with `JSON.stringify` and can be decoded with
|
||||
* `JSON.parse`.
|
||||
*
|
||||
* The following property should hold:
|
||||
* `JSON.parse(JSON.stringify(key))===key`
|
||||
*
|
||||
* At the moment the only safe values are number and string.
|
||||
*
|
||||
* @typedef {(number|string|Object)} encodable
|
||||
*/
|
||||
|
||||
/**
|
||||
* A Yjs instance handles the state of shared data.
|
||||
* @extends Observable<string>
|
||||
*/
|
||||
export class Y extends Observable {
|
||||
/**
|
||||
@@ -33,6 +18,9 @@ export class Y extends Observable {
|
||||
super()
|
||||
this.gcEnabled = conf.gc || false
|
||||
this.clientID = random.uint32()
|
||||
/**
|
||||
* @type {Map<string, AbstractType>}
|
||||
*/
|
||||
this.share = new Map()
|
||||
this.store = new StructStore()
|
||||
/**
|
||||
@@ -65,7 +53,7 @@ export class Y extends Observable {
|
||||
* that happened inside of the transaction are sent as one message to the
|
||||
* other peers.
|
||||
*
|
||||
* @param {Function} f The function that should be executed as a transaction
|
||||
* @param {function(Transaction):void} f The function that should be executed as a transaction
|
||||
* @param {?Boolean} remote Optional. Whether this transaction is initiated by
|
||||
* a remote peer. This should not be set manually!
|
||||
* Defaults to false.
|
||||
@@ -78,7 +66,7 @@ export class Y extends Observable {
|
||||
this.emit('beforeTransaction', [this, this._transaction, remote])
|
||||
}
|
||||
try {
|
||||
f(this)
|
||||
f(this._transaction)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
@@ -88,7 +76,7 @@ export class Y extends Observable {
|
||||
this._transaction = null
|
||||
// emit change events on changed types
|
||||
transaction.changed.forEach((subs, itemtype) => {
|
||||
if (!itemtype._deleted) {
|
||||
if (!itemtype._item.deleted) {
|
||||
itemtype.type._callObserver(transaction, subs, remote)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
import { DeleteStore } from './DeleteSet'
|
||||
|
||||
export class HistorySnapshot {
|
||||
/**
|
||||
* @param {DeleteStore} ds delete store
|
||||
* @param {Map<number,number>} sm state map
|
||||
* @param {Map<number,string>} userMap
|
||||
*/
|
||||
constructor (ds, sm, userMap) {
|
||||
this.ds = new DeleteStore()
|
||||
this.sm = sm
|
||||
this.userMap = userMap
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Item} item
|
||||
* @param {HistorySnapshot} [snapshot]
|
||||
*/
|
||||
export const isVisible = (item, snapshot) => snapshot === undefined ? !item._deleted : (
|
||||
snapshot.sm.has(item._id.user) && (snapshot.sm.get(item._id.user) || 0) > item._id.clock && !snapshot.ds.isDeleted(item._id)
|
||||
)
|
||||
@@ -1,13 +1,10 @@
|
||||
import * as encoding from 'lib0/encoding.js'
|
||||
import * as decoding from 'lib0/decoding.js'
|
||||
import { getStructReference } from './structReferences.js'
|
||||
import { AbstractStruct, AbstractRef } from '../structs/AbstractStruct.js'
|
||||
import { ID, createID, writeID, writeNullID } from './ID.js'
|
||||
import * as binary from 'lib0/binary.js'
|
||||
|
||||
export const writeStructToTransaction = (transaction, struct) => {
|
||||
transaction.encodedStructsLen++
|
||||
struct._toBinary(transaction.encodedStructs)
|
||||
}
|
||||
import { Transaction } from './Transaction.js'
|
||||
import { findIndex } from './StructStore.js'
|
||||
|
||||
const structRefs = [
|
||||
ItemBinaryRef
|
||||
@@ -18,7 +15,6 @@ const structRefs = [
|
||||
*
|
||||
* This is called when data is received from a remote peer.
|
||||
*
|
||||
* @param {Y} y The Yjs instance that this Item belongs to.
|
||||
* @param {decoding.Decoder} decoder The decoder object to read data from.
|
||||
* @return {AbstractRef}
|
||||
*
|
||||
@@ -28,3 +24,23 @@ export const read = decoder => {
|
||||
const info = decoding.readUint8(decoder)
|
||||
return new structRefs[binary.BITS5 & info](decoder, info)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {encoding.Encoder} encoder
|
||||
* @param {Transaction} transaction
|
||||
*/
|
||||
export const writeStructsFromTransaction = (encoder, transaction) => {
|
||||
const stateUpdates = transaction.stateUpdates
|
||||
const y = transaction.y
|
||||
encoding.writeVarUint(encoder, stateUpdates.size)
|
||||
stateUpdates.forEach((clock, client) => {
|
||||
/**
|
||||
* @type {Array<AbstractStruct>}
|
||||
*/
|
||||
// @ts-ignore
|
||||
const structs = y.store.clients.get(client)
|
||||
for (let i = findIndex(structs, clock); i < structs.length; i++) {
|
||||
structs[i].write(encoder, 0)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user