From 70a75863abc911107aa878726ed1db7fd7da59b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Tue, 3 Feb 2015 22:23:32 +0100 Subject: [PATCH] Rudimentary multi pass dependency resolution for view models Largely based on a patch by @mrbeam - big thanks for that! Conflicts: src/octoprint/static/js/app/main.js --- src/octoprint/static/js/app/main.js | 119 ++++++++++++++++++---------- 1 file changed, 76 insertions(+), 43 deletions(-) diff --git a/src/octoprint/static/js/app/main.js b/src/octoprint/static/js/app/main.js index 228a4aa4..e2e0da18 100644 --- a/src/octoprint/static/js/app/main.js +++ b/src/octoprint/static/js/app/main.js @@ -77,6 +77,7 @@ $(function() { var logViewModel = new LogViewModel(loginStateViewModel); + // the view model map is our basic look up table for dependencies that may be injected into other view models var viewModelMap = { loginStateViewModel: loginStateViewModel, printerProfilesViewModel: printerProfilesViewModel, @@ -96,53 +97,85 @@ $(function() { slicingViewModel: slicingViewModel, }; - var _createViewModelInstance = function(viewModel, viewModelMap){ - var viewModelClass = viewModel[0]; + // helper to create a view model instance with injected constructor parameters from the view model map + var _createViewModelInstance = function(viewModel, viewModelMap){ + var viewModelClass = viewModel[0]; var viewModelParameters = viewModel[1]; - var constructorParameters = []; - for (var idx = 0; idx < viewModelParameters.length; idx++) { - var parameter = viewModelParameters[idx]; + // now we'll try to resolve all of the view model's constructor parameters via our view model map + var constructorParameters = _.map(viewModelParameters, function(parameter){ + return viewModelMap[parameter] + }); - if (_.has(viewModelMap, parameter)) { - constructorParameters.push(viewModelMap[parameter]); - } else { - console.warn("postponing", viewModel[0].name, 'missing param: ', parameter); - return; - } - } - var viewModelInstance = new viewModelClass(constructorParameters); - return viewModelInstance; - }; - - var _getViewModelId = function(viewModel){ - var name = viewModel[0].name; - return name.substr(0, 1).toLowerCase() + name.substr(1); - }; - - var vmtmp = ADDITIONAL_VIEWMODELS.slice(); + if (_.some(constructorParameters, function(parameter) { return parameter === undefined; })) { + var _extractName = function(entry) { return entry[0]; }; + var _onlyUnresolved = function(entry) { return entry[1] === undefined; }; + var missingParameters = _.map(_.filter(_.zip(viewModelParameters, constructorParameters), _onlyUnresolved), _extractName); + console.log("postponing", viewModel[0].name, "due to missing parameters:", missingParameters.join(", ")); + return; + } + + // if we came this far then we could resolve all constructor parameters, so let's construct that view model + return new viewModelClass(constructorParameters); + }; + + // helper for translating the name of a view model class into an identifier for the view model map + var _getViewModelId = function(viewModel){ + var name = viewModel[0].name; + return name.substr(0, 1).toLowerCase() + name.substr(1); // FooBarViewModel => fooBarViewModel + }; + + // instantiation loop, will make multiple passes over the list of unprocessed view models until all + // view models have been successfully instantiated with all of their dependencies or no changes can be made + // any more which means not all view models can be instantiated due to missing dependencies + var unprocessedViewModels = ADDITIONAL_VIEWMODELS.slice(); var additionalViewModels = []; - var attempt = 0; - while(vmtmp.length > 0 && attempt < 3){ - while(vmtmp.length > 0){ - var viewModel = vmtmp.shift(); - var viewModelInstance = _createViewModelInstance(viewModel, viewModelMap); - if(viewModelInstance !== undefined){ - var viewModelBindTarget = viewModel[2]; - var viewModelId = _getViewModelId(viewModel); - if(viewModelMap[viewModelId] !== undefined){ - console.error("Duplicate class name while instantiating viewModel ", viewModelId); - } else { - additionalViewModels.push([viewModelInstance, viewModelBindTarget]); - viewModelMap[viewModelId] = viewModelInstance; - } - } else { - vmtmp.push(viewModel); - } - } - attempt++; - } - + var pass = 1; + while (unprocessedViewModels.length > 0) { + console.log("View model dependency resolution, pass #" + pass++); + var startLength = unprocessedViewModels.length; + var postponed = []; + + // now try to instantiate every one of our as of yet unprocessed view model descriptors + while (unprocessedViewModels.length > 0){ + var viewModel = unprocessedViewModels.shift(); + var viewModelId = _getViewModelId(viewModel); + + // make sure that we don't have to view models going by the same name + if (_.has(viewModelMap, viewModelId)) { + console.error("Duplicate class name while instantiating viewModel ", viewModelId); + continue; + } + + var viewModelInstance = _createViewModelInstance(viewModel, viewModelMap); + + // our view model couldn't yet be instantiated, so postpone it for a bit + if (viewModelInstance === undefined) { + postponed.push(viewModel); + continue; + } + + // we could resolve the depdendencies and the view model is not defined yet => add it, it's now fully processed + var viewModelBindTarget = viewModel[2]; + additionalViewModels.push([viewModelInstance, viewModelBindTarget]); + viewModelMap[viewModelId] = viewModelInstance; + } + + // anything that's now in the postponed list has to be readded to the unprocessedViewModels + unprocessedViewModels = unprocessedViewModels.concat(postponed); + + // if we still have the same amount of items in our list of unprocessed view models it means that we + // couldn't instantiate any more view models over a whole iteration, which in turn mean we can't resolve the + // dependencies of remaining ones, so log that as an error and then quit the loop + if (unprocessedViewModels.length == startLength) { + console.error("Could not instantiate the following view models due to unresolvable dependencies:"); + _.each(unprocessedViewModels, function(entry) { + console.error(entry[0].name, "(missing:", _.filter(entry[1], function(id) { return !_.has(viewModelMap, id); }).join(", "), ")"); + }); + break; + } + } + console.log("View model dependency resolution done"); var allViewModels = _.values(viewModelMap); var dataUpdater = new DataUpdater(allViewModels);