mirror of https://codeberg.org/pzp/zooboard.git
fix concurrent load and update, add support for gc'ing elements
This commit is contained in:
parent
633682db8a
commit
7a98e3cbcc
70
main.js
70
main.js
|
@ -106,14 +106,6 @@ async function loadAccount() {
|
||||||
globalAccountID = id
|
globalAccountID = id
|
||||||
await p(peer.set.load)(id)
|
await p(peer.set.load)(id)
|
||||||
await p(peer.dict.load)(id)
|
await p(peer.dict.load)(id)
|
||||||
peer.conductor.start(
|
|
||||||
id,
|
|
||||||
[
|
|
||||||
['profile@dict', 'zooboardElements@newest-100', 'hubs@set'],
|
|
||||||
['profile@dict', 'zooboardElements@newest-100'],
|
|
||||||
],
|
|
||||||
64_000_000
|
|
||||||
)
|
|
||||||
|
|
||||||
// Read hubs
|
// Read hubs
|
||||||
const multiaddrs = peer.set.values('hubs')
|
const multiaddrs = peer.set.values('hubs')
|
||||||
|
@ -134,38 +126,82 @@ async function setProfileName(ev, name) {
|
||||||
return name
|
return name
|
||||||
}
|
}
|
||||||
|
|
||||||
async function writeElements(ev, actions) {
|
async function writeElements(ev, elements) {
|
||||||
await loadAccount() // FIXME: ideally the frontend shouldn't do this
|
|
||||||
if (globalAccountID === null) throw new Error('account not loaded')
|
if (globalAccountID === null) throw new Error('account not loaded')
|
||||||
for (const action of actions) {
|
for (const element of elements) {
|
||||||
await p(peer.db.feed.publish)({
|
await p(peer.db.feed.publish)({
|
||||||
account: globalAccountID,
|
account: globalAccountID,
|
||||||
domain: 'zooboardElements',
|
domain: 'zooboardElements',
|
||||||
data: action,
|
data: element,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let hasSubscribedToReadElements = false
|
||||||
function subscribeToReadElements() {
|
function subscribeToReadElements() {
|
||||||
|
if (hasSubscribedToReadElements) return
|
||||||
|
hasSubscribedToReadElements = true
|
||||||
|
|
||||||
|
// Load initial elements and inform renderer
|
||||||
const elementsByID = new Map()
|
const elementsByID = new Map()
|
||||||
for (const msg of peer.db.msgs()) {
|
const msgIDToElemID = new Map()
|
||||||
|
for (const { id: msgID, msg } of peer.db.records()) {
|
||||||
if (msg.data && msg.metadata.domain === 'zooboardElements') {
|
if (msg.data && msg.metadata.domain === 'zooboardElements') {
|
||||||
const { id, isDeleted } = msg.data
|
const { id: elemID, isDeleted } = msg.data
|
||||||
if (isDeleted) {
|
if (isDeleted) {
|
||||||
elementsByID.delete(id)
|
elementsByID.delete(elemID)
|
||||||
} else {
|
} else {
|
||||||
elementsByID.set(id, msg.data)
|
msgIDToElemID.set(msgID, elemID)
|
||||||
|
elementsByID.set(elemID, msg.data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const initialElements = [...elementsByID.values()]
|
const initialElements = [...elementsByID.values()]
|
||||||
mainWindow.webContents.send('readElements', initialElements)
|
mainWindow.webContents.send('readElements', initialElements)
|
||||||
|
|
||||||
peer.db.onRecordAdded(({ msg }) => {
|
// Subscribe to new elements and inform renderer
|
||||||
|
peer.db.onRecordAdded(({ id: msgID, msg }) => {
|
||||||
if (msg.data && msg.metadata.domain === 'zooboardElements') {
|
if (msg.data && msg.metadata.domain === 'zooboardElements') {
|
||||||
|
const { id: elemID, isDeleted } = msg.data
|
||||||
|
if (isDeleted) {
|
||||||
|
elementsByID.delete(elemID)
|
||||||
|
} else {
|
||||||
|
msgIDToElemID.set(msgID, elemID)
|
||||||
|
elementsByID.set(elemID, msg.data)
|
||||||
|
}
|
||||||
mainWindow.webContents.send('readElements', [msg.data])
|
mainWindow.webContents.send('readElements', [msg.data])
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Subscribe to deleted elements and inform renderer
|
||||||
|
peer.db.onRecordDeletedOrErased((msgID) => {
|
||||||
|
const elemID = msgIDToElemID.get(msgID)
|
||||||
|
if (!elemID) return
|
||||||
|
msgIDToElemID.delete(msgID)
|
||||||
|
// Is there some other msgID that supports this elemID? If so, bail out
|
||||||
|
for (const [, remainingElemID] of msgIDToElemID) {
|
||||||
|
if (remainingElemID === elemID) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If not, delete the element
|
||||||
|
elementsByID.delete(elemID)
|
||||||
|
mainWindow.webContents.send('readElements', [
|
||||||
|
{ id: elemID, isDeleted: true },
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
// Finally safe to kickstart replication and garbage collection
|
||||||
|
setTimeout(() => {
|
||||||
|
peer.conductor.start(
|
||||||
|
globalAccountID,
|
||||||
|
[
|
||||||
|
['profile@dict', 'zooboardElements@newest-100', 'hubs@set'],
|
||||||
|
['profile@dict', 'zooboardElements@newest-100'],
|
||||||
|
],
|
||||||
|
64_000
|
||||||
|
)
|
||||||
|
}, 32)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function scheduleWithHub(multiaddr) {
|
async function scheduleWithHub(multiaddr) {
|
||||||
|
|
|
@ -3,6 +3,9 @@ import { Excalidraw } from '@excalidraw/excalidraw'
|
||||||
import './App.css'
|
import './App.css'
|
||||||
import debounce from 'debounce'
|
import debounce from 'debounce'
|
||||||
|
|
||||||
|
const elemsPersisted = new Map()
|
||||||
|
let sceneInitialized = false
|
||||||
|
|
||||||
function MyAccount() {
|
function MyAccount() {
|
||||||
const nameInput = createRef()
|
const nameInput = createRef()
|
||||||
const [loaded, setLoaded] = useState(false)
|
const [loaded, setLoaded] = useState(false)
|
||||||
|
@ -52,7 +55,6 @@ function MyAccount() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const elemsPersisted = new Map()
|
|
||||||
const [excalidrawAPI, setExcalidrawAPI] = useState(null)
|
const [excalidrawAPI, setExcalidrawAPI] = useState(null)
|
||||||
|
|
||||||
function loadExcalidraw(api) {
|
function loadExcalidraw(api) {
|
||||||
|
@ -67,14 +69,15 @@ function App() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
api.updateScene({ elements: [...elemsPersisted.values()] })
|
api.updateScene({ elements: [...elemsPersisted.values()] })
|
||||||
|
if (!sceneInitialized) sceneInitialized = true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateElements = debounce((elems) => {
|
const updateElements = debounce((elems) => {
|
||||||
if (excalidrawAPI) return
|
if (!sceneInitialized) return
|
||||||
const actions = []
|
const actions = []
|
||||||
for (const elem of elems) {
|
for (const elem of elems) {
|
||||||
const oldVersion = elemsPersisted.get(elem.id)?.version ?? 0
|
const oldVersion = elemsPersisted.get(elem.id)?.version ?? -1
|
||||||
if (elem.version > oldVersion) {
|
if (elem.version > oldVersion) {
|
||||||
actions.push(elem)
|
actions.push(elem)
|
||||||
elemsPersisted.set(elem.id, { ...elem })
|
elemsPersisted.set(elem.id, { ...elem })
|
||||||
|
|
Loading…
Reference in New Issue