diff --git a/src/game-lib-system-storage.js b/src/game-lib-system-storage.js index 377628c..f52ccfd 100644 --- a/src/game-lib-system-storage.js +++ b/src/game-lib-system-storage.js @@ -70,6 +70,11 @@ GameLib.System.Storage = function( } this.onComponentError = onComponentError; + this.loaded = []; + this.loading = []; + this.failed = []; + this.otherDependencies = []; + this.loginSubscription = null; this.saveSubscription = null; this.loadSubscription = null; @@ -318,353 +323,340 @@ GameLib.System.Storage.prototype.save = function(data) { }; -GameLib.System.Storage.prototype.loadComponent = function(apiUrl, toProcess, includeDependencies, clientCallback, clientErrorCallback) { +GameLib.System.Storage.prototype.createRuntimeObject = function(responseText, clientErrorCallback) { - var loaded = []; + try { + var object = JSON.parse(responseText); + } catch (errorObject) { - var loading = []; - - toProcess.map(function(id) { - if (loading.indexOf(id) === -1) { - loading.push(id); + if (this.onComponentError) { + this.onComponentError(errorObject); } - }); - /** - * We just do an initial check if these components to process are already in the register - - * if so we remove them since we probably want to overwrite them with stale DB versions. - * - * We don't override runtime versions of the dependencies of the loading components - since they could be later. - * But we do override runtime versions of the loading component since the user actually selected them and clicked 'load' - */ - loading.map( - function(id) { + if (clientErrorCallback) { + clientErrorCallback({ + message : errorObject.message || 'JSON parse error' + }) + } - var component = GameLib.EntityManager.Instance.findComponentById(id); + GameLib.Event.Emit( + GameLib.Event.LOAD_COMPONENT_ERROR, + {error: errorObject} + ); - if (component) { - component.remove(); - } - } - ); + return null; + } - return function download(id, parentEntity) { + if (object.result !== 'success') { - var onComponentLoaded = this.onComponentLoaded; + if (this.onComponentError) { + this.onComponentError(id, object); + } - var onComponentProgress = this.onComponentProgress; + if (clientErrorCallback) { + clientErrorCallback({ + message : object.message || 'Server load error' + }) + } - var onComponentError = this.onComponentError; + GameLib.Event.Emit( + GameLib.Event.LOAD_COMPONENT_ERROR, + {error : object} + ); - var xhr = new XMLHttpRequest(); + return null; + } - xhr.onload = function(__system) { + var runtimeComponent = null; - return function () { + /** + * 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 component = object.component[0]; - var error = false; + var componentName = GameLib.Component.GetComponentName(component.componentType); + + var componentClass = eval(componentName); + + var fn = componentClass['FromObject']; + + if (component.componentType === GameLib.Component.COMPONENT_ENTITY) { + + runtimeComponent = fn(component, GameLib.EntityManager.Instance); + + } else { + + try { + + runtimeComponent = fn(component); + + } catch (error) { + + if (this.coder) { try { - var object = JSON.parse(this.responseText); - } catch (errorObject) { + runtimeComponent = fn(this.coder, component); + } catch (error) { - if (onComponentError) { - onComponentError(errorObject); - } - - if (clientErrorCallback) { - clientErrorCallback({ - message : errorObject.message || 'JSON parse error' - }) - } - - GameLib.Event.Emit( - GameLib.Event.LOAD_COMPONENT_ERROR, - { - error: errorObject - } - ); - - error = true; } - if (!error && object.result !== 'success') { + } - if (onComponentError) { - onComponentError(id, object); - } + if (!runtimeComponent && this.graphics) { + try { + runtimeComponent = fn(this.graphics, component); + } catch (error) { - if (clientErrorCallback) { - clientErrorCallback({ - message : object.message || 'Server load error' - }) - } - - GameLib.Event.Emit( - GameLib.Event.LOAD_COMPONENT_ERROR, - {error : object} - ); - - error = true; } + } - var runtimeComponent = null; - - if (!error) { - + if (!runtimeComponent && this.physics) { + try { + runtimeComponent = fn(this.physics, component); + } catch (error) { /** - * 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 + * ok - we don't cannot create this component */ - var component = object.component[0]; + } + } - var componentName = GameLib.Component.GetComponentName(component.componentType); + } - var componentClass = eval(componentName); + if (!runtimeComponent) { + if (clientErrorCallback) { + clientErrorCallback({ + result: 'failure', + message: 'Could not create a runtime component: ' + component.name + }); + } + } - var fn = componentClass['FromObject']; + } - if (component.componentType === GameLib.Component.COMPONENT_ENTITY) { - runtimeComponent = fn(component, GameLib.EntityManager.Instance); - runtimeComponent.parentEntity = parentEntity; - parentEntity = runtimeComponent; - } else { + return runtimeComponent; +}; - try { +GameLib.System.Storage.prototype.loadComponent = function(apiUrl, toProcess, includeDependencies, clientCallback, clientErrorCallback) { - runtimeComponent = fn(component); + /** + * We just do an initial check if these components to process are already in the register - + * if so we remove them since we probably want to overwrite them with stale DB versions. + * + * We don't override runtime versions of the dependencies of the loading components - since they could be later. + * But we do override runtime versions of the loading component since the user actually selected them and clicked 'load' + */ + toProcess.map( + function(id) { - } catch (error) { + GameLib.Utils.PushUnique(this.loading, id); - if (__system.coder) { + var component = GameLib.EntityManager.Instance.findComponentById(id); - try { - runtimeComponent = fn(__system.coder, component); - } catch (error) { + if (component) { + component.remove(); + } + }.bind(this) + ); - } + toProcess.map( - } + function(id) { - if (!runtimeComponent && __system.graphics) { - try { - runtimeComponent = fn(__system.graphics, component); - } catch (error) { + var xhr = new XMLHttpRequest(); + xhr.onload = function(__system) { + + return function () { + + var runtimeComponent = __system.createRuntimeObject.bind(__system)(this.responseText); + + if (!runtimeComponent) { + __system.failed.push(id); + return; + } + + if ( + runtimeComponent.parentEntity && + typeof runtimeComponent.parentEntity === 'string' + ) { + GameLib.EntityManager.Instance.queryComponents(GameLib.Entity).map( + function (entity) { + if (runtimeComponent.parentEntity === entity.id) { + runtimeComponent.parentEntity = entity; } } - - if (!runtimeComponent && __system.physics) { - try { - runtimeComponent = fn(__system.physics, component); - } catch (error) { - /** - * ok - we don't cannot create this component - */ - } - } - - } - - if (!runtimeComponent) { - if (clientErrorCallback) { - clientErrorCallback({ - result: 'failure', - message: 'Could not create a runtime component: ' + component.name - }); - } - //throw new Error('Could not create a runtime component: ' + component.name); - } - - if (parentEntity !== null && runtimeComponent) { - runtimeComponent.parentEntity = parentEntity; - } + ); } - } - if (runtimeComponent) { - loaded.push(runtimeComponent); - } - - if (includeDependencies && runtimeComponent) { - /** - * Before we announce the creation of this component, we should get - * a list of all dependencies of this component, because once we announce - * the creation of this component - the linking system will attempt to resolve - * all dependencies - */ - var dependencies = runtimeComponent.getDependencies(); - - /** - * 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) - * - * We don't override runtime versions of the same component in the database because the user - * could be working with it and it should be the latest version. - */ - - dependencies = dependencies.reduce( - function (result, dependency) { - - if (GameLib.EntityManager.Instance.findComponentById(dependency)) { - /** - * Don't add the dependency - */ - } else { - 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, component) { - if (component.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); - } - } - ) - } - - /** - * 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 components, 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 (runtimeComponent && onComponentLoaded) { - onComponentLoaded(runtimeComponent); - } - - if (runtimeComponent) { GameLib.Event.Emit( GameLib.Event.COMPONENT_CREATED, { component: runtimeComponent } ); + + __system.loaded.push(runtimeComponent.id); + + if (includeDependencies) { + + /** + * Before we announce the creation of this component, we should get + * a list of all dependencies of this component, because once we announce + * the creation of this component - the linking system will attempt to resolve + * all dependencies + */ + var dependencies = runtimeComponent.getDependencies(); + + __system.otherDependencies.map( + function(id) { + + var index = dependencies.indexOf(id); + + if (index !== -1) { + dependencies.splice(index, 1); + } + } + ); + + dependencies.map( + function(id) { + GameLib.Utils.PushUnique(this.otherDependencies, id); + }.bind(__system) + ); + + /** + * Don't try to download failed components again + */ + dependencies = dependencies.reduce( + function(result, id) { + if (__system.failed.indexOf(id) === -1) { + result.push(id); + } else { + console.log('ignoring failed component : ' + id); + } + return result; + }.bind(__system), + [] + ); + + /** + * 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) + * + * We don't override runtime versions of the same component in the database because the user + * could be working with it and it should be the latest version. + */ + dependencies = dependencies.reduce( + + function (result, dependency) { + + if (GameLib.EntityManager.Instance.findComponentById(dependency)) { + /** + * Don't add the dependency + */ + } else { + result.push(dependency); + } + + return result; + }, + [] + ); + + /** + * Also check if this dependency is not already in our loaded + */ + dependencies = dependencies.reduce( + function (result, dependency) { + + if (__system.loaded.indexOf(dependency) === -1) { + 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) { + GameLib.Utils.PushUnique(__system.loading, dependency); + } + ); + + __system.loadComponent(apiUrl, dependencies, includeDependencies, clientCallback, clientErrorCallback); + + GameLib.Event.Emit( + GameLib.Event.LOAD_PROGRESS, + { + loaded : __system.loaded.length, + toProcess : dependencies.length + } + ); + } + + // GameLib.Event.Emit( + // GameLib.Event.COMPONENT_DOWNLOAD_COMPLETE, + // { + // loaded: __system.loaded + // } + // ); + + if (__system.onComponentLoaded) { + __system.onComponentLoaded(runtimeComponent); + } + } - var toProcess = GameLib.Utils.Difference(loaded.map(function(component){return component.id}), loading); + }(this); - if (!runtimeComponent) { - /** - * Make sure we don't try to download this component again, it failed - */ - var index = toProcess.indexOf(component.id); + xhr.onprogress = function(__id) { + return function (progressEvent) { - if (index !== -1) { - toProcess.splice(index, 1); + var progress = 0; + + if (progressEvent.total !== 0) { + progress = Math.round(Number(progressEvent.loaded / progressEvent.total) * 100); } - } - GameLib.Event.Emit( - GameLib.Event.LOAD_PROGRESS, - { - loaded : loaded.length, - toProcess : toProcess.length + if (this.onComponentProgress) { + this.onComponentProgress(__id, progress) } - ); + }.bind(this); + }(id); - if (toProcess.length === 0) { + xhr.onerror = function(__id) { + return function (error) { + console.warn('component load failed for component ID ' + __id); - if (clientCallback) { - clientCallback({ - components : loaded + if (this.onComponentError) { + this.onComponentError(__id, error) + } + + if (clientErrorCallback) { + clientErrorCallback({ + message : 'xhr request failure' }) } + }.bind(this); + }(id); - GameLib.Event.Emit( - GameLib.Event.COMPONENT_DOWNLOAD_COMPLETE, - { - loaded: loaded - } - ) - } else { - download.bind(__system)(toProcess.pop(), parentEntity); - } + xhr.open( + 'GET', + apiUrl + '/component/load/' + id + ); - }; - }(this); + xhr.send(); - xhr.onprogress = function(__id) { - return function (progressEvent) { + }.bind(this) - var progress = 0; + ); - if (progressEvent.total !== 0) { - progress = Math.round(Number(progressEvent.loaded / progressEvent.total) * 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) - } - - if (clientErrorCallback) { - clientErrorCallback({ - message : 'xhr request failure' - }) - } - - }; - }(id); - - xhr.open( - 'GET', - apiUrl + '/component/load/' + id - ); - - xhr.send(); - }.bind(this); }; @@ -689,7 +681,7 @@ GameLib.System.Storage.prototype.load = function(data, clientCallback, clientErr data.includeDependencies, clientCallback, clientErrorCallback - )(data.ids[0], null); + ); } else { console.log('No components selected'); }