Re: Resource allocation behaviour
Lee Baylis <lee-gZngeWTYz/[email protected]> Mon, 17 Nov 2008 02:52:00 +0000
| Newsgroups | gmane.comp.gnome.apps.planner.devel |
|---|---|
| Message-ID | <[email protected]> |
Hi All, Apologies in advance for the essay! Lee Baylis wrote: >> I would also like to extend the mrp-resource data schema with a >> 'maximum allocatable >> units' field definable on each resource. It turns out this is already there in the code (resource->priv- >units), but I can't see any UI routines for users to set the value, or find anywhere where it is used (certainly 100 has been hard coded into planner in many places as a max_usage value). So, I will look at adding these UI routines instead of extending the schema for this one. Maurice van der Pot wrote: > I have mentioned before on this list that I would like to limit > file/database format changes to as few releases as possible. > A few possible options are then: > > - implementing as much as possible of the other features that also > need > changes and only release a new version when all of it has been > included. > > - first spend time on getting a release out with a lot of the > regular bugfixes/features that don't require format changes. > Then add your work to SVN and focus on the change-requiring features > as above. Only the option on the project to set overload behaviour is left, so I will use a flag for now, then look at adding it to the schema when I start introducing algorithms to choose from - at that point I will have a better idea as to what behaviour this option will need to hold. Maurice van der Pot wrote: > Do send the simple version you have to the list, because I don't want > you to do a lot of rework if it is reviewed only after you have put in > an awful lot of time. > > In fact I would have liked to have had some updates on the approach > you > have taken to implement this. We have not been able to suggest issues > with the approach or come to an (informal) spec. I've still not come to a final spec with John - I broke it down into: 1) Putting in triggers to determine when an action has overloaded a resource, 2) Adding one or more algorithms from the discussion on this list and bugzilla to fire when this happens, 3) Modifying the UI to graphically handle some of the scenarios which can arise. It is details regarding the algorithms which are still under discussion, so for now I have just concentrated on working to the first step, and my thinking was that the best way to demonstrate it would be to start just by rejecting actions which caused a resource to be overloaded. On that point, very little of what I have completed so far has represented a massive leap or me coding off into the unknown. My main observation was that there is already code in planner relating to detecting overallocation, in the Resource Usage view routines. The steps I have taken so far have been to tidy that up a little, move some of it to library files instead of the planner-usage-row file, and add a few functions to calculate and evaluate resource allocations. In the interests of submitting smaller steps, I've attached the completed patches from just that piece of work, but note that no new user functionality is introduced by incorporating these patches into planner - they just set the stage for the next steps. planner-usage-row: - I have taken the Date struct, expanded it slightly, and more formally named it MrpAssignmentEdge, since it essentially contains information about the start and finish edges of an assignment. I have moved it, the date_type and the date_compare function to the mrp- assignment library, and taken code which initialised the old Date members out of the draw functions and moved it to a function in the assignment library, where overallocation routines can make use of it too. - I have introduced a PlannerUsageRowColorScheme struct which simplifies some of the draw functions by moving logic which didn't need to be in them elsewhere. color_schemes are now passed between functions instead of allocation units, and the calculations which the functions used to perform are moved to the mrp-resource library, where overallocation routines can make use of them too. mrp-assignment: Aside from moving the MrpAssignmentEdge struct as detailed above, I have introduced several new functions which are useful for manipulating allocation edges, isolating time periods of interest, and collecting running allocation totals across a project mrp-resource: Aside from moving the resource allocation status calculations as detailed above, I have introduced several new functions which are useful for populating allocation edges from a resource, and calculating overallocation conditions. *** The slightly trickier part has been working out where to fire these new library routines, and this is what I have been following up on over the last few days, and had hoped to have achieved by now. I'll run through my thinking and observations below. I realised potentially any action which causes the task manager to initiate a recalculation of the Gantt chart could result in tasks moving around to the point where some resource or other becomes overallocated, so I set out to investigate which actions are cause for concern. I have so far enumerated those actions which are capable of moving in time tasks which are already allocated to a resource. Whenever this movement can occur relative to other allocated tasks, resource overallocation is possible. Please let me know if you think I have missed any: 1) Changing a resource's calendar 2) Changing project calendars (assuming different resources are using different calendars) 3) Altering project start date (under same assumption as above) 4) Indenting a task into a parent 5) Un-indenting a task from a parent 6) Deleting a task and associated tree 7) Changing the maximum number of units associatable with a resource (currently not implemented in the UI) 8) Assigning a task to a resource 9) Removing a resource assignment 10) Changing the number of units of an assignment 11) Adding a relation to a task via the dialog or dragging between tasks 12) Changing the relation associated with a task 13) Removing a relation from a task 14) Altering the work of a task via the dialog or clicking on the task 15) Altering the duration of a fixed task 16) Constraining a task In terms of catching all of these, I had originally been following up on an idea which worked well for simpler actions and allowed me to create a build where several items in the list above caused pop up messages and blocked overloading, but the idea has not been as straightforward as I would have liked for the more complicated actions, so I welcome some discussion: I think by far the most convenient method for determining whether a resource has become overloaded is to allow mrp_task_manager_recalc to run, and then apply a test, followed by taking any necessary corrective action. Any other method of determining whether an action will cause an overload, as far as I can see, will just end up duplicating most of what mrp_task_manager_recalc already performs. My first idea was that the best way to determine whether any given action has overloaded a resource would be to allow that action to fire function calls all the way down to mrp_task_manager_recalc, then run the overallocation test. Should the test fail, I had hoped to then be able to trigger some action at the mrp_task_manager_recalc level in order to handle resource overallocation scenarios. This approach may still be useful for situations where the user has specified at the project level that some fixed algorithm be ran any time any resource becomes overloaded - however there are two obvious scenarios for which it is insufficient: 1) If the user has specified at the project level that resource overloading is to be prohibited - in which case, the action and subsequent recalculation resulting in the overload has already run, and needs to be undone 2) Those (hopefully not too ambitious) scenarios where the user has not specified a resource overallocation behaviour at the project level, but rather has asked to be prompted with a choice of behaviours every time overallocation occurs. Again, the recalculation has already been performed at this point, so needs to be undone and then re-performed with whichever behaviour the user has selected to be active. Faced with these scenarios, my first thought, and the one I have been playing with for a while, was that each of the _do functions resulting from an action would need to perform a check for overallocation and then be prepared to undo the actions it took. For simpler actions (assigning a task to a resource, for example), this was not much code and worked well for a while. However: - undo data for some of these actions can be quite complicated, for example deleting a task tree - the planner cmd manager already seems equipped to handle reversing actions as a consequence of providing edit->undo - I found the start of an attempt at cmd transaction support in the cmd manager code, although it has not yet been completed to the point where it deals with custom errors mid-transaction Having made these observations, I am now inclined to change tactic and try and use the planner cmd manager (probably via transaction support) to perform actions, check for overload, and then roll them back or reapply them specifying a different overallocation behaviour. I wondered if anyone can see any issues with this approach, or has any other ideas. I have come up with some issues myself: 1) All of the actions I have looked at so far fill out cmds in the cmd manager which invoke the same cmd_undo routine regardless of whether or not the original cmd_do succeeded or not. I'm not sure if this is very clever design or an oversight - as far as I can tell there aren't currently any scenarios in planner where this behaviour causes a problem. However, introducing additional failure criteria (i.e., resource has been overloaded) for some of the do cmds may upset this balance. For example, again, deleting a task tree. At the moment no facility is provided for this action to return failure, and the undo routine involves recreating the entire task tree which was deleted. Now, if the ability for a task tree deletion to fail is introduced, meaning the cmd can run but not delete the tree, it looks to me like running the undo will create a duplicate tree. I am writing some code to test whether this actually happens at the moment using the delete tree action. Undos for other actions could meet with similar complications. I can think of a couple of fixes though: i) The obvious one is to use the ability for a cmd object to record the failure of its cmd_do actions, to modify each action's undo routine and include clauses depending on whether the original cmd succeeded. A transaction manager could then roll back the failed cmd by applying the more flexible undo. ii) feels a bit dirty and involves finding a way to retroactively persuade the cmd manager that the cmd_do didn't actually run in the first place, then optionally trying the do cmd again, but specifying different overallocation behaviour. 2) Undo and redo on the edit menu might offer stages that the user wasn't aware of unless transaction support is used, although it is already the case in planner that some actions remain in the undo/redo menu even though they failed (creating circular task dependencies, for example) If transaction support were the way to go, since it is unfinished, I can see a few ways to finish it: i) Halting as soon as an error is encountered, rolling back to the start of the transaction, and then freeing the cmds in the transaction so that nothing appears in the redo menu ii) Entering the end_transaction marker when an error is encountered, then rolling back actions to the start of the transaction - in this case, the transaction does appear in the redo menu, but presumably fails again if someone clicks it and can be configured to roll back again on redo error iii) Ignoring the error, carrying on with the transaction, naturally writing the end_transaction marker, and then evaluating whether there was an error and rolling back if there was. This method would probably be better if we had any expectation that the error mid- transaction was temporary and that the transaction might succeed on redo. Personally, I prefer the first, since I don't think most of our transactions are the kind of thing that might turn out to be be magically fixed the next time someone clicks redo, and I can't see the point of offering the opportunity to redo something we know is just going to fail again. Also, i) can be modified with the kind of "if error occurs try this instead" behaviour I am looking for. That's all my thoughts for now - thanks for reading this far! Thanks, lee _______________________________________________ Planner-dev-list mailing list [email protected] http://mail.gnome.org/mailman/listinfo/planner-dev-list
planner-usage-row.c.patch
(application/octet-stream, 14 KB)
Index: src/planner-usage-row.c
===================================================================
--- src/planner-usage-row.c (revision 940)
+++ src/planner-usage-row.c (working copy)
@@ -30,6 +30,7 @@
#include <stdio.h>
#include <libplanner/mrp-project.h>
#include <libplanner/mrp-resource.h>
+#include <libplanner/mrp-assignment.h>
#include <libplanner/mrp-task.h>
#include <glib/gi18n.h>
#include <libgnomecanvas/gnome-canvas.h>
@@ -175,23 +176,15 @@
static GdkBitmap *break_stipple = NULL;
static gchar break_stipple_pattern[] = { 0x03 };
-static GdkColor color_normal;
-static GdkColor color_normal_light;
-static GdkColor color_normal_dark;
+struct _PlannerUsageRowColorScheme {
+ GdkColor plain;
+ GdkColor light;
+ GdkColor dark;
+};
+typedef struct _PlannerUsageRowColorScheme PlannerUsageRowColorScheme;
-static GdkColor color_free;
-static GdkColor color_free_light;
-static GdkColor color_free_dark;
+static PlannerUsageRowColorScheme color_schemes[MRP_RESOURCE_LAST_ALLOCATION];
-static GdkColor color_underuse;
-static GdkColor color_underuse_light;
-static GdkColor color_underuse_dark;
-
-static GdkColor color_overuse;
-static GdkColor color_overuse_light;
-static GdkColor color_overuse_dark;
-
-
GType
planner_usage_row_get_type (void)
{
@@ -750,22 +743,21 @@
g_object_add_weak_pointer (G_OBJECT (complete_stipple),
(gpointer) & complete_stipple);
-
- gnome_canvas_get_color (item->canvas, "LightSkyBlue3", &color_normal);
- gnome_canvas_get_color (item->canvas, "#9ac7e0", &color_normal_light);
- gnome_canvas_get_color (item->canvas, "#7da1b5", &color_normal_dark);
+ gnome_canvas_get_color (item->canvas, "LightSkyBlue3", &color_schemes[MRP_RESOURCE_ALLOCATED].plain);
+ gnome_canvas_get_color (item->canvas, "#9ac7e0", &color_schemes[MRP_RESOURCE_ALLOCATED].light);
+ gnome_canvas_get_color (item->canvas, "#7da1b5", &color_schemes[MRP_RESOURCE_ALLOCATED].dark);
- gnome_canvas_get_color (item->canvas, "indian red", &color_overuse);
- gnome_canvas_get_color (item->canvas, "#de6464", &color_overuse_light);
- gnome_canvas_get_color (item->canvas, "#ba5454", &color_overuse_dark);
+ gnome_canvas_get_color (item->canvas, "indian red", &color_schemes[MRP_RESOURCE_OVERALLOCATED].plain);
+ gnome_canvas_get_color (item->canvas, "#de6464", &color_schemes[MRP_RESOURCE_OVERALLOCATED].light);
+ gnome_canvas_get_color (item->canvas, "#ba5454", &color_schemes[MRP_RESOURCE_OVERALLOCATED].dark);
- gnome_canvas_get_color (item->canvas, "grey", &color_underuse);
- gnome_canvas_get_color (item->canvas, "#d6d6d6", &color_underuse_light);
- gnome_canvas_get_color (item->canvas, "#a8a8a8", &color_underuse_dark);
+ gnome_canvas_get_color (item->canvas, "grey", &color_schemes[MRP_RESOURCE_UNDERALLOCATED].plain);
+ gnome_canvas_get_color (item->canvas, "#d6d6d6", &color_schemes[MRP_RESOURCE_UNDERALLOCATED].light);
+ gnome_canvas_get_color (item->canvas, "#a8a8a8", &color_schemes[MRP_RESOURCE_UNDERALLOCATED].dark);
- gnome_canvas_get_color (item->canvas, "medium sea green", &color_free);
- gnome_canvas_get_color (item->canvas, "#43c77e", &color_free_light);
- gnome_canvas_get_color (item->canvas, "#359e64", &color_free_dark);
+ gnome_canvas_get_color (item->canvas, "medium sea green", &color_schemes[MRP_RESOURCE_FREE].plain);
+ gnome_canvas_get_color (item->canvas, "#43c77e", &color_schemes[MRP_RESOURCE_FREE].light);
+ gnome_canvas_get_color (item->canvas, "#359e64", &color_schemes[MRP_RESOURCE_FREE].dark);
} else {
g_object_ref (complete_stipple);
}
@@ -829,45 +821,6 @@
}
typedef enum {
- START_ASSIGN,
- END_ASSIGN
-} date_type;
-
-typedef struct {
- date_type type;
- mrptime time;
- gint units;
- MrpAssignment *assignment;
- MrpTask *task;
-} Date;
-
-static gint
-usage_row_date_compare (gconstpointer date1,
- gconstpointer date2)
-{
- const Date *a, *b;
-
- a = date1;
- b = date2;
-
- if (a->time < b->time) {
- return -1;
- }
- else if (a->time == b->time) {
- if (a->type < b->type) {
- return -1;
- }
- else if (a->type == b->type) {
- return 0;
- } else {
- return 1;
- }
- } else {
- return 1;
- }
-}
-
-typedef enum {
ROW_MIDDLE = 0,
ROW_START = 1 << 0,
ROW_END = 1 << 1,
@@ -875,16 +828,16 @@
} RowChunk;
static void
-usage_row_draw_resource_ival (mrptime start,
- mrptime end,
- gint units,
- RowChunk chunk,
- GdkDrawable *drawable,
- GnomeCanvasItem *item,
- gint x,
- gint y,
- gint width,
- gint height)
+usage_row_draw_resource_ival (mrptime start,
+ mrptime end,
+ PlannerUsageRowColorScheme *color_scheme,
+ RowChunk chunk,
+ GdkDrawable *drawable,
+ GnomeCanvasItem *item,
+ gint x,
+ gint y,
+ gint width,
+ gint height)
{
PlannerUsageRow *row;
PlannerUsageRowPriv *priv;
@@ -972,17 +925,7 @@
return;
}
- if (units == 0) {
- gdk_gc_set_foreground (priv->fill_gc, &color_free);
- }
- else if (units < 100) {
- gdk_gc_set_foreground (priv->fill_gc, &color_underuse);
- }
- else if (units == 100) {
- gdk_gc_set_foreground (priv->fill_gc, &color_normal);
- } else {
- gdk_gc_set_foreground (priv->fill_gc, &color_overuse);
- }
+ gdk_gc_set_foreground (priv->fill_gc, &color_scheme->plain);
/* Draw the central part of the chunk */
if (rr_xend >= rr_xstart && rr_yend >= rr_ystart) {
@@ -993,20 +936,10 @@
rr_xend - rr_xstart + 1, rr_yend - rr_ystart + 1);
}
- if (units == 0) {
- gdk_gc_set_foreground (priv->fill_gc, &color_free_light);
- }
- else if (units < 100) {
- gdk_gc_set_foreground (priv->fill_gc, &color_underuse_light);
- }
- else if (units == 100) {
- gdk_gc_set_foreground (priv->fill_gc, &color_normal_light);
- } else {
- gdk_gc_set_foreground (priv->fill_gc, &color_overuse_light);
- }
-
//gdk_gc_set_foreground (priv->fill_gc, &color_high);
+ gdk_gc_set_foreground (priv->fill_gc, &color_scheme->light);
+
/* Top of the shadow. */
if (cs_ystart == rs_ystart) {
gdk_draw_line (drawable, priv->fill_gc, r_xstart, rs_ystart,
@@ -1019,20 +952,10 @@
rs_xstart, cs_yend);
}
- if (units == 0) {
- gdk_gc_set_foreground (priv->fill_gc, &color_free_dark);
- }
- else if (units < 100) {
- gdk_gc_set_foreground (priv->fill_gc, &color_underuse_dark);
- }
- else if (units == 100) {
- gdk_gc_set_foreground (priv->fill_gc, &color_normal_dark);
- } else {
- gdk_gc_set_foreground (priv->fill_gc, &color_overuse_dark);
- }
-
//gdk_gc_set_foreground (priv->fill_gc, &color_shadow);
+ gdk_gc_set_foreground (priv->fill_gc, &color_scheme->dark);
+
/* Bottom of the shadow. */
if (cs_yend == rs_yend) {
gdk_draw_line (drawable, priv->fill_gc, r_xstart, rs_yend,
@@ -1076,7 +999,7 @@
r_yend);
}
}
-
+
static void
usage_row_draw_resource (PlannerUsageRow *row,
GdkDrawable *drawable,
@@ -1086,52 +1009,24 @@
gint width,
gint height)
{
- GList *dates;
- MrpResource *resource;
- MrpTask *root;
- MrpAssignment *assignment;
- MrpTask *task;
- MrpProject *project;
- GList *assignments;
- GList *a, *d;
- Date *date, *date0, *date1;
- mrptime work_start, finish, previous_time;
- gint units;
- RowChunk chunk;
+ GList *e, *edges;
+ MrpResource *resource;
+ MrpTask *root;
+ MrpProject *project;
+ MrpAssignmentEdge *edge;
+ mrptime finish, previous_time;
+ gint units;
+ RowChunk chunk;
+ MrpResourceAllocation status;
+ PlannerUsageRowColorScheme *color_scheme;
resource = row->priv->resource;
- dates = NULL;
-
project = mrp_object_get_project (MRP_OBJECT (resource));
- assignments = mrp_resource_get_assignments (resource);
- for (a = assignments; a; a = a->next) {
- assignment = a->data;
+ edges = NULL;
+ edges = mrp_resource_get_assignment_edges(resource, edges);
- task = mrp_assignment_get_task (assignment);
- work_start = mrp_task_get_work_start (task);
- finish = mrp_task_get_finish (task);
-
- units = mrp_assignment_get_units (assignment);
-
- date0 = g_new0 (Date, 1);
- date0->type = START_ASSIGN;
- date0->time = work_start;
- date0->units = units;
- date0->assignment = assignment;
- date0->task = task;
-
- date1 = g_new0 (Date, 1);
- date1->type = END_ASSIGN;
- date1->time = finish;
- date1->units = units;
- date1->assignment = assignment;
- date1->task = task;
- dates = g_list_insert_sorted (dates, date0, usage_row_date_compare);
- dates = g_list_insert_sorted (dates, date1, usage_row_date_compare);
- }
-
units = 0;
previous_time = mrp_project_get_project_start (project);
@@ -1140,43 +1035,42 @@
chunk = ROW_START;
- for (d = dates; d; d = d->next) {
- date = d->data;
+ for (e = edges; e; e = e->next) {
+ edge = e->data;
- if (date->time != previous_time) {
- if (date->time == finish) {
+ if (edge->time != previous_time) {
+ if (edge->time == finish) {
chunk |= ROW_END;
}
+ /* Set the color scheme pointer based on allocation units */
+
+ status = mrp_resource_allocation_status(resource, units);
+ color_scheme = color_schemes + (int)status;
usage_row_draw_resource_ival (previous_time,
- date->time,
- units,
- chunk,
- drawable, item,
- x, y, width, height);
+ edge->time,
+ color_scheme,
+ chunk,
+ drawable, item,
+ x, y, width, height);
chunk &= ~ROW_START;
- previous_time = date->time;
+ previous_time = edge->time;
}
-
- if (date->type == START_ASSIGN) {
- units += date->units;
- } else {
- units -= date->units;
- }
- g_free (date);
+ units += mrp_assignment_edge_get_delta_units(edge);
}
- g_list_free (dates);
+ g_list_free (edges);
if (!(chunk & ROW_END)) {
chunk |= ROW_END;
usage_row_draw_resource_ival (previous_time,
finish,
- units,
+ &color_schemes[MRP_RESOURCE_FREE],
chunk,
drawable, item,
x, y, width, height);
}
+ return;
}
static void
@@ -1257,7 +1151,7 @@
priv->complete_gc);
}
- gdk_gc_set_foreground (priv->fill_gc, &color_normal);
+ gdk_gc_set_foreground (priv->fill_gc, &color_schemes[MRP_RESOURCE_ALLOCATED].plain);
gdk_draw_rectangle (drawable,
priv->fill_gc,
@@ -1276,7 +1170,7 @@
gdk_draw_line (drawable, priv->frame_gc, rx1, cy1, rx2, cy1);
gdk_draw_line (drawable, priv->frame_gc, rx1, cy2, rx2, cy2);
- gdk_gc_set_foreground (priv->fill_gc, &color_normal_light);
+ gdk_gc_set_foreground (priv->fill_gc, &color_schemes[MRP_RESOURCE_ALLOCATED].light);
gdk_draw_line (drawable,
priv->fill_gc,
rx1 + 0, cy1 + 1, rx2 - 0, cy1 + 1);
@@ -1287,7 +1181,7 @@
rx1 + 1, cy1 + 1, rx1 + 1, cy2 - 1);
}
- gdk_gc_set_foreground (priv->fill_gc, &color_normal_dark);
+ gdk_gc_set_foreground (priv->fill_gc, &color_schemes[MRP_RESOURCE_ALLOCATED].dark);
gdk_draw_line (drawable,
priv->fill_gc,
rx1 + 0, cy2 - 1, rx2 - 0, cy2 - 1);
mrp-assignment.c.patch
(application/octet-stream, 9 KB)
Index: libplanner/mrp-assignment.c =================================================================== --- libplanner/mrp-assignment.c (revision 940) +++ libplanner/mrp-assignment.c (working copy) @@ -3,6 +3,7 @@ * Copyright (C) 2001-2003 CodeFactory AB * Copyright (C) 2001-2003 Richard Hult <[email protected]> * Copyright (C) 2001-2002 Mikael Hallendal <[email protected]> + * Copyright (C) 2008 Lee Baylis <lee-gZngeWTYz/[email protected]> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -35,6 +36,11 @@ gint units; }; +typedef enum { + TO_LAST_EDGE_BEFORE, + FROM_FIRST_EDGE_AFTER +} AssignmentEdgeRemovalMarker; + /* Properties */ enum { PROP_0, @@ -43,19 +49,23 @@ PROP_UNITS }; +static void assignment_class_init (MrpAssignmentClass *klass); +static void assignment_init (MrpAssignment *assignment); +static void assignment_finalize (GObject *object); +static void assignment_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec); +static void assignment_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec); +static gint assignment_edge_compare (gconstpointer edge1, + gconstpointer edge2); +static GList * assignment_edges_remove (GList *edges, + AssignmentEdgeRemovalMarker marker, + mrptime time); -static void assignment_class_init (MrpAssignmentClass *klass); -static void assignment_init (MrpAssignment *assignment); -static void assignment_finalize (GObject *object); -static void assignment_set_property (GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec); -static void assignment_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec); - static MrpObjectClass *parent_class; GType @@ -226,6 +236,70 @@ } } +static gint +assignment_edge_compare (gconstpointer edge1, + gconstpointer edge2) +{ + const MrpAssignmentEdge *a, *b; + + a = edge1; + b = edge2; + + if (a->time < b->time) { + return -1; + } + else if (a->time == b->time) { + /* if times are equal, overallocation calculations require that end_edges are sorted before start_edges */ + if (a->type < b->type) { + return 1; + } + else if (a->type == b->type) { + return 0; + } else { + return -1; + } + } else { + return 1; + } +} + +static GList * +assignment_edges_remove(GList *edges, + AssignmentEdgeRemovalMarker marker, + mrptime time) +{ + mrptime times[2]; + GList *e, *first; + MrpAssignmentEdge *edge; + + times[1-marker] = time; + if (edges){ + if (marker){ + edges = g_list_reverse(edges); + } + + first = g_list_first(edges); + e = first->next; + if (e){ + edge = e->data; + times[marker] = edge->time; + while (times[0] <= times[1]){ + edges = g_list_remove(edges, first->data); + first = g_list_first(edges); + e = first->next; + if (!e) {break;} + edge = e->data; + times[marker] = edge->time; + } + } + } + if (marker){ + edges = g_list_reverse(edges); + } + return edges; + +} + /** * mrp_assignment_new: * @@ -294,3 +368,168 @@ return assignment->priv->units; } + +/** + * mrp_assignment_edges_insert_sorted_from_assignment: + * @edges: a #GList optionally containing #MrpAssignmentEdge objects + * @assignment: an #MrpAssignment + * + * Determines the start and end edges (essentially times + assignment units) from an assignment's associated task, and inserts them into the list of edges sorted by mrptime. Mostly useful for tracking a MrpResource's total assignment units whilst scanning across a project. + * + * Return Value: pointer to the updated edges GList. + **/ +GList * +mrp_assignment_edges_insert_sorted_from_assignment (GList *edges, + MrpAssignment *assignment) +{ + MrpTask *task; + gint units; + MrpAssignmentEdge *start_edge, *end_edge; + + g_return_val_if_fail (MRP_IS_ASSIGNMENT (assignment), edges); + + task = mrp_assignment_get_task (assignment); + units = mrp_assignment_get_units (assignment); + + start_edge = g_new0 (MrpAssignmentEdge, 1); + start_edge->type = START_EDGE; + start_edge->time = mrp_task_get_work_start(task); + start_edge->units = units; + start_edge->assignment = assignment; + start_edge->task = task; + + edges = g_list_insert_sorted (edges, start_edge, assignment_edge_compare); + + end_edge = g_new0 (MrpAssignmentEdge, 1); + end_edge->type = END_EDGE; + end_edge->time = mrp_task_get_finish(task); + end_edge->units = units; + end_edge->assignment = assignment; + end_edge->task = task; + + edges = g_list_insert_sorted (edges, end_edge, assignment_edge_compare); + + return edges; +} + +/** + * mrp_assignment_edges_insert_sorted_from_assignment_list: + * @edges: a #GList containing #MrpAssignmentEdge objects + * @assignments: a #GList contianing #MrpAssignment objects + * + * Determines the sorted start and end edges for all passed assignments. Mostly useful for scanning a #MrpResource object's total assignment units across a project. + * + * Return Value: pointer to the updated edges #GList. + **/ +GList * +mrp_assignment_edges_insert_sorted_from_assignment_list (GList *edges, + GList *assignments) +{ + GList *a; + MrpAssignment *assignment; + + for (a = assignments; a; a = a->next) { + assignment = a->data; + edges = mrp_assignment_edges_insert_sorted_from_assignment(edges, assignment); + } + return edges; +} + +/** + * mrp_assignment_edges_plot_units_in_use: + * @edges: a #GList containing #MrpAssignmentEdge objects + * + * Iterates the #GList of #MrpAssignmentEdge objects calculating a running total for each #MrpAssignmentEdge by adding units for start edges and subtracting them for end edges. Mostly useful for calculating a #MrpResource object's allocation within a period. + * + * Return Value: pointer to the updated edges #GList. + **/ +GList * +mrp_assignment_edges_plot_units_in_use (GList *edges) +{ + GList *e; + MrpAssignmentEdge *edge; + gint old_units = 0; + + for (e = edges; e; e = e->next) { + edge = e->data; + old_units += mrp_assignment_edge_get_delta_units(edge); + edge->units_in_use = old_units; + } + return edges; +} + +/** + * mrp_assignment_edges_calculate_max_units_in_use: + * @edges: a #GList containing #MrpAssignmentEdge objects + * + * Iterates the #GList of #MrpAssignmentEdge objects consulting the running total for each #MrpAssignmentEdge and storing the maximum. + * + * Return Value: #gint containing the maximum units in use. + **/ +gint +mrp_assignment_edges_calculate_max_units_in_use (GList *edges) +{ + GList *e; + MrpAssignmentEdge *edge; + gint units = 0; + + for (e = edges; e; e = e->next){ + edge = e->data; + if (edge->units_in_use > units) { + units = edge->units_in_use; + } + } + return units; +} + +/** + * mrp_assignment_edges_remove_to_last_before: + * @edges: a #GList containing #MrpAssignmentEdge objects + * @time: a #mrptime + * + * Removes all but the last #MrpAssignmentEdge object before @time from @edges. + * Mostly useful for isolating time periods for later calculation. + * + * Return Value: pointer to the updated edges #GList. + **/ +GList * +mrp_assignment_edges_remove_to_last_before(GList *edges, + mrptime time) +{ + return assignment_edges_remove(edges, TO_LAST_EDGE_BEFORE, time); +} + +/** + * mrp_assignment_edges_remove_from_first_after: + * @edges: a #GList containing #MrpAssignmentEdge objects + * @time: a #mrptime + * + * Removes all but the last #MrpAssignmentEdge object after @time from @edges. + * Mostly useful for isolating time periods for later calculation. + * + * Return Value: pointer to the updated edges #GList. + **/ +GList * +mrp_assignment_edges_remove_from_first_after(GList *edges, + mrptime time) +{ + return assignment_edges_remove(edges, FROM_FIRST_EDGE_AFTER, time); +} + +/** + * mrp_assignment_edge_get_delta_units: + * @edge: a #MrpAssignmentEdge + * + * Edge-sensing function: returns the change in units associated with an assignment, either signed positive for a start edge, or negative for an end edge. + * + * Return Value: gint representing the change in assignment units at the edge. + **/ +gint +mrp_assignment_edge_get_delta_units (MrpAssignmentEdge *edge) +{ + if (edge->type == START_EDGE) { + return edge->units; + } else { + return 0 - edge->units; + } +}
mrp-assignment.h.patch
(application/octet-stream, 2.9 KB)
Index: libplanner/mrp-assignment.h =================================================================== --- libplanner/mrp-assignment.h (revision 940) +++ libplanner/mrp-assignment.h (working copy) @@ -3,6 +3,7 @@ * Copyright (C) 2001-2002 CodeFactory AB * Copyright (C) 2001-2002 Richard Hult <[email protected]> * Copyright (C) 2001-2002 Mikael Hallendal <[email protected]> + * Copyright (C) 2008 Lee Baylis <lee-gZngeWTYz/[email protected]> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -36,6 +37,8 @@ typedef struct _MrpAssignmentClass MrpAssignmentClass; typedef struct _MrpAssignmentPriv MrpAssignmentPriv; +typedef enum _MrpAssignmentEdgeType MrpAssignmentEdgeType; +typedef struct _MrpAssignmentEdge MrpAssignmentEdge; struct _MrpAssignment { MrpObject parent; @@ -47,12 +50,36 @@ MrpObjectClass parent_class; }; -GType mrp_assignment_get_type (void) G_GNUC_CONST; +enum _MrpAssignmentEdgeType{ + START_EDGE, + END_EDGE +}; -MrpAssignment *mrp_assignment_new (void); +struct _MrpAssignmentEdge { + MrpAssignmentEdgeType type; + mrptime time; + gint units; + gint units_in_use; + MrpAssignment *assignment; + MrpTask *task; +}; -MrpTask *mrp_assignment_get_task (MrpAssignment *assignment); -MrpResource *mrp_assignment_get_resource (MrpAssignment *assignment); -gint mrp_assignment_get_units (MrpAssignment *assignment); +GType mrp_assignment_get_type (void) G_GNUC_CONST; +MrpAssignment *mrp_assignment_new (void); + +MrpTask *mrp_assignment_get_task (MrpAssignment *assignment); +MrpResource *mrp_assignment_get_resource (MrpAssignment *assignment); +gint mrp_assignment_get_units (MrpAssignment *assignment); +GList * mrp_assignment_edges_insert_sorted_from_assignment (GList *edges, + MrpAssignment *assignment); +GList * mrp_assignment_edges_insert_sorted_from_assignment_list (GList *edges, + GList *assignments); +GList * mrp_assignment_edges_plot_units_in_use (GList *edges); +gint mrp_assignment_edges_calculate_max_units_in_use (GList *edges); +GList * mrp_assignment_edges_remove_to_last_before (GList *edges, + mrptime time); +GList * mrp_assignment_edges_remove_from_first_after (GList *edges, + mrptime time); +gint mrp_assignment_edge_get_delta_units (MrpAssignmentEdge *edge); #endif /* __MRP_ASSIGNMENT_H__ */
mrp-resource.c.patch
(application/octet-stream, 9.3 KB)
Index: libplanner/mrp-resource.c =================================================================== --- libplanner/mrp-resource.c (revision 940) +++ libplanner/mrp-resource.c (working copy) @@ -5,6 +5,7 @@ * Copyright (C) 2002-2003 Richard Hult <[email protected]> * Copyright (C) 2002 Mikael Hallendal <[email protected]> * Copyright (C) 2004 Alvaro del Castillo <[email protected]> + * Copyright (C) 2008 Lee Baylis <lee-gZngeWTYz/[email protected]> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -32,6 +33,9 @@ #include "mrp-task.h" #include "mrp-resource.h" +/* Constant for usage calculations +FIXME: need to alter the UI so that users can set the priv->units value per resource, then can remove this constant */ +#define USAGE_MAX_UNITS 100 struct _MrpResourcePriv { gchar *name; @@ -69,25 +73,27 @@ }; -static void resource_class_init (MrpResourceClass *klass); -static void resource_init (MrpResource *resource); -static void resource_finalize (GObject *object); -static void resource_set_property (GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec); -static void resource_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec); -static void resource_calendar_changed (MrpCalendar *calendar, - MrpResource *resource); -static void resource_removed (MrpObject *object); -static void resource_invalidate_task_costs (MrpResource *resource); -static void resource_assignment_removed_cb (MrpAssignment *assignment, - MrpResource *resource); -static void resource_group_removed_cb (MrpGroup *group, - MrpResource *resource); +static void resource_class_init (MrpResourceClass *klass); +static void resource_init (MrpResource *resource); +static void resource_finalize (GObject *object); +static void resource_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec); +static void resource_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec); +static void resource_calendar_changed (MrpCalendar *calendar, + MrpResource *resource); +static void resource_removed (MrpObject *object); +static void resource_invalidate_task_costs (MrpResource *resource); +static void resource_assignment_removed_cb (MrpAssignment *assignment, + MrpResource *resource); +static void resource_group_removed_cb (MrpGroup *group, + MrpResource *resource); +static GList *resource_get_edges_with_units_in_use (MrpResource *resource, + GList *edges); static MrpObjectClass *parent_class; @@ -826,3 +832,196 @@ g_object_set (resource, "calendar", calendar, NULL); } + +/* There are two ways to look for overallocation: + + - the first is to chart a running total of a resource's allocation + across the whole project, and scan through it looking for total values + of allocation at either a time or some condition. + + This is the technique previously adopted in planner-usage-row, + now moved here and embellished upon. + + - The second is to scan a resource's allocations for those which are + active at a given time, and add up the usage. Whilst this is likely a + more efficient method for larger projects, we are rarely interested in + an individual time, more usually a period between two times, and so it + is not clear right now whether implementing this second will be of any use. +*/ + +/** + * mrp_resource_get_assignment_edges: + * @resource: a #MrpResource + * @edges: a #GList optionally containing #MrpAssignmentEdge objects + * + * Adds all the assignment edges for the resource to the edges glist, sorted by mrptime. Usually you want this list to be empty before calling this function. Free the edges list when done. Mostly useful for calculating total resource allocation over the project. + * + * Return Value: the updated edges GList + **/ +GList * +mrp_resource_get_assignment_edges (MrpResource *resource, + GList *edges) +{ + GList *assignments; + g_return_val_if_fail (MRP_IS_RESOURCE (resource), edges); + + assignments = mrp_resource_get_assignments (resource); + edges = mrp_assignment_edges_insert_sorted_from_assignment_list(edges, assignments); + + return edges; +} + +/** + * mrp_resource_allocation_status: + * @resource: a #MrpResource + * @units: a #gint + * + * Lookup table for allocation status based on the number of units of a resource which are allocated + * + * Return Value: an entry from the MrpResourceAllocation enum + **/ +MrpResourceAllocation +mrp_resource_allocation_status (MrpResource *resource, gint units) +{ + g_return_val_if_fail (MRP_IS_RESOURCE (resource), MRP_RESOURCE_OVERALLOCATED); +/* FIXME: replace USAGE_MAX_UNITS on a per-resource basis rather than a defined constant */ + if (units == 0) { + return MRP_RESOURCE_FREE; + } + else if (units < USAGE_MAX_UNITS) { + return MRP_RESOURCE_UNDERALLOCATED; + } + else if (units == USAGE_MAX_UNITS) { + return MRP_RESOURCE_ALLOCATED; + } else { + return MRP_RESOURCE_OVERALLOCATED; + } +} + +/** + * mrp_resource_test_if_overallocated: + * @resource: a #MrpResource + * + * Scans the project duration for the resource and determines if it is + * ever overallocated. + * + * Return Value: gboolean inidcating whether resource is ever overallocated + **/ +gboolean +mrp_resource_test_if_overallocated (MrpResource *resource) +{ + gint units; + + g_return_val_if_fail (MRP_IS_RESOURCE (resource), TRUE); + + units = mrp_resource_get_max_units_used_between(resource, NULL, NULL); + + if (mrp_resource_allocation_status(resource, units) == MRP_RESOURCE_OVERALLOCATED){ + return TRUE; + } + return FALSE; +} + +/** + * resource_get_edges_with_units_in_use: + * @resource: a #MrpResource + * + * Return Value: a GList of MrpAssignmentEdge objects with the running total + * of units in use calculated. + * + **/ +GList * +resource_get_edges_with_units_in_use (MrpResource *resource, + GList *edges) +{ + g_return_val_if_fail (MRP_IS_RESOURCE (resource), edges); + + edges = mrp_resource_get_assignment_edges(resource, edges); + edges = mrp_assignment_edges_plot_units_in_use(edges); + + return edges; +} + + +/** + * mrp_resource_get_max_units_used_between: + * @resource: a #MrpResource + * @start: a #mrptime marking the start of a period, or project start if #NULL + * @end: a #mrptime marking the end of a period, or project end if #NULL + * + * Plots cumulative allocation for the resource across the project, then scans between the start and end times given, and calculates the maximum allocation in this period. + * + * Return Value: gint indicating the maximum units which have been allocated to the resouce in this period. + **/ + +gint +mrp_resource_get_max_units_used_between (MrpResource *resource, + mrptime start, + mrptime end) +{ + GList *edges = NULL; + gint units; + + /* FIXME: replace USAGE_MAX_UNITS on a per-resource basis rather than a defined constant */ + g_return_val_if_fail (MRP_IS_RESOURCE (resource), USAGE_MAX_UNITS + 1); + + edges = resource_get_edges_with_units_in_use(resource, edges); + + if (start){ + edges = mrp_assignment_edges_remove_to_last_before(edges, start); + } + if (end){ + edges = mrp_assignment_edges_remove_from_first_after(edges, end); + } + + units = mrp_assignment_edges_calculate_max_units_in_use(edges); + + g_list_free(edges); + + return units; +} + +/** + * mrp_resource_get_available_units_between: + * @resource: a #MrpResource + * @start: a #mrptime + * @end: a #mrptime + * + * Return Value: gint indicating the maximum units which are available for the resource in this period without causing overallocation. + **/ +gint +mrp_resource_get_available_units_between (MrpResource *resource, + mrptime start, + mrptime end) +{ +/* FIXME: replace USAGE_MAX_UNITS on a per-resource basis rather than a defined constant */ + g_return_val_if_fail (MRP_IS_RESOURCE (resource), -1); + /* gint available; + + available =*/ + return USAGE_MAX_UNITS - mrp_resource_get_max_units_used_between(resource, start, end); + + /* if (available < 0){ + available = 0; + } + return available;*/ +} + +/** + * mrp_resource_get_available_units_during: + * @resource: a #MrpResource + * @task: a #MrpTask + * + * Return Value: gint indicating the maximum units which are available for the resource between the times the task is running, without causing overallocation. + **/ +gint +mrp_resource_get_available_units_during (MrpResource *resource, + MrpTask *task) +{ + g_return_val_if_fail (MRP_IS_RESOURCE (resource), -1); + g_return_val_if_fail (MRP_IS_TASK (task), -1); + + return mrp_resource_get_available_units_between(resource, + mrp_task_get_work_start(task), + mrp_task_get_finish(task)); +}
mrp-resource.h.patch
(application/octet-stream, 4 KB)
Index: libplanner/mrp-resource.h =================================================================== --- libplanner/mrp-resource.h (revision 940) +++ libplanner/mrp-resource.h (working copy) @@ -4,6 +4,7 @@ * Copyright (C) 2002 CodeFactory AB * Copyright (C) 2002 Richard Hult <[email protected]> * Copyright (C) 2002 Mikael Hallendal <[email protected]> + * Copyright (C) 2008 Lee Baylis <lee-gZngeWTYz/[email protected]> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -55,29 +56,50 @@ MRP_RESOURCE_TYPE_MATERIAL } MrpResourceType; -GType mrp_resource_get_type (void) G_GNUC_CONST; -MrpResource * mrp_resource_new (void); -const gchar * mrp_resource_get_name (MrpResource *resource); -void mrp_resource_set_name (MrpResource *resource, - const gchar *name); -const gchar * mrp_resource_get_short_name (MrpResource *resource); -void mrp_resource_set_short_name (MrpResource *resource, - const gchar *short_name); -void mrp_resource_assign (MrpResource *resource, - MrpTask *task, - gint units); +typedef enum { + MRP_RESOURCE_FREE, + MRP_RESOURCE_UNDERALLOCATED, + MRP_RESOURCE_ALLOCATED, + MRP_RESOURCE_OVERALLOCATED, + MRP_RESOURCE_LAST_ALLOCATION +} MrpResourceAllocation; -GList * mrp_resource_get_assignments (MrpResource *resource); +GType mrp_resource_get_type (void) G_GNUC_CONST; +MrpResource * mrp_resource_new (void); +const gchar * mrp_resource_get_name (MrpResource *resource); +void mrp_resource_set_name (MrpResource *resource, + const gchar *name); +const gchar * mrp_resource_get_short_name (MrpResource *resource); +void mrp_resource_set_short_name (MrpResource *resource, + const gchar *short_name); +void mrp_resource_assign (MrpResource *resource, + MrpTask *task, + gint units); +GList * mrp_resource_get_assignments (MrpResource *resource); -GList * mrp_resource_get_assigned_tasks (MrpResource *resource); +GList * mrp_resource_get_assigned_tasks (MrpResource *resource); -gint mrp_resource_compare (gconstpointer a, - gconstpointer b); +gint mrp_resource_compare (gconstpointer a, + gconstpointer b); -MrpCalendar * mrp_resource_get_calendar (MrpResource *resource); +MrpCalendar * mrp_resource_get_calendar (MrpResource *resource); -void mrp_resource_set_calendar (MrpResource *resource, - MrpCalendar *calendar); +void mrp_resource_set_calendar (MrpResource *resource, + MrpCalendar *calendar); +GList * mrp_resource_get_assignment_edges (MrpResource *resource, + GList *edges); +MrpResourceAllocation mrp_resource_allocation_status (MrpResource *resource, + gint units); +gboolean mrp_resource_test_if_overallocated (MrpResource *resource); +gint mrp_resource_get_max_units_used_between (MrpResource *resource, + mrptime start, + mrptime end); +gint mrp_resource_get_available_units_between(MrpResource *resource, + mrptime start, + mrptime end); +gint mrp_resource_get_available_units_during (MrpResource *resource, + MrpTask *task); + #endif /* __MRP_RESOURCE_H__ */