make[1]: Entering directory `/home/bill/dev/schedule'
Line | Hits | Source |
---|---|---|
1 | // indexOf compares searchElement to elements of the Array using strict | |
2 | // equality (the same method used by the ===, or triple-equals, operator). | |
3 | // | |
4 | // https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/indexOf | |
5 | // | |
6 | 1 | if (!Array.prototype.indexOf) { |
7 | 0 | Array.prototype.indexOf = function (searchElement /*, fromIndex */ ) { |
8 | 0 | "use strict"; |
9 | 0 | if (this == null) { |
10 | 0 | throw new TypeError(); |
11 | } | |
12 | 0 | var t = Object(this); |
13 | 0 | var len = t.length >>> 0; |
14 | 0 | if (len === 0) { |
15 | 0 | return -1; |
16 | } | |
17 | 0 | var n = 0; |
18 | 0 | if (arguments.length > 1) { |
19 | 0 | n = Number(arguments[1]); |
20 | 0 | if (n != n) { // shortcut for verifying if it's NaN |
21 | 0 | n = 0; |
22 | 0 | } else if (n != 0 && n != Infinity && n != -Infinity) { |
23 | 0 | n = (n > 0 || -1) * Math.floor(Math.abs(n)); |
24 | } | |
25 | } | |
26 | 0 | if (n >= len) { |
27 | 0 | return -1; |
28 | } | |
29 | 0 | var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0); |
30 | 0 | for (; k < len; k++) { |
31 | 0 | if (k in t && t[k] === searchElement) { |
32 | 0 | return k; |
33 | } | |
34 | } | |
35 | 0 | return -1; |
36 | } | |
37 | } |
Line | Hits | Source |
---|---|---|
1 | // Returns true if an object is an array, false if it is not. | |
2 | // | |
3 | // From https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray | |
4 | ||
5 | ||
6 | 1 | if(!Array.isArray) { |
7 | 0 | Array.isArray = function (vArg) { |
8 | 0 | return Object.prototype.toString.call(vArg) === "[object Array]"; |
9 | }; | |
10 | } |
Line | Hits | Source |
---|---|---|
1 | /** | |
2 | * Schedule create | |
3 | * (c) 2013 Bill, BunKat LLC. | |
4 | * | |
5 | * Creates a schedule for each task that respects the task schedules, task | |
6 | * dependencies, resource schedules, project schedule, and start date provided. | |
7 | * | |
8 | * Schedule is freely distributable under the MIT license. | |
9 | * For all details and documentation: | |
10 | * http://github.com/bunkat/schedule | |
11 | */ | |
12 | ||
13 | 1 | schedule.create = function(tasks, resources, sched, startDate) { |
14 | ||
15 | 22 | var taskGraph = schedule.dependencyGraph(tasks), |
16 | resMgr = schedule.resourceManager(resources, startDate), | |
17 | scheduledTasks = {}; | |
18 | ||
19 | /** | |
20 | * Main function, coordinates the process of creating a schedule. | |
21 | */ | |
22 | 22 | function generateSchedule() { |
23 | 22 | var range, failedTasks = []; |
24 | ||
25 | // add required resources not supplied in resources array, the project | |
26 | // schedule and all task schedules to the resource manager (these will | |
27 | // be treated as resources to calculate valid reservations with) | |
28 | 22 | resMgr.addResource(taskGraph.resources, '', startDate); |
29 | 22 | resMgr.addResource([{id: '_proj', schedule: sched}], '', startDate); |
30 | 22 | resMgr.addResource(tasks, '_task', startDate); |
31 | ||
32 | 22 | forwardPass(taskGraph.roots); |
33 | 22 | range = getSummary(tasks, failedTasks); |
34 | 22 | backwardPass(taskGraph.leaves, range[1]); |
35 | ||
36 | 22 | return { |
37 | scheduledTasks: scheduledTasks, | |
38 | failedTasks: failedTasks.length ? failedTasks : null, | |
39 | success: failedTasks.length === 0, | |
40 | start: range[0], | |
41 | end: range[1] | |
42 | }; | |
43 | } | |
44 | ||
45 | /** | |
46 | * Schedules each task as their dependencies are met, tracking dependency | |
47 | * end dates in the dependencies map. | |
48 | */ | |
49 | 22 | function forwardPass(roots) { |
50 | 22 | var readyTasks = roots.slice(0), |
51 | dependencies = {}; // holds count and earliest start date of dependencies | |
52 | ||
53 | 22 | for(var i = 0, len = roots.length; i < len; i++) { |
54 | 46 | dependencies[roots[i]] = [0, startDate.getTime()]; |
55 | } | |
56 | ||
57 | 22 | while(readyTasks.length) { |
58 | 69 | schedule.sort.tasks(taskGraph, readyTasks); |
59 | ||
60 | 69 | var task = taskGraph.tasks[readyTasks.pop()], |
61 | start = dependencies[task.id][1], | |
62 | end = forwardPassTask(task, start); | |
63 | ||
64 | 69 | if(end && task.requiredBy) { |
65 | 20 | updateDependencies(readyTasks, dependencies, task.requiredBy, end); |
66 | 20 | resMgr.optimize(getMinStart(dependencies)); // clean up expired exceptions |
67 | } | |
68 | } | |
69 | } | |
70 | ||
71 | /** | |
72 | * Finds the next available time that all of a tasks constraints are met and | |
73 | * makes the appropriate resource reservations. A task may be scheduled in a | |
74 | * single contiguous block or multiple blocks of time. | |
75 | */ | |
76 | 22 | function forwardPassTask(task, start) { |
77 | 69 | var resAll = ['_proj', '_task' + task.id], |
78 | resources = task.resources ? resAll.concat(task.resources) : resAll, | |
79 | duration = task.duration, | |
80 | next = start, | |
81 | scheduledTask = {schedule: [], duration: task.duration}; | |
82 | ||
83 | 69 | while(duration) { |
84 | 73 | var r = resMgr.makeReservation(resources, next, task.minSchedule || 1, duration); |
85 | 75 | if(!r.success) return undefined; |
86 | ||
87 | 71 | scheduledTask.earlyStart = scheduledTask.earlyStart || r.start; |
88 | 71 | scheduledTask.schedule.push(r); |
89 | 71 | duration -= r.duration; |
90 | 71 | next = r.end; |
91 | } | |
92 | ||
93 | 67 | scheduledTask.earlyFinish = next; |
94 | 67 | scheduledTasks[task.id] = scheduledTask; |
95 | ||
96 | 67 | return next; |
97 | } | |
98 | ||
99 | /** | |
100 | * Finds the start and end date of the schedule and adds any tasks that were | |
101 | * scheduled to the failedTasks array. | |
102 | */ | |
103 | 22 | function getSummary(tasks, failedTasks) { |
104 | 22 | var start, end; |
105 | ||
106 | 22 | for(var i = 0, len = tasks.length; i < len; i++) { |
107 | 72 | var t = scheduledTasks[tasks[i].id]; |
108 | 72 | if(t) { |
109 | 67 | start = !start || t.earlyStart < start ? t.earlyStart : start; |
110 | 67 | end = !end || t.earlyFinish > end ? t.earlyFinish : end; |
111 | } | |
112 | else { | |
113 | 5 | failedTasks.push(tasks[i].id); |
114 | } | |
115 | } | |
116 | ||
117 | 22 | return [start, end]; |
118 | } | |
119 | ||
120 | /** | |
121 | * As tasks are scheduled, the information is tracked in the dependencies | |
122 | * array. As a tasks dependencies are all met, the task is pushed onto the | |
123 | * readyTasks array which means it is available to be scheduled. | |
124 | */ | |
125 | 22 | function updateDependencies(readyTasks, dependencies, tasks, end) { |
126 | 20 | for(var i = 0, len = tasks.length; i < len; i++) { |
127 | 34 | var tid = tasks[i], |
128 | dependsOn = taskGraph.tasks[tid].dependsOn, | |
129 | metDeps = dependencies[tid] || (dependencies[tid] = [0, 0]); | |
130 | ||
131 | 34 | metDeps[0] += 1; |
132 | 34 | metDeps[1] = end > metDeps[1] ? end : metDeps[1]; |
133 | ||
134 | 34 | if(!dependsOn || metDeps[0] >= dependsOn.length) { |
135 | 23 | readyTasks.push(tid); |
136 | } | |
137 | } | |
138 | } | |
139 | ||
140 | /** | |
141 | * Finds the earliest time that any of the remaining tasks could be scheduled | |
142 | * for. It is used to optimize the resource manager since nothing can be | |
143 | * scheduled before this time. | |
144 | */ | |
145 | 22 | function getMinStart(dependencies) { |
146 | 20 | var min; |
147 | 20 | for(var id in dependencies) { |
148 | 89 | if(!min || min > dependencies[id][1]) { |
149 | 20 | min = dependencies[id][1]; |
150 | } | |
151 | } | |
152 | 20 | return min; |
153 | } | |
154 | ||
155 | /** | |
156 | * Calculates when a task must be completed by before it ends up slipping | |
157 | * one of its dependencies or the schedule. Tasks with zero float amount | |
158 | * are in the critical path. | |
159 | */ | |
160 | 22 | function backwardPass(tasks, finishDate) { |
161 | 59 | for(var i = 0, len = tasks.length; i < len; i++) { |
162 | 114 | var sTask = scheduledTasks[tasks[i]], |
163 | dependsOn = taskGraph.tasks[tasks[i]].dependsOn; | |
164 | ||
165 | 114 | if(sTask) { |
166 | 112 | sTask.lateFinish = finishDate; |
167 | 112 | sTask.floatAmt = (sTask.lateFinish - sTask.earlyFinish) / later.MIN; |
168 | ||
169 | 112 | if(dependsOn) { |
170 | 37 | backwardPass(dependsOn, sTask.earlyStart); |
171 | } | |
172 | } | |
173 | } | |
174 | } | |
175 | ||
176 | 22 | return generateSchedule(); |
177 | }; |
Line | Hits | Source |
---|---|---|
1 | /** | |
2 | * Dependency graph | |
3 | * (c) 2013 Bill, BunKat LLC. | |
4 | * | |
5 | * Generates a dependency graph from a set of tasks and finds the root nodes, | |
6 | * leaf nodes, depth, and optimistic float (time between when a schedule starts | |
7 | * and when it must start to prevent a schedule slip). This information is used | |
8 | * by the schedule generator to schedule tasks against an actual timeline. | |
9 | * | |
10 | * Schedule is freely distributable under the MIT license. | |
11 | * For all details and documentation: | |
12 | * http://github.com/bunkat/schedule | |
13 | */ | |
14 | ||
15 | 1 | schedule.dependencyGraph = function(taskArr) { |
16 | ||
17 | /** | |
18 | * Starting point for creating the dependency graph, clones the tasks and | |
19 | * then fills out the graph properties. | |
20 | */ | |
21 | 33 | function createDependencyGraph(tasks) { |
22 | 33 | var graph = { |
23 | tasks: {}, | |
24 | roots: [], | |
25 | leaves: [], | |
26 | resources: [], | |
27 | depth: 0, | |
28 | end : 0 | |
29 | }; | |
30 | ||
31 | 33 | for(var i = 0, len = tasks.length; i < len; i++) { |
32 | 107 | var t = tasks[i]; |
33 | 107 | graph.tasks[t.id] = { |
34 | id: t.id, | |
35 | duration: t.duration, | |
36 | priority: t.priority, | |
37 | schedule: t.schedule, | |
38 | minSchedule: t.minSchedule, | |
39 | dependsOn: t.dependsOn, | |
40 | resources: t.resources | |
41 | }; | |
42 | } | |
43 | ||
44 | 33 | setResources(graph); |
45 | 33 | setRequiredBy(graph.tasks); |
46 | 33 | setRootsAndLeaves(graph); |
47 | ||
48 | 33 | setDepth(graph, graph.leaves, 0); |
49 | 33 | graph.depth += 1; // increment depth so it is 1 based |
50 | ||
51 | 33 | forwardPass(graph, {}, graph.roots, 0); |
52 | 33 | setEnd(graph, graph.leaves); |
53 | 33 | backwardPass(graph, {}, graph.leaves, graph.end); |
54 | ||
55 | 33 | return graph; |
56 | } | |
57 | ||
58 | /** | |
59 | * Creates an array of all the unique resources that are used by the tasks. | |
60 | */ | |
61 | 33 | function setResources(graph) { |
62 | 33 | for(var id in graph.tasks) { |
63 | 107 | var task = graph.tasks[id]; |
64 | 107 | if(!isEmpty(task.resources)) { |
65 | 38 | for(var i = 0, len = task.resources.length; i < len; i++) { |
66 | 40 | var resId = task.resources[i]; |
67 | 40 | if(graph.resources.indexOf(resId) === -1) { |
68 | 13 | graph.resources.push(resId); |
69 | } | |
70 | } | |
71 | } | |
72 | } | |
73 | } | |
74 | ||
75 | /** | |
76 | * Creates the back links from child to parent based on the dependsOn property. | |
77 | */ | |
78 | 33 | function setRequiredBy(tasks) { |
79 | 33 | for(var id in tasks) { |
80 | 107 | var child = tasks[id], |
81 | dependsOn = child.dependsOn; | |
82 | ||
83 | 107 | if(!isEmpty(dependsOn)) { |
84 | 48 | for(var i = 0, len = dependsOn.length; i < len; i++) { |
85 | 63 | var parent = tasks[dependsOn[i]]; |
86 | 63 | (parent.requiredBy || (parent.requiredBy = [])).push(child.id); |
87 | } | |
88 | } | |
89 | } | |
90 | } | |
91 | ||
92 | /** | |
93 | * Finds the roots and leaves of the dependency graph. | |
94 | */ | |
95 | 33 | function setRootsAndLeaves(graph) { |
96 | 33 | for(var id in graph.tasks) { |
97 | 107 | var task = graph.tasks[id]; |
98 | ||
99 | 107 | if(isEmpty(task.dependsOn)) { |
100 | 59 | graph.roots.push(task.id); |
101 | } | |
102 | ||
103 | 107 | if(isEmpty(task.requiredBy)) { |
104 | 66 | graph.leaves.push(task.id); |
105 | } | |
106 | } | |
107 | } | |
108 | ||
109 | /** | |
110 | * Determines the depth (maximum number of nodes that depend on the current | |
111 | * node) of each node in the dependency graph. | |
112 | */ | |
113 | 33 | function setDepth(graph, tasks, depth) { |
114 | 96 | for(var i = 0, len = tasks.length; i < len; i++) { |
115 | 162 | var task = graph.tasks[tasks[i]], |
116 | dependsOn = task.dependsOn; | |
117 | ||
118 | 162 | task.depth = !task.depth || depth > task.depth ? depth : task.depth; |
119 | 162 | graph.depth = depth > graph.depth ? depth : graph.depth; |
120 | ||
121 | 162 | if(!isEmpty(dependsOn)) { |
122 | 63 | setDepth(graph, dependsOn, task.depth + 1); |
123 | } | |
124 | } | |
125 | } | |
126 | ||
127 | /** | |
128 | * Generates an optimistic (assume all resources are available when needed) | |
129 | * forward schedule for each node in the graph, respecting node dependencies. | |
130 | */ | |
131 | 33 | function forwardPass(graph, depEnds, tasks, start) { |
132 | 71 | updateDependencies(depEnds, tasks, start); |
133 | 71 | for(var i = 0, len = tasks.length; i < len; i++) { |
134 | 119 | var tid = tasks[i], |
135 | task = graph.tasks[tid], | |
136 | dependsOn = task.dependsOn, | |
137 | dep = depEnds[tid]; | |
138 | ||
139 | 119 | if(!task.earlyFinish && (isEmpty(dependsOn) || (dep && dep[0] === dependsOn.length))) { |
140 | 104 | task.earlyStart = dep[1]; |
141 | 104 | task.earlyFinish = dep[1] + task.duration; |
142 | ||
143 | 104 | if(!isEmpty(task.requiredBy)) { |
144 | 38 | forwardPass(graph, depEnds, task.requiredBy, task.earlyFinish); |
145 | } | |
146 | } | |
147 | } | |
148 | } | |
149 | ||
150 | /** | |
151 | * Finds the end of the optimistic forward pass schedule. | |
152 | */ | |
153 | 33 | function setEnd(graph, tasks) { |
154 | 33 | for(var i = 0, len = tasks.length; i < len; i++) { |
155 | 66 | var finish = graph.tasks[tasks[i]].earlyFinish; |
156 | 66 | graph.end = finish > graph.end ? finish : graph.end; |
157 | } | |
158 | } | |
159 | ||
160 | /** | |
161 | * Generates an optimistic (assume all resources are available when needed) | |
162 | * backward schedule for each node in the graph, respecting node dependencies. | |
163 | * Computes the float (time between earliest finish and latest finish). | |
164 | */ | |
165 | 33 | function backwardPass(graph, depEnds, tasks, end) { |
166 | 78 | updateDependencies(depEnds, tasks, end, true); |
167 | 78 | for(var i = 0, len = tasks.length; i < len; i++) { |
168 | 125 | var tid = tasks[i], |
169 | task = graph.tasks[tid], | |
170 | requiredBy = task.requiredBy, | |
171 | dep = depEnds[tid]; | |
172 | ||
173 | 125 | if(isEmpty(requiredBy) || (dep && dep[0] === requiredBy.length)) { |
174 | 103 | task.lateStart = dep[1] - task.duration; |
175 | 103 | task.lateFinish = dep[1]; |
176 | 103 | task.floatAmt = task.lateFinish - task.earlyFinish; |
177 | ||
178 | 103 | if(!isEmpty(task.dependsOn)) { |
179 | 45 | backwardPass(graph, depEnds, task.dependsOn, task.lateStart); |
180 | } | |
181 | } | |
182 | } | |
183 | } | |
184 | ||
185 | /** | |
186 | * Tracks dependencies between nodes to ensure nodes are only scheduled once | |
187 | * their dependencies have completed. | |
188 | */ | |
189 | 33 | function updateDependencies(deps, tasks, start, rev) { |
190 | 171 | var compare = rev ? function(a,b) { return b > a; } : |
191 | 14 | function(a,b) { return a > b; }; |
192 | ||
193 | 149 | for(var i = 0, len = tasks.length; i < len; i++) { |
194 | 244 | var id = tasks[i]; |
195 | ||
196 | 244 | if(deps[id]) { |
197 | 36 | deps[id][0] = deps[id][0] + 1; |
198 | 36 | deps[id][1] = compare(start, deps[id][1]) ? start : deps[id][1]; |
199 | } | |
200 | else { | |
201 | 208 | deps[id] = [1, start]; |
202 | } | |
203 | } | |
204 | } | |
205 | ||
206 | /** | |
207 | * Returns true if the array is undefined or empty. | |
208 | */ | |
209 | 33 | function isEmpty(arr) { |
210 | 1041 | return !arr || arr.length === 0; |
211 | } | |
212 | ||
213 | 33 | return createDependencyGraph(taskArr); |
214 | }; |
Line | Hits | Source |
---|---|---|
1 | /** | |
2 | * Resource manager | |
3 | * (c) 2013 Bill, BunKat LLC. | |
4 | * | |
5 | * Manages all of the resources and schedule constraints (project schedule, | |
6 | * task schedule, and resource schedules) and reserves resources as needed. Finds | |
7 | * the earliest time that a set of resources can be reserved. | |
8 | * | |
9 | * Schedule is freely distributable under the MIT license. | |
10 | * For all details and documentation: | |
11 | * http://github.com/bunkat/schedule | |
12 | */ | |
13 | ||
14 | 1 | schedule.resourceManager = function(resourceDefinitions, startDate) { |
15 | ||
16 | 36 | var defaultSched = {schedules: [{fd_a: [startDate.getTime()]}]}, |
17 | rMap = buildResourceMap(resourceDefinitions, startDate); | |
18 | ||
19 | /** | |
20 | * Creates a map from the resource definitions that contains the schedule | |
21 | * information for each of the resources (specifically when the resource | |
22 | * will be next available and how to calculate future availability). | |
23 | */ | |
24 | 36 | function buildResourceMap(resourceDefinitions, start) { |
25 | 36 | var map = {}; |
26 | 36 | if(resourceDefinitions) { |
27 | 35 | for(var i = 0, len = resourceDefinitions.length; i < len; i++) { |
28 | 90 | addResourceToMap(map, resourceDefinitions[i], start); |
29 | } | |
30 | } | |
31 | ||
32 | 36 | return map; |
33 | } | |
34 | ||
35 | /** | |
36 | * Adds a resource to the resource map. | |
37 | */ | |
38 | 36 | function addResourceToMap(map, def, start) { |
39 | 188 | var sched = JSON.parse(JSON.stringify(def.schedule || defaultSched)), |
40 | nextFn = schedule.memoizedRangeFn(later.schedule(sched).nextRange); | |
41 | ||
42 | 188 | map[def.id] = { schedule: sched, next: nextFn, nextAvail: nextFn(start) }; |
43 | } | |
44 | ||
45 | /** | |
46 | * Attempts to find the next time that all resources are available, starting | |
47 | * from the start time, with a duration of at least min minutes but no more | |
48 | * than max minutes. | |
49 | */ | |
50 | 36 | function getReservation(resources, start, min, max) { |
51 | 92 | var reservation, schedules = [], delays = {}; |
52 | 92 | maxTries = 50; |
53 | ||
54 | 92 | initRanges(resources, start, schedules, delays); |
55 | 92 | while(!(reservation = tryReservation(schedules, min, max)).success && --maxTries) { |
56 | 199 | updateRanges(schedules, nextValidStart(schedules), delays); |
57 | } | |
58 | ||
59 | 92 | reservation.delays = delays; |
60 | 92 | return reservation; |
61 | } | |
62 | ||
63 | /** | |
64 | * Initializes the resource schedule availablity based on the start date | |
65 | * provided. Resources that were not immediately available are captured in | |
66 | * the delays array to be reported with the reservation. | |
67 | */ | |
68 | 36 | function initRanges(resources, start, ranges, delays) { |
69 | 115 | for(var i = 0, len = resources.length; i < len; i++) { |
70 | 282 | var resId = resources[i]; |
71 | ||
72 | // handles nested resources (OR) | |
73 | 282 | if(Array.isArray(resId)) { |
74 | 23 | var subRanges = [], subDelays = {}; |
75 | 23 | initRanges(resId, start, subRanges, subDelays); |
76 | ||
77 | 23 | var longDelay = getLongestDelay(subDelays); |
78 | 23 | if(longDelay) { |
79 | 21 | delays[longDelay] = subDelays[longDelay]; |
80 | } | |
81 | ||
82 | 23 | var schedule = {subRanges: subRanges}; |
83 | 23 | setEarliestSubRange(schedule); |
84 | 23 | ranges.push(schedule); |
85 | } | |
86 | else { | |
87 | 259 | var res = rMap[resId], |
88 | range = res.nextAvail[0] >= start ? res.nextAvail : | |
89 | res.next(start); | |
90 | ||
91 | 259 | if(range[0] > start && resId !== '_proj') { |
92 | 89 | delays[resId] = { needed: start, available: range[0] }; |
93 | } | |
94 | ||
95 | 259 | ranges.push({id: resId, range: range}); |
96 | } | |
97 | } | |
98 | } | |
99 | ||
100 | /** | |
101 | * Determines if the current schedules overlap for at least min minutes. If | |
102 | * they do, a reservation is created, otherwise a failure is reported. | |
103 | */ | |
104 | 36 | function tryReservation(schedules, min,max) { |
105 | 291 | var reservation = {success: false}, |
106 | resources = [], start, end; | |
107 | ||
108 | 291 | for(var i = 0, len = schedules.length; i < len; i++) { |
109 | 733 | var schedule = schedules[i], |
110 | range = schedule.range; | |
111 | ||
112 | 733 | if(!isInternal(schedule)) { |
113 | 239 | resources.push(schedule.id); |
114 | } | |
115 | ||
116 | 733 | start = !start || range[0] > start ? range[0] : start; |
117 | 733 | end = !end || range[1] < end ? range[1] : end; |
118 | } | |
119 | ||
120 | 291 | var duration = (end - start) / later.MIN; |
121 | 291 | if(duration >= min) { |
122 | 89 | duration = max && duration > max ? max : duration; |
123 | 89 | reservation = createReservation(resources, start, duration); |
124 | } | |
125 | ||
126 | 291 | return reservation; |
127 | } | |
128 | ||
129 | /** | |
130 | * Generates a new reservation object and reserves the associated resources. | |
131 | */ | |
132 | 36 | function createReservation(resources, start, duration) { |
133 | 89 | var end = start + (duration * later.MIN), |
134 | reservation = { | |
135 | resources: resources, | |
136 | start: start, | |
137 | end: end, | |
138 | duration: duration, | |
139 | success: true | |
140 | }; | |
141 | ||
142 | 89 | applyReservation(resources, start, end); |
143 | 89 | return reservation; |
144 | } | |
145 | ||
146 | /** | |
147 | * Updates ranges after a failed reservation attempt. Resources that were not | |
148 | * immediately available are captured in the delays array to be reported with | |
149 | * the reservation. | |
150 | */ | |
151 | 36 | function updateRanges(resources, start, delays) { |
152 | 206 | for(var i = 0, len = resources.length; i < len; i++) { |
153 | 511 | var res = resources[i]; |
154 | 789 | if(res.range[1] > start) continue; |
155 | ||
156 | 233 | if(res.subRanges) { |
157 | 7 | updateRanges(res.subRanges, start, {}); |
158 | 7 | setEarliestSubRange(res); |
159 | } | |
160 | else { | |
161 | 226 | res.range = rMap[res.id].next(start); |
162 | ||
163 | 226 | if(res.id !== '_proj' && !delays[res.id]) { |
164 | 15 | delays[res.id] = { needed: start, available: res.range[0] }; |
165 | } | |
166 | } | |
167 | } | |
168 | } | |
169 | ||
170 | /** | |
171 | * Applies a schedule reservation (by adding schedule exceptions) to any | |
172 | * reservable resources that are indicated. | |
173 | */ | |
174 | 36 | function applyReservation(resources, start, end) { |
175 | 89 | for(var i = 0, len = resources.length; i < len; i++) { |
176 | 69 | var res = rMap[resources[i]]; |
177 | ||
178 | // skip if this resource should not be reserved for single use | |
179 | 69 | if(res.isNotReservable) continue; |
180 | ||
181 | 69 | if(start !== res.nextAvail[0]) { |
182 | 48 | if(!res.schedule.exceptions) res.schedule.exceptions = []; |
183 | 38 | res.schedule.exceptions.push({fd_a: [start], fd_b: [end] }); |
184 | 38 | res.next = schedule.memoizedRangeFn(later.schedule(res.schedule).nextRange); |
185 | 38 | end = res.nextAvail[0]; |
186 | } | |
187 | ||
188 | 69 | res.nextAvail = res.next(end); |
189 | } | |
190 | } | |
191 | ||
192 | /** | |
193 | * Determines the earliest time that a schedule goes invalid which is the | |
194 | * time that should be used to update resource ranges from. | |
195 | */ | |
196 | 36 | function nextValidStart(schedules) { |
197 | 199 | var latest; |
198 | 199 | for(var i = 0, len = schedules.length; i < len; i++) { |
199 | 497 | var end = schedules[i].range[1]; |
200 | 497 | latest = !latest || end < latest ? end : latest; |
201 | } | |
202 | ||
203 | 199 | return latest; |
204 | } | |
205 | ||
206 | /** | |
207 | * Resources that are OR'd together (using a nested array) are treated as a | |
208 | * single resource with sub resources. This function determines the resource | |
209 | * that has the earliest start date which is then used for future calculations. | |
210 | */ | |
211 | 36 | function setEarliestSubRange(schedule) { |
212 | 30 | var minId, minRange; |
213 | 30 | for(var i = 0, len = schedule.subRanges.length; i < len; i++) { |
214 | 60 | var sub = schedule.subRanges[i]; |
215 | ||
216 | 60 | if(!minId || (sub.range[0] < minRange[0])) { |
217 | 40 | minId = sub.id; |
218 | 40 | minRange = sub.range; |
219 | } | |
220 | } | |
221 | ||
222 | 30 | schedule.id = minId; |
223 | 30 | schedule.range = minRange; |
224 | } | |
225 | ||
226 | /** | |
227 | * Determines the longest delay amongst a set of delays. Used to determine | |
228 | * which resource to report for resources that are OR'd together. | |
229 | */ | |
230 | 36 | function getLongestDelay(delays) { |
231 | 23 | var latest, lid; |
232 | 23 | for(var id in delays) { |
233 | 40 | var available = delays[id].available; |
234 | 40 | if(!latest || available < latest) { |
235 | 27 | latest = available; |
236 | 27 | lid = id; |
237 | } | |
238 | } | |
239 | ||
240 | 23 | return lid; |
241 | } | |
242 | ||
243 | /** | |
244 | * Returns true if resource provided is an internal (not user specified) | |
245 | * resource. | |
246 | */ | |
247 | 36 | function isInternal(resource) { |
248 | 733 | return resource.id[0] === '_'; |
249 | } | |
250 | ||
251 | 36 | return { |
252 | ||
253 | /** | |
254 | * Returns the current resource schedule state for the specified resoruce id. | |
255 | */ | |
256 | getResource: function(id) { | |
257 | 4 | return rMap[id]; |
258 | }, | |
259 | ||
260 | /** | |
261 | * Adds a new resource to the resource map if a resource doesn't already exist | |
262 | * with that id. Expects resources to be passed in as an array and will | |
263 | * prefix each resource with the prefix specified. | |
264 | */ | |
265 | addResource: function(arr, prefix, start) { | |
266 | 66 | for(var i = 0, len = arr.length; i < len; i++) { |
267 | 107 | var def = typeof arr[i] !== 'object' ? |
268 | { id: prefix + arr[i] } : | |
269 | { id: prefix + arr[i].id, schedule: arr[i].schedule, isNotReservable: arr[i].isNotReservable }; | |
270 | ||
271 | 107 | if(!rMap[def.id]) { |
272 | 98 | addResourceToMap(rMap, def, start); |
273 | } | |
274 | } | |
275 | }, | |
276 | ||
277 | /** | |
278 | * Attempts to reserve the set of resources at the earliest possible time from | |
279 | * start time provide with a duration of at least min and no more than max | |
280 | * minutes. | |
281 | */ | |
282 | makeReservation: function(resources, start, min, max) { | |
283 | 92 | start = start ? new Date(start) : new Date(); |
284 | 92 | return getReservation(resources, start.getTime(), min || 1, max); |
285 | }, | |
286 | ||
287 | /** | |
288 | * Optimizes the resource schedules by eliminating schedule reservations that | |
289 | * occur before the start date provided (i.e. ones that can never occur | |
290 | * again). | |
291 | */ | |
292 | optimize: function(start) { | |
293 | 23 | for(var id in rMap) { |
294 | 174 | var res = rMap[id]; |
295 | ||
296 | 174 | if(res.schedule.exceptions) { |
297 | 28 | var curExceptions = res.schedule.exceptions; |
298 | 28 | res.schedule.exceptions = []; |
299 | ||
300 | 28 | for(var i = 0, len = curExceptions.length; i < len; i++) { |
301 | 33 | if(!curExceptions[i].fd_b || curExceptions[i].fd_b > start) { |
302 | 26 | res.schedule.exceptions.push(curExceptions[i]); |
303 | } | |
304 | } | |
305 | 28 | res.next = schedule.memoizedRangeFn(later.schedule(res.schedule).nextRange); |
306 | } | |
307 | ||
308 | 174 | if(res.nextAvail[0] < start) { |
309 | 18 | res.nextAvail = res.next(start); |
310 | } | |
311 | } | |
312 | } | |
313 | }; | |
314 | ||
315 | }; |
Line | Hits | Source |
---|---|---|
1 | /** | |
2 | * Resources | |
3 | * (c) 2013 Bill, BunKat LLC. | |
4 | * | |
5 | * Takes an array of objects and generates an array of valid schedule resources | |
6 | * objects. | |
7 | * | |
8 | * Schedule is freely distributable under the MIT license. | |
9 | * For all details and documentation: | |
10 | * http://github.com/bunkat/schedule | |
11 | */ | |
12 | ||
13 | 1 | schedule.resources = function() { |
14 | 9 | var id = resourcesId, |
15 | sched = resourcesSched, | |
16 | isNotReservable = resourcesIsNotReservable; | |
17 | ||
18 | /** | |
19 | * Takes an array of objects and returns an array of schedule resource objects. | |
20 | */ | |
21 | 9 | function resources(data) { |
22 | 6 | var items = [], |
23 | fid = schedule.functor(id), | |
24 | fsched = schedule.functor(sched), | |
25 | freserve = schedule.functor(isNotReservable); | |
26 | ||
27 | 6 | for(var i = 0, len = data.length; i < len; i++) { |
28 | 16 | var resource = data[i], |
29 | rId = fid.call(this, resource, i), | |
30 | rSched = fsched.call(this, resource, i), | |
31 | rReserve = freserve.call(this, resource, i); | |
32 | ||
33 | 16 | items.push({id: rId, schedule: rSched, isNotReservable: rReserve}); |
34 | } | |
35 | ||
36 | 6 | return items; |
37 | } | |
38 | ||
39 | /** | |
40 | * The function or value that should be used to generate the resource id. Sets the | |
41 | * value to the argument passed in, returns current value if no arguments are | |
42 | * passed in. | |
43 | */ | |
44 | 9 | resources.id = function(_) { |
45 | 3 | if (!arguments.length) return id; |
46 | 3 | id = _; |
47 | 3 | return resources; |
48 | }; | |
49 | ||
50 | /** | |
51 | * The function or value that should be used to generate the resource schedule. The | |
52 | * schedule must be a valid Later.js schedule. Sets the value to the argument | |
53 | * passed in, returns current value if no arguments are passed in. | |
54 | */ | |
55 | 9 | resources.schedule = function(_) { |
56 | 3 | if (!arguments.length) return sched; |
57 | 3 | sched = _; |
58 | 3 | return resources; |
59 | }; | |
60 | ||
61 | /** | |
62 | * The function or value that should be used to generate the resource is not | |
63 | * reservable value. Sets the value to the argument passed in, returns current | |
64 | * value if no arguments are passed in. | |
65 | */ | |
66 | 9 | resources.isNotReservable = function(_) { |
67 | 1 | if (!arguments.length) return isNotReservable; |
68 | 1 | isNotReservable = _; |
69 | 1 | return resources; |
70 | }; | |
71 | ||
72 | 9 | return resources; |
73 | }; | |
74 | ||
75 | /** | |
76 | * The default id function. | |
77 | */ | |
78 | 1 | function resourcesId(d) { |
79 | 9 | return d.id; |
80 | } | |
81 | ||
82 | /** | |
83 | * The default schedule function. | |
84 | */ | |
85 | 1 | function resourcesSched(d) { |
86 | 9 | return d.schedule; |
87 | } | |
88 | ||
89 | /** | |
90 | * The default is not reservable function. | |
91 | */ | |
92 | 1 | function resourcesIsNotReservable(d) { |
93 | 13 | return d.isNotReservable || false; |
94 | } |
Line | Hits | Source |
---|---|---|
1 | /** | |
2 | * Tasks | |
3 | * (c) 2013 Bill, BunKat LLC. | |
4 | * | |
5 | * Takes an array of objects and generates an of array valid schedule task objects. | |
6 | * | |
7 | * Schedule is freely distributable under the MIT license. | |
8 | * For all details and documentation: | |
9 | * http://github.com/bunkat/schedule | |
10 | */ | |
11 | ||
12 | 1 | schedule.tasks = function() { |
13 | 9 | var id = tasksId, |
14 | duration = tasksDuration, | |
15 | sched = tasksSched, | |
16 | resources = tasksResources, | |
17 | dependsOn = tasksDependsOn, | |
18 | minSchedule = tasksMinSchedule, | |
19 | priority = tasksPriority; | |
20 | ||
21 | /** | |
22 | * Takes an array of objects and returns an array of schedule task objects. | |
23 | */ | |
24 | 9 | function tasks(data) { |
25 | 3 | var items = [], |
26 | fid = schedule.functor(id), | |
27 | fduration = schedule.functor(duration), | |
28 | fsched = schedule.functor(sched), | |
29 | fresources = schedule.functor(resources), | |
30 | fdependsOn = schedule.functor(dependsOn), | |
31 | fminschedule = schedule.functor(minSchedule), | |
32 | fpriority = schedule.functor(priority); | |
33 | ||
34 | 3 | for(var i = 0, len = data.length; i < len; i++) { |
35 | 32 | var task = data[i], |
36 | item = { | |
37 | id: fid.call(this, task, i), | |
38 | duration: fduration.call(this, task, i), | |
39 | schedule: fsched.call(this, task, i), | |
40 | resources: fresources.call(this, task, i), | |
41 | dependsOn: fdependsOn.call(this, task, i), | |
42 | minSchedule: fminschedule.call(this, task, i), | |
43 | priority: fpriority.call(this, task, i) | |
44 | }; | |
45 | ||
46 | 32 | items.push(item); |
47 | } | |
48 | ||
49 | 3 | return items; |
50 | } | |
51 | ||
52 | /** | |
53 | * The function or value that should be used to generate the task id. Sets the | |
54 | * value to the argument passed in, returns current value if no arguments are | |
55 | * passed in. | |
56 | */ | |
57 | 9 | tasks.id = function(_) { |
58 | 2 | if (!arguments.length) return id; |
59 | 2 | id = _; |
60 | 2 | return tasks; |
61 | }; | |
62 | ||
63 | /** | |
64 | * The function or value that should be used to generate the task duration. Sets the | |
65 | * value to the argument passed in, returns current value if no arguments are | |
66 | * passed in. | |
67 | */ | |
68 | 9 | tasks.duration = function(_) { |
69 | 2 | if (!arguments.length) return duration; |
70 | 2 | duration = _; |
71 | 2 | return tasks; |
72 | }; | |
73 | ||
74 | /** | |
75 | * The function or value that should be used to generate the task schedule. The | |
76 | * schedule must be a valid Later.js schedule. Sets the value to the argument | |
77 | * passed in, returns current value if no arguments are passed in. | |
78 | */ | |
79 | 9 | tasks.schedule = function(_) { |
80 | 2 | if (!arguments.length) return sched; |
81 | 2 | sched = _; |
82 | 2 | return tasks; |
83 | }; | |
84 | ||
85 | /** | |
86 | * The function or value that should be used to generate the resources array. Sets the | |
87 | * value to the argument passed in, returns current value if no arguments are | |
88 | * passed in. | |
89 | */ | |
90 | 9 | tasks.resources = function(_) { |
91 | 3 | if (!arguments.length) return resources; |
92 | 3 | resources = _; |
93 | 3 | return tasks; |
94 | }; | |
95 | ||
96 | /** | |
97 | * The function or value that should be used to generate the dependency array. Sets the | |
98 | * value to the argument passed in, returns current value if no arguments are | |
99 | * passed in. | |
100 | */ | |
101 | 9 | tasks.dependsOn = function(_) { |
102 | 0 | if (!arguments.length) return dependsOn; |
103 | 0 | dependsOn = _; |
104 | 0 | return tasks; |
105 | }; | |
106 | ||
107 | /** | |
108 | * The function or value that should be used to generate the min schedule. Sets the | |
109 | * value to the argument passed in, returns current value if no arguments are | |
110 | * passed in. | |
111 | */ | |
112 | 9 | tasks.minSchedule = function(_) { |
113 | 2 | if (!arguments.length) return minSchedule; |
114 | 2 | minSchedule = _; |
115 | 2 | return tasks; |
116 | }; | |
117 | ||
118 | /** | |
119 | * The function or value that should be used to generate the priority. Sets the | |
120 | * value to the argument passed in, returns current value if no arguments are | |
121 | * passed in. | |
122 | */ | |
123 | 9 | tasks.priority = function(_) { |
124 | 1 | if (!arguments.length) return priority; |
125 | 1 | priority = _; |
126 | 1 | return tasks; |
127 | }; | |
128 | ||
129 | 9 | return tasks; |
130 | }; | |
131 | ||
132 | /** | |
133 | * The default id function. | |
134 | */ | |
135 | 1 | function tasksId(d) { |
136 | 0 | return d.id; |
137 | } | |
138 | ||
139 | /** | |
140 | * The default duration function. | |
141 | */ | |
142 | 1 | function tasksDuration(d) { |
143 | 0 | return d.duration; |
144 | } | |
145 | ||
146 | /** | |
147 | * The default schedule function. | |
148 | */ | |
149 | 1 | function tasksSched(d) { |
150 | 0 | return d.schedule; |
151 | } | |
152 | ||
153 | /** | |
154 | * The default resources function. | |
155 | */ | |
156 | 1 | function tasksResources(d) { |
157 | 0 | return d.resources; |
158 | } | |
159 | ||
160 | /** | |
161 | * The default depends on function. | |
162 | */ | |
163 | 1 | function tasksDependsOn(d) { |
164 | 32 | return d.dependsOn; |
165 | } | |
166 | ||
167 | /** | |
168 | * The default min schedule function. | |
169 | */ | |
170 | 1 | function tasksMinSchedule(d) { |
171 | 0 | return d.minSchedule; |
172 | } | |
173 | ||
174 | /** | |
175 | * The default priority function. | |
176 | */ | |
177 | 1 | function tasksPriority(d) { |
178 | 10 | return d.priority; |
179 | } |
Line | Hits | Source |
---|---|---|
1 | 1 | schedule.date = {}; |
Line | Hits | Source |
---|---|---|
1 | /** | |
2 | * Timezone | |
3 | * (c) 2013 Bill, BunKat LLC. | |
4 | * | |
5 | * Configures Schedule to use local time or UTC. Schedule uses UTC time by default. | |
6 | * | |
7 | * Schedule is freely distributable under the MIT license. | |
8 | * For all details and documentation: | |
9 | * http://github.com/bunkat/schedule | |
10 | */ | |
11 | ||
12 | // pass through to Later to configure timezones | |
13 | 1 | schedule.date.UTC = function() { later.date.UTC(); }; |
14 | 5 | schedule.date.localTime = function() { later.date.localTime(); }; |
Line | Hits | Source |
---|---|---|
1 | 1 | schedule.sort = {}; |
Line | Hits | Source |
---|---|---|
1 | /** | |
2 | * Sort tasks | |
3 | * (c) 2013 Bill, BunKat LLC. | |
4 | * | |
5 | * Determines the order that tasks are scheduled in when multiple tasks can be | |
6 | * scheduled in parallel. Default it to do highest priority tasks first, then | |
7 | * tasks that have been determined to have the largest float. | |
8 | * | |
9 | * Schedule is freely distributable under the MIT license. | |
10 | * For all details and documentation: | |
11 | * http://github.com/bunkat/schedule | |
12 | */ | |
13 | ||
14 | 1 | schedule.sort.tasks = function(taskGraph, readyTasks) { |
15 | 69 | readyTasks.sort(function(a,b) { |
16 | 155 | var ta = taskGraph.tasks[a], |
17 | tb = taskGraph.tasks[b]; | |
18 | ||
19 | 155 | if(tb.priority && (!ta.priority || tb.priority > ta.priority)) { |
20 | 118 | return -1; |
21 | } | |
22 | ||
23 | 37 | if(ta.priority && (!tb.priority || ta.priority > tb.priority)) { |
24 | 19 | return 1; |
25 | } | |
26 | ||
27 | 18 | return taskGraph.tasks[b].floatAmt > taskGraph.tasks[a].floatAmt; |
28 | }); | |
29 | }; |
Line | Hits | Source |
---|---|---|
1 | /** | |
2 | * Functor | |
3 | * | |
4 | * Wraps values in functions so that they can be called. Usage inspired by | |
5 | * Mike Bostock in d3. | |
6 | * | |
7 | * Schedule is freely distributable under the MIT license. | |
8 | * For all details and documentation: | |
9 | * http://github.com/bunkat/schedule | |
10 | */ | |
11 | ||
12 | 1 | schedule.functor = function(v) { |
13 | 64 | return typeof v === "function" ? v : function() { return v; }; |
14 | }; |
Line | Hits | Source |
---|---|---|
1 | /** | |
2 | * MemoizedRangeFn | |
3 | * (c) 2013 Bill, BunKat LLC. | |
4 | * | |
5 | * Wraps later.schedule().nextRange to provide memoization of results. Calculating | |
6 | * valid occurrences can be expensive and so we want to reduce the amount of times | |
7 | * we calculate them as much as possible. Also cleans up undefined values so that | |
8 | * we don't have to deal with them later. | |
9 | * | |
10 | * Schedule is freely distributable under the MIT license. | |
11 | * For all details and documentation: | |
12 | * http://github.com/bunkat/schedule | |
13 | */ | |
14 | ||
15 | 1 | schedule.memoizedRangeFn = function(fn) { |
16 | 254 | var cache = {}; // local store for memoization results |
17 | ||
18 | 254 | return function(start) { |
19 | 566 | if(!cache[start]) { |
20 | 550 | var result = fn(1, start); |
21 | 550 | cache[start] = [ |
22 | result[0] ? result[0].getTime() : 4102444800000,// Jan 1, 2100 | |
23 | result[1] ? result[1].getTime() : 4102444800000 // Jan 1, 2100 | |
24 | ]; | |
25 | } | |
26 | ||
27 | 566 | return cache[start]; |
28 | }; | |
29 | }; |