Skip to content
Snippets Groups Projects
Commit fde3f7f1 authored by Moritz Langenstein's avatar Moritz Langenstein
Browse files

(ml5717) Initial yjs union type implementation

parent 269d4a22
No related branches found
No related tags found
1 merge request!60yjs union type implementation
Pipeline #105546 passed
import uuidv4 from "uuid/v4"
import yArray from "y-array"
import yMap from "y-map"
import yUnion, { Union } from "./y-union.js"
import yMemory from "y-memory"
import Y from "yjs"
......@@ -8,15 +9,13 @@ import yP2PMesh from "./y-p2p-mesh.js"
import WebRTCConnection from "./connection/WebRTC.js"
yMemory(Y)
Y.Struct.Union = Union
yUnion(Y)
yMap(Y)
yArray(Y)
yP2PMesh(Y)
import {
combineErasureIntervals,
spreadErasureIntervals,
flattenErasureIntervals,
} from "./erasure.js"
import { spreadErasureIntervals, flattenErasureIntervals } from "./erasure.js"
class Room extends EventTarget {
constructor(name) {
......@@ -33,7 +32,10 @@ class Room extends EventTarget {
addPath([x, y, w, colour]) {
const id = uuidv4()
this.shared.strokePoints.set(id, Y.Array).push([[x, y, w, colour]])
this.shared.eraseIntervals.set(id, Y.Union)
return id
}
......@@ -42,25 +44,9 @@ class Room extends EventTarget {
}
extendErasureIntervals(pathID, pointID, newIntervals) {
const self = this
// eslint-disable-next-line require-yield
this._y.db.requestTransaction(function* requestTransaction() {
const prevJSON = self.shared.eraseIntervals.get(pathID) || "[]"
const pathIntervals = JSON.parse(prevJSON)
const combinedIntervals = combineErasureIntervals(
[pathIntervals],
[flattenErasureIntervals({ [pointID]: newIntervals })],
)[0]
const postJSON = JSON.stringify(combinedIntervals)
if (prevJSON == postJSON) {
return
}
self.shared.eraseIntervals.set(pathID, postJSON)
})
this.shared.eraseIntervals
.get(pathID)
.merge(flattenErasureIntervals({ [pointID]: newIntervals }))
}
getPaths() {
......@@ -90,7 +76,7 @@ class Room extends EventTarget {
if (!intervals) return []
return spreadErasureIntervals(JSON.parse(intervals))
return spreadErasureIntervals(intervals.get())
}
inviteUser(id) {
......@@ -191,7 +177,13 @@ class Room extends EventTarget {
}
})
this.shared.eraseIntervals.observe((lineEvent) => {
dispatchRemovedIntervalsEvent(lineEvent)
if (lineEvent.type == "add") {
dispatchRemovedIntervalsEvent(lineEvent)
lineEvent.value.observe(() => {
dispatchRemovedIntervalsEvent(lineEvent)
})
}
})
}
}
......
/* global Y */
import { combineErasureIntervals } from "./erasure.js"
export const Union = {
create: function(id) {
return {
id: id,
union: -1,
struct: "Union",
}
},
encode: function(op) {
const e = {
struct: "Union",
type: op.type,
id: op.id,
union: -1,
}
if (op.requires != null) {
e.requires = op.requires
}
if (op.info != null) {
e.info = op.info
}
return e
},
requiredOps: function() {
return []
},
execute: function*() {},
}
export default function extendYUnion(Y) {
class YUnion extends Y.utils.CustomType {
constructor(os, model, contents) {
super()
this._model = model.id
this._parent = null
this._deepEventHandler = new Y.utils.EventListenerHandler()
this.os = os
this.union = Y.utils.copyObject(model.union)
this.contents = contents
this.eventHandler = new Y.utils.EventHandler((op) => {
// compute op event
if (op.struct === "Insert") {
if (!Y.utils.compareIds(op.id, this.union)) {
const mergedContents = this._merge(JSON.parse(op.content[0]))
this.union = op.id
if (this.contents == mergedContents) {
return
}
this.contents = mergedContents
Y.utils.bubbleEvent(this, {
object: this,
type: "merge",
})
}
} else {
throw new Error("Unexpected Operation!")
}
})
}
_getPathToChild(/*childId*/) {
return undefined
}
_destroy() {
this.eventHandler.destroy()
this.eventHandler = null
this.contents = null
this._model = null
this._parent = null
this.os = null
this.union = null
}
get() {
return JSON.parse(this.contents)
}
_merge(newIntervals) {
const prevIntervals = this.get()
const mergedIntervals = combineErasureIntervals(
[prevIntervals],
[newIntervals],
)[0]
return JSON.stringify(mergedIntervals)
}
merge(newIntervals) {
const mergedContents = this._merge(newIntervals)
if (this.contents == mergedContents) {
return
}
const insert = {
id: this.os.getNextOpId(1),
left: null,
right: null,
origin: null,
parent: this._model,
content: [mergedContents],
struct: "Insert",
}
const eventHandler = this.eventHandler
this.os.requestTransaction(function*() {
yield* eventHandler.awaitOps(this, this.applyCreatedOperations, [
[insert],
])
})
// always remember to do that after this.os.requestTransaction
// (otherwise values might contain a undefined reference to type)
eventHandler.awaitAndPrematurelyCall([insert])
}
observe(f) {
this.eventHandler.addEventListener(f)
}
observeDeep(f) {
this._deepEventHandler.addEventListener(f)
}
unobserve(f) {
this.eventHandler.removeEventListener(f)
}
unobserveDeep(f) {
this._deepEventHandler.removeEventListener(f)
}
// eslint-disable-next-line require-yield
*_changed(transaction, op) {
this.eventHandler.receivedOp(op)
}
}
Y.extend(
"Union",
new Y.utils.CustomTypeDefinition({
name: "Union",
class: YUnion,
struct: "Union",
initType: function* YUnionInitializer(os, model) {
const union = model.union
const contents =
union != -1 ? yield* this.getOperation(union).content[0] : "[]"
return new YUnion(os, model, contents)
},
createType: function YUnionCreator(os, model) {
const union = new YUnion(os, model, "[]")
return union
},
}),
)
}
if (typeof Y !== "undefined") {
extendYUnion(Y)
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment