graph-ecs/README.md
2024-11-30 05:03:10 -06:00

3.2 KiB

GraphECS

this is a prototype demonstrating representing underlying ecs data as a graph with a cypher-like query language

the purpose of this prototype is not to prove the usefulness of a query DSL with ECS per se. instead, the purpose is to demonstrate the advantages of thinking of the game state as a database.

Syntax

graphecs uses a modified subset of neo4j's cypher query language. neo4j is a graph database with a focus on relationships between data. it has many similar features to SQL-like languages but the ergonomics of relating data is significantly better.

Querying entities

MATCH (e)
RETURN e

this is a basic query to return every entity in current ecs world. MATCH clauses begin any expression that queries for data. RETURN defines what data will be returned by the query. an example of the output may look like this

[
    { e: 0 },
    { e: 1 },
    { e: 2 },
]
MATCH (e:Entity, :Player)
RETURN e

this query specifies two components to match for a single node. here, it explicitly identifies the :Entity component but this is optional -- binding a variable without any component label will be bound to the entity's id. additionally, it requires the node to also have the Player component. it doesn't bind that component to any variable, however. matching against multiple components in a single node acts like an AND operator -- in this case, Entity AND Player.

MATCH (:Player)-[:Knows]->(n:NPC)
RETURN n

this query matches against a relationship: any Player which Knows an NPC. it will return a list of all NPCs which have this relationship with the player. in the prototype, edges are just entities with from and to properties

const Item = defineComponent({ damage: Types.ui8 })
query('MATCH (item:Item) RETURN item.damage', engine)

in this example, we have an Item component with a single property: damage. it can be useful to only return a single property from a component. the result of this query may look like the following

[
    { item: { damage: 10 } },
    { item: { damage: 15 } },
    { item: { damage: 20 } },
]

accessing nested data like this can get a bit unwieldy, but its possible to alias variables

MATCH (item:Item)
RETURN item.damage AS damage
[
    { damage: 10 },
    { damage: 15 },
    { damage: 20 },
]

Filtering queries

MATCH (h:Health)
WHERE h.current < 10
RETURN h

it is also possible to filter results via the WHERE clause. the following operators are supported in graphecs: > >= < <= = AND OR XOR ( )

MATCH (n)-[d:Damaged]->(e, h:Health)
WHERE d.damage > h.current AND h.current > 0
RETURN e

this would return every entity id that has a relationship that deals more damage than its remaining health and isn't already dead

MATCH (e, h:Health { current: 35 })
RETURN e

it is also possible to match against specific component properties. in this query, it will match any entity which has a Health component where its current is equal to 35

Selecting worlds

USE ui
MATCH (button:Button)
WHERE button.type = 1
RETURN button

it's also possible to select different worlds with the USE clause