Skip to content
Open
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
42 changes: 42 additions & 0 deletions docs/Intro.md
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,48 @@ addComponent(world, eid, Position, Velocity, Mass)
removeComponent(world, eid, Position, Velocity)
```

### Default Initialization

Components can define a default initializer method keyed by the `$default` symbol that will be called automatically when the component is added to an entity. The method receives the entity ID as a parameter.

The `Default()` function can be used to pass a parameter to the default initializer.

```ts
import { $default, Default } from 'bitecs'

const Position = {
x: [] as number[],
y: [] as number[],
[$default](eid: number, { x = 0, y = 0 } = {}) {
this.x[eid] = x
this.y[eid] = y
}
}

const Health = Object.assign({
new Float32Array(10000),
[$default](eid: number) { this[eid] = 100 }
})

observe(world, onSet(Health), (eid, value) => {
Health[eid] = value
})

// The default initializer is called automatically.
addComponent(world, eid, Position)
assert(Position.x[eid] === 0)

// You can still override defaults with set().
addComponent(world, eid, set(Health, 42))
assert(Health[eid] === 42)

// You can also pass a parameter to the default initializer.
// This helps avoid unnecessary allocations and garbage collection.
addComponent(world, eid, Default(Position, { x: 10, y: 20 }))
assert(Position[eid].x === 10)
assert(Position[eid].y === 20)
```

## Query

Queries are used to retrieve information from the world, which acts as a dynamic database. You can query for entities based on their components, relationships, or hierarchies. A query returns a list of all entities that match the specified criteria.
Expand Down
22 changes: 22 additions & 0 deletions src/core/Component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ import {
import { createObservable, Observable } from './utils/Observer'
import { $internal, InternalWorld, World, WorldContext } from './World'

export const $default = Symbol.for('bitecs-default')
const $defaultParams = Symbol.for('bitecs-default-params')

/**
* Represents a reference to a component.
* @typedef {any} ComponentRef
Expand Down Expand Up @@ -202,6 +205,8 @@ export const addComponent = (world: World, eid: EntityId, ...components: (Compon

if (!ctx.componentMap.has(component)) registerComponent(world, component)

setDefault(world, eid, component, componentOrSet[$defaultParams])

const componentData = ctx.componentMap.get(component)!
if (data !== undefined) {
componentData.setObservable.notify(eid, data)
Expand Down Expand Up @@ -315,3 +320,20 @@ export const removeComponent = (world: World, eid: EntityId, ...components: Comp
* Alias for removeComponent.
*/
export const removeComponents = removeComponent

export function Default(component: ComponentRef, params: any) {
return {
component,
[$defaultParams]: params
}
}

function setDefault(world: World, eid: EntityId, component: ComponentRef, params: any) {
if (component[$default] !== undefined) {
if (typeof component[$default] !== 'function') {
throw new Error(`${$default.toString()} must be a function. Got "${typeof component[$default]}".`)
}

component[$default](eid, params)
}
}
2 changes: 2 additions & 0 deletions src/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ export {
} from './EntityIndex'

export {
$default,
Default,
registerComponent,
registerComponents,
hasComponent,
Expand Down
89 changes: 88 additions & 1 deletion test/core/Component.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,22 @@
import assert from 'assert'
import { describe, it } from 'vitest'
import { registerComponent, addComponent, hasComponent, removeComponent, InternalWorld, createWorld, addEntity, removeEntity, $internal, getEntityComponents } from '../../src/core'
import {
$default,
Default,
registerComponent,
addComponent,
hasComponent,
removeComponent,
InternalWorld,
createWorld,
addEntity,
removeEntity,
$internal,
getEntityComponents,
set,
observe,
onSet
} from '../../src/core'

describe('Component Tests', () => {

Expand Down Expand Up @@ -126,4 +142,75 @@ describe('Component Tests', () => {
assert(hasComponent(world, eid, component), `Component ${component.index} should be present using hasComponent`)
})
})

it('should handle default initialization via $default method', () => {
const world = createWorld()
const eid = addEntity(world)

const TestComponent = {
x: [] as number[],
y: [] as number[],
[$default](eid: number) {
this.x[eid] = 1
this.y[eid] = 1
}
}

const TestComponent2 = Object.assign(
new Float32Array(10000),
{ [$default](eid: number) { this[eid] = 100 }}
)

const TestComponent3 = Object.assign(
[] as number[],
{ [$default](eid: number) { this[eid] = 100 }}
)

observe(world, onSet(TestComponent3), (eid, value) => {
TestComponent3[eid] = value
})

addComponent(world, eid, TestComponent)
assert(TestComponent.x[eid] === 1)
assert(TestComponent.y[eid] === 1)

addComponent(world, eid, TestComponent2)
assert(TestComponent2[eid] === 100)

addComponent(world, eid, set(TestComponent3, 42))
assert(TestComponent3[eid] === 42)
})

it('should pass params to default initializer via Default()', () => {
const world = createWorld()
const eid = addEntity(world)
const eid2 = addEntity(world)

const TestComponent = Object.assign(
[],
{[$default](eid: number, params: number) {
this[eid] = new Float32Array(params || 10000)
}}
)

addComponent(world, eid, TestComponent)
assert(TestComponent[eid].length === 10000)

addComponent(world, eid2, Default(TestComponent, 42))
assert(TestComponent[eid2].length === 42)
})

it('should throw if $default is not a function', () => {
const world = createWorld()
const eid = addEntity(world)

const BadComponent = {
value: [] as number[],
[$default]: 42
}

assert.throws(() => {
addComponent(world, eid, BadComponent)
})
})
})