src | ||
tests | ||
.gitignore | ||
package-lock.json | ||
package.json | ||
README.md |
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