Skip to content
This repository has been archived by the owner on Feb 10, 2023. It is now read-only.

Commit

Permalink
groups and an EcsBuilder
Browse files Browse the repository at this point in the history
  • Loading branch information
prescientmoon committed Nov 21, 2019
1 parent 1e56922 commit 8712076
Show file tree
Hide file tree
Showing 14 changed files with 310 additions and 73 deletions.
24 changes: 20 additions & 4 deletions src/classes/CompactComponentManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,27 @@ export class CompactComponentManager<T> implements ComponentManager<T> {

public eidsToIndices = new DoubleMap<number, number>()

public register(eid: number, component: T) {
public register(eid: number, component: T, verbooe = true) {
if (this.eidsToIndices.getFromHead(eid) !== undefined) {
throw new Error(`Component with eid ${eid} already registered`)
if (verbooe) {
throw new Error(`Component with eid ${eid} already registered`)
}
return
}

const index = this.components.push(component) - 1

this.eidsToIndices.set(eid, index)
}

public unregister(eid: number) {
public unregister(eid: number, verboose = true) {
const index = this.eidsToIndices.getFromHead(eid)

if (index === undefined) {
throw new Error(`Entity ${eid} was never registered`)
if (verboose) {
throw new Error(`Entity ${eid} was never registered`)
}
return
}

this.eidsToIndices.deleteByHead(eid)
Expand Down Expand Up @@ -50,6 +56,16 @@ export class CompactComponentManager<T> implements ComponentManager<T> {
return this.components[index]
}

public setComponentByEid(eid: number, value: T) {
const index = this.eidsToIndices.getFromHead(eid)

if (index === undefined) {
throw new Error(`Cannot find component with eid: ${eid}`)
}

this.components[index] = value
}

public mutateAll(callback: (old: T) => T) {
for (let index = 0; index < this.components.length; index++) {
const oldComponent = this.components[index]
Expand Down
106 changes: 64 additions & 42 deletions src/classes/Ecs.ts
Original file line number Diff line number Diff line change
@@ -1,60 +1,84 @@
import { ComponentManagerClass } from './../types/ComponentManager'
import {
SystemMap,
ComponentManagerMap,
ComponentList,
ComponentManagerBuilderMap,
FunctionalManagerBuilder
} from '../types/EcsHelpers'
import { System } from './../types/System'
import { EidGenerator } from './EidGenerator'
import { ComponentManager } from '../types/ComponentManager'
import { MappedComponentManager } from './MappedComponentManager'

type ComponentManagerClassMap<T extends object> = {
[K in keyof T]: { new (capacity: number): ComponentManager<T[K]> }
}

type ComponentManagerMap<T extends object> = {
[K in keyof T]: ComponentManager<T[K]>
}

type ComponentList<T> = (keyof T)[]
type SystemMap<T extends object> = { [K in keyof T]: System<T[K]>[] }

export class Ecs<T extends object> {
import {
typesafeIterable,
typesafeKeys,
typesafeEntries
} from '../helpers/typesafeIterable'
import { withProp } from '../helpers/whichHaveProp'

export class Ecs<T extends object = {}> {
private componentLists = new MappedComponentManager<ComponentList<T>>()
private systems = {} as SystemMap<T>
public components = {} as ComponentManagerMap<T>

public constructor(
components: ComponentManagerClassMap<T>,
components: ComponentManagerBuilderMap<T>,
public capacity = 10000,
private generator = new EidGenerator()
) {
for (const [key, Component] of Object.entries(components)) {
this.components[
key
] = new (Component as typeof components[keyof typeof components])(
capacity
)
for (const [key, Component] of typesafeEntries(components)) {
try {
this.components[key] = (Component as FunctionalManagerBuilder<
T[keyof T]
>)(this)
} catch {
this.components[key] = new (Component as ComponentManagerClass<
T[keyof T]
>)(capacity)
}
}

for (const key in components) {
this.systems[key] = []
}
}

public create(components: Partial<T>) {
public create(components: Partial<Pick<T, keyof T>>) {
const eid = this.generator.create()

for (const name in components) {
const typedName = name as keyof typeof components
// perform beforeCreate hook
for (const name of typesafeIterable<keyof T>(Object.keys(components)))
for (const system of withProp(this.systems[name], 'beforeCreate')) {
const result = system.beforeCreate(components[name]!)

this.components[typedName].register(eid, components[typedName]!)
}
if (result === false) {
this.generator.destroy(eid)

// perform didCreate hook
for (const name in components) {
const typedName = name as keyof typeof components
return eid
}
}

for (const name of typesafeKeys(components)) {
let component = components[name]!

// perform onUpdate hook
for (const system of withProp(this.systems[name], 'onCreate')) {
const result = system.onCreate(component)

for (const system of this.systems[typedName]) {
if (system.didCreate) {
system.didCreate!(components[typedName]!, eid)
if (result !== undefined) {
component = result as typeof component
}
}

this.components[name].register(eid, component)
}

// perform didCreate hook
for (const name of typesafeKeys(components)) {
for (const system of withProp(this.systems[name], 'didCreate')) {
system.didCreate(components[name]!, eid)
}
}

this.componentLists.register(
Expand Down Expand Up @@ -89,6 +113,8 @@ export class Ecs<T extends object> {
name: K
) {
this.systems[name].push(new SystemClass(this.components[name]))

return this
}

public getComponentsByEid<K extends keyof T>(eid: number) {
Expand Down Expand Up @@ -131,6 +157,11 @@ export class Ecs<T extends object> {

const componentManager = this.components[componentName]

// run beforeUpdate hook
for (const system of hasBeforeUpdate) {
system.beforeUpdate!(componentManager)
}

// We don't need to iterate over everything if we don't need the mutations
if (needUpdate.length) {
componentManager.mutateAll(oldComponent => {
Expand Down Expand Up @@ -159,18 +190,9 @@ export class Ecs<T extends object> {
for (const componentName in this.systems) {
const componentManager = this.components[componentName]

const needRender = this.systems[componentName].filter(
system => system.onRender
)

// We don't need to iterate over everything if we don't need the mutations
if (!needRender.length) {
return
}

for (const system of needRender) {
for (const system of withProp(this.systems[componentName], 'onRender')) {
for (const component of componentManager) {
system.onRender!(component)
system.onRender(component)
}
}
}
Expand Down
51 changes: 51 additions & 0 deletions src/classes/EcsBuilder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { Ecs } from './Ecs'
import {
ComponentManagerBuilderMap,
ComponentManagerBuilder,
ExtractComponentManagerType
} from '../types/EcsHelpers'
import { GroupComponentManager } from './GroupComponentManager'

class EcsSafeBuilder<T extends object = {}> {
public constructor(private managers: ComponentManagerBuilderMap<T>) {}

public addManager<K extends string, L>(
name: K,
manager: ComponentManagerBuilder<L>
) {
return new EcsSafeBuilder<T & Record<K, L>>({
...this.managers,
[name]: manager
} as any)
}

public group<K extends string, L extends keyof T>(
name: K,
components: Exclude<L, K>[]
) {
const group = (ecs: Ecs<T>) =>
new GroupComponentManager(ecs.capacity, ecs, components)

return new EcsSafeBuilder<
T & Record<K, ExtractComponentManagerType<ReturnType<typeof group>>>
>({
...this.managers,
[name]: group
} as any)
}

public build(capacity = 10000) {
return new Ecs(this.managers, capacity)
}
}

export class EcsBuilder {
public addManager<K extends string, L>(
name: K,
manager: ComponentManagerBuilder<L>
) {
return new EcsSafeBuilder<Record<K, L>>({
[name]: manager
} as any)
}
}
75 changes: 75 additions & 0 deletions src/classes/GroupComponentManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { Ecs } from './Ecs'
import { ComponentManager } from '../types/ComponentManager'
import { typesafeKeys } from '../helpers/typesafeIterable'

export class GroupComponentManager<T extends object, K extends keyof T>
implements ComponentManager<Pick<T, K>> {
private eids = new Set<number>()

public constructor(
public capacity: number,
private ecs: Ecs<T>,
private components: K[]
) {}

private get managers() {
return this.components.map(
name => [this.ecs.components[name], name] as const
)
}

public register(eid: number, components: Pick<T, K>) {
for (const [manager, name] of this.managers) {
manager.register(eid, components[name], false)
}

this.eids.add(eid)
}

public unregister(eid: number) {
for (const [manager] of this.managers) {
manager.unregister(eid, false)
}

this.eids.delete(eid)
}

public getComponentByEid(eid: number) {
if (!this.eids.has(eid)) {
throw new Error(`Cannot find component with eid ${eid}`)
}

const result = {} as Pick<T, K>

for (const [manager, name] of this.managers) {
result[name] = manager.getComponentByEid(eid)
}

return result
}

public setComponentByEid(eid: number, value: Pick<T, K>) {
if (!this.eids.has(eid)) {
throw new Error(`Cannot find component with eid ${eid}`)
}

for (const [manager, name] of this.managers) {
manager.setComponentByEid(eid, value[name])
}
}

public mutateAll(callback: (v: Pick<T, K>) => Pick<T, K>) {
for (const eid of this.eids) {
const original = this.getComponentByEid(eid)
const changed = callback(original)

this.setComponentByEid(eid, changed)
}
}

public *[Symbol.iterator]() {
for (const eid of this.eids) {
yield this.getComponentByEid(eid)
}
}
}
8 changes: 8 additions & 0 deletions src/classes/MappedComponentManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ export class MappedComponentManager<T> implements ComponentManager<T> {
return result
}

public setComponentByEid(eid: number, value: T) {
if (!this.valueMap.has(eid)) {
throw new Error(`Cannot find component with eid ${eid}`)
}

this.valueMap.set(eid, value)
}

public mutateAll(callback: (old: T) => T) {
for (const [eid, value] of this.valueMap) {
const newValue = callback(value)
Expand Down
Loading

0 comments on commit 8712076

Please sign in to comment.