118 lines
2.9 KiB
JavaScript
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
|
|
})
|
|
})
|
|
|