Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add component wrapping for non-table values #12

Merged
merged 4 commits into from
Nov 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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