Skip to content

Commit

Permalink
Merge pull request #52 from ayebear/world
Browse files Browse the repository at this point in the history
World context + getters/setters
  • Loading branch information
ayebear authored Oct 19, 2021
2 parents 6b060d6 + f859c19 commit 145a5eb
Show file tree
Hide file tree
Showing 5 changed files with 447 additions and 394 deletions.
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
v14.18.1
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "picoes",
"version": "1.0.0",
"version": "1.1.0-rc0",
"description": "Pico Entity System for JavaScript",
"main": "index.js",
"files": [
Expand Down Expand Up @@ -40,8 +40,8 @@
"esdoc-unexported-identifier-plugin": "^1.0.0",
"gh-pages": "^3.2.3",
"husky": "^7.0.2",
"jest": "^27.1.1",
"prettier": "^2.3.2",
"jest": "^27.3.0",
"prettier": "^2.4.1",
"pretty-quick": "^3.1.1"
},
"dependencies": {},
Expand Down
82 changes: 65 additions & 17 deletions src/world.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,24 +23,20 @@ export class World {
*/
constructor(options) {
/** @ignore */
this.systems = new SystemStorage(this)
this._systems = new SystemStorage(this)
/** @ignore */
this.entities = new EntityStorage(this)

// Register components, context, and systems
if (options) {
if (options.components) {
for (const name in options.components) {
this.component(name, options.components[name])
}
this.components = options.components
}
if (options.context) {
this.context(options.context)
this.context = options.context
}
if (options.systems) {
for (const systemClass of options.systems) {
this.system(systemClass)
}
this.systems = options.systems
}
}
}
Expand Down Expand Up @@ -86,6 +82,28 @@ export class World {
this.entities.registerComponent(name, componentClass)
}

/**
* Registers all components in an object. Merges with existing registered components.
*
* @example
* world.components = { position: Position }
*/
set components(comps) {
for (let key in comps) {
this.entities.registerComponent(key, comps[key])
}
}

/**
* Returns currently registered components.
*
* @example
* const { position: Position } = world.components
*/
get components() {
return this.entities.componentClasses
}

/**
* Creates a new entity in the world
*
Expand All @@ -110,13 +128,21 @@ export class World {
* @example
* const state = { app: new PIXI.Application() }
* const world = new World()
* world.context(state) // new and existing systems can directly use this.app
* world.context = state // new and existing systems can directly use this.app
* world.system(...)
*/
set context(data) {
this._systems.setContext(data)
}

/**
* Returns currently set context object.
*
* @return {Entity} The new entity created
* @example
* const { app } = world.context
*/
context(data) {
this.systems.setContext(data)
get context() {
return this._systems.context
}

/**
Expand Down Expand Up @@ -151,8 +177,8 @@ export class World {
* }
* }
* }
* // Inject context (see world.context())
* world.context({ keyboard: new Keyboard() })
* // Inject context (see world.context)
* world.context = { keyboard: new Keyboard() }
* // Register systems in order (this method)
* world.system(InputSystem, 'w') // pass arguments to init/constructor
* world.system(MovementSystem)
Expand All @@ -163,12 +189,34 @@ export class World {
* constructor(), init(), run(), or any other custom methods/properties.
*
* @param {...Object} [args] - The arguments to forward to the system's constructor and init.
* Note that it is recommended to use init if using context, see world.context().
* Note that it is recommended to use init if using context, see world.context.
* Passing args here is still useful, because it can be specific to each system, where
* the same context is passed to all systems.
*/
system(systemClass, ...args) {
this.systems.register(systemClass, ...args)
this._systems.register(systemClass, ...args)
}

/**
* Registers additional systems, in the order specified. See world.system().
*
* @example
* world.systems = [inputSystem, movementSystem]
*/
set systems(values) {
for (const sys of values) {
this._systems.register(sys)
}
}

/**
* Returns currently added systems, in the order added.
*
* @example
* const [inputSystem, movementSystem] = world.systems
*/
get systems() {
return this._systems.systems
}

/**
Expand All @@ -190,7 +238,7 @@ export class World {
* @param {...Object} [args] - The arguments to forward to the systems' methods
*/
run(...args) {
this.systems.run(...args)
this._systems.run(...args)
}

/**
Expand Down
46 changes: 23 additions & 23 deletions test/world.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,28 +21,28 @@ test('world: create a world with options', () => {
// Make worlds
const world1 = new World({})
expect(world1).toBeInstanceOf(World)
expect(world1.systems.systems).toHaveLength(0)
expect(getSize(world1.entities.componentClasses)).toBe(0)
expect(getSize(world1.systems.context)).toBe(1)
expect(world1.systems).toHaveLength(0)
expect(getSize(world1.components)).toBe(0)
expect(getSize(world1.context)).toBe(1)
const world2 = new World({
components: {},
systems: [],
context: {},
})
expect(world2).toBeInstanceOf(World)
expect(world2.systems.systems).toHaveLength(0)
expect(getSize(world2.entities.componentClasses)).toBe(0)
expect(getSize(world2.systems.context)).toBe(1)
expect(world2.systems).toHaveLength(0)
expect(getSize(world2.components)).toBe(0)
expect(getSize(world2.context)).toBe(1)
const world3 = new World({
components: { position, velocity },
systems: [input, physics, render],
context: { state },
})
expect(world3).toBeInstanceOf(World)
expect(world3.systems.systems).toHaveLength(3)
expect(getSize(world3.entities.componentClasses)).toBe(2)
expect(getSize(world3.systems.context)).toBe(2)
expect(Object.keys(world3.systems.context)).toEqual(['state', 'world'])
expect(world3.systems).toHaveLength(3)
expect(getSize(world3.components)).toBe(2)
expect(getSize(world3.context)).toBe(2)
expect(Object.keys(world3.context)).toEqual(['state', 'world'])
})

test('component: define a component', () => {
Expand All @@ -56,8 +56,8 @@ test('component: define a component', () => {
}
})
let ent = world.entity().set('position', 1, 2)
expect('position' in world.entities.componentClasses).toBeTruthy()
expect(Object.keys(world.entities.componentClasses).length == 1).toBeTruthy()
expect('position' in world.components).toBeTruthy()
expect(Object.keys(world.components).length == 1).toBeTruthy()
expect(ent.has('position')).toBeTruthy()
expect(ent.get('position').x === 1).toBeTruthy()
expect(ent.get('position').y === 2).toBeTruthy()
Expand All @@ -78,8 +78,8 @@ test('component: define a component', () => {
}
)
let ent2 = world.entity().set('velocity', 1, 2)
expect('velocity' in world.entities.componentClasses).toBeTruthy()
expect(Object.keys(world.entities.componentClasses).length == 2).toBeTruthy()
expect('velocity' in world.components).toBeTruthy()
expect(Object.keys(world.components).length == 2).toBeTruthy()
expect(ent2.has('velocity')).toBeTruthy()
expect(ent2.get('velocity').x === 1).toBeTruthy()
expect(ent2.get('velocity').y === 2).toBeTruthy()
Expand Down Expand Up @@ -118,7 +118,7 @@ test('component: define invalid components', () => {
expect(() => {
world.component('invalid', 555)
}).toThrow()
expect(Object.keys(world.entities.componentClasses).length === 0).toBeTruthy()
expect(Object.keys(world.components).length === 0).toBeTruthy()
})

test('component: use an empty component', () => {
Expand Down Expand Up @@ -348,7 +348,7 @@ test('component: test detached entities', () => {
test('system: define a system', () => {
const world = new World()
world.system(class {})
expect(world.systems.systems.length == 1).toBeTruthy()
expect(world.systems.length == 1).toBeTruthy()
})

test('system: define a system with arguments', () => {
Expand All @@ -361,9 +361,9 @@ test('system: define a system with arguments', () => {
}
}
world.system(velocitySystem, 3500, 'someCanvas', ['textures.png'])
expect(world.systems.systems[0].maxVelocity === 3500).toBeTruthy()
expect(world.systems.systems[0].canvas === 'someCanvas').toBeTruthy()
expect(world.systems.systems[0].textures[0] === 'textures.png').toBeTruthy()
expect(world.systems[0].maxVelocity === 3500).toBeTruthy()
expect(world.systems[0].canvas === 'someCanvas').toBeTruthy()
expect(world.systems[0].textures[0] === 'textures.png').toBeTruthy()
})

test('system: define a system with context (no key)', () => {
Expand Down Expand Up @@ -392,9 +392,9 @@ test('system: define a system with context (no key)', () => {
}
}
world.system(velocitySystem, true) // Existing system
world.context(state) // Set keyless context
world.context = state // Set keyless context
world.system(velocitySystem, false) // New system
expect(world.systems.systems.length).toEqual(2)
expect(world.systems.length).toEqual(2)
world.run()
expect(ran).toEqual([false, true, false])
})
Expand Down Expand Up @@ -425,9 +425,9 @@ test('system: define a system with context (specific key)', () => {
}
}
world.system(velocitySystem, true) // Existing system
world.context({ state }) // Set keyed context
world.context = { state } // Set keyed context
world.system(velocitySystem, false) // New system
expect(world.systems.systems.length).toEqual(2)
expect(world.systems.length).toEqual(2)
world.run()
expect(ran).toEqual([false, true, false])
})
Expand Down
Loading

0 comments on commit 145a5eb

Please sign in to comment.