Re: resource allocation behaviour

Lee Baylis <lee-gZngeWTYz/[email protected]> Fri, 28 Nov 2008 04:56:49 +0000
Newsgroups gmane.comp.gnome.apps.planner.devel
Message-ID <[email protected]>
Hi,

>>>> I would also like to extend the mrp-resource data schema with a 
>>>> 'maximum
>>>> allocatable
>>>> units' field definable on each resource.
>
> What is the definition of this number of units and how does it differ
> from the time that a resource has at a certain moment according to its
> calendar?

When one assigns a task to a resource in the GUI, a dialog appears 
offering the option to assign a number of units (the default presented 
in the current release of planner is 100, implying 100% - this is 
already configurable by the user).

In the resource usage view, the number of these units assigned at any 
one time is added up, and if it goes over 100 (again, implying 100%), 
then the resource is classed as overallocated and marked in red. Here, 
the figure of 100 was not configurable by the user, but was hard-coded 
in the planner-usage-row file.

The idea, as far as I can tell from reading discussions in bugzilla, is 
that people should be able to choose whether or not they actually want 
100(%) to be hard-coded as a maximum. Enabling this property on the 
resource allows a user to choose some other figure. Once set, usage 
would have to go above this new figure before the resource was marked 
red in the resource usage view and classed as overallocated. Also, 
different resources can be given different limits.

One example I have been using this for when talking to people is if 
someone only works part time, but as you say, you can already get around 
this by changing the calendar for the resource.

However, it is also usable to represent other circumstances - if you 
know one resource is more efficient than another, for example. Then 
resource 1, an experienced worker, could have a maximum of 100, and 
resource 2, an apprentice, could have a maximum of maybe 65.

Another scenario may be if you want to allocate with more precision than 
1/100 units - 1/1000, say, or one in a million.

In any case, I think having something which is configurable with a 
sensible default is better than having something which is hard-coded, as 
is currently the case.

>> 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.
>
> I'm not sure what you mean by flag. Do you mean a custom project
> property?

I have been working with a #define variable in the mrp-project.h file, 
which if set to true turns on overload checking, and false turns it off 
and goes back to 'normal'. We can leave it set to false by default in 
any further patches I release until we feel it is time to add it to the 
schema. The other possibility is having it as a compile-time option, and 
this might be worth looking into if it looks like putting through a 
schema change is going to take any length of time.

>> I reviewed everything and in general it looks pretty good. Most of my
> comments below are about rather minor issues. Most of them I would just
> fix myself before committing, but I'll mention them anyway. The number
> in front is the line number.

OK, thanks - I have attached revised patches for most of your comments 
and a changelog entry.

Further discussion:

> The first is that units_in_use is sort of a caching variable. There are
> times when the values stored in them are not up-to-date. Some functions
> have as a precondition that those values are up-to-date, so I would like
> to have them verify that precondition. To enable them to do that, some
> other functions should 'invalidate' that cache somehow.

I hadn't covered this, mainly because it's not just that units_in_use is 
a cached variable - the larger issue is that GLists of 
MrpAssignmentEdges also effectively cache information. Anything one does 
within the project which invalidates units_in_use probably invalidates 
at least part of any edges GList in existence at the same time. For that 
reason, the life cycle of all the functions I have written is to build, 
analyse and then immediately free edges GLists, also freeing any cached 
units_in_use values. Any change to the project which requires these 
functions to be called again in order to recalculate units_in_use then 
requires a rebuilding of the edges GList. This is pretty much the same 
behaviour which was already present in planner in the resource usage view.

I think this problem intellectually boils down to a design choice when 
planner was originally written, regarding whether the edge or the task 
was chosen as the fundamental object detailing project entries. For 
whatever reason, the task won, so all this current work with edges is 
made more difficult, as they have to be derived from tasks when needed. 
On the other hand, who knows what might be made more difficult if the 
edge had been chosen as the fundamental object (a task becoming more of 
a 'container' for edge objects). In any case, planner is pretty advanced 
now as it is, working with tasks.

Coming back to your point, I agree, as it stands, another developer 
could still use the functions to build and analyse an edges GList 
without freeing it, then make the mistake of assuming it represented 
live data and not a cache. I could either get around this:

