Skip to content

Instantly share code, notes, and snippets.

@GoodNovember
Created October 2, 2023 20:02
Show Gist options
  • Save GoodNovember/2953e36893b9e20d772d6279b47b96f0 to your computer and use it in GitHub Desktop.
Save GoodNovember/2953e36893b9e20d772d6279b47b96f0 to your computer and use it in GitHub Desktop.
an Entity Component System written in JavaScript
// ENTITIES
let counter = 0
let entities = new Set()
/**
* Creates a new entity and returns its id
* @returns {number} id of the new entity
*/
export function makeEntity(){
let id = counter++
if(id > Number.MAX_SAFE_INTEGER){
throw new Error('Too many entities!')
}
entities.add(id)
return id
}
/**
* Destroys an entity
* @param {number} entity id of the entity to destroy
* @returns {void}
* @throws if the entity does not exist
**/
export function destroyEntity(entity){
ensureEntityExists(entity)
// remove all components from the entity
const names = getComponentNamesForEntity(entity)
for(let name of names){
removeComponent(entity, name)
}
// remove the entity
entities.delete(entity)
}
export function clear(){
counter = 0
entities.clear()
components.clear()
}
/**
* Checks if an entity exists
* @param {number} id id of the entity to check
* @returns {boolean} true if the entity exists, false otherwise
*/
export function entityExists(id){
return entities.has(id)
}
/**
* Checks if an entity does not exist,
* error if it does not exist
* @param {number} id id of the entity to check
* @throws if the entity does not exist
*/
function ensureEntityExists(id){
if(entityExists(id) === false){
throw new Error(`Entity ${id} does not exist`)
}
}
/**
* Gets all entities
* @returns {Set} set of all entities
*/
export function getEntities(){
return entities
}
/**
* Creates an entity from an object,
* where the keys are the component names
* @param {object} ingredients object of components
* @returns {number} id of the new entity
*/
export function makeEntityFromObject(ingredients){
const keys = Object.keys(ingredients)
const entity = makeEntity()
for(let key of keys){
addComponent(entity, key, ingredients[key])
}
return entity
}
// COMPONENTS
let components = new Map()
/**
* Ensure a component was registered.
* If it was not, register it and create a new map for it
* @param {string} name
*/
function ensureComponentWasRegistered(name){
if(!components.has(name)){
components.set(name, new Map())
}
}
/**
* Adds a component to an entity
* @param {number} entity the entity to add the component to
* @param {string} name name of the component
* @param {*} component the component to add
*/
export function addComponent(entity, name, component){
ensureEntityExists(entity)
ensureComponentWasRegistered(name)
components.get(name).set(entity, component)
}
/**
* Removes a component from an entity
* @param {number} entity the entity to remove the component from
* @param {string} name the name of the component to remove
*/
export function removeComponent(entity, name){
ensureEntityExists(entity)
ensureComponentWasRegistered(name)
components.get(name).delete(entity)
}
/**
* Gets a component from an entity
* @param {number} entity the entity to get the component from
* @param {string} name the name of the component to get
* @returns {*} the component of the entity
*/
export function getComponent(entity, name){
ensureEntityExists(entity)
ensureComponentWasRegistered(name)
return components.get(name).get(entity)
}
/**
* Checks if an entity has a component
* @param {number} entity the entity to check
* @param {string} name the name of the component to check
* @returns {boolean} true if the entity has the component, false otherwise
* @throws if the entity does not exist
*/
export function hasComponent(entity, name){
ensureEntityExists(entity)
ensureComponentWasRegistered(name)
return components.get(name).has(entity)
}
/**
* Gets all entities with a component
* @param {string} name the name of the component
* @returns {Set} set of all entities with the component
*/
export function getEntitiesWithComponent(name){
ensureComponentWasRegistered(name)
return components.get(name).keys()
}
/**
* Gets all entities with a set of components
* @param {[string]} names the names of the components
* @returns {Set} set of all entities with the components
*/
export function getEntitiesWithComponents(names){
let entities = new Set()
for(let name of names){
ensureComponentWasRegistered(name)
for(let entity of components.get(name).keys()){
entities.add(entity)
}
}
return entities
}
/**
* Gets an array of all component names for an entity
* @param {number} entity the entity to get the components of
* @returns {[string]} array of component names
*/
export function getComponentNamesForEntity(entity){
ensureEntityExists(entity)
let names = []
for(let [name, map] of components){
if(map.has(entity)){
names.push(name)
}
}
return names
}
/**
* Sets a component of an entity
* @param {number} entity the entity to set the component of
* @param {string} name the name of the component to set
* @param {*} component the component to set
*/
export function setComponent(entity, name, component){
ensureEntityExists(entity)
ensureComponentWasRegistered(name)
components.get(name).set(entity, component)
}
/**
* Checks if an entity has a component
* @param {number} entity the entity to check
* @param {string} name the name of the component to check
* @returns {boolean} true if the entity has the component, false otherwise
*/
export function entityHasComponent(entity, name){
ensureEntityExists(entity)
ensureComponentWasRegistered(name)
return components.get(name).has(entity)
}
/**
* Checks if an entity has all components in a list
* @param {number} entity the entity to check
* @param {[string]} names the names of the components to check
* @returns {boolean} true if the entity has all the components, false otherwise
*/
export function entityHasComponents(entity, names){
ensureEntityExists(entity)
for(let name of names){
if(!entityHasComponent(entity, name)){
return false
}
}
return true
}
/**
* Checks if an entity does not have a component
* @param {number} entity the entity to check
* @param {string} name the name of the component to check
* @returns {boolean} true if the entity does not have the component, false otherwise
*/
export function entityHasNoComponent(entity, name){
return !entityHasComponent(entity, name)
}
/**
* Executes a callback for each entity with a given component
* @param {string} name the name of the component
* @param {function} callback the callback to call for each entity with the component
*/
export function eachEntityWithComponent(name, callback){
ensureComponentWasRegistered(name)
let entities = getEntitiesWithComponent(name)
for(let entity of entities){
callback(entity)
}
}
/**
* Executes a callback for each entity with a given set of components
* @param {[string]} names the names of the components
* @param {function} callback the callback to call for each entity with the components
*/
export function eachEntityWithComponents(names, callback){
const entities = getEntitiesWithComponents(names)
for(let entity of entities){
callback(entity)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment