diff --git a/src/game-lib-system-storage.js b/src/game-lib-system-storage.js index 377628c..6136158 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,327 @@ 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); - - if (component) { - component.remove(); - } - } - ); - - return function download(id, parentEntity) { - - var onComponentLoaded = this.onComponentLoaded; - - var onComponentProgress = this.onComponentProgress; - - var onComponentError = this.onComponentError; - - var xhr = new XMLHttpRequest(); - - xhr.onload = function(__system) { - - return function () { - - var error = false; - - try { - var object = JSON.parse(this.responseText); - } catch (errorObject) { - - 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 (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) { - - /** - * 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 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); - runtimeComponent.parentEntity = parentEntity; - parentEntity = runtimeComponent; - } else { - - try { - - runtimeComponent = fn(component); - - } catch (error) { - - if (__system.coder) { - - try { - runtimeComponent = fn(__system.coder, component); - } catch (error) { - - } - - } - - if (!runtimeComponent && __system.graphics) { - try { - runtimeComponent = fn(__system.graphics, component); - } catch (error) { - - } - } - - 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 - } - ); - } - - var toProcess = GameLib.Utils.Difference(loaded.map(function(component){return component.id}), loading); - - if (!runtimeComponent) { - /** - * Make sure we don't try to download this component again, it failed - */ - var index = toProcess.indexOf(component.id); - - if (index !== -1) { - toProcess.splice(index, 1); - } - } - - GameLib.Event.Emit( - GameLib.Event.LOAD_PROGRESS, - { - loaded : loaded.length, - toProcess : toProcess.length - } - ); - - if (toProcess.length === 0) { - - if (clientCallback) { - clientCallback({ - components : loaded - }) - } - - GameLib.Event.Emit( - GameLib.Event.COMPONENT_DOWNLOAD_COMPLETE, - { - loaded: loaded - } - ) - } else { - download.bind(__system)(toProcess.pop(), parentEntity); - } - - }; - }(this); - - xhr.onprogress = function(__id) { - return function (progressEvent) { - - 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 + GameLib.Event.Emit( + GameLib.Event.LOAD_COMPONENT_ERROR, + {error: errorObject} ); - xhr.send(); - }.bind(this); + return null; + } + + if (object.result !== 'success') { + + if (this.onComponentError) { + this.onComponentError(id, object); + } + + if (clientErrorCallback) { + clientErrorCallback({ + message : object.message || 'Server load error' + }) + } + + GameLib.Event.Emit( + GameLib.Event.LOAD_COMPONENT_ERROR, + {error : object} + ); + + return null; + } + + var runtimeComponent = null; + + /** + * 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 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 { + runtimeComponent = fn(this.coder, component); + } catch (error) { + + } + + } + + if (!runtimeComponent && this.graphics) { + try { + runtimeComponent = fn(this.graphics, component); + } catch (error) { + + } + } + + if (!runtimeComponent && this.physics) { + try { + runtimeComponent = fn(this.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 + }); + } + } + + } + + return runtimeComponent; +}; + +GameLib.System.Storage.prototype.loadComponent = function(apiUrl, toProcess, includeDependencies, clientCallback, clientErrorCallback) { + + /** + * 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) { + + GameLib.Utils.PushUnique(this.loading, id); + + var component = GameLib.EntityManager.Instance.findComponentById(id); + + if (component) { + component.remove(); + } + }.bind(this) + ); + + this.loading = this.loading.reduce( + + function(result, id) { + + 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; + } + + __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(); + } + } + ); + + 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 (this.loaded.indexOf(dependency) === -1) { + result.push(dependency); + } + + return result; + }.bind(__system), + [] + ); + + /** + * We should now check our 'loading' list and add all dependencies which are not already in there + */ + dependencies.map( + function (dependency) { + GameLib.Utils.PushUnique(this.loading, dependency); + }.bind(__system) + ); + + if (__system.onComponentLoaded) { + __system.onComponentLoaded(runtimeComponent); + } + + GameLib.Event.Emit( + GameLib.Event.COMPONENT_CREATED, + { + component: runtimeComponent + } + ); + + GameLib.Event.Emit( + GameLib.Event.LOAD_PROGRESS, + { + loaded : __system.loaded.length, + toProcess : dependencies.length + } + ); + + __system.loadComponent(apiUrl, dependencies,includeDependencies, clientCallback, clientErrorCallback); + + // GameLib.Event.Emit( + // GameLib.Event.COMPONENT_DOWNLOAD_COMPLETE, + // { + // loaded: __system.loaded + // } + // ) + } + } + + }(this); + + xhr.onprogress = function(__id) { + return function (progressEvent) { + + var progress = 0; + + if (progressEvent.total !== 0) { + progress = Math.round(Number(progressEvent.loaded / progressEvent.total) * 100); + } + + if (this.onComponentProgress) { + this.onComponentProgress(__id, progress) + } + }.bind(this); + }(id); + + xhr.onerror = function(__id) { + return function (error) { + console.warn('component load failed for component ID ' + __id); + + if (this.onComponentError) { + this.onComponentError(__id, error) + } + + if (clientErrorCallback) { + clientErrorCallback({ + message : 'xhr request failure' + }) + } + }.bind(this); + }(id); + + xhr.open( + 'GET', + apiUrl + '/component/load/' + id + ); + + xhr.send(); + + return result; + }.bind(this), + [] + ); + }; @@ -689,7 +668,7 @@ GameLib.System.Storage.prototype.load = function(data, clientCallback, clientErr data.includeDependencies, clientCallback, clientErrorCallback - )(data.ids[0], null); + ); } else { console.log('No components selected'); }