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 }) })