graph-ecs/src/query.js
2024-11-20 03:54:35 -06:00

118 lines
2.9 KiB
JavaScript

import { defineQuery, hasComponent, pipe } from 'bitecs'
import { query as q } from './query-parser/index.js'
import { parseAll } from './parser.js'
import { curry, of, is, reduce, flip, concat, map } from './fn.js'
import { Edge, Node, Relationship } from './query-parser/types.js'
/*
* necessary engine structure
* worlds: World[]
* Component: Component[]
*/
const isEmpty = obj => {
for (const _ in obj) {
return false
}
return true
}
const prepareQuery = (query, component) => {
const { match, returnValues } = query
const relationship = is(Relationship, match)
if (relationship) {
const { from: f, to: t, edge: e } = match
const [from, to, edge] = [f, t, e].map(q => prepareQuery({ match: q, returnValues }, component))
return { from, to, edge, relationship, schema: query }
} else {
const label = match.label?.value?.value
const name = match.name?.value?.value
const type = component[label]
return { name, label, type, relationship, query: defineQuery([type]), schema: query }
}
}
const getEndNode = end => end.relationship ? end.from : end
const matches = (world, type, query, set = new Set()) => entity => {
if (set.has(entity)) {
return true
} else if (hasComponent(world, type, entity) && query.includes(entity)) {
set.add(entity)
return true
} else {
return false
}
}
const queryRelationship = (from, edge, to, world) => {
const matchesFrom = matches(world, from.type, from.results)
const matchesTo = matches(world, to.type, to.results)
const Edge = edge.type
const result = []
for (let i = 0; i < edge.results.length; i++) {
const eid = edge.results[i]
if (matchesFrom(Edge.from[eid]) && matchesTo(Edge.to[eid])) {
result.push(eid)
}
}
return result
}
const cat = flip(concat)
const executeQuery = (query, world) => {
if (!query.relationship) {
return map(of, query.query(world))
}
const { from, to: _to, edge, returnValues } = query
const to = getEndNode(_to)
const Edge = edge.type
const edges = queryRelationship(
{ type: from.type, results: from.query(world) },
{ type: Edge, results: edge.query(world) },
{ type: to.type, results: to.query(world) },
world
)
if (_to.relationship) {
return reduce((acc, eid) => {
const next = {
..._to,
from: {
...to,
query: () => of(Edge.to[eid])
}
}
const results = map(cat([eid]), executeQuery(next, world))
return [...acc, ...results]
}, [], edges)
} else {
return map(of, edges)
}
}
export const query = curry((input, engine) => {
return parseAll(q, input).map(({ use, ...rest }) => {
const worldName = use && use.value || 'default'
const world = engine.world[worldName]
const prepared = prepareQuery(rest, engine.component)
const results = executeQuery(prepared, world)
// TODO: handle return value mapping
return results
})
})