Skip to content

Commit

Permalink
feat: remove world.bulk, align world.update events with world.remove …
Browse files Browse the repository at this point in the history
…events
  • Loading branch information
isaac-mason committed Feb 13, 2024
1 parent c80a3df commit a31e200
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 91 deletions.
8 changes: 8 additions & 0 deletions .changeset/angry-poets-glow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@arancini/core": minor
"arancini": minor
---

feat: remove `world.bulk` api

Adding and removing multiple components can be achieved with `world.update` instead.
6 changes: 6 additions & 0 deletions .changeset/khaki-keys-study.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@arancini/core": minor
"arancini": minor
---

feat: change world.update to emit events with removed components, same as world.remove
19 changes: 3 additions & 16 deletions packages/arancini-core/src/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,18 +69,6 @@ export const getFirstQueryResult = <E>(

return undefined
}
// export const getFirstQueryResult = <E>(
// queryBitSets: QueryBitSets,
// entities: Iterable<E>
// ): E | undefined => {
// for (const entity of entities) {
// if (evaluateQueryBitSets(queryBitSets, entity)) {
// return entity
// }
// }

// return undefined
// }

export const evaluateQueryConditions = <E>(
conditions: QueryConditions<E>,
Expand All @@ -89,17 +77,17 @@ export const evaluateQueryConditions = <E>(
for (const condition of conditions) {
if (
condition.type === 'all' &&
!condition.components.every((c) => entity[c])
!condition.components.every((c) => entity[c] !== undefined)
) {
return false
} else if (
condition.type === 'any' &&
!condition.components.some((c) => entity[c])
!condition.components.some((c) => entity[c] !== undefined)
) {
return false
} else if (
condition.type === 'not' &&
condition.components.some((c) => entity[c])
condition.components.some((c) => entity[c] !== undefined)
) {
return false
}
Expand Down Expand Up @@ -141,7 +129,6 @@ export const getQueryConditions = (
}

export const getQueryDedupeString = (
// componentRegistry: ComponentRegistry,
queryConditions: QueryConditions<unknown>
): string => {
return queryConditions
Expand Down
82 changes: 7 additions & 75 deletions packages/arancini-core/src/world.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
getQueryResults,
} from './query'

const DEFAULT_QUERY_HANDLE = 'standalone'
const DEFAULT_QUERY_HANDLE = Symbol('standalone')

export type ComponentRegistry = { [name: string]: number }

Expand All @@ -28,9 +28,6 @@ export class World<E extends AnyEntity = any> extends EntityContainer<E> {
private entityToId = new WeakMap<E, number>()
private entityIdCounter = 0

private bulkUpdateInProgress = false
private bulkUpdateEntities: Set<E> = new Set()

/**
* Removes all entities from the world. Components remain registered, and queries are not destroyed.
*/
Expand Down Expand Up @@ -192,78 +189,18 @@ export class World<E extends AnyEntity = any> extends EntityContainer<E> {
entity: E,
updateFnOrPartial: ((entity: E) => void) | Partial<E>
): void {
const future = { ...entity }

if (typeof updateFnOrPartial === 'function') {
const updateFn = updateFnOrPartial

const proxy = new Proxy(entity, {
set: (_target, key, value) => {
const component = key as keyof E

const has = component in entity

if (has && value === undefined) {
this.remove(entity, component)
} else if (!has) {
this.add(entity, component, value)
} else {
Reflect.set(entity, key, value)
}

return true
},
deleteProperty: (_target, key) => {
this.remove(entity, key as keyof E)

return true
},
})

this.bulk(() => {
updateFn(proxy)
})
updateFn(future)
} else {
const partial = updateFnOrPartial

this.bulk(() => {
for (const component in partial) {
const value = partial[component]

if (value !== undefined) {
this.add(entity, component as keyof E, value)
} else {
this.remove(entity, component as keyof E)
}
}
})
Object.assign(future, updateFnOrPartial)
}
}

/**
* Utility method for adding and removing components from entities in bulk.
* @param updateFn callback to update entities in the World
*
* @example
* ```ts
* const entity = world.create({ health: 10, poisioned: true })
*
* world.bulk(() => {
* world.add(entity, 'position', { x: 0, y: 0 })
* world.remove(entity, 'poisioned')
* })
* ```
*/
bulk(updateFn: () => void): void {
this.bulkUpdateInProgress = true

updateFn()
this.index(entity, future)

this.bulkUpdateInProgress = false

for (const entity of this.bulkUpdateEntities) {
this.index(entity)
}

this.bulkUpdateEntities.clear()
Object.assign(entity, future)
}

/**
Expand Down Expand Up @@ -401,11 +338,6 @@ export class World<E extends AnyEntity = any> extends EntityContainer<E> {
private index(entity: E, future: E = entity) {
if (!this.has(entity)) return

if (this.bulkUpdateInProgress) {
this.bulkUpdateEntities.add(entity)
return
}

for (const query of this.queries.values()) {
const matchesQuery = evaluateQueryConditions(query.conditions, future)
const inQuery = query.has(entity)
Expand Down
58 changes: 58 additions & 0 deletions packages/arancini-core/tst/queries.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,64 @@ describe('Queries', () => {
expect(removed).toBe(2)
})

it('emits events when an entity is added to a query', () => {
const world = new World<Entity>()

const query = world.query((q) => q.has('foo'))

let added = 0

query.onEntityAdded.add(() => {
added++
})

const entity = { foo: 'test' }

world.create(entity)

expect(added).toBe(1)
})

it('emits events when an entity is removed from a query', () => {
const world = new World<Entity>()

const query = world.query((q) => q.has('foo'))

const events: Entity[] = []

query.onEntityRemoved.add((entity) => {
events.push({ ...entity })
})

const entity = {}
world.create(entity)

// add to query
world.add(entity, 'foo', '')

// remove from query
world.remove(entity, 'foo')

// add to query
world.update(entity, { foo: '' })

// remove from query
world.update(entity, { foo: undefined })

// add to query
world.update(entity, (e) => {
e.foo = ''
})

// remove from query
world.update(entity, {
foo: undefined,
})

expect(events.length).toBe(3)
expect(events.every((e) => e.foo !== undefined)).toBe(true)
})

it('should update an entity in bulk when calling update', () => {
const world = new World<Entity>()

Expand Down

0 comments on commit a31e200

Please sign in to comment.