Skip to content

Instantly share code, notes, and snippets.

@mflux
Created September 23, 2015 23:50
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mflux/c32e2066948d5c8b591b to your computer and use it in GitHub Desktop.
Save mflux/c32e2066948d5c8b591b to your computer and use it in GitHub Desktop.
import R from 'ramda';
function newId(){
let d = new Date().getTime();
let uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
const r = (d + Math.random()*16)%16 | 0;
d = Math.floor(d/16);
return (c=='x' ? r : (r&0x3|0x8)).toString(16);
});
return uuid;
}
export const newWorld = () => ({});
export const newEntity = () => ({});
export const addEntity = R.curry( function( world, entity ){
world[ newId() ] = entity;
return entity;
});
export const removeEntity = R.curry( function( world, entity ){
for( let i in world ){
if( world[ i ] === entity ){
delete world[ i ];
return entity;
}
}
});
export const getEntityById = R.curry( function( world, id ){
return world[ id ];
});
export const lookupEntityId = R.curry( function( world, entity ){
for( let id in world ){
if( world[ id ] === entity ){
return id;
}
}
});
export const createComponent = function( componentType, componentData ){
const component = new componentType( componentData );
return component;
};
export const addComponent = R.curry( function( entity, component ){
entity[ component.name ] = component;
return component;
});
export const removeComponent = R.curry( function( entity, component ){
delete entity[ component.name ];
return component;
});
export const getEntities = R.curry(function( world, ...components ){
return R.filter( hasKeys( components ), R.values( world ) );
});
const hasKeys = R.curry( function( keys, object ){
const objectKeys = R.keys( object );
return R.all( function( keyName ){
return objectKeys.indexOf( keyName ) >= 0;
}, keys );
});
export const defineComponent = function( componentTemplate ){
const componentType = function( data ){
const initializer = componentTemplate.init.bind( this );
initializer( data );
};
componentType.prototype.name = componentTemplate.name;
return componentType;
};
export const world = function( entities ){
const systems = createSystems( entities );
return {
systems: systems,
addEntity: addEntity( entities ),
removeEntity: removeEntity( entities ),
getEntities: ( ...args ) => getEntities( entities, ...args ),
get: () => entities,
clone: () => R.clone( entities ),
getEntityById: getEntityById( entities ),
lookupEntityId: lookupEntityId( entities ),
};
};
const onlyAdditions = R.filter( R.propEq( 'type', 'add' ) );
const onlyDeletions = R.filter( R.propEq( 'type', 'delete' ) );
const createSystems = function( entities ){
Object.observe( entities, function( changes ){
R.forEach( observeEntities, changes );
});
function observeEntities( entityChange ){
const entityId = entityChange.name;
observeEntity( getEntityFromChange( entityChange ), entityId );
}
function observeEntity( entity, entityId ){
const diffComponents = R.keys( entity );
handleComponentsChanged( addedSystems, entity, diffComponents, entityId );
Object.observe( entity, entityChanged( entity, entityId ) );
}
const entityChanged = R.curry( function( entity, entityId, changes ){
const addedComponents = getComponentsFromChanges( onlyAdditions( changes ) );
handleComponentsChanged( addedSystems, entity, addedComponents, entityId );
const removedComponents = getComponentsFromChanges( onlyDeletions( changes ) );
handleComponentsChanged( removedSystems, entity, removedComponents, entityId );
});
function handleComponentsChanged( systems, entity, diffComponents, entityId ){
const existingComponents = R.difference( R.keys( entity ), diffComponents );
R.forEach( function( system ){
if( overlapsComponents( system.components, existingComponents, diffComponents ) ){
// console.log('triggering system', system, JSON.stringify(entity), diffComponents );
system.callback( entity, R.intersection( diffComponents, system.components ), entityId );
}
}, systems );
}
function overlapsComponents( query, existingComponents, diffComponents ){
const intersected = R.intersection( query, R.union( existingComponents, diffComponents ) );
return sameElements( query, intersected ) && overlaps( diffComponents, query );
}
function sameElements( a, b ){
return R.isEmpty( R.difference( a, b ) );
}
function overlaps( a, b ){
return R.not( R.isEmpty( R.intersection( a, b ) ) );
}
const addedSystems = [];
function added( components, callback ){
addedSystems.push({
components: components,
callback: callback
});
}
const removedSystems = [];
function removed( components, callback ){
removedSystems.push({
components: components,
callback: callback
});
}
return {
added: added,
removed: removed
};
};
function getEntityFromChange( change ){
if( change.type === 'add' ){
return change.object[ change.name ];
}
if( change.type === 'delete' ){
return change.oldValue;
}
}
const getComponentsFromChanges = R.map( R.prop( 'name' ) );
@mflux
Copy link
Author

mflux commented Sep 24, 2015

//  creating empty world data, serialize-ready
let entities = EC.newWorld(); // or literally {}

//  save the game...
const serialized = JSON.stringify( entities );

//  load the game...
entities = JSON.parse( serialized );

//  world provides all the functionality that wraps around entities
const world = EC.world( entities );

//  creating an empty entity
const entity = EC.newEntity(); // or literally {}

//  adding the component to the world
world.addEntity( entity );

//  defining a component
const actorComponent = EC.defineComponent({
  name: 'actor',
  init: function( data ){
    this.activity = data.activity;
  }
});

//  instantiating a component
const component = EC.createComponent( actorComponent, { activity: 'idle' } );

//  adding a component to an entity
EC.addComponent( entity, component );

//  creating a system that detects actor and animation components
world.systems.added( [ 'actor', 'animation' ], function( entity, components, id ){

  //  gives entities with actor component
  const actor = entity.getComponent( 'actor' );
  const animation = entity.getComponent( 'animation' );

  //  or the uuid and components
  console.log( 'entity', entity, id, 'just got', components );
});

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment