propertiesgit status :3

This commit is contained in:
Rowan 2024-11-30 04:53:00 -06:00
parent 9a5225fcfa
commit 0a1524644d
5 changed files with 103 additions and 33 deletions

View file

@ -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 any = curry((predicates, v) => some(applyTo(v), predicates))
export const isOk = value => !is(Result, value) || value.isOk() export const isOk = value => !is(Result, value) || value.isOk()
export const eq = curry((a, b) => a === b) export const eq = curry((a, b) => a === b)
// classes // classes
export const construct = Type => args => new Type(...args) export const construct = Type => args => new Type(...args)

View file

@ -1,7 +1,7 @@
import { identifier, Keyword, literal, Symbols, ws } from './common.js' 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 { 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 const { Bracket, Colon, Comma, Hyphen } = Symbols
@ -36,7 +36,7 @@ const makeObj = Type => params => {
return new Type(name, label, properties) 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) const components = list(trim(Comma), component)
export const node = map( export const node = map(

View file

@ -368,6 +368,18 @@ export class KeyValuePair {
this.key = key this.key = key
this.value = value 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 { export class Match {

View file

@ -1,7 +1,7 @@
import { defineQuery, hasComponent } from 'bitecs' import { defineQuery, hasComponent } from 'bitecs'
import { query as q } from './query-parser/index.js' import { query as q } from './query-parser/index.js'
import { parseAll } from './parser.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' import { Relationship } from './query-parser/types.js'
const nodeComponent = component => node => { const nodeComponent = component => node => {
@ -9,11 +9,34 @@ const nodeComponent = component => node => {
const name = node.name?.valueOf() const name = node.name?.valueOf()
const type = label && component[label] || identity 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 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 prepareQuery = (match, component) => {
const relationship = is(Relationship, match) const relationship = is(Relationship, match)
@ -24,8 +47,9 @@ const prepareQuery = (match, component) => {
return { from, to, edge, relationship, schema: query } return { from, to, edge, relationship, schema: query }
} else { } else {
const components = match.components.map(nodeComponent(component)) const components = match.components.map(nodeComponent(component))
const types = components.filter(notEntity).map(prop('type')) // const types = components.filter(notEntity).map(prop('type'))
return { components, relationship, query: defineQuery(types), schema: match } const query = definePropertyQuery(components)
return { components, relationship, query, schema: match }
} }
} }
@ -33,7 +57,9 @@ const hasComponents = (world, types, entity) => (
types.every(type => hasComponent(world, type, entity)) types.every(type => hasComponent(world, type, entity))
) )
const matches = (world, types, results, set = new Set()) => entity => { const matches = (world, node, results, set = new Set()) => {
const types = node.components.filter(notEntity).map(prop('type'))
return entity => {
if (set.has(entity)) { if (set.has(entity)) {
return true return true
} else if (hasComponents(world, types, entity) && (results == null || results.includes(entity))) { } else if (hasComponents(world, types, entity) && (results == null || results.includes(entity))) {
@ -42,6 +68,7 @@ const matches = (world, types, results, set = new Set()) => entity => {
} else { } else {
return false return false
} }
}
} }
const queryRelationship = (query, world) => { const queryRelationship = (query, world) => {
@ -50,17 +77,15 @@ const queryRelationship = (query, world) => {
return node.relationship ? node.from : node return node.relationship ? node.from : node
}) })
const [fc, tc] = [from, to].map(n => n.components.filter(notEntity).map(prop('type'))) //const [fc, tc] = [from, to].map(n => n.components.filter(notEntity).map(prop('type')))
const matchesFrom = matches(world, fc, from.query(world)) const matchesFrom = matches(world, from, from.query(world))
const matchesTo = matches(world, tc) const matchesTo = matches(world, to)
const Edge = edge.components[0].type const Edge = edge.components[0].type
const edges = edge.query(world) const edges = edge.query(world)
const result = [] const result = []
for (let i = 0; i < edges.length; i++) { for (let i = 0; i < edges.length; i++) {
const eid = edges[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])) { if (matchesFrom(Edge.from[eid]) && matchesTo(Edge.to[eid])) {
result.push(eid) result.push(eid)
} }

View file

@ -70,16 +70,16 @@ describe('query', () => {
assert.deepEqual( assert.deepEqual(
query('MATCH (player:Player)-[e1:Knows]->(a:NPC) RETURN player, e1, a', engine).unwrap(), query('MATCH (player:Player)-[e1:Knows]->(a:NPC) RETURN player, e1, a', engine).unwrap(),
[ [
{ player: {}, e1: { from: 0, to: 1 }, a: {} }, { player: {}, e1: { from: player, to: a }, a: {} },
{ player: {}, e1: { from: 0, to: 3 }, a: {} } { player: {}, e1: { from: player, to: c }, a: {} }
] ]
) )
assert.deepEqual( assert.deepEqual(
query('MATCH (player:Player)-[e1:Knows]->(a:NPC)-[e2:Knows]->(b:NPC) RETURN player, e1, a, e2, b', engine).unwrap(), 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: player, to: a }, a: {}, e2: { from: a, to: b }, 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: c }, b: {} }
] ]
) )
}) })
@ -95,12 +95,12 @@ describe('query', () => {
// an unspecified component should return an Entity // an unspecified component should return an Entity
assert.deepEqual( assert.deepEqual(
query('MATCH (e, h:Health) RETURN e, h.max', engine).unwrap(), query('MATCH (e, h:Health) RETURN e, h.max', engine).unwrap(),
[{ e: 11, h: { max: 50 } }] [{ e: player, h: { max: 50 } }]
) )
assert.deepEqual( assert.deepEqual(
query('MATCH (e:Entity, h:Health) RETURN e, h.max', engine).unwrap(), 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: 25 }, player)
update(Health, { max: 50, current: 35 }, enemy) 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) update(Damaged, { damage: 10 }, edge)
assert.deepEqual( assert.deepEqual(
query('MATCH (e, h:Health) WHERE h.current < 30 RETURN e, h.max', engine).unwrap(), 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( assert.deepEqual(
query('MATCH (e, h:Health) WHERE e = 13 AND h.max = 50 OR h.current < 25 RETURN e, h.max', engine).unwrap(), 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( assert.deepEqual(
query('MATCH (e, h:Health) WHERE h.max = 50 OR h.current > 45 RETURN e, h.max AS maxHealth', engine).unwrap(), 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: player, maxHealth: 50 },
{ e: 13, maxHealth: 50 } { e: enemy, maxHealth: 50 }
] ]
) )
assert.deepEqual( 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(), 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( assert.deepEqual(
@ -145,7 +145,39 @@ describe('query', () => {
update(Damaged, { damage: 50 }, edge) update(Damaged, { damage: 50 }, edge)
assert.deepEqual( assert.deepEqual(
query('MATCH (n)-[d:Damaged]->(e, h:Health) WHERE d.damage > h.current AND h.current > 0 RETURN e', engine).unwrap(), 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 }]
) )
}) })
}) })