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

View file

@ -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(

View file

@ -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 {

View file

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

View file

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