leaving for flight, brb

This commit is contained in:
Rowan 2024-11-25 11:00:48 -06:00
parent a8d9f36b34
commit c1520b7a59
4 changed files with 119 additions and 20 deletions

View file

@ -1,6 +1,6 @@
import { join } from '../fn.js'
import { alpha, alphanumeric, any, char, digit, list, many, map, maybe, noCaseString, separated, seq, skip, string, until } from '../parser.js'
import { Alias, Identifier, Literal, ObjectPath } from './types.js'
import { Alias, Identifier, Literal, ObjectPath, Property } from './types.js'
export const Symbol = Object.freeze({
Bracket: Object.freeze({
@ -47,7 +47,7 @@ const str = quoted
const toBoolean = v => v === 'false' ? false : true
const boolean = map(([v]) => toBoolean(v), any(string('true'), string('false')))
const property = seq(skip(Symbol.Period), identifier)
const property = map(([x]) => new Property(x), seq(skip(Symbol.Period), identifier))
const accessor = map(x => new ObjectPath(...x), seq(identifier, property, many(property)))
export const literal = map(([x]) => new Literal(x), any(float, integer, str, boolean))

View file

@ -1,4 +1,5 @@
import { is } from '../fn.js'
import { pipe } from 'bitecs'
import { assoc, assocPath, is, nth } from '../fn.js'
class Value {
#value
@ -26,6 +27,40 @@ export class Identifier extends Value {
constructor(value) {
super(value)
}
toString() {
return this.value.toString()
}
from(component) {
return component
}
}
export class ObjectIdentifier extends Identifier {
constructor(value) {
super(value)
}
from(component) {
return new Proxy(component, {
get(target, entity, _receiver) {
return Object.fromEntries(
Object.entries(target).map(([k, v]) => [k, v[entity]])
)
}
})
}
}
export class Property extends Identifier {
constructor(value) {
super(value)
}
from(component) {
return component[this.value]
}
}
export class Name extends Identifier {
@ -67,11 +102,11 @@ export class ObjectPath extends Identifier {
}
toString() {
return this.path.reduce((acc, val) => acc + val, this.value)
return `${this.value}.${this.path.join('.')}`
}
from(obj) {
return this.path.reduce((obj, key) => obj && obj[key], obj)
from(component) {
return this.path.reduce((acc, v) => v.from(acc), component)
}
equals(value) {
@ -139,15 +174,62 @@ export class Relationship {
}
}
const isExactly = (Type, value) => Object.getPrototypeOf(value) === Type.prototype
export class ReturnValues {
constructor(...args) {
this.values = args
this.values = args.map(x => isExactly(Identifier, x) ? new ObjectIdentifier(x) : x)
}
#isTag(type) {
type && Object.getOwnPropertySymbols(type).find(
(s) => s.description === 'tagStore'
) || false
}
#forComponentType(value, type) {
if (this.#isTag(type)) {
return () => ({})
} else if (typeof type === 'function') {
return type
} else {
return value.from(type)
//return Object.fromEntries(
// Object.entries(type).map(([k, v]) => [k, v[entity]])
//)
}
}
// TODO: adjust froms to accent entity, return data with built up objects as necessary
from(components) {
/*
* const { entity, query: { components } } = node
* const returns2 = components.map(c => ({ ...c, returnValue: returns.find(x => x.equals(c.name)) }))
* returns2.map(({ returnValue, type }) => getValue(returnValue, type, entity))
* const values = returns2.filter(({ name }) => returns.includes(name))
* .map(({ name, type, returnValue }) => ({ [name]: getValue(returnValue, type, entity) }))
* const values = components.filter(({ name }) => returns.includes(name))
* .map(({ name, type }) => ({ [name]: getValue(type, entity) }))
* return Object.assign(obj, ...values)
*/
const values = components.map(component => ({ ...component, returnValue: this.findName(component.name) }))
.map(({ name, type, returnValue }) => ([returnValue.toString(), this.#forComponentType(returnValue, type)]))
console.log(this, values)
}
find(fn) {
return this.values.find(fn)
}
findName(name) {
return this.find(value => value.equals(name))
}
filter(fn) {
return this.values.filter(fn)
}
includes(value) {
return this.find(x => x.equals(value))
}

View file

@ -101,24 +101,32 @@ const isTag = type => (
) || false
)
const getValue = (type, entity) => {
const getValue = (returnValue, type, entity) => {
if (isTag(type)) {
return {}
} else if (typeof type === 'function') {
return type(entity)
} else {
return Object.fromEntries(
Object.entries(type).map(([k, v]) => [k, v[entity]])
)
return returnValue.from(type)
//return Object.fromEntries(
// Object.entries(type).map(([k, v]) => [k, v[entity]])
//)
}
}
const resolveNode = curry((node, obj) => {
const returns = path('query.schema.returnValues', node)
const { entity, query: { components } } = node
const values = components.filter(({ name }) => returns.includes(name))
.map(({ name, type }) => ({ [name]: getValue(type, entity) }))
return Object.assign(obj, ...values)
// TODO: do this work in prepareQuery
returns.from(components)
//const returns2 = components.map(c => ({ ...c, returnValue: returns.find(x => x.equals(c.name)) }))
//returns2.map(({ returnValue, type }) => getValue(returnValue, type, entity))
//const values = returns2.filter(({ name }) => returns.includes(name))
// .map(({ name, type, returnValue }) => ({ [name]: getValue(returnValue, type, entity) }))
//const values = components.filter(({ name }) => returns.includes(name))
// .map(({ name, type }) => ({ [name]: getValue(type, entity) }))
//return Object.assign(obj, ...values)
})
const resolveRelationship = edges => {

View file

@ -1,8 +1,7 @@
import { before, describe, it } from 'node:test'
import { addComponent, addEntity, createWorld, defineComponent, Types } from 'bitecs'
import { before, beforeEach, describe, it } from 'node:test'
import { addComponent, addEntity, createWorld, defineComponent, resetWorld, Types } from 'bitecs'
import assert from './assert.js'
import { query } from '../src/query.js'
import { identity } from '../src/fn.js'
let engine = {}
@ -26,7 +25,6 @@ describe('query', () => {
before(() => {
const world = { default: createWorld() }
const component = {
Entity: identity,
Player: defineComponent(null, 10),
NPC: defineComponent(null, 10),
Health: defineComponent({ current: Types.ui8, max: Types.ui8 }),
@ -39,7 +37,11 @@ describe('query', () => {
engine = { world, component }
})
it('should execute basic queries', () => {
beforeEach(() => {
Object.values(engine.world).forEach(world => resetWorld(world))
})
it('should query on single components', () => {
const world = engine.world.default
const { Player, NPC, Knows } = engine.component
@ -82,9 +84,16 @@ describe('query', () => {
Health.max[player] = 50
Health.current[player] = 25
// 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 } }]
)
assert.deepEqual(
query('MATCH (e:Entity, h:Health) return e, h.max', engine).unwrap(),
[{ e: 11, h: { current: 25, max: 50 } }]
[{ e: 11, h: { max: 50 } }]
)
})
})