propertiesgit status :3
This commit is contained in:
parent
9a5225fcfa
commit
0a1524644d
5 changed files with 103 additions and 33 deletions
|
@ -57,6 +57,7 @@ export const all = curry((predicates, v) => every(applyTo(v), predicates))
|
|||
export const any = curry((predicates, v) => some(applyTo(v), predicates))
|
||||
export const isOk = value => !is(Result, value) || value.isOk()
|
||||
export const eq = curry((a, b) => a === b)
|
||||
|
||||
// classes
|
||||
export const construct = Type => args => new Type(...args)
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { identifier, Keyword, literal, Symbols, ws } from './common.js'
|
||||
import { Node, Edge, KeyValuePair, Label, Name, Direction, DirectedEdge, Relationship, Component, Match } from './types.js'
|
||||
import { Node, Edge, KeyValuePair, Label, Name, Direction, DirectedEdge, Relationship, Component, Match, Properties } from './types.js'
|
||||
import { construct, curry, head, is } from '../fn.js'
|
||||
import { many, maybe, map, seq, skip, between, separated, list, any } from '../parser.js'
|
||||
import { many, maybe, map, seq, skip, between, separated, list, any, char, noCaseString, anyChar } from '../parser.js'
|
||||
|
||||
const { Bracket, Colon, Comma, Hyphen } = Symbols
|
||||
|
||||
|
@ -36,7 +36,7 @@ const makeObj = Type => params => {
|
|||
return new Type(name, label, properties)
|
||||
}
|
||||
|
||||
const component = map(makeObj(Component), seq(id, maybe(properties)))
|
||||
const component = map(makeObj(Component), seq(id, ws, maybe(properties)))
|
||||
const components = list(trim(Comma), component)
|
||||
|
||||
export const node = map(
|
||||
|
|
|
@ -368,6 +368,18 @@ export class KeyValuePair {
|
|||
this.key = key
|
||||
this.value = value
|
||||
}
|
||||
|
||||
valueOf() {
|
||||
return { [this.key.valueOf()]: this.value.valueOf() }
|
||||
}
|
||||
}
|
||||
|
||||
export class Properties {
|
||||
constructor(kvps) {
|
||||
kvps.forEach(e => {
|
||||
this[e.key] = e.value
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export class Match {
|
||||
|
|
59
src/query.js
59
src/query.js
|
@ -1,7 +1,7 @@
|
|||
import { defineQuery, hasComponent } from 'bitecs'
|
||||
import { query as q } from './query-parser/index.js'
|
||||
import { parseAll } from './parser.js'
|
||||
import { curry, of, is, map, always, assocPath, prop, identity, mergeRightDeep, reduce } from './fn.js'
|
||||
import { curry, of, is, map, always, assocPath, prop, identity, mergeRightDeep, pipe, filter, some, path, tap } from './fn.js'
|
||||
import { Relationship } from './query-parser/types.js'
|
||||
|
||||
const nodeComponent = component => node => {
|
||||
|
@ -9,11 +9,34 @@ const nodeComponent = component => node => {
|
|||
const name = node.name?.valueOf()
|
||||
const type = label && component[label] || identity
|
||||
|
||||
return { name, label, type }
|
||||
return { name, label, type, properties: node.properties }
|
||||
}
|
||||
|
||||
const notEntity = ({ label }) => label != null && label.valueOf() !== 'Entity'
|
||||
|
||||
const definePropertyQuery = components => {
|
||||
const query = pipe(
|
||||
filter(notEntity),
|
||||
map(prop('type')),
|
||||
defineQuery
|
||||
)(components)
|
||||
|
||||
if (some(c => c.properties.length > 0, components)) {
|
||||
return world => {
|
||||
const comps = filter(c => c.properties.length > 0, components)
|
||||
return query(world).filter(eid =>
|
||||
comps.every(cmp =>
|
||||
cmp.properties.every(({ key, value }) =>
|
||||
cmp.type[key][eid] === value.valueOf()
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
return query
|
||||
}
|
||||
}
|
||||
|
||||
const prepareQuery = (match, component) => {
|
||||
const relationship = is(Relationship, match)
|
||||
|
||||
|
@ -24,8 +47,9 @@ const prepareQuery = (match, component) => {
|
|||
return { from, to, edge, relationship, schema: query }
|
||||
} else {
|
||||
const components = match.components.map(nodeComponent(component))
|
||||
const types = components.filter(notEntity).map(prop('type'))
|
||||
return { components, relationship, query: defineQuery(types), schema: match }
|
||||
// const types = components.filter(notEntity).map(prop('type'))
|
||||
const query = definePropertyQuery(components)
|
||||
return { components, relationship, query, schema: match }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -33,14 +57,17 @@ const hasComponents = (world, types, entity) => (
|
|||
types.every(type => hasComponent(world, type, entity))
|
||||
)
|
||||
|
||||
const matches = (world, types, results, set = new Set()) => entity => {
|
||||
if (set.has(entity)) {
|
||||
return true
|
||||
} else if (hasComponents(world, types, entity) && (results == null || results.includes(entity))) {
|
||||
set.add(entity)
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
const matches = (world, node, results, set = new Set()) => {
|
||||
const types = node.components.filter(notEntity).map(prop('type'))
|
||||
return entity => {
|
||||
if (set.has(entity)) {
|
||||
return true
|
||||
} else if (hasComponents(world, types, entity) && (results == null || results.includes(entity))) {
|
||||
set.add(entity)
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -50,17 +77,15 @@ const queryRelationship = (query, world) => {
|
|||
return node.relationship ? node.from : node
|
||||
})
|
||||
|
||||
const [fc, tc] = [from, to].map(n => n.components.filter(notEntity).map(prop('type')))
|
||||
const matchesFrom = matches(world, fc, from.query(world))
|
||||
const matchesTo = matches(world, tc)
|
||||
//const [fc, tc] = [from, to].map(n => n.components.filter(notEntity).map(prop('type')))
|
||||
const matchesFrom = matches(world, from, from.query(world))
|
||||
const matchesTo = matches(world, to)
|
||||
|
||||
const Edge = edge.components[0].type
|
||||
const edges = edge.query(world)
|
||||
const result = []
|
||||
for (let i = 0; i < edges.length; i++) {
|
||||
const eid = edges[i]
|
||||
// console.log(eid, Edge.from[eid], '->', Edge.to[eid], matchesFrom(Edge.from[eid]), matchesTo(Edge.to[eid]))
|
||||
|
||||
if (matchesFrom(Edge.from[eid]) && matchesTo(Edge.to[eid])) {
|
||||
result.push(eid)
|
||||
}
|
||||
|
|
|
@ -70,16 +70,16 @@ describe('query', () => {
|
|||
assert.deepEqual(
|
||||
query('MATCH (player:Player)-[e1:Knows]->(a:NPC) RETURN player, e1, a', engine).unwrap(),
|
||||
[
|
||||
{ player: {}, e1: { from: 0, to: 1 }, a: {} },
|
||||
{ player: {}, e1: { from: 0, to: 3 }, a: {} }
|
||||
{ player: {}, e1: { from: player, to: a }, a: {} },
|
||||
{ player: {}, e1: { from: player, to: c }, a: {} }
|
||||
]
|
||||
)
|
||||
|
||||
assert.deepEqual(
|
||||
query('MATCH (player:Player)-[e1:Knows]->(a:NPC)-[e2:Knows]->(b:NPC) RETURN player, e1, a, e2, b', engine).unwrap(),
|
||||
[
|
||||
{ player: {}, e1: { from: 0, to: 1 }, a: {}, e2: { from: 1, to: 2 }, b: {} },
|
||||
{ player: {}, e1: { from: 0, to: 1 }, a: {}, e2: { from: 1, to: 3 }, b: {} }
|
||||
{ player: {}, e1: { from: player, to: a }, a: {}, e2: { from: a, to: b }, b: {} },
|
||||
{ player: {}, e1: { from: player, to: a }, a: {}, e2: { from: a, to: c }, b: {} }
|
||||
]
|
||||
)
|
||||
})
|
||||
|
@ -95,12 +95,12 @@ describe('query', () => {
|
|||
// an unspecified component should return an Entity
|
||||
assert.deepEqual(
|
||||
query('MATCH (e, h:Health) RETURN e, h.max', engine).unwrap(),
|
||||
[{ e: 11, h: { max: 50 } }]
|
||||
[{ e: player, h: { max: 50 } }]
|
||||
)
|
||||
|
||||
assert.deepEqual(
|
||||
query('MATCH (e:Entity, h:Health) RETURN e, h.max', engine).unwrap(),
|
||||
[{ e: 11, h: { max: 50 } }]
|
||||
[{ e: player, h: { max: 50 } }]
|
||||
)
|
||||
})
|
||||
|
||||
|
@ -112,29 +112,29 @@ describe('query', () => {
|
|||
|
||||
update(Health, { max: 50, current: 25 }, player)
|
||||
update(Health, { max: 50, current: 35 }, enemy)
|
||||
const [edge] = relate(world, player, Damaged, enemy)
|
||||
const [edge] = relate(world, player, Damaged, enemy) // 14
|
||||
update(Damaged, { damage: 10 }, edge)
|
||||
|
||||
assert.deepEqual(
|
||||
query('MATCH (e, h:Health) WHERE h.current < 30 RETURN e, h.max', engine).unwrap(),
|
||||
[{ e: 12, h: { max: 50 } }]
|
||||
[{ e: player, h: { max: 50 } }]
|
||||
)
|
||||
|
||||
assert.deepEqual(
|
||||
query('MATCH (e, h:Health) WHERE e = 13 AND h.max = 50 OR h.current < 25 RETURN e, h.max', engine).unwrap(),
|
||||
[{ e: 13, h: { max: 50 } }]
|
||||
[{ e: enemy, h: { max: 50 } }]
|
||||
)
|
||||
|
||||
assert.deepEqual(
|
||||
query('MATCH (e, h:Health) WHERE h.max = 50 OR h.current > 45 RETURN e, h.max AS maxHealth', engine).unwrap(),
|
||||
[
|
||||
{ e: 12, maxHealth: 50 },
|
||||
{ e: 13, maxHealth: 50 }
|
||||
{ e: player, maxHealth: 50 },
|
||||
{ e: enemy, maxHealth: 50 }
|
||||
]
|
||||
)
|
||||
assert.deepEqual(
|
||||
query('MATCH (e, :Player, h:Health) WHERE (h.max = 50 OR h.current > 45) AND e = 12 RETURN e, h.max AS maxHealth', engine).unwrap(),
|
||||
[{ e: 12, maxHealth: 50 }]
|
||||
[{ e: player, maxHealth: 50 }]
|
||||
)
|
||||
|
||||
assert.deepEqual(
|
||||
|
@ -145,7 +145,39 @@ describe('query', () => {
|
|||
update(Damaged, { damage: 50 }, edge)
|
||||
assert.deepEqual(
|
||||
query('MATCH (n)-[d:Damaged]->(e, h:Health) WHERE d.damage > h.current AND h.current > 0 RETURN e', engine).unwrap(),
|
||||
[{ e: 13 }]
|
||||
[{ e: enemy }]
|
||||
)
|
||||
})
|
||||
|
||||
it('properties should match', () => {
|
||||
const world = engine.world.default
|
||||
const { Player, Health, Damaged } = engine.component
|
||||
const player = create(world, Player, Health)
|
||||
const enemy = create(world, Health)
|
||||
|
||||
update(Health, { max: 50, current: 25 }, player)
|
||||
update(Health, { max: 50, current: 35 }, enemy)
|
||||
const [edge] = relate(world, player, Damaged, enemy) // 14
|
||||
update(Damaged, { damage: 10 }, edge)
|
||||
|
||||
assert.deepEqual(
|
||||
query('MATCH (e, h:Health { current: 35 }) RETURN e', engine).unwrap(),
|
||||
[{ e: enemy }]
|
||||
)
|
||||
|
||||
assert.deepEqual(
|
||||
query('MATCH (e, h:Health { max: 50 }) RETURN e', engine).unwrap(),
|
||||
[{ e: player }, { e: enemy }]
|
||||
)
|
||||
|
||||
assert.deepEqual(
|
||||
query('MATCH (e, h:Health { current: 35, max: 50 }) RETURN e', engine).unwrap(),
|
||||
[{ e: enemy }]
|
||||
)
|
||||
|
||||
assert.deepEqual(
|
||||
query('MATCH (a)-[:Damaged { damage: 10 }]->(b) RETURN a, b', engine).unwrap(),
|
||||
[{ a: player, b: enemy }]
|
||||
)
|
||||
})
|
||||
})
|
||||
|
|
Loading…
Reference in a new issue