More consistent reactive indices (#8353)

- Renames all `ReactiveIndex` and `ReactiveDb`s into the format `<key>To<value>`
- `ReactiveMapping`s (in `graphDatabase.ts`) currently have not been renamed - I'm not sure they need to be
- Removes various methods that have been made unnecessary by this change
- Simplifies some other methods
- Changes `SuggestionDb` to extend `ReactiveDb`

# Important Notes
None
This commit is contained in:
somebody1234 2023-11-23 14:48:12 +10:00 committed by GitHub
parent 268e595ec1
commit 1cf5ea96d6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 110 additions and 129 deletions

View File

@ -57,7 +57,7 @@ watchEffect(() => {
const dom = document.createElement('div') const dom = document.createElement('div')
const astSpan = ast.span() const astSpan = ast.span()
let foundNode: ExprId | undefined let foundNode: ExprId | undefined
for (const [id, node] of graphStore.db.allNodes()) { for (const [id, node] of graphStore.db.nodeIdToNode.entries()) {
if (rangeEncloses(node.rootSpan.span(), astSpan)) { if (rangeEncloses(node.rootSpan.span(), astSpan)) {
foundNode = id foundNode = id
break break

View File

@ -102,8 +102,8 @@ test.each([
profilingInfo: [], profilingInfo: [],
}) })
const mockGraphDb = GraphDb.Mock(computedValueRegistryMock) const mockGraphDb = GraphDb.Mock(computedValueRegistryMock)
mockGraphDb.nodes.set(operator1Id, mockNode('operator1', operator1Id)) mockGraphDb.nodeIdToNode.set(operator1Id, mockNode('operator1', operator1Id))
mockGraphDb.nodes.set(operator2Id, mockNode('operator2', operator2Id)) mockGraphDb.nodeIdToNode.set(operator2Id, mockNode('operator2', operator2Id))
const input = useComponentBrowserInput(mockGraphDb) const input = useComponentBrowserInput(mockGraphDb)
input.code.value = code input.code.value = code

View File

@ -58,7 +58,8 @@ const name = computed<Opt<QualifiedName>>(() => {
// === Breadcrumbs === // === Breadcrumbs ===
const color = computed(() => { const color = computed(() => {
const groupIndex = db.entries.get(props.selectedEntry)?.groupIndex const groupIndex =
props.selectedEntry != null ? db.entries.get(props.selectedEntry)?.groupIndex : undefined
return groupColorStyle(tryGetIndex(db.groups, groupIndex)) return groupColorStyle(tryGetIndex(db.groups, groupIndex))
}) })

View File

@ -108,7 +108,7 @@ export function lookupDocumentation(db: SuggestionDb, id: SuggestionId): Docs {
function getChildren(db: SuggestionDb, id: SuggestionId, kind: SuggestionKind): Docs[] { function getChildren(db: SuggestionDb, id: SuggestionId, kind: SuggestionKind): Docs[] {
if (!id) return [] if (!id) return []
const children = Array.from(db.parent.reverseLookup(id)) const children = Array.from(db.childIdToParentId.reverseLookup(id))
return children.reduce((acc: Docs[], id: SuggestionId) => { return children.reduce((acc: Docs[], id: SuggestionId) => {
if (db.get(id)?.kind === kind) { if (db.get(id)?.kind === kind) {
const docs = lookupDocumentation(db, id) const docs = lookupDocumentation(db, id)

View File

@ -54,14 +54,14 @@ const interactionBindingsHandler = interactionBindings.handler({
click: (e) => (e instanceof MouseEvent ? interaction.handleClick(e) : false), click: (e) => (e instanceof MouseEvent ? interaction.handleClick(e) : false),
}) })
// This is where the component browser should be placed when it is opened. /** Where the component browser should be placed when it is opened. */
function targetComponentBrowserPosition() { function targetComponentBrowserPosition() {
const editedInfo = graphStore.editedNodeInfo const editedInfo = graphStore.editedNodeInfo
const isEditingNode = editedInfo != null const isEditingNode = editedInfo != null
const hasNodeSelected = nodeSelection.selected.size > 0 const hasNodeSelected = nodeSelection.selected.size > 0
const nodeSize = new Vec2(0, 24) const nodeSize = new Vec2(0, 24)
if (isEditingNode) { if (isEditingNode) {
const targetNode = graphStore.db.nodes.get(editedInfo.id) const targetNode = graphStore.db.nodeIdToNode.get(editedInfo.id)
const targetPos = targetNode?.position ?? Vec2.Zero const targetPos = targetNode?.position ?? Vec2.Zero
return targetPos.add(COMPONENT_BROWSER_TO_NODE_OFFSET) return targetPos.add(COMPONENT_BROWSER_TO_NODE_OFFSET)
} else if (hasNodeSelected) { } else if (hasNodeSelected) {
@ -75,7 +75,7 @@ function targetComponentBrowserPosition() {
} }
} }
// This is the current position of the component browser. /** The current position of the component browser. */
const componentBrowserPosition = ref<Vec2>(Vec2.Zero) const componentBrowserPosition = ref<Vec2>(Vec2.Zero)
const graphEditorSourceNode = computed(() => { const graphEditorSourceNode = computed(() => {
@ -132,7 +132,7 @@ const graphBindingsHandler = graphBindings.handler({
graphStore.transact(() => { graphStore.transact(() => {
const allVisible = set const allVisible = set
.toArray(nodeSelection.selected) .toArray(nodeSelection.selected)
.every((id) => !(graphStore.db.getNode(id)?.vis?.visible !== true)) .every((id) => !(graphStore.db.nodeIdToNode.get(id)?.vis?.visible !== true))
for (const nodeId of nodeSelection.selected) { for (const nodeId of nodeSelection.selected) {
graphStore.setNodeVisualizationVisible(nodeId, !allVisible) graphStore.setNodeVisualizationVisible(nodeId, !allVisible)
@ -158,7 +158,7 @@ const codeEditorHandler = codeEditorBindings.handler({
}, },
}) })
/// Track play button presses. /** Track play button presses. */
function onPlayButtonPress() { function onPlayButtonPress() {
projectStore.lsRpcConnection.then(async () => { projectStore.lsRpcConnection.then(async () => {
const modeValue = projectStore.executionMode const modeValue = projectStore.executionMode
@ -169,7 +169,7 @@ function onPlayButtonPress() {
}) })
} }
/// Watch for changes in the execution mode. // Watch for changes in the execution mode.
watch( watch(
() => projectStore.executionMode, () => projectStore.executionMode,
(modeValue) => { (modeValue) => {
@ -252,10 +252,10 @@ async function handleFileDrop(event: DragEvent) {
function onComponentBrowserCommit(content: string) { function onComponentBrowserCommit(content: string) {
if (content != null && graphStore.editedNodeInfo != null) { if (content != null && graphStore.editedNodeInfo != null) {
/// We finish editing a node. // We finish editing a node.
graphStore.setNodeContent(graphStore.editedNodeInfo.id, content) graphStore.setNodeContent(graphStore.editedNodeInfo.id, content)
} else if (content != null) { } else if (content != null) {
/// We finish creating a new node. // We finish creating a new node.
const nodePosition = componentBrowserPosition.value const nodePosition = componentBrowserPosition.value
graphStore.createNode(nodePosition.sub(COMPONENT_BROWSER_TO_NODE_OFFSET), content) graphStore.createNode(nodePosition.sub(COMPONENT_BROWSER_TO_NODE_OFFSET), content)
} }
@ -270,16 +270,13 @@ function onComponentBrowserCancel() {
interaction.setCurrent(undefined) interaction.setCurrent(undefined)
} }
/**
*
*/
function getNodeContent(id: ExprId): string { function getNodeContent(id: ExprId): string {
const node = graphStore.db.nodes.get(id) const node = graphStore.db.nodeIdToNode.get(id)
if (node == null) return '' if (node == null) return ''
return node.rootSpan.repr() return node.rootSpan.repr()
} }
// Watch the editedNode in the graph store // Watch the `editedNode` in the graph store
watch( watch(
() => graphStore.editedNodeInfo, () => graphStore.editedNodeInfo,
(editedInfo) => { (editedInfo) => {
@ -304,25 +301,25 @@ const breadcrumbs = computed(() => {
}) })
}) })
/// === Clipboard === // === Clipboard ===
const ENSO_MIME_TYPE = 'web application/enso' const ENSO_MIME_TYPE = 'web application/enso'
/// The data that is copied to the clipboard. /** The data that is copied to the clipboard. */
interface ClipboardData { interface ClipboardData {
nodes: CopiedNode[] nodes: CopiedNode[]
} }
/// Node data that is copied to the clipboard. Used for serializing and deserializing the node information. /** Node data that is copied to the clipboard. Used for serializing and deserializing the node information. */
interface CopiedNode { interface CopiedNode {
expression: string expression: string
metadata: NodeMetadata | undefined metadata: NodeMetadata | undefined
} }
/// Copy the content of the selected node to the clipboard. /** Copy the content of the selected node to the clipboard. */
function copyNodeContent() { function copyNodeContent() {
const id = nodeSelection.selected.values().next().value const id = nodeSelection.selected.values().next().value
const node = graphStore.db.nodes.get(id) const node = graphStore.db.nodeIdToNode.get(id)
if (node == null) return if (node == null) return
const content = node.rootSpan.repr() const content = node.rootSpan.repr()
const metadata = projectStore.module?.getNodeMetadata(id) ?? undefined const metadata = projectStore.module?.getNodeMetadata(id) ?? undefined

View File

@ -24,7 +24,7 @@ const sourceNode = computed(() => {
// When the source is not set (i.e. edge is dragged), use the currently hovered over expression // When the source is not set (i.e. edge is dragged), use the currently hovered over expression
// as the source, as long as it is not from the same node as the target. // as the source, as long as it is not from the same node as the target.
if (setSource == null && selection?.hoveredNode != null) { if (setSource == null && selection?.hoveredNode != null) {
const rawTargetNode = graph.db.getExpressionNodeId(props.edge.target) const rawTargetNode = props.edge.target && graph.db.getExpressionNodeId(props.edge.target)
if (selection.hoveredNode != rawTargetNode) return selection.hoveredNode if (selection.hoveredNode != rawTargetNode) return selection.hoveredNode
} }
return setSource return setSource
@ -40,7 +40,9 @@ const targetExpr = computed(() => {
return setTarget return setTarget
}) })
const targetNode = computed(() => graph.db.getExpressionNodeId(targetExpr.value)) const targetNode = computed(
() => targetExpr.value && graph.db.getExpressionNodeId(targetExpr.value),
)
const targetNodeRect = computed(() => targetNode.value && graph.nodeRects.get(targetNode.value)) const targetNodeRect = computed(() => targetNode.value && graph.nodeRects.get(targetNode.value))
const targetRect = computed<Rect | null>(() => { const targetRect = computed<Rect | null>(() => {

View File

@ -21,7 +21,7 @@ const editingEdge: Interaction = {
if (graph.unconnectedEdge == null) return false if (graph.unconnectedEdge == null) return false
const source = graph.unconnectedEdge.source ?? selection?.hoveredNode const source = graph.unconnectedEdge.source ?? selection?.hoveredNode
const target = graph.unconnectedEdge.target ?? selection?.hoveredPort const target = graph.unconnectedEdge.target ?? selection?.hoveredPort
const targetNode = graph.db.getExpressionNodeId(target) const targetNode = target && graph.db.getExpressionNodeId(target)
graph.transact(() => { graph.transact(() => {
if (source != null && source != targetNode) { if (source != null && source != targetNode) {
if (target == null) { if (target == null) {
@ -42,11 +42,13 @@ interaction.setWhen(() => graph.unconnectedEdge != null, editingEdge)
function disconnectEdge(target: ExprId) { function disconnectEdge(target: ExprId) {
graph.setExpressionContent(target, '_') graph.setExpressionContent(target, '_')
} }
function createNodeFromEdgeDrop(source: ExprId) { function createNodeFromEdgeDrop(source: ExprId) {
console.log(`TODO: createNodeFromEdgeDrop(${JSON.stringify(source)})`) console.log(`TODO: createNodeFromEdgeDrop(${JSON.stringify(source)})`)
} }
function createEdge(source: ExprId, target: ExprId) { function createEdge(source: ExprId, target: ExprId) {
const sourceNode = graph.db.getNode(source) const sourceNode = graph.db.nodeIdToNode.get(source)
if (sourceNode == null) return if (sourceNode == null) return
// TODO: Check alias analysis to see if the binding is shadowed. // TODO: Check alias analysis to see if the binding is shadowed.
graph.setExpressionContent(target, sourceNode.binding) graph.setExpressionContent(target, sourceNode.binding)

View File

@ -45,7 +45,7 @@ const uploadingFiles = computed<[FileName, File][]>(() => {
<template> <template>
<GraphNode <GraphNode
v-for="[id, node] in graphStore.db.allNodes()" v-for="[id, node] in graphStore.db.nodeIdToNode.entries()"
:key="id" :key="id"
:node="node" :node="node"
:edited="id === graphStore.editedNodeInfo?.id" :edited="id === graphStore.editedNodeInfo?.id"

View File

@ -105,7 +105,7 @@ export function useDragging() {
function* draggedNodes(): Generator<[ExprId, DraggedNode]> { function* draggedNodes(): Generator<[ExprId, DraggedNode]> {
const ids = selection?.isSelected(movedId) ? selection.selected : [movedId] const ids = selection?.isSelected(movedId) ? selection.selected : [movedId]
for (const id of ids) { for (const id of ids) {
const node = graphStore.db.nodes.get(id) const node = graphStore.db.nodeIdToNode.get(id)
if (node != null) yield [id, { initialPos: node.position, currentPos: node.position }] if (node != null) yield [id, { initialPos: node.position, currentPos: node.position }]
} }
} }
@ -125,7 +125,7 @@ export function useDragging() {
const rects: Rect[] = [] const rects: Rect[] = []
for (const [id, { initialPos }] of this.draggedNodes) { for (const [id, { initialPos }] of this.draggedNodes) {
const rect = graphStore.nodeRects.get(id) const rect = graphStore.nodeRects.get(id)
const node = graphStore.db.nodes.get(id) const node = graphStore.db.nodeIdToNode.get(id)
if (rect != null && node != null) rects.push(new Rect(initialPos.add(newOffset), rect.size)) if (rect != null && node != null) rects.push(new Rect(initialPos.add(newOffset), rect.size))
} }
const snap = this.grid.snappedMany(rects, DRAG_SNAP_THRESHOLD) const snap = this.grid.snappedMany(rects, DRAG_SNAP_THRESHOLD)
@ -161,7 +161,7 @@ export function useDragging() {
updateNodesPosition() { updateNodesPosition() {
for (const [id, dragged] of this.draggedNodes) { for (const [id, dragged] of this.draggedNodes) {
const node = graphStore.db.nodes.get(id) const node = graphStore.db.nodeIdToNode.get(id)
if (node == null) continue if (node == null) continue
// If node was moved in other way than current dragging, we want to stop dragging it. // If node was moved in other way than current dragging, we want to stop dragging it.
if (node.position.distanceSquared(dragged.currentPos) > 1.0) { if (node.position.distanceSquared(dragged.currentPos) > 1.0) {

View File

@ -34,7 +34,9 @@ const selection = injectGraphSelection(true)
const isHovered = ref(false) const isHovered = ref(false)
const hasConnection = computed(() => graph.db.connections.reverseLookup(portId.value).size > 0) const hasConnection = computed(
() => graph.db.sourceIdToTargetId.reverseLookup(portId.value).size > 0,
)
const isCurrentEdgeHoverTarget = computed( const isCurrentEdgeHoverTarget = computed(
() => isHovered.value && graph.unconnectedEdge != null && selection?.hoveredPort === portId.value, () => isHovered.value && graph.unconnectedEdge != null && selection?.hoveredPort === portId.value,
) )
@ -166,6 +168,7 @@ export const widgetDefinition = defineWidget(
}, },
) )
</script> </script>
<template> <template>
<span <span
ref="rootNode" ref="rootNode"

View File

@ -23,8 +23,8 @@ import {
import { ref, type Ref } from 'vue' import { ref, type Ref } from 'vue'
export class GraphDb { export class GraphDb {
nodes = new ReactiveDb<ExprId, Node>() nodeIdToNode = new ReactiveDb<ExprId, Node>()
idents = new ReactiveIndex(this.nodes, (_id, entry) => { nodeIdToBinding = new ReactiveIndex(this.nodeIdToNode, (_id, entry) => {
const idents: [ExprId, string][] = [] const idents: [ExprId, string][] = []
entry.rootSpan.visitRecursive((span) => { entry.rootSpan.visitRecursive((span) => {
if (span.isTree(Ast.Tree.Type.Ident)) { if (span.isTree(Ast.Tree.Type.Ident)) {
@ -35,30 +35,31 @@ export class GraphDb {
}) })
return idents return idents
}) })
private nodeExpressions = new ReactiveIndex(this.nodes, (id, entry) => { nodeIdToExprId = new ReactiveIndex(this.nodeIdToNode, (id, entry) => {
const exprs = new Set<ExprId>() const exprs = new Set<ExprId>()
for (const ast of entry.rootSpan.walkRecursive()) { for (const ast of entry.rootSpan.walkRecursive()) {
exprs.add(ast.astId) exprs.add(ast.astId)
} }
return Array.from(exprs, (expr) => [id, expr]) return Array.from(exprs, (expr) => [id, expr])
}) })
nodeByBinding = new ReactiveIndex(this.nodes, (id, entry) => [[entry.binding, id]]) bindingToNodeId = new ReactiveIndex(this.nodeIdToNode, (id, entry) => [[entry.binding, id]])
connections = new ReactiveIndex(this.nodes, (id, entry) => { sourceIdToTargetId = new ReactiveIndex(this.nodeIdToNode, (id, entry) => {
const usageEntries: [ExprId, ExprId][] = [] const usageEntries: [ExprId, ExprId][] = []
const usages = this.idents.reverseLookup(entry.binding) const usages = this.nodeIdToBinding.reverseLookup(entry.binding)
for (const usage of usages) { for (const usage of usages) {
usageEntries.push([id, usage]) usageEntries.push([id, usage])
} }
return usageEntries return usageEntries
}) })
nodeMainSuggestion = new ReactiveMapping(this.nodes, (id, _entry) => { nodeMainSuggestion = new ReactiveMapping(this.nodeIdToNode, (id, _entry) => {
const expressionInfo = this.getExpressionInfo(id) const expressionInfo = this.getExpressionInfo(id)
const method = expressionInfo?.methodCall?.methodPointer const method = expressionInfo?.methodCall?.methodPointer
if (method == null) return if (method == null) return
const suggestionId = this.suggestionDb.findByMethodPointer(method) const suggestionId = this.suggestionDb.findByMethodPointer(method)
if (suggestionId == null) return
return this.suggestionDb.get(suggestionId) return this.suggestionDb.get(suggestionId)
}) })
private nodeColors = new ReactiveMapping(this.nodes, (id, _entry) => { nodeColor = new ReactiveMapping(this.nodeIdToNode, (id, _entry) => {
const index = this.nodeMainSuggestion.lookup(id)?.groupIndex const index = this.nodeMainSuggestion.lookup(id)?.groupIndex
const group = tryGetIndex(this.groups.value, index) const group = tryGetIndex(this.groups.value, index)
if (group == null) { if (group == null) {
@ -68,24 +69,12 @@ export class GraphDb {
return groupColorStyle(group) return groupColorStyle(group)
}) })
getNode(id: ExprId): Node | undefined { getExpressionNodeId(exprId: ExprId): ExprId | undefined {
return this.nodes.get(id) return set.first(this.nodeIdToExprId.reverseLookup(exprId))
}
allNodes(): IterableIterator<[ExprId, Node]> {
return this.nodes.entries()
}
allNodeIds(): IterableIterator<ExprId> {
return this.nodes.keys()
}
getExpressionNodeId(exprId: ExprId | undefined): ExprId | undefined {
return exprId && set.first(this.nodeExpressions.reverseLookup(exprId))
} }
getIdentDefiningNode(ident: string): ExprId | undefined { getIdentDefiningNode(ident: string): ExprId | undefined {
return set.first(this.nodeByBinding.lookup(ident)) return set.first(this.bindingToNodeId.lookup(ident))
} }
getExpressionInfo(id: ExprId): ExpressionInfo | undefined { getExpressionInfo(id: ExprId): ExpressionInfo | undefined {
@ -102,17 +91,18 @@ export class GraphDb {
const methodCall = this.getExpressionInfo(id)?.methodCall const methodCall = this.getExpressionInfo(id)?.methodCall
if (methodCall == null) return if (methodCall == null) return
const suggestionId = this.suggestionDb.findByMethodPointer(methodCall.methodPointer) const suggestionId = this.suggestionDb.findByMethodPointer(methodCall.methodPointer)
if (suggestionId == null) return
const suggestion = this.suggestionDb.get(suggestionId) const suggestion = this.suggestionDb.get(suggestionId)
if (suggestion == null) return if (suggestion == null) return
return { methodCall, suggestion } return { methodCall, suggestion }
} }
getNodeColorStyle(id: ExprId): string { getNodeColorStyle(id: ExprId): string {
return (id && this.nodeColors.lookup(id)) ?? 'var(--node-color-no-type)' return this.nodeColor.lookup(id) ?? 'var(--node-color-no-type)'
} }
moveNodeToTop(id: ExprId) { moveNodeToTop(id: ExprId) {
this.nodes.moveToLast(id) this.nodeIdToNode.moveToLast(id)
} }
getNodeWidth(node: Node) { getNodeWidth(node: Node) {
@ -133,11 +123,11 @@ export class GraphDb {
for (const nodeAst of functionAst.visit(getFunctionNodeExpressions)) { for (const nodeAst of functionAst.visit(getFunctionNodeExpressions)) {
const newNode = nodeFromAst(nodeAst) const newNode = nodeFromAst(nodeAst)
const nodeId = newNode.rootSpan.astId const nodeId = newNode.rootSpan.astId
const node = this.nodes.get(nodeId) const node = this.nodeIdToNode.get(nodeId)
const nodeMeta = getMeta(nodeId) const nodeMeta = getMeta(nodeId)
currentNodeIds.add(nodeId) currentNodeIds.add(nodeId)
if (node == null) { if (node == null) {
this.nodes.set(nodeId, newNode) this.nodeIdToNode.set(nodeId, newNode)
} else { } else {
if (node.binding !== newNode.binding) { if (node.binding !== newNode.binding) {
node.binding = newNode.binding node.binding = newNode.binding
@ -170,9 +160,9 @@ export class GraphDb {
} }
} }
for (const nodeId of this.allNodeIds()) { for (const nodeId of this.nodeIdToNode.keys()) {
if (!currentNodeIds.has(nodeId)) { if (!currentNodeIds.has(nodeId)) {
this.nodes.delete(nodeId) this.nodeIdToNode.delete(nodeId)
} }
} }
@ -191,10 +181,10 @@ export class GraphDb {
}, },
) )
let nodeIndex = 0 let nodeIndex = 0
for (const nodeId of this.allNodeIds()) { for (const nodeId of this.nodeIdToNode.keys()) {
const meta = getMeta(nodeId) const meta = getMeta(nodeId)
if (meta) continue if (meta) continue
const node = this.nodes.get(nodeId)! const node = this.nodeIdToNode.get(nodeId)!
const size = new Vec2(this.getNodeWidth(node), theme.node.height) const size = new Vec2(this.getNodeWidth(node), theme.node.height)
const position = new Vec2( const position = new Vec2(
rectsPosition.x, rectsPosition.x,

View File

@ -109,7 +109,7 @@ export const useGraphStore = defineStore('graph', () => {
for (const [id, op] of event.changes.keys) { for (const [id, op] of event.changes.keys) {
if (op.action === 'update' || op.action === 'add') { if (op.action === 'update' || op.action === 'add') {
const data = meta.get(id) const data = meta.get(id)
const node = db.getNode(id as ExprId) const node = db.nodeIdToNode.get(id as ExprId)
if (data != null && node != null) { if (data != null && node != null) {
db.assignUpdatedMetadata(node, data) db.assignUpdatedMetadata(node, data)
} }
@ -121,14 +121,14 @@ export const useGraphStore = defineStore('graph', () => {
let ident: string let ident: string
do { do {
ident = randomString() ident = randomString()
} while (db.idents.hasValue(ident)) } while (db.nodeIdToBinding.hasValue(ident))
return ident return ident
} }
const edges = computed(() => { const edges = computed(() => {
const disconnectedEdgeTarget = unconnectedEdge.value?.disconnectedEdgeTarget const disconnectedEdgeTarget = unconnectedEdge.value?.disconnectedEdgeTarget
const edges = [] const edges = []
for (const [target, sources] of db.connections.allReverse()) { for (const [target, sources] of db.sourceIdToTargetId.allReverse()) {
if (target === disconnectedEdgeTarget) continue if (target === disconnectedEdgeTarget) continue
for (const source of sources) { for (const source of sources) {
edges.push({ source, target }) edges.push({ source, target })
@ -177,13 +177,13 @@ export const useGraphStore = defineStore('graph', () => {
} }
function deleteNode(id: ExprId) { function deleteNode(id: ExprId) {
const node = db.getNode(id) const node = db.nodeIdToNode.get(id)
if (node == null) return if (node == null) return
proj.module?.deleteExpression(node.outerExprId) proj.module?.deleteExpression(node.outerExprId)
} }
function setNodeContent(id: ExprId, content: string) { function setNodeContent(id: ExprId, content: string) {
const node = db.getNode(id) const node = db.nodeIdToNode.get(id)
if (node == null) return if (node == null) return
setExpressionContent(node.rootSpan.astId, content) setExpressionContent(node.rootSpan.astId, content)
} }
@ -201,13 +201,13 @@ export const useGraphStore = defineStore('graph', () => {
} }
function replaceNodeSubexpression(nodeId: ExprId, range: ContentRange, content: string) { function replaceNodeSubexpression(nodeId: ExprId, range: ContentRange, content: string) {
const node = db.getNode(nodeId) const node = db.nodeIdToNode.get(nodeId)
if (node == null) return if (node == null) return
proj.module?.replaceExpressionContent(node.rootSpan.astId, content, range) proj.module?.replaceExpressionContent(node.rootSpan.astId, content, range)
} }
function setNodePosition(nodeId: ExprId, position: Vec2) { function setNodePosition(nodeId: ExprId, position: Vec2) {
const node = db.getNode(nodeId) const node = db.nodeIdToNode.get(nodeId)
if (node == null) return if (node == null) return
proj.module?.updateNodeMetadata(nodeId, { x: position.x, y: -position.y }) proj.module?.updateNodeMetadata(nodeId, { x: position.x, y: -position.y })
} }
@ -231,13 +231,13 @@ export const useGraphStore = defineStore('graph', () => {
} }
function setNodeVisualizationId(nodeId: ExprId, vis: Opt<VisualizationIdentifier>) { function setNodeVisualizationId(nodeId: ExprId, vis: Opt<VisualizationIdentifier>) {
const node = db.getNode(nodeId) const node = db.nodeIdToNode.get(nodeId)
if (node == null) return if (node == null) return
proj.module?.updateNodeMetadata(nodeId, { vis: normalizeVisMetadata(vis, node.vis?.visible) }) proj.module?.updateNodeMetadata(nodeId, { vis: normalizeVisMetadata(vis, node.vis?.visible) })
} }
function setNodeVisualizationVisible(nodeId: ExprId, visible: boolean) { function setNodeVisualizationVisible(nodeId: ExprId, visible: boolean) {
const node = db.getNode(nodeId) const node = db.nodeIdToNode.get(nodeId)
if (node == null) return if (node == null) return
proj.module?.updateNodeMetadata(nodeId, { vis: normalizeVisMetadata(node.vis, visible) }) proj.module?.updateNodeMetadata(nodeId, { vis: normalizeVisMetadata(node.vis, visible) })
} }
@ -269,7 +269,7 @@ export const useGraphStore = defineStore('graph', () => {
} }
function getNodeBinding(id: ExprId): string { function getNodeBinding(id: ExprId): string {
return db.nodes.get(id)?.binding ?? '' return db.nodeIdToNode.get(id)?.binding ?? ''
} }
return { return {

View File

@ -42,22 +42,22 @@ test('Parent-children indexing', () => {
const db = new SuggestionDb() const db = new SuggestionDb()
applyUpdates(db, test.addUpdatesForExpected(), test.groups) applyUpdates(db, test.addUpdatesForExpected(), test.groups)
// Parent lookup. // Parent lookup.
expect(db.parent.lookup(1)).toEqual(new Set([])) expect(db.childIdToParentId.lookup(1)).toEqual(new Set([]))
expect(db.parent.lookup(2)).toEqual(new Set([1])) expect(db.childIdToParentId.lookup(2)).toEqual(new Set([1]))
expect(db.parent.lookup(3)).toEqual(new Set([2])) expect(db.childIdToParentId.lookup(3)).toEqual(new Set([2]))
expect(db.parent.lookup(4)).toEqual(new Set([2])) expect(db.childIdToParentId.lookup(4)).toEqual(new Set([2]))
expect(db.parent.lookup(5)).toEqual(new Set([2])) expect(db.childIdToParentId.lookup(5)).toEqual(new Set([2]))
expect(db.parent.lookup(6)).toEqual(new Set([1])) expect(db.childIdToParentId.lookup(6)).toEqual(new Set([1]))
expect(db.parent.lookup(7)).toEqual(new Set([1])) expect(db.childIdToParentId.lookup(7)).toEqual(new Set([1]))
// Children lookup. // Children lookup.
expect(db.parent.reverseLookup(1)).toEqual(new Set([2, 6, 7])) expect(db.childIdToParentId.reverseLookup(1)).toEqual(new Set([2, 6, 7]))
expect(db.parent.reverseLookup(2)).toEqual(new Set([3, 4, 5])) expect(db.childIdToParentId.reverseLookup(2)).toEqual(new Set([3, 4, 5]))
expect(db.parent.reverseLookup(3)).toEqual(new Set([])) expect(db.childIdToParentId.reverseLookup(3)).toEqual(new Set([]))
expect(db.parent.reverseLookup(4)).toEqual(new Set([])) expect(db.childIdToParentId.reverseLookup(4)).toEqual(new Set([]))
expect(db.parent.reverseLookup(5)).toEqual(new Set([])) expect(db.childIdToParentId.reverseLookup(5)).toEqual(new Set([]))
expect(db.parent.reverseLookup(6)).toEqual(new Set([])) expect(db.childIdToParentId.reverseLookup(6)).toEqual(new Set([]))
expect(db.parent.reverseLookup(7)).toEqual(new Set([])) expect(db.childIdToParentId.reverseLookup(7)).toEqual(new Set([]))
// Add new entry. // Add new entry.
const modifications: lsTypes.SuggestionsDatabaseUpdate[] = [ const modifications: lsTypes.SuggestionsDatabaseUpdate[] = [
@ -78,22 +78,22 @@ test('Parent-children indexing', () => {
}, },
] ]
applyUpdates(db, modifications, test.groups) applyUpdates(db, modifications, test.groups)
expect(db.parent.lookup(8)).toEqual(new Set([2])) expect(db.childIdToParentId.lookup(8)).toEqual(new Set([2]))
expect(db.parent.reverseLookup(8)).toEqual(new Set([])) expect(db.childIdToParentId.reverseLookup(8)).toEqual(new Set([]))
expect(db.parent.reverseLookup(2)).toEqual(new Set([3, 4, 5, 8])) expect(db.childIdToParentId.reverseLookup(2)).toEqual(new Set([3, 4, 5, 8]))
// Remove entry. // Remove entry.
const modifications2: lsTypes.SuggestionsDatabaseUpdate[] = [{ type: 'Remove', id: 3 }] const modifications2: lsTypes.SuggestionsDatabaseUpdate[] = [{ type: 'Remove', id: 3 }]
applyUpdates(db, modifications2, test.groups) applyUpdates(db, modifications2, test.groups)
expect(db.parent.lookup(3)).toEqual(new Set([])) expect(db.childIdToParentId.lookup(3)).toEqual(new Set([]))
expect(db.parent.reverseLookup(2)).toEqual(new Set([4, 5, 8])) expect(db.childIdToParentId.reverseLookup(2)).toEqual(new Set([4, 5, 8]))
// Modify entry. Moving new method from `Standard.Base.Type` to `Standard.Base`. // Modify entry. Moving new method from `Standard.Base.Type` to `Standard.Base`.
db.get(8)!.memberOf = 'Standard.Base' as QualifiedName db.get(8)!.memberOf = 'Standard.Base' as QualifiedName
expect(db.parent.reverseLookup(1)).toEqual(new Set([2, 6, 7, 8])) expect(db.childIdToParentId.reverseLookup(1)).toEqual(new Set([2, 6, 7, 8]))
expect(db.parent.lookup(8)).toEqual(new Set([1])) expect(db.childIdToParentId.lookup(8)).toEqual(new Set([1]))
expect(db.parent.reverseLookup(8)).toEqual(new Set([])) expect(db.childIdToParentId.reverseLookup(8)).toEqual(new Set([]))
expect(db.parent.reverseLookup(2)).toEqual(new Set([4, 5])) expect(db.childIdToParentId.reverseLookup(2)).toEqual(new Set([4, 5]))
}) })
test("Modifying suggestion entries' fields", () => { test("Modifying suggestion entries' fields", () => {

View File

@ -10,10 +10,9 @@ import { LanguageServer } from 'shared/languageServer'
import type { MethodPointer } from 'shared/languageServerTypes' import type { MethodPointer } from 'shared/languageServerTypes'
import { markRaw, ref, type Ref } from 'vue' import { markRaw, ref, type Ref } from 'vue'
export class SuggestionDb { export class SuggestionDb extends ReactiveDb<SuggestionId, SuggestionEntry> {
_internal = new ReactiveDb<SuggestionId, SuggestionEntry>() nameToId = new ReactiveIndex(this, (id, entry) => [[entryQn(entry), id]])
nameToId = new ReactiveIndex(this._internal, (id, entry) => [[entryQn(entry), id]]) childIdToParentId = new ReactiveIndex(this, (id, entry) => {
parent = new ReactiveIndex(this._internal, (id, entry) => {
let qualifiedName: Opt<QualifiedName> let qualifiedName: Opt<QualifiedName>
if (entry.memberOf) { if (entry.memberOf) {
qualifiedName = entry.memberOf qualifiedName = entry.memberOf
@ -27,19 +26,6 @@ export class SuggestionDb {
return [] return []
}) })
set(id: SuggestionId, entry: SuggestionEntry): void {
this._internal.set(id, entry)
}
get(id: SuggestionId | null | undefined): SuggestionEntry | undefined {
return id != null ? this._internal.get(id) : undefined
}
delete(id: SuggestionId): boolean {
return this._internal.delete(id)
}
entries(): IterableIterator<[SuggestionId, SuggestionEntry]> {
return this._internal.entries()
}
findByMethodPointer(method: MethodPointer): SuggestionId | undefined { findByMethodPointer(method: MethodPointer): SuggestionId | undefined {
if (method == null) return if (method == null) return
const moduleName = tryQualifiedName(method.definedOnType) const moduleName = tryQualifiedName(method.definedOnType)

View File

@ -36,18 +36,18 @@ test('metadata index', () => {
const b = toVisualizationId({ module: { kind: 'Builtin' }, name: 'b' }) const b = toVisualizationId({ module: { kind: 'Builtin' }, name: 'b' })
db.set(a, { name: 'a', inputType: 'B | C' }) db.set(a, { name: 'a', inputType: 'B | C' })
db.set(b, { name: 'b', inputType: 'C | D | E' }) db.set(b, { name: 'b', inputType: 'C | D | E' })
expect(db.types.lookup(a)).toEqual(new Set(['B', 'C'])) expect(db.visualizationIdToType.lookup(a)).toEqual(new Set(['B', 'C']))
expect(db.types.lookup(b)).toEqual(new Set(['C', 'D', 'E'])) expect(db.visualizationIdToType.lookup(b)).toEqual(new Set(['C', 'D', 'E']))
expect(db.types.reverseLookup('B')).toEqual(new Set([a])) expect(db.visualizationIdToType.reverseLookup('B')).toEqual(new Set([a]))
expect(db.types.reverseLookup('C')).toEqual(new Set([a, b])) expect(db.visualizationIdToType.reverseLookup('C')).toEqual(new Set([a, b]))
expect(db.types.reverseLookup('D')).toEqual(new Set([b])) expect(db.visualizationIdToType.reverseLookup('D')).toEqual(new Set([b]))
expect(db.types.reverseLookup('E')).toEqual(new Set([b])) expect(db.visualizationIdToType.reverseLookup('E')).toEqual(new Set([b]))
db.delete(b) db.delete(b)
expect(db.types.lookup(a)).toEqual(new Set(['B', 'C'])) expect(db.visualizationIdToType.lookup(a)).toEqual(new Set(['B', 'C']))
expect(db.types.lookup(b)).toEqual(new Set()) expect(db.visualizationIdToType.lookup(b)).toEqual(new Set())
expect(db.types.reverseLookup('B')).toEqual(new Set([a])) expect(db.visualizationIdToType.reverseLookup('B')).toEqual(new Set([a]))
expect(db.types.reverseLookup('C')).toEqual(new Set([a])) expect(db.visualizationIdToType.reverseLookup('C')).toEqual(new Set([a]))
expect(db.types.reverseLookup('D')).toEqual(new Set()) expect(db.visualizationIdToType.reverseLookup('D')).toEqual(new Set())
expect(db.types.reverseLookup('E')).toEqual(new Set()) expect(db.visualizationIdToType.reverseLookup('E')).toEqual(new Set())
}) })

View File

@ -224,8 +224,8 @@ export const useVisualizationStore = defineStore('visualization', () => {
type == null type == null
? metadata.keys() ? metadata.keys()
: new Set([ : new Set([
...(metadata.types.reverseLookup(type) ?? []), ...(metadata.visualizationIdToType.reverseLookup(type) ?? []),
...(metadata.types.reverseLookup('Any') ?? []), ...(metadata.visualizationIdToType.reverseLookup('Any') ?? []),
]) ])
for (const type of types) yield fromVisualizationId(type) for (const type of types) yield fromVisualizationId(type)
} }

View File

@ -29,7 +29,7 @@ export function fromVisualizationId(key: VisualizationId): VisualizationIdentifi
} }
export class VisualizationMetadataDb extends ReactiveDb<VisualizationId, VisualizationMetadata> { export class VisualizationMetadataDb extends ReactiveDb<VisualizationId, VisualizationMetadata> {
types = new ReactiveIndex(this, (key, metadata) => visualizationIdToType = new ReactiveIndex(this, (key, metadata) =>
getTypesFromUnion(metadata.inputType).map((type) => [key, type]), getTypesFromUnion(metadata.inputType).map((type) => [key, type]),
) )
} }

View File

@ -266,7 +266,7 @@ export class ReactiveIndex<K, V, IK, IV> {
export type Mapper<K, V, IV> = (key: K, value: V) => IV | undefined export type Mapper<K, V, IV> = (key: K, value: V) => IV | undefined
/** /**
* A one-to-one mapping for values in a {@link ReactiveDb} instance. Allows only one value per key. * A one-to-one mapping for values in a {@link ReactiveDb} instance. Allows only one value per key.
* It can be thought of as a collection of `computed` values per each key in the `ReactiveDb`. The * It can be thought of as a collection of `computed` values per each key in the `ReactiveDb`. The
* mapping is automatically updated when any of its dependencies change, and is properly cleaned up * mapping is automatically updated when any of its dependencies change, and is properly cleaned up
* when any key is removed from {@link ReactiveDb}. Only accessed keys are ever actually computed. * when any key is removed from {@link ReactiveDb}. Only accessed keys are ever actually computed.