- using a similar technique to the one you describe above, although I 
have no feeling right now whether it would be better to store a 
stale-cache gboolean within the edge objects, or within some other 
entity (the resource?) which then acts as a cache manager. I think, to 
be honest, this approach would not completely remove the problem - any 
other developer could still accidentally ignore a stale-cache gboolean 
when implementing other routines.

- or, alternatively, provide for a permanent GList of edge objects to be 
bound to a resource, and make provision in the code for this list to be 
updated (presumably via signals) every time the resource's assignments 
are updated. This is much more work, and would require my delving 
further into the guts of planner, but might work out more efficient in 
the end since the edges list wouldn't have to be rebuilt every time a 
calculation is made.

I did think about doing something like this latter, but I rejected it as 
it felt too much like trying to rewrite planner so that edges became a 
more fundamental quantity, the down side being duplication of 
information between edges and tasks, and confusion as to which type to 
use in different scenarios.

>> mrp-assignment:
>
> 276: this function is hard to read (because of things like using the
> enum in a subtraction and as a boolean condition). I think it can be
> made more readable and faster at the same time by not splitting out the
> 'common' part of mrp_assignment_edges_remove_to_last_before and
> mrp_assignment_edges_remove_from_first_after.

Point taken. I have removed it and the enum, and reworked the before and 
after functions as you suggested. However, the functions don't quite 
perform in the same way as the examples you gave, so they remain a 
little more long-winded. Now that they are two separate functions, 
however, I have ditched the two g_list_reverses, and used g_list_last 
instead.

> 911: I don't see why mrp_resource_test_if_overallocated() should exist.
> The MrpResourceAllocation enum is visible from the same places as this
> function is, so people could just call mrp_resource_allocation_status
> and compare.

This function doesn't just call mrp_resource_allocation_status - it 
makes a call to mrp_resource_get_max_units_used_between as well. In 
effect, it first finds out what the max units in use are, then looks up 
that number in a 'table' of overallocation conditions to see whether 
overallocation has occurred. People would have to make both these calls 
and the comparison every time they wanted to check a resource's 
overallocation status.

_______________________________________________
Planner-dev-list mailing list
[email protected]
http://mail.gnome.org/mailman/listinfo/planner-dev-list
ChangeLog (text/plain, 133 KB) - not displayed
mrp-assignment.c.patch (text/x-diff, 8.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
@@ -43,19 +44,20 @@
         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 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 +228,33 @@
 	}
 }
 
+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;
+        }
+}
+
 /**
  * mrp_assignment_new:
  *
@@ -267,8 +296,8 @@
  * 
  * Retrieves the #MrpResource associated with @assignment.
  * 
- * Return value: the resource associated with the assignment object. The reference
- * count of the resource is not increased.
+ * Return value: the resource associated with the assignment object. 
+ * The reference count of the resource is not increased.
  **/
 MrpResource *
 mrp_assignment_get_resource (MrpAssignment *assignment)
@@ -294,3 +323,212 @@
 	
 	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 units = 0;
+
+        for (e = edges; e; e = e->next) {
+		edge = e->data;
+		units += mrp_assignment_edge_get_delta_units(edge);
+		edge->units_in_use = 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)
+{
+	GList             *e;
+	MrpAssignmentEdge *edge;
+	
+	if (edges){
+		e = edges->next;
+		if (e){
+			edge = e->data;
+			while (edge->time <= time){
+				edges = g_list_delete_link(edges, edges);
+				e = edges->next;
+				if (!e) {break;}
+				edge = e->data;
+			}
+		}
+	}
+	return edges;
+}
+
+/**
+ * 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)
+{
+	GList             *e, *last;
+	MrpAssignmentEdge *edge;
+
+	if (edges){
+		last = g_list_last(edges);
+		e = last->prev;
+		if (e){
+			edge = e->data;
+			while (time <= edge->time){
+				edges = g_list_delete_link(edges, last);
+				last = g_list_last(edges);
+				e = last->prev;
+				if (!e) {break;}
+				edge = e->data;
+			}
+		}
+	}
+
+	return edges;
+}
+
+/**
+ * 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 (text/x-diff, 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 (text/x-diff, 9.4 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,206 @@
 
 	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 resource 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)); 
+}
planner-usage-row.c.patch (text/x-diff, 13.9 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,39 +1035,37 @@
 
         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);
@@ -1257,7 +1150,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 +1169,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 +1180,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);