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 astSpan = ast.span()
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)) {
foundNode = id
break

View File

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

View File

@ -58,7 +58,8 @@ const name = computed<Opt<QualifiedName>>(() => {
// === Breadcrumbs ===
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))
})

View File

@ -108,7 +108,7 @@ export function lookupDocumentation(db: SuggestionDb, id: SuggestionId): Docs {
function getChildren(db: SuggestionDb, id: SuggestionId, kind: SuggestionKind): Docs[] {
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) => {
if (db.get(id)?.kind === kind) {
const docs = lookupDocumentation(db, id)

View File

@ -54,14 +54,14 @@ const interactionBindingsHandler = interactionBindings.handler({
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() {
const editedInfo = graphStore.editedNodeInfo
const isEditingNode = editedInfo != null
const hasNodeSelected = nodeSelection.selected.size > 0
const nodeSize = new Vec2(0, 24)
if (isEditingNode) {
const targetNode = graphStore.db.nodes.get(editedInfo.id)
const targetNode = graphStore.db.nodeIdToNode.get(editedInfo.id)
const targetPos = targetNode?.position ?? Vec2.Zero
return targetPos.add(COMPONENT_BROWSER_TO_NODE_OFFSET)
} 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 graphEditorSourceNode = computed(() => {
@ -132,7 +132,7 @@ const graphBindingsHandler = graphBindings.handler({
graphStore.transact(() => {
const allVisible = set
.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) {
graphStore.setNodeVisualizationVisible(nodeId, !allVisible)
@ -158,7 +158,7 @@ const codeEditorHandler = codeEditorBindings.handler({
},
})
/// Track play button presses.
/** Track play button presses. */
function onPlayButtonPress() {
projectStore.lsRpcConnection.then(async () => {
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(
() => projectStore.executionMode,
(modeValue) => {
@ -252,10 +252,10 @@ async function handleFileDrop(event: DragEvent) {
function onComponentBrowserCommit(content: string) {
if (content != null && graphStore.editedNodeInfo != null) {
/// We finish editing a node.
// We finish editing a node.
graphStore.setNodeContent(graphStore.editedNodeInfo.id, content)
} else if (content != null) {
/// We finish creating a new node.
// We finish creating a new node.
const nodePosition = componentBrowserPosition.value
graphStore.createNode(nodePosition.sub(COMPONENT_BROWSER_TO_NODE_OFFSET), content)
}
@ -270,16 +270,13 @@ function onComponentBrowserCancel() {
interaction.setCurrent(undefined)
}
/**
*
*/
function getNodeContent(id: ExprId): string {
const node = graphStore.db.nodes.get(id)
const node = graphStore.db.nodeIdToNode.get(id)
if (node == null) return ''
return node.rootSpan.repr()
}
// Watch the editedNode in the graph store
// Watch the `editedNode` in the graph store
watch(
() => graphStore.editedNodeInfo,
(editedInfo) => {
@ -304,25 +301,25 @@ const breadcrumbs = computed(() => {
})
})
/// === Clipboard ===
// === Clipboard ===
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 {
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 {
expression: string
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() {
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
const content = node.rootSpan.repr()
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
// as the source, as long as it is not from the same node as the target.
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
}
return setSource
@ -40,7 +40,9 @@ const targetExpr = computed(() => {
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 targetRect = computed<Rect | null>(() => {

View File

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

View File

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

View File

@ -105,7 +105,7 @@ export function useDragging() {
function* draggedNodes(): Generator<[ExprId, DraggedNode]> {
const ids = selection?.isSelected(movedId) ? selection.selected : [movedId]
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 }]
}
}
@ -125,7 +125,7 @@ export function useDragging() {
const rects: Rect[] = []
for (const [id, { initialPos }] of this.draggedNodes) {
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))
}
const snap = this.grid.snappedMany(rects, DRAG_SNAP_THRESHOLD)
@ -161,7 +161,7 @@ export function useDragging() {
updateNodesPosition() {
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 was moved in other way than current dragging, we want to stop dragging it.
if (node.position.distanceSquared(dragged.currentPos) > 1.0) {

View File

@ -34,7 +34,9 @@ const selection = injectGraphSelection(true)
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(
() => isHovered.value && graph.unconnectedEdge != null && selection?.hoveredPort === portId.value,
)
@ -166,6 +168,7 @@ export const widgetDefinition = defineWidget(
},
)
</script>
<template>
<span
ref="rootNode"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -224,8 +224,8 @@ export const useVisualizationStore = defineStore('visualization', () => {
type == null
? metadata.keys()
: new Set([
...(metadata.types.reverseLookup(type) ?? []),
...(metadata.types.reverseLookup('Any') ?? []),
...(metadata.visualizationIdToType.reverseLookup(type) ?? []),
...(metadata.visualizationIdToType.reverseLookup('Any') ?? []),
])
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> {
types = new ReactiveIndex(this, (key, metadata) =>
visualizationIdToType = new ReactiveIndex(this, (key, metadata) =>
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
/**
* 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
* 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.