Created
September 23, 2015 23:50
-
-
Save mflux/c32e2066948d5c8b591b to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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' ) ); |
Author
mflux
commented
Sep 24, 2015
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment