Skip to content

Commit

Permalink
Merge pull request #12 from rbxts-flamework/feature/component-wrapping
Browse files Browse the repository at this point in the history
Add component wrapping for non-table values
  • Loading branch information
Ukendio authored Nov 13, 2024
2 parents 90d76ad + 528df6f commit ce4e2de
Show file tree
Hide file tree
Showing 4 changed files with 294 additions and 97 deletions.
155 changes: 115 additions & 40 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ The Flamework transformer must be configured in your `tsconfig.json`. The fields
{
"transform": "rbxts-transformer-flamework",
},
]
}
],
},
}
```

Expand All @@ -67,60 +67,135 @@ You should find the entry for `node_modules` and modify it to include `@flamewor

You may need to delete the `out` folder and recompile for Flamework's transformer to begin working. Afterwards, you are ready to use flamecs.

## Demo
## Components

```ts
const positionEntity = spawn<[Vector3]>([new Vector3(10, 20, 30)]);
print(has<Vector3>(positionEntity));
```typescript
interface Position {
x: number;
y: number;
}

start({}, () => {
if (useThrottle(5)) {
for (const [entity, position, orientation] of query<[Vector3, CFrame]>()) {
print(`Entity: ${entity}, Position: ${position}, Orientation: ${orientation}`);
}
}
});
// Tag (no data)
interface Player extends Tag {}

// Components can be wrapped to use non-table data
interface Name extends Wrap<string> {}
interface Health extends Wrap<number> {}
```

## Entities

### Spawning Entities

```typescript
const entity = spawn();

// When spawning with tags the bundle list can be omitted
const marcus = spawn<[Player]>();

const ketchup = spawn<[Position, Player]>([{ x: 0, y: 0 }]);

// Get the runtime entity id from a component
const positionComponent = component<Position>();
```

### Modifying Entities

```typescript
add<Player>(entity);

set<Position>(entity, { x: 10, y: 20 });
set<Name>(entity, "Alice");

// Insert can be used to add/set multiple components
insert<[Name, Health, Player]>(entity, ["Bob", 100]);

remove<Player>(entity);

if (has<Player>(entity)) {
// ...
}

const pos = get<Position>(entity);
const name = get<Name>(entity);

despawn(entity);
```

## Queries

```typescript
for (const [entity, pos, name] of query<[Position, Name]>()) {
print(`${name} at ${pos.x}, ${pos.y}`);
}

for (const [entity, position] of query<[Vector3, Without<[CFrame]>]>()) {
print(`Entity: ${entity}, Position: ${position}`);
for (const [entity, pos] of query<[Position, Without<Player>]>()) {
// ...
}

// Example of using pair relationships between entities
interface Likes {}
interface Eats {
count: number;
for (const [entity, pos] of query<[Position, With<[Player, Health]>]>()) {
// ...
}
```

interface Fruit {}
## Relationships

const alice = spawn();
const bob = spawn();
const charlie = spawn();
### Defining Relationships

const banana = spawn();
add<Fruit>(banana);
```typescript
interface Likes extends Tag {}
interface Owns extends Wrap<number> {}

// Alice likes Bob
add<Pair<Likes>>(alice, bob);
add<Pair<Likes>>(alice, charlie);

add<Pair<Likes, Fruit>>(bob);
// Alice owns 5 items
set<Pair<Owns, Item>>(alice, 5);
```

set<Pair<Eats>>(bob, banana, { count: 5 });
set<Pair<Eats, Fruit>>(alice, { count: 12 });
### Querying Relationships

for (const [entity] of query().pair<Likes>(alice)) {
const likedEntity = target<Likes>(entity);
print(`Entity ${entity} likes ${likedEntity}`);
```typescript
// Query all entities that like something Pair<Likes, Wildcard>
for (const [entity] of query<[Pair<Likes>]>()) {
const target = target<Likes>(entity);
print(`${entity} likes ${target}`);
}

for (const [entity, eatsData] of query<[Pair<Eats, Fruit>]>()) {
const eatsTarget = target<Eats>(entity);
print(`Entity ${entity} eats ${eatsData.count} fruit (${eatsTarget})`);
// Query specific relationships where the object is a runtime id
for (const [entity] of query().pair<Likes>(bob)) {
// ...
}
```

// Using Pair<P> to match any target (wildcard), equivalent to Pair<Likes, Wildcard>
for (const [entity] of query<[Pair<Likes>]>()) {
const likedTarget = target<Likes>(entity);
print(`Entity ${entity} likes ${likedTarget}`);
}
## Signals

```typescript
added<Position>().connect((entity) => {
print(`Position added to ${entity}`);
});

removed<Position>().connect((entity) => {
print(`Position removed from ${entity}`);
});

changed<Position>().connect((entity, newValue) => {
print(`Position of ${entity} changed to ${newValue}`);
});
```

## Hooks and Systems

```typescript
// Hooks must be used within a `start()` function.
start({}, () => {
if (useThrottle(0.5)) {
// ...
}

for (const [player] of useEvent(Players.PlayerAdded)) {
const entity = spawn<[Name, Player]>([player.Name]);
// ...
}
});
```
3 changes: 2 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
export * from "./hooks/use-event";
export * from "./hooks/use-throttle";
export * from "./query";
export type { ChildOf, Entity, Id, Pair, Tag, Wildcard } from "./registry";
export type { ChildOf, Entity, Id, Name, Pair, Tag, Wildcard, Wrap } from "./registry";
export {
add,
added,
Expand All @@ -17,6 +17,7 @@ export {
remove,
removed,
reserve,
reset,
set,
spawn,
target,
Expand Down
16 changes: 9 additions & 7 deletions src/query.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import type { Modding } from "@flamework/core";
import * as ecs from "@rbxts/jecs";

import type { Entity, FilterPairs, Id, ResolveKeys } from "./registry";
import type { ComponentKey, Entity, Id, ResolveKeys, ResolveValues, Unwrap } from "./registry";
import { component, getId, registry } from "./registry";

// Almost full credits to @fireboltofdeath for all of these types.
Expand Down Expand Up @@ -37,7 +36,10 @@ type Calculate<T extends Array<unknown>, B extends Bounds = Bounds> = T extends
: Calculate<Skip<T>, PushBound<B, "query", T[0]>>;

type ToIds<T> = T extends [] ? undefined : ResolveKeys<T>;
type ExtractQueryTypes<T extends Array<unknown>> = Reconstruct<FilterPairs<Calculate<T>["query"]>>;

type ExtractQueryTypes<T extends Array<unknown>> = Reconstruct<
ResolveValues<Calculate<T>["query"]>
>;

type QueryHandle<T extends Array<unknown>> = {
__iter(): IterableFunction<LuaTuple<[Entity, ...T]>>;
Expand All @@ -53,19 +55,19 @@ type QueryHandle<T extends Array<unknown>> = {
* @returns A new QueryHandle with the pair filter added.
* @metadata macro
*/
pair<P>(object: Entity, predicate?: Modding.Generic<P, "id">): QueryHandle<[...T, P]>;
pair<P>(object: Entity, predicate?: ComponentKey<P>): QueryHandle<[...T, Unwrap<P>]>;
terms?: Array<Id>;
} & IterableFunction<LuaTuple<[Entity, ...T]>>;

function queryPair<T extends Array<unknown>, P>(
this: QueryHandle<T>,
object: Entity,
predicate?: Modding.Generic<P, "id">,
): QueryHandle<[...T, P]> {
predicate?: ComponentKey<P>,
): QueryHandle<[...T, Unwrap<P>]> {
assert(predicate);
const id = ecs.pair(component(predicate), object);
this.terms = this.terms ? [...this.terms, id] : [id];
return this as unknown as QueryHandle<[...T, P]>;
return this as unknown as QueryHandle<[...T, Unwrap<P>]>;
}

function queryIter<T extends Array<unknown>>(
Expand Down
Loading

0 comments on commit ce4e2de

Please sign in to comment.