From 9b89113ed98591a63b2e98df53c95ac60f37ae90 Mon Sep 17 00:00:00 2001 From: "Theunis J. Botha" Date: Tue, 27 Jun 2017 13:27:27 +0200 Subject: [PATCH] linking system --- src/game-lib-a-1-event.js | 2 +- src/game-lib-a-component-a.js | 132 ++---- src/game-lib-entity-manager.js | 457 +-------------------- src/game-lib-system-0.js | 1 + src/game-lib-system-linker.js | 710 +++++++++++++++++++++++++++++++++ src/game-lib-system-storage.js | 603 +++++++++++----------------- 6 files changed, 990 insertions(+), 915 deletions(-) create mode 100644 src/game-lib-system-linker.js diff --git a/src/game-lib-a-1-event.js b/src/game-lib-a-1-event.js index 6ded6ce..b6eb094 100644 --- a/src/game-lib-a-1-event.js +++ b/src/game-lib-a-1-event.js @@ -27,7 +27,6 @@ GameLib.Event.SAVE_COMPONENT_ERROR = 0xa; GameLib.Event.COMPONENT_SAVED = 0xb; GameLib.Event.LOAD_COMPONENT = 0xc; GameLib.Event.LOAD_COMPONENT_ERROR = 0xd; -GameLib.Event.COMPONENT_LOADED = 0xe; GameLib.Event.LOGGED_IN = 0xf; GameLib.Event.COMPONENT_CREATED = 0x10; GameLib.Event.SCENE_INSTANCE_CREATED = 0x11; @@ -42,6 +41,7 @@ GameLib.Event.MESH_INSTANCE_CREATED = 0x19; GameLib.Event.MESH_INSTANCE_UPDATED = 0x1a; GameLib.Event.LIGHT_INSTANCE_CREATED = 0x1b; GameLib.Event.LIGHT_INSTANCE_UPDATED = 0x1c; +GameLib.Event.DELETE_COMPONENT = 0x1d; /** * Subscribe to some events diff --git a/src/game-lib-a-component-a.js b/src/game-lib-a-component-a.js index ca01c3c..e8e803e 100644 --- a/src/game-lib-a-component-a.js +++ b/src/game-lib-a-component-a.js @@ -39,17 +39,20 @@ GameLib.Component = function( this.dependencies = this.getDependencies(); - this.publish( - GameLib.Event.COMPONENT_CREATED, - { - component : this - } - ); + if (this.dependencies.length === 0) { + delete this.dependencies; + this.loaded = true; + this.instance = this.createInstance(); + } }; GameLib.Component.prototype = Object.create(GameLib.API.Component.prototype); GameLib.Component.prototype.constructor = GameLib.Component; +GameLib.Component.prototype.createInstance = function() { + +}; + GameLib.Component.prototype.getDependencies = function() { var dependencies = []; @@ -241,108 +244,21 @@ GameLib.Component.prototype.buildIdToObject = function() { this.built = false; }; -/** - * Some components need to be loaded first, and are referenced by ID string from other objects. - * These components should implement this 'linkObjects' function - this will be implementation specific. - * Example: Scene has meshes which have materials which have textures. - * However, we don't store the texture inside the material inside the mesh, since they are re-usable among meshes. - * Instead, we store them on the scene object, reference them by string id from the mesh, and after scene has loaded, - * we link the objects - */ -// GameLib.Component.prototype.linkObjects = function(idToObject) { -// -// if (this.loaded) { -// return; -// } -// -// this.loaded = true; -// -// for (var property in this.linkedObjects) { -// if ( -// this.linkedObjects.hasOwnProperty(property) && -// this.hasOwnProperty(property) && -// this[property] -// ) { -// -// if (this.linkedObjects[property] instanceof Array) { -// if (this[property] instanceof Array) { -// -// this[property] = this[property].map( -// function(p) { -// if (p instanceof Object) { -// -// /** -// * This object is already an object, does not need to be linked -// */ -// if (p.linkObjects) { -// p.linkObjects(idToObject); -// } -// -// return p; -// } else if (typeof p === 'string') { -// -// if (!idToObject[p]) { -// console.warn('Could not locate the object to be linked in array - fix this'); -// throw new Error('Could not locate the object to be linked in array - fix this'); -// } -// -// /** -// * Perform deep-linking -// */ -// if (idToObject[p].linkObjects) { -// idToObject[p].linkObjects(idToObject); -// } -// -// return idToObject[p]; -// } else { -// console.warn('Unhandled type : ', p); -// throw new Error('Unhandled type : ', p); -// } -// -// }.bind(this) -// ) -// } else { -// console.warn('Incompatible Link Type - should be instance of array'); -// throw new Error('Incompatible Link Type - should be instance of array'); -// } -// } else { -// -// if (this[property] instanceof Object) { -// -// /** -// * This object is already an object, perform deep linking -// */ -// if (this[property].linkObjects) { -// this[property].linkObjects(idToObject); -// } -// -// } else if (typeof this[property] === 'string') { -// -// if (!idToObject[this[property]]) { -// console.warn('Could not locate the object to be linked - fix this'); -// throw new Error('Could not locate the object to be linked - fix this'); -// } -// -// this[property] = idToObject[this[property]]; -// -// //this[property].parentObjects.push(this); -// -// /** -// * Perform deep-linking -// */ -// if (this[property].linkObjects) { -// this[property].linkObjects(idToObject); -// } -// } else { -// console.warn('Unhandled property type - fix this : ' + typeof this[property]); -// throw new Error('Unhandled property type - fix this : ' + typeof this[property]); -// } -// } -// } -// } -// -// this.loaded = false; -// }; +GameLib.Component.prototype.generateNewIds = function() { + + this.buildIdToObject(); + + for (var property in this.idToObject) { + if (this.idToObject.hasOwnProperty(property)) { + + var oldId = this.idToObject[property].id; + var newId = GameLib.Utils.RandomId(); + + this.idToObject[property].id = newId; + this.idToObject[property].name = this.idToObject[property].name.replace(oldId,newId); + } + } +}; GameLib.Component.prototype.clone = function() { diff --git a/src/game-lib-entity-manager.js b/src/game-lib-entity-manager.js index 90a5638..2efa9eb 100644 --- a/src/game-lib-entity-manager.js +++ b/src/game-lib-entity-manager.js @@ -10,16 +10,6 @@ GameLib.EntityManager = function() { this.entities = []; - this.loading = []; - - this.dependencies = {}; - - this.subscriptions = []; - - this.checkRegister = []; - - this.registerCallbacks(); - GameLib.Component.call( this, GameLib.Component.COMPONENT_ENTITY_MANAGER, @@ -124,14 +114,6 @@ GameLib.EntityManager.prototype.addEntity = function(entity) { this.entities.push(entity); }; -// /** -// * Adds a system to this manager -// * @param system GameLib.System -// */ -// GameLib.EntityManager.prototype.addSystem = function(system) { -// this.systems.push(system); -// }; - /** * Returns entity by name * @param name @@ -206,27 +188,32 @@ GameLib.EntityManager.prototype.query = function(components) { */ GameLib.EntityManager.prototype.queryComponents = function(constructors) { - return this.checkRegister.reduce( - function(result, object) { + return this.entities.reduce( + function(result, entity) { - if (constructors instanceof Array) { - constructors.map( - function(constructor) { - if (object instanceof constructor) { - result.push(object); + entity.components.map( + function(component) { + if (constructors instanceof Array) { + constructors.map( + function(constructor) { + if (component instanceof constructor) { + result.push(component); + } + } + ); + } else { + if (component instanceof constructors) { + result.push(component); } } - ); - } else { - if (object instanceof constructors) { - result.push(object); } - } + ); return result; }, [] ); + }; /** @@ -264,413 +251,3 @@ GameLib.EntityManager.FromObject = function(objectEntityManager) { return entityManager; }; - -/** - * Defines what should happen when a parent scene changes - * @param data - */ -GameLib.EntityManager.prototype.onParentSceneChange = function(data) { - - if ( - data.object instanceof GameLib.D3.Mesh || - data.object instanceof GameLib.D3.Light - ) { - - /** - * We remove the helper (if any) from the old scene and add it to the new scene - */ - var helper = this.findHelperByObject(data.object); - if (helper) { - - if (data.originalScene && data.originalScene.instance) { - data.originalScene.instance.remove(helper.instance); - } - data.newScene.instance.add(helper.instance); - } - - /** - * We remove the mesh from the old scene and add it to the new scene - */ - if (data.originalScene && data.originalScene.removeObject) { - data.originalScene.removeObject(data.object); - } - data.newScene.addObject(data.object); - - /** - * We inherit the parent entity of this new scene - */ - var originalEntity = null; - var newEntity = null; - - if (data.object.hasOwnProperty('parentEntity')) { - originalEntity = data.object.parentEntity - } - - if (data.newScene.hasOwnProperty('parentEntity')) { - newEntity = data.newScene.parentEntity; - } - - var gui = null; - - if (originalEntity) { - - if (originalEntity.removeComponent) { - if (helper) { - originalEntity.removeComponent(helper); - } - originalEntity.removeComponent(data.object); - } - - if (originalEntity.getFirstComponent) { - gui = originalEntity.getFirstComponent(GameLib.GUI); - if (gui) { - gui.removeObject(data.object); - gui.build(this); - } - } - } - - if (newEntity) { - - if (newEntity.addComponent) { - if (helper) { - newEntity.addComponent(helper); - } - newEntity.addComponent(data.object); - } - - if (newEntity.getFirstComponent) { - gui = newEntity.getFirstComponent(GameLib.GUI); - if (gui) { - gui.addObject(data.object); - gui.build(this); - } - } - } - } - -}; - -/** - * Change parent entity - * TODO: also change parent entity of children objects - * @param data - */ -GameLib.EntityManager.prototype.onParentEntityChange = function(data) { - - if (data.originalEntity) { - data.originalEntity.removeComponent(data.object); - } - - data.newEntity.addComponent(data.object); - - // - ok not so cool - we may have parent entities of entities - - // - so not all children should inherit the parent entity - // data.object.buildIdToObject(); - // - // for (var property in data.object.idToObject) { - // if (data.object.idToObject.hasOwnProperty(property)) { - // if (data.object.idToObject[property].hasOwnProperty('parentEntity')) { - // data.object.idToObject[property].parentEntity = data.newEntity; - // } - // } - // } - -}; - - -GameLib.EntityManager.prototype.link = function(component, data) { - for (var property in component.linkedObjects) { - if (component.linkedObjects.hasOwnProperty(property)) { - if (component.linkedObjects[property] instanceof Array) { - component[property] = component[property].map(function (entry) { - if (entry === data.component.id) { - return data.component; - } else { - return entry; - } - }); - - } else { - if (component[property] && - component[property] === data.component.id) { - component[property] = data.component; - } - } - } - } -}; - -GameLib.EntityManager.prototype.componentCreated = function() { - - var loading = []; - - return function(data) { - - /** - * Register this component immediately - */ - this.checkRegister.push(data.component); - - /** - * If we notify ourselves - ignore it - */ - if (data.component === this) { - return; - } - - /** - * Store this component into our 'loaded' list - */ - loading.push(data.component); - - /** - * Store the dependencies too - */ - data.component.dependencies.map(function (id) { - - /** - * Check if we already processed a component on which this component is dependent - */ - - var processedComponent = this.checkRegister.reduce( - function(result, component){ - if (component.id === id){ - result = component; - } - return result; - }, - null - ); - - if (processedComponent) { - - /** - * Remove this dependency from the dependency list - */ - var index = data.component.dependencies.indexOf(id); - if (index === -1) { - console.log('failed to locate dependency which should exist'); - } - data.component.dependencies.splice(index, 1); - - /** - * Now link the component - */ - this.link(data.component, {component: processedComponent}); - - } else { - - if (GameLib.Utils.UndefinedOrNull(this.dependencies[id])) { - this.dependencies[id] = []; - } - - /** - * Don't store duplicate dependencies - */ - if (this.dependencies[id].indexOf(data.component === -1)) { - this.dependencies[id].push(data.component); - } - } - - }.bind(this)); - - /** - * Now find all the components which depend on this component - */ - if (GameLib.Utils.UndefinedOrNull(this.dependencies[data.component.id])) { - - /** - * We don't know about any dependencies on this object - but maybe a component still - * has to load which has dependencies to this object - this object is in the 'checkRegister' - */ - - } else { - - /** - * Otherwise, now - for each dependency - check if its loaded - */ - this.dependencies[data.component.id] = this.dependencies[data.component.id].reduce( - - function (result, component) { - - /** - * Remove the actual dependency - */ - var index = component.dependencies.indexOf(data.component.id); - if (index === -1) { - console.warn('dependency mismatch'); - } else { - component.dependencies.splice(index, 1); - } - - /** - * Find the actual place where this object should be linked - and link them - */ - this.link(component, data); - - /** - * If we now managed to link the objects, and this object has no more dependencies - */ - if (component.dependencies.length === 0) { - component.loaded = true; - component.instance = component.createInstance(); - this.emitInstanceEvents(component); - delete component.dependencies; - } - - /** - * Also remove this from the current dependency list - */ - return result; - }.bind(this), - [] - ); - - delete this.dependencies[data.component.id]; - } - - /** - * Now if this new component has no dependencies, load it - */ - if (data.component.dependencies.length === 0) { - data.component.loaded = true; - data.component.instance = data.component.createInstance(); - this.emitInstanceEvents(data.component); - delete data.component.dependencies; - } - - /** - * Now check if all components are loaded, i.e., no more dependencies - if so - create their instance objects - */ - var loaded = true; - for (var i = 0; i < loading.length; i++) { - if (!loading[i].loaded) { - loaded = false; - break - } - } - - /** - * All components loaded - */ - if (loaded) { - loading = []; - } - }; -}; - -GameLib.EntityManager.prototype.emitInstanceEvents = function (component) { - - if ( - component instanceof GameLib.D3.Mesh - ) { - GameLib.Event.Emit( - GameLib.Event.MESH_INSTANCE_CREATED, - { - mesh: component - } - ) - } - - if ( - component instanceof GameLib.D3.Light - ) { - GameLib.Event.Emit( - GameLib.Event.LIGHT_INSTANCE_CREATED, - { - light: component - } - ) - } - - if ( - component instanceof GameLib.D3.Scene - ) { - GameLib.Event.Emit( - GameLib.Event.SCENE_INSTANCE_CREATED, - { - scene: component - } - ); - } - - if ( - component instanceof GameLib.D3.Material - ) { - GameLib.Event.Emit( - GameLib.Event.MATERIAL_INSTANCE_CREATED, - { - material: component - } - ); - } - - if ( - component instanceof GameLib.D3.Texture - ) { - GameLib.Event.Emit( - GameLib.Event.TEXTURE_INSTANCE_CREATED, - { - texture: component - } - ); - } -}; - -/** - * - */ -GameLib.EntityManager.prototype.registerCallbacks = function() { - - this.subscriptions.push( - this.subscribe( - GameLib.Event.PARENT_SCENE_CHANGE, - this.onParentSceneChange - ) - ); - - this.subscriptions.push( - this.subscribe( - GameLib.Event.PARENT_ENTITY_CHANGE, - this.onParentEntityChange - ) - ); - - this.subscriptions.push( - this.subscribe( - GameLib.Event.COMPONENT_CREATED, - this.componentCreated().bind(this) - ) - ); -}; - -/** - * Links object Ids to actual objects - * @param idToObject - */ -// GameLib.EntityManager.prototype.linkObjects = function(idToObject) { -// -// this.entities.map( -// function(entity) { -// entity.components.map( -// function (componentId, index, array) { -// if (componentId instanceof GameLib.Component) { -// array[index] = componentId; -// } else { -// array[index] = idToObject[componentId]; -// } -// -// Object.keys(array[index].linkedObjects).map( -// function (propertyName) { -// array[index][propertyName] = idToObject[array[index][propertyName]]; -// } -// ); -// -// array[index].loaded = true; -// } -// ) -// } -// ); -// -// }; \ No newline at end of file diff --git a/src/game-lib-system-0.js b/src/game-lib-system-0.js index 12b9659..1d8caf2 100644 --- a/src/game-lib-system-0.js +++ b/src/game-lib-system-0.js @@ -41,6 +41,7 @@ GameLib.System.SYSTEM_TYPE_INPUT = 0x4; GameLib.System.SYSTEM_TYPE_STORAGE = 0x8; GameLib.System.SYSTEM_TYPE_GUI = 0x10; GameLib.System.SYSTEM_TYPE_PHYSICS = 0x20; +GameLib.System.SYSTEM_TYPE_LINKING = 0x40; GameLib.System.SYSTEM_TYPE_ALL = 0xFFFF; GameLib.System.prototype.createInstance = function() { diff --git a/src/game-lib-system-linker.js b/src/game-lib-system-linker.js new file mode 100644 index 0000000..e93e5c9 --- /dev/null +++ b/src/game-lib-system-linker.js @@ -0,0 +1,710 @@ +/** + * Linking System takes care of linking components and dependencies (after they have loaded) - + * and managing the relationships between objects - ex. what happens when a parent entity changes, + * or a parent scene changes. + * @param apiSystem GameLib.API.System + * @constructor + */ +GameLib.System.Linking = function( + apiSystem +) { + GameLib.System.call( + this, + apiSystem + ); + + /** + * The dependencies of each component is tracked through this dependencies object - + * it maps the id of the object on which a component depends back to the component which depends on it, + * ex. texture.image = 'abcdefghi', then this.dependencies = {'abcdefghi' : [texture]} + * @type {{}} + */ + this.dependencies = {}; + + /** + * The 'register' array is a register of each component currently loaded - when the linking + * system starts it also loads all the current components from the entity manager + * @type {Array} + */ + this.register = []; + + this.componentCreatedSubscription = null; + this.parentSceneChangeSubscription = null; + this.parentEntityChangeSubscription = null; + this.meshInstanceCreatedSubscription = null; + this.lightInstanceCreatedSubscription = null; + this.sceneInstanceCreatedSubscription = null; + this.imageInstanceCreatedSubscription = null; + this.textureInstanceCreatedSubscription = null; + this.materialInstanceCreatedSubscription = null; +}; + +GameLib.System.Linking.prototype = Object.create(GameLib.System.prototype); +GameLib.System.Linking.prototype.constructor = GameLib.System.Linking; + +GameLib.System.Linking.prototype.start = function() { + + this.register = GameLib.EntityManager.Instance.queryComponents([GameLib.Component]); + + this.componentCreatedSubscription = this.subscribe( + GameLib.Event.COMPONENT_CREATED, + this.componentCreated().bind(this) + ); + + this.parentSceneChangeSubscription = this.subscribe( + GameLib.Event.PARENT_SCENE_CHANGE, + this.onParentSceneChange + ); + + this.parentEntityChangeSubscription = this.subscribe( + GameLib.Event.PARENT_ENTITY_CHANGE, + this.onParentEntityChange + ); + + this.sceneInstanceCreatedSubscription = this.subscribe( + GameLib.Event.SCENE_INSTANCE_CREATED, + this.sceneInstanceCreated + ); + + this.meshInstanceCreatedSubscription = this.subscribe( + GameLib.Event.MESH_INSTANCE_CREATED, + this.meshInstanceCreated + ); + + this.lightInstanceCreatedSubscription = this.subscribe( + GameLib.Event.LIGHT_INSTANCE_CREATED, + this.lightInstanceCreated + ); + + this.imageInstanceCreatedSubscription = this.subscribe( + GameLib.Event.IMAGE_INSTANCE_CREATED, + this.imageInstanceCreated + ); + + this.textureInstanceCreatedSubscription = this.subscribe( + GameLib.Event.TEXTURE_INSTANCE_CREATED, + this.textureInstanceCreated + ); + + this.materialInstanceCreatedSubscription = this.subscribe( + GameLib.Event.MATERIAL_INSTANCE_CREATED, + this.materialInstanceCreated + ); + +}; + +GameLib.EntityManager.prototype.emitInstanceEvents = function (component) { + + if ( + component instanceof GameLib.D3.Mesh + ) { + GameLib.Event.Emit( + GameLib.Event.MESH_INSTANCE_CREATED, + { + mesh: component + } + ) + } + + if ( + component instanceof GameLib.D3.Light + ) { + GameLib.Event.Emit( + GameLib.Event.LIGHT_INSTANCE_CREATED, + { + light: component + } + ) + } + + if ( + component instanceof GameLib.D3.Scene + ) { + GameLib.Event.Emit( + GameLib.Event.SCENE_INSTANCE_CREATED, + { + scene: component + } + ); + } + + if ( + component instanceof GameLib.D3.Material + ) { + GameLib.Event.Emit( + GameLib.Event.MATERIAL_INSTANCE_CREATED, + { + material: component + } + ); + } + + if ( + component instanceof GameLib.D3.Texture + ) { + GameLib.Event.Emit( + GameLib.Event.TEXTURE_INSTANCE_CREATED, + { + texture: component + } + ); + } +}; + +GameLib.System.Linking.prototype.link = function(component, data) { + for (var property in component.linkedObjects) { + if (component.linkedObjects.hasOwnProperty(property)) { + if (component.linkedObjects[property] instanceof Array) { + component[property] = component[property].map(function (entry) { + if (entry === data.component.id) { + return data.component; + } else { + return entry; + } + }); + + } else { + if (component[property] && + component[property] === data.component.id) { + component[property] = data.component; + } + } + } + } +}; + +GameLib.System.Linking.prototype.componentCreated = function(data) { + + /** + * Shorthand + */ + var component = data.component; + + /** + * Register this component immediately + */ + this.register.push(component); + + /** + * We only care about components with unloaded dependencies - + * other components will have already had their instance objects created + */ + if (component.dependencies.length > 0) { + + component.dependencies = component.dependencies.reduce( + function(result, id) { + + /** + * Check if we already processed a component on which this component is dependent + */ + var processedComponent = this.register.reduce( + function(result, component){ + if (component.id === id){ + result = component; + } + return result; + }, + null + ); + + if (processedComponent) { + + /** + * Link the component + */ + this.link(component, {component: processedComponent}); + + } else { + + /** + * Create a new link if none exists + */ + if (GameLib.Utils.UndefinedOrNull(this.dependencies[id])) { + this.dependencies[id] = []; + } + + /** + * Don't store duplicate dependencies + */ + if (this.dependencies[id].indexOf(component === -1)) { + this.dependencies[id].push(component); + } + + /** + * Also - we remember that this component has a dependency + */ + result.push(id); + } + + return result; + }.bind(this), + [] + ); + + if (component.dependencies.length === 0) { + component.loaded = true; + component.instance = component.createInstance(); + this.emitInstanceEvents(component); + delete component.dependencies; + } + } + + var parentComponents = this.dependencies[component.id]; + + /** + * Now find all the components which depend on this component + */ + if (GameLib.Utils.UndefinedOrNull(parentComponents)) { + + /** + * We don't know about components which depend on this component - but it could still load. + * However, it is stored in the register for later use + */ + + } else { + + parentComponents.map( + function(parentComponent) { + this.link(parentComponent, {component: component}); + + /** + * Remove the actual dependency + */ + var index = parentComponent.dependencies.indexOf(component.id); + if (index === -1) { + console.warn('dependency mismatch'); + } else { + parentComponent.dependencies.splice(index, 1); + } + + /** + * If we now managed to link the objects, and this object has no more dependencies + */ + if (parentComponent.dependencies.length === 0) { + parentComponent.loaded = true; + parentComponent.instance = parentComponent.createInstance(); + this.emitInstanceEvents(parentComponent); + delete parentComponent.dependencies; + } + + }.bind(this) + ); + + delete this.dependencies[component.id]; + } + +}; + +GameLib.System.Linking.prototype.meshInstanceCreated = function(data) { + + var scenes = GameLib.EntityManager.Instance.queryComponents([GameLib.D3.Scene]); + + scenes.map(function(scene){ + if (data.mesh.parentScene === scene) { + scene.addObject(data.mesh); + } + }); + +}; + +GameLib.System.Linking.prototype.lightInstanceCreated = function(data) { + + var scenes = GameLib.EntityManager.Instance.queryComponents([GameLib.D3.Scene]); + + scenes.map(function(scene){ + if (data.light.parentScene === scene) { + scene.addObject(data.light); + } + }); + +}; + +GameLib.System.Linking.prototype.sceneInstanceCreated = function(data) { + + var scene = data.scene; + + scene.images.map( + function(image){ + GameLib.Event.Emit( + GameLib.Event.LOAD_IMAGE, + { + onLoaded : function(image) { + if (this.onImageLoaded) { + this.onImageLoaded(image); + } + }, + onProgress : function(image, progress) { + if (this.onImageProgress) { + this.onImageProgress(image, progress); + } + }, + onError : function(image, error) { + if (this.onImageError) { + this.onImageError(image, error); + } + }, + image : image + } + ); + }.bind(this) + ); + + /** + * Add all meshes and lights + */ + var object = GameLib.EntityManager.Instance.queryComponents([ + GameLib.D3.Mesh, + GameLib.D3.Light + ]); + object.map(function(object){ + if ( + object.parentScene === scene + ) { + scene.addObject(object); + } + }); + +}; + +GameLib.System.Linking.prototype.imageInstanceCreated = function(data) { + + var textures = GameLib.EntityManager.Instance.queryComponents([GameLib.D3.Texture]); + + textures.map( + function(texture) { + /** + * Only work with images that belong to this texture + */ + if ( + texture.image === data.image + ) { + /** + * Update instance, if already an instance + */ + if (texture.instance) { + texture.updateInstance(); + GameLib.Event.Emit( + GameLib.Event.TEXTURE_INSTANCE_UPDATED, + { + texture : texture + } + ) + } else { + /** + * Create a new instance + */ + texture.instance = texture.createInstance(); + GameLib.Event.Emit( + GameLib.Event.TEXTURE_INSTANCE_CREATED, + { + texture : texture + } + ) + } + } + } + ); +}; + +GameLib.System.Linking.prototype.textureInstanceCreated = function(data) { + + var materials = GameLib.EntityManager.Instance.queryComponents([GameLib.D3.Material]); + + materials.map( + + function(material) { + + if (!material.instance) { + console.log('No material instance for ' + material.name); + return; + } + + var modified = false; + + /** + * We also need to check if the image of the texture is assigned - + * if not we should disable the map + */ + if (material.alphaMap === data.texture) { + + if (data.texture.image) { + material.instance.alphaMap = data.texture.instance; + } else { + material.instance.alphaMap = null; + } + + modified = true; + } + if (material.aoMap === data.texture) { + + if (data.texture.image) { + material.instance.aoMap = data.texture.instance; + } else { + material.instance.aoMap = null; + } + modified = true; + } + if (material.bumpMap === data.texture) { + + if (data.texture.image) { + material.instance.bumpMap = data.texture.instance; + } else { + material.instance.bumpMap = null; + } + modified = true; + } + if (material.diffuseMap === data.texture) { + + if (data.texture.image) { + material.instance.map = data.texture.instance; + } else { + material.instance.map = null; + } + modified = true; + } + if (material.displacementMap === data.texture) { + + if (data.texture.image) { + material.instance.displacementMap = data.texture.instance; + } else { + material.instance.displacementMap = null; + } + modified = true; + } + if (material.emissiveMap === data.texture) { + + if (data.texture.image) { + material.instance.emissiveMap = data.texture.instance; + } else { + material.instance.emissiveMap = null; + } + modified = true; + } + if (material.environmentMap === data.texture) { + + if (data.texture.image) { + material.instance.envMap = data.texture.instance; + } else { + material.instance.envMap = null; + } + modified = true; + } + if (material.lightMap === data.texture) { + + if (data.texture.image) { + material.instance.lightMap = data.texture.instance; + } else { + material.instance.lightMap = null; + } + modified = true; + } + if (material.metalnessMap === data.texture) { + + if (data.texture.image) { + material.instance.metalnessMap = data.texture.instance; + } else { + material.instance.metalnessMap = null; + } + modified = true; + } + if (material.normalMap === data.texture) { + + if (data.texture.image) { + material.instance.normalMap = data.texture.instance; + } else { + material.instance.normalMap = null; + } + modified = true; + } + if (material.roughnessMap === data.texture) { + + if (data.texture.image) { + material.instance.roughnessMap = data.texture.instance; + } else { + material.instance.roughnessMap = null; + } + modified = true; + } + if (material.specularMap === data.texture) { + + if (data.texture.image) { + material.instance.specularMap = data.texture.instance; + } else { + material.instance.specularMap = null; + } + modified = true; + } + + if (modified) { + material.updateInstance(); + GameLib.Event.Emit( + GameLib.Event.MATERIAL_INSTANCE_UPDATED, + { + material : material + } + ) + } + } + ); +}; + +GameLib.System.Linking.prototype.materialInstanceCreated = function(data) { + + var meshes = GameLib.EntityManager.Instance.queryComponents([GameLib.D3.Mesh]); + + meshes.map(function(mesh){ + + if (!mesh.instance) { + return; + } + + /** + * Only work with materials assigned to us + */ + if (mesh.materials[0] !== data.material) { + return; + } + + if (mesh.instance.material === data.material.instance) { + //mesh.instance.geometry.uvsNeedUpdate = true; + return; + } + + if (mesh.materials[0] === data.material) { + + if (mesh.instance.material !== data.material.instance) { + mesh.instance.material = data.material.instance; + } + + //mesh.instance.geometry.uvsNeedUpdate = true; + } + }); + +}; + +/** + * Defines what should happen when a parent scene changes + * @param data + */ +GameLib.System.Linking.prototype.onParentSceneChange = function(data) { + + if ( + data.object instanceof GameLib.D3.Mesh || + data.object instanceof GameLib.D3.Light + ) { + + /** + * We remove the helper (if any) from the old scene and add it to the new scene + */ + var helper = this.findHelperByObject(data.object); + if (helper) { + + if (data.originalScene && data.originalScene.instance) { + data.originalScene.instance.remove(helper.instance); + } + data.newScene.instance.add(helper.instance); + } + + /** + * We remove the mesh from the old scene and add it to the new scene + */ + if (data.originalScene && data.originalScene.removeObject) { + data.originalScene.removeObject(data.object); + } + data.newScene.addObject(data.object); + + /** + * We inherit the parent entity of this new scene + */ + var originalEntity = null; + var newEntity = null; + + if (data.object.hasOwnProperty('parentEntity')) { + originalEntity = data.object.parentEntity + } + + if (data.newScene.hasOwnProperty('parentEntity')) { + newEntity = data.newScene.parentEntity; + } + + var gui = null; + + if (originalEntity) { + + if (originalEntity.removeComponent) { + if (helper) { + originalEntity.removeComponent(helper); + } + originalEntity.removeComponent(data.object); + } + + if (originalEntity.getFirstComponent) { + gui = originalEntity.getFirstComponent(GameLib.GUI); + if (gui) { + gui.removeObject(data.object); + gui.build(this); + } + } + } + + if (newEntity) { + + if (newEntity.addComponent) { + if (helper) { + newEntity.addComponent(helper); + } + newEntity.addComponent(data.object); + } + + if (newEntity.getFirstComponent) { + gui = newEntity.getFirstComponent(GameLib.GUI); + if (gui) { + gui.addObject(data.object); + gui.build(this); + } + } + } + } + +}; + +/** + * Change parent entity + * TODO: also change parent entity of children objects + * @param data + */ +GameLib.System.Linking.prototype.onParentEntityChange = function(data) { + + if (data.originalEntity) { + data.originalEntity.removeComponent(data.object); + } + + data.newEntity.addComponent(data.object); + + // - ok not so cool - we may have parent entities of entities - + // - so not all children should inherit the parent entity + // data.object.buildIdToObject(); + // + // for (var property in data.object.idToObject) { + // if (data.object.idToObject.hasOwnProperty(property)) { + // if (data.object.idToObject[property].hasOwnProperty('parentEntity')) { + // data.object.idToObject[property].parentEntity = data.newEntity; + // } + // } + // } + +}; + +GameLib.System.Linking.prototype.stop = function() { + this.register = []; + this.componentCreatedSubscription.remove(); + this.parentSceneChangeSubscription.remove(); + this.parentEntityChangeSubscription.remove(); + this.meshInstanceCreatedSubscription.remove(); + this.lightInstanceCreatedSubscription.remove(); + this.sceneInstanceCreatedSubscription.remove(); + this.imageInstanceCreatedSubscription.remove(); + this.textureInstanceCreatedSubscription.remove(); + this.materialInstanceCreatedSubscription.remove(); +}; + diff --git a/src/game-lib-system-storage.js b/src/game-lib-system-storage.js index c3093ac..cd9236e 100644 --- a/src/game-lib-system-storage.js +++ b/src/game-lib-system-storage.js @@ -7,6 +7,9 @@ * @param onImageLoaded * @param onImageProgress * @param onImageError + * @param onComponentLoaded + * @param onComponentProgress + * @param onComponentError * @constructor */ GameLib.System.Storage = function( @@ -16,7 +19,10 @@ GameLib.System.Storage = function( apiUploadUrl, onImageLoaded, onImageProgress, - onImageError + onImageError, + onComponentLoaded, + onComponentProgress, + onComponentError ) { GameLib.System.call( this, @@ -55,17 +61,25 @@ GameLib.System.Storage = function( } this.onImageError = onImageError; + if (GameLib.Utils.UndefinedOrNull(onComponentLoaded)) { + onComponentLoaded = null; + } + this.onComponentLoaded = onComponentLoaded; + + if (GameLib.Utils.UndefinedOrNull(onComponentProgress)) { + onComponentProgress = null; + } + this.onComponentProgress = onComponentProgress; + + if (GameLib.Utils.UndefinedOrNull(onComponentError)) { + onComponentError = null; + } + this.onComponentError = onComponentError; + this.loginSubscription = null; this.saveSubscription = null; this.loadSubscription = null; this.loadImageSubscription = null; - this.meshInstanceCreatedSubscription = null; - this.lightInstanceCreatedSubscription = null; - this.sceneInstanceCreatedSubscription = null; - this.imageInstanceCreatedSubscription = null; - this.textureInstanceCreatedSubscription = null; - this.materialInstanceCreatedSubscription = null; - }; GameLib.System.Storage.prototype = Object.create(GameLib.System.prototype); @@ -94,36 +108,6 @@ GameLib.System.Storage.prototype.start = function() { GameLib.Event.LOAD_IMAGE, this.loadImage ); - - this.sceneInstanceCreatedSubscription = this.subscribe( - GameLib.Event.SCENE_INSTANCE_CREATED, - this.sceneInstanceCreated - ); - - this.meshInstanceCreatedSubscription = this.subscribe( - GameLib.Event.MESH_INSTANCE_CREATED, - this.meshInstanceCreated - ); - - this.lightInstanceCreatedSubscription = this.subscribe( - GameLib.Event.LIGHT_INSTANCE_CREATED, - this.lightInstanceCreated - ); - - this.imageInstanceCreatedSubscription = this.subscribe( - GameLib.Event.IMAGE_INSTANCE_CREATED, - this.imageInstanceCreated - ); - - this.textureInstanceCreatedSubscription = this.subscribe( - GameLib.Event.TEXTURE_INSTANCE_CREATED, - this.textureInstanceCreated - ); - - this.materialInstanceCreatedSubscription = this.subscribe( - GameLib.Event.MATERIAL_INSTANCE_CREATED, - this.materialInstanceCreated - ); }; /** @@ -194,51 +178,231 @@ GameLib.System.Storage.prototype.load = function(data) { return; } - var xhr = new XMLHttpRequest(); + /** + * Load all the ids into our 'loading' list + */ + var loading = data.ids.reduce( + function(result, id) { - xhr.open( - 'GET', - data.url + if (result.indexOf(id) === -1) { + result.push(id); + } + + return result; + }, + false ); - xhr.onreadystatechange = function (xhr) { - return function () { - if (xhr.readyState === 4) { + var loaded = []; - try { - var object = JSON.parse(xhr.responseText); - } catch (error) { - this.publish( - GameLib.Event.LOAD_COMPONENT_ERROR, - { - error : error - } - ); - return; + var includeDependencies = data.includeDependencies; + + var onComponentLoaded = this.onComponentLoaded; + + var onComponentProgress = this.onComponentProgress; + + var onComponentError = this.onComponentError; + + while (loading.length > 0) { + + var id = loading.pop(); + + var xhr = new XMLHttpRequest(); + + xhr.onload = function () { + + try { + var object = JSON.parse(this.responseText); + } catch (error) { + + if (onComponentError) { + onComponentError(error); } - if (object.result !== 'success') { - this.publish( - GameLib.Event.LOAD_COMPONENT_ERROR, - { - error : object - } - ); - return; - } - - this.publish( - GameLib.Event.COMPONENT_LOADED, + GameLib.Event.Emit( + GameLib.Event.LOAD_COMPONENT_ERROR, { - response : object, - includeDependencies : data.includeDependencies + error: error + } + ); + + return; + } + + if (object.result !== 'success') { + + if (onComponentError) { + onComponentError(error); + } + + GameLib.Event.Emit( + GameLib.Event.LOAD_COMPONENT_ERROR, + { + error: error + } + ); + + return; + } + + /** + * Now we need to create the runtime component - this happens systematically. + * First, we create an API object from the Object, then a Runtime object from the API object + * Each component has a function 'FromObject' which essentially does this for you + */ + var componentName = GameLib.Component.GetComponentName(object.componentType); + + var componentClass = eval(componentName); + + var fn = componentClass['FromObject']; + + var runtimeComponent = null; + + if (object.componentType === GameLib.Component.COMPONENT_ENTITY) { + runtimeComponent = fn(object, GameLib.EntityManager.Instance); + } else { + runtimeComponent = fn(this.graphics, object); + } + + loaded.push(runtimeComponent.id); + + if (includeDependencies) { + /** + * Before we announce the creation of this component, we should get + * a list of all dependencies of this object, because once we announce + * the creation of this object - the linking system will attempt to resolve + * all dependencies + */ + var dependencies = runtimeComponent.dependencies.map(function (dependency) { + return dependency; + }); + + /** + * Now - we should systematically check if we have the dependency already + * loaded (in our runtime environment) - if we have - we just ignore loading this dependency (for now) + * TODO: decide what to do with runtime versions of 'stale' dependencies + */ + var components = GameLib.EntityManager.Instance.queryComponents(componentClass); + + dependencies = dependencies.reduce( + function (result, dependency) { + + var found = components.reduce( + function (result, component) { + if (component.id === dependency) { + found = true; + } + return result; + }, + false + ); + + if (!found) { + result.push(dependency); + } + + return result; + }, + [] + ); + + /** + * Also check if we did not already load this component quite recently + */ + dependencies = dependencies.reduce( + function (result, dependency) { + + var found = loaded.reduce( + function (result, id) { + if (id === dependency) { + result = true; + } + return result; + }, + false + ); + + if (!found) { + result.push(dependency); + } + + return result; + }, + [] + ); + + /** + * We should now check our 'loading' list and add all dependencies which are not already in there + */ + dependencies.map( + function (dependency) { + if (loading.indexOf(dependency) === -1) { + loading.push(dependency); + } } ) } - } - }(xhr).bind(this); - xhr.send(); + /** + * Ok - now we have a super good idea of which components still need to load - + * they live in the 'loading' list. + * + * At this point - the runtime components are created, but they are not ready + * to be used. They may have dependencies to other objects, which still need + * to load, or may never be loaded. + * + * It is however safe, to announce, that we created the + * runtime version of it, however it could still have some dependencies. + * + * The Linking system will then kick in and try to resolve all dependencies + */ + + if (onComponentLoaded) { + onComponentLoaded(runtimeComponent); + } + + GameLib.Event.Emit( + GameLib.Event.COMPONENT_CREATED, + { + component: runtimeComponent + } + ) + }; + + xhr.onprogress = function(__id) { + return function (progressEvent) { + + var progress = 0; + + if (progressEvent.total !== 0) { + progress = Number(progressEvent.loaded / progressEvent.total); + progress *= 100; + } + + if (onComponentProgress) { + onComponentProgress(__id, progress) + } + }; + }(id); + + xhr.onerror = function(__id) { + return function (error) { + console.warn('component load failed for component ID ' + __id); + if (onComponentError) { + onComponentError(__id, error) + } + }; + }(id); + + xhr.open( + 'GET', + this.apiUrl + '/component/load/' + id + ); + + xhr.send(); + } + + }; GameLib.System.Storage.prototype.loadImage = function(data) { @@ -247,7 +411,7 @@ GameLib.System.Storage.prototype.loadImage = function(data) { var onLoaded = this.onImageLoaded; - var onProgress = this.onImageProgress;; + var onProgress = this.onImageProgress; var onError = this.onImageError; @@ -353,303 +517,10 @@ GameLib.System.Storage.prototype.loadImage = function(data) { preflight.send(); }; -GameLib.System.Storage.prototype.meshInstanceCreated = function(data) { - - var scenes = GameLib.EntityManager.Instance.queryComponents([GameLib.D3.Scene]); - - scenes.map(function(scene){ - if (data.mesh.parentScene === scene) { - scene.addObject(data.mesh); - } - }); - -}; - -GameLib.System.Storage.prototype.lightInstanceCreated = function(data) { - - var scenes = GameLib.EntityManager.Instance.queryComponents([GameLib.D3.Scene]); - - scenes.map(function(scene){ - if (data.light.parentScene === scene) { - scene.addObject(data.light); - } - }); - -}; - -GameLib.System.Storage.prototype.sceneInstanceCreated = function(data) { - - var scene = data.scene; - - scene.images.map( - function(image){ - GameLib.Event.Emit( - GameLib.Event.LOAD_IMAGE, - { - onLoaded : function(image) { - if (this.onImageLoaded) { - this.onImageLoaded(image); - } - }, - onProgress : function(image, progress) { - if (this.onImageProgress) { - this.onImageProgress(image, progress); - } - }, - onError : function(image, error) { - if (this.onImageError) { - this.onImageError(image, error); - } - }, - image : image - } - ); - }.bind(this) - ); - - /** - * Add all meshes and lights - */ - var object = GameLib.EntityManager.Instance.queryComponents([ - GameLib.D3.Mesh, - GameLib.D3.Light - ]); - object.map(function(object){ - if ( - object.parentScene === scene - ) { - scene.addObject(object); - } - }); - -}; - -GameLib.System.Storage.prototype.imageInstanceCreated = function(data) { - - var textures = GameLib.EntityManager.Instance.queryComponents([GameLib.D3.Texture]); - - textures.map( - function(texture) { - /** - * Only work with images that belong to this texture - */ - if ( - texture.image === data.image - ) { - /** - * Update instance, if already an instance - */ - if (texture.instance) { - texture.updateInstance(); - GameLib.Event.Emit( - GameLib.Event.TEXTURE_INSTANCE_UPDATED, - { - texture : texture - } - ) - } else { - /** - * Create a new instance - */ - texture.instance = texture.createInstance(); - GameLib.Event.Emit( - GameLib.Event.TEXTURE_INSTANCE_CREATED, - { - texture : texture - } - ) - } - } - } - ); -}; - -GameLib.System.Storage.prototype.textureInstanceCreated = function(data) { - - var materials = GameLib.EntityManager.Instance.queryComponents([GameLib.D3.Material]); - - materials.map( - - function(material) { - - if (!material.instance) { - console.log('No material instance for ' + material.name); - return; - } - - var modified = false; - - /** - * We also need to check if the image of the texture is assigned - - * if not we should disable the map - */ - if (material.alphaMap === data.texture) { - - if (data.texture.image) { - material.instance.alphaMap = data.texture.instance; - } else { - material.instance.alphaMap = null; - } - - modified = true; - } - if (material.aoMap === data.texture) { - - if (data.texture.image) { - material.instance.aoMap = data.texture.instance; - } else { - material.instance.aoMap = null; - } - modified = true; - } - if (material.bumpMap === data.texture) { - - if (data.texture.image) { - material.instance.bumpMap = data.texture.instance; - } else { - material.instance.bumpMap = null; - } - modified = true; - } - if (material.diffuseMap === data.texture) { - - if (data.texture.image) { - material.instance.map = data.texture.instance; - } else { - material.instance.map = null; - } - modified = true; - } - if (material.displacementMap === data.texture) { - - if (data.texture.image) { - material.instance.displacementMap = data.texture.instance; - } else { - material.instance.displacementMap = null; - } - modified = true; - } - if (material.emissiveMap === data.texture) { - - if (data.texture.image) { - material.instance.emissiveMap = data.texture.instance; - } else { - material.instance.emissiveMap = null; - } - modified = true; - } - if (material.environmentMap === data.texture) { - - if (data.texture.image) { - material.instance.envMap = data.texture.instance; - } else { - material.instance.envMap = null; - } - modified = true; - } - if (material.lightMap === data.texture) { - - if (data.texture.image) { - material.instance.lightMap = data.texture.instance; - } else { - material.instance.lightMap = null; - } - modified = true; - } - if (material.metalnessMap === data.texture) { - - if (data.texture.image) { - material.instance.metalnessMap = data.texture.instance; - } else { - material.instance.metalnessMap = null; - } - modified = true; - } - if (material.normalMap === data.texture) { - - if (data.texture.image) { - material.instance.normalMap = data.texture.instance; - } else { - material.instance.normalMap = null; - } - modified = true; - } - if (material.roughnessMap === data.texture) { - - if (data.texture.image) { - material.instance.roughnessMap = data.texture.instance; - } else { - material.instance.roughnessMap = null; - } - modified = true; - } - if (material.specularMap === data.texture) { - - if (data.texture.image) { - material.instance.specularMap = data.texture.instance; - } else { - material.instance.specularMap = null; - } - modified = true; - } - - if (modified) { - material.updateInstance(); - GameLib.Event.Emit( - GameLib.Event.MATERIAL_INSTANCE_UPDATED, - { - material : material - } - ) - } - } - ); -}; - -GameLib.System.Storage.prototype.materialInstanceCreated = function(data) { - - var meshes = GameLib.EntityManager.Instance.queryComponents([GameLib.D3.Mesh]); - - meshes.map(function(mesh){ - - if (!mesh.instance) { - return; - } - - /** - * Only work with materials assigned to us - */ - if (mesh.materials[0] !== data.material) { - return; - } - - if (mesh.instance.material === data.material.instance) { - //mesh.instance.geometry.uvsNeedUpdate = true; - return; - } - - if (mesh.materials[0] === data.material) { - - if (mesh.instance.material !== data.material.instance) { - mesh.instance.material = data.material.instance; - } - - //mesh.instance.geometry.uvsNeedUpdate = true; - } - }); - -}; - GameLib.System.Storage.prototype.stop = function() { this.loginSubscription.remove(); this.loadSubscription.remove(); this.saveSubscription.remove(); this.loadImageSubscription.remove(); - this.meshInstanceCreatedSubscription.remove(); - this.lightInstanceCreatedSubscription.remove(); - this.sceneInstanceCreatedSubscription.remove(); - this.imageInstanceCreatedSubscription.remove(); - this.textureInstanceCreatedSubscription.remove(); - this.materialInstanceCreatedSubscription.remove(); };