[Planner Dev] patch to add support for FF and SF relationships



Hi All,

Here's my first cut of FF and SF relationship support.  It seems pretty
solid, I just want to do some more testing to be sure it doesn't break
anything.  Please feel free to give it a spin and provide feedback.

As part of adding support for these relationships, I added the following
checks, disallowing the following combinations of constraints and
relationships.

* 'Must Start On' and any predecessor relationship are mutually
exclusive (you can't add one if you have the other)

* Combining 'Start No Earlier Than' and SF or FF relationships is
prohibited.

* Combining SF or FF and any other relationship is prohibited

Without the above checks you can end up with some pretty odd results and
a confusing Gantt chart.

These checks pop up a message dialog if you try to violate them.  Should
help keep scheduling sane and reasonably easy for the user to follow.

-- 
Kurt Maute <kurt maute us>
? planner/docs/user-guide/eu/Makefile
? planner/docs/user-guide/eu/Makefile.in
? planner/docs/user-guide/eu/omf_timestamp
? planner/docs/user-guide/eu/planner-eu.omf.out
? planner/libplanner/.mrp-task-manager.c.swp
Index: planner/libplanner/mrp-private.h
===================================================================
RCS file: /cvs/gnome/planner/libplanner/mrp-private.h,v
retrieving revision 1.7
diff -u -r1.7 mrp-private.h
--- planner/libplanner/mrp-private.h	4 Oct 2004 22:29:19 -0000	1.7
+++ planner/libplanner/mrp-private.h	3 Apr 2006 03:07:36 -0000
@@ -89,8 +89,8 @@
 void              imrp_task_set_work                 (MrpTask         *task,
 						      gint             work);
 MrpTaskGraphNode *imrp_task_get_graph_node           (MrpTask         *task);
-MrpConstraint     impr_task_get_constraint           (MrpTask         *task);
-void              impr_task_set_constraint           (MrpTask         *task,
+MrpConstraint     imrp_task_get_constraint           (MrpTask         *task);
+void              imrp_task_set_constraint           (MrpTask         *task,
 						      MrpConstraint    constraint);
 gint              imrp_task_get_depth                (MrpTask         *task);
 GNode *           imrp_task_get_node                 (MrpTask         *task);
Index: planner/libplanner/mrp-task-manager.c
===================================================================
RCS file: /cvs/gnome/planner/libplanner/mrp-task-manager.c,v
retrieving revision 1.18
diff -u -r1.18 mrp-task-manager.c
--- planner/libplanner/mrp-task-manager.c	25 Oct 2005 21:26:40 -0000	1.18
+++ planner/libplanner/mrp-task-manager.c	3 Apr 2006 03:07:36 -0000
@@ -115,6 +115,12 @@
 
 static GObjectClass *parent_class;
 
+static mrptime
+task_manager_calculate_task_start_from_finish (MrpTaskManager *manager,
+				    		MrpTask        *task,
+						mrptime         finish,
+						gint           *duration);
+
 
 GType
 mrp_task_manager_get_type (void)
@@ -1098,78 +1104,23 @@
 	manager->priv->needs_recalc = TRUE;
 }
 
-/* Calcluate the earliest start time that a particular predesessor relation
- * allows given.
- */
-static mrptime
-task_manager_calc_relation (MrpTask	*task,
-			    MrpRelation	*relation,
-			    MrpTask	*predecessor)
-{
-	MrpRelationType type;
-	mrptime         time;
-	/*mrptime         start, finish;*/
-	
-	/* FIXME: This does not work correctly for FF and SF. The problem is
-	 * that the start and finish times of task is not known at this stage,
-	 * so we can't really use them.
-	 */
-
-	type = mrp_relation_get_relation_type (relation);
-	
-	switch (type) {
-#if 0
-	case MRP_RELATION_FF:
-		/* finish-to-finish */
-		start =  mrp_task_get_start (task);
-		finish =  mrp_task_get_finish (task);
-
-		time = mrp_task_get_finish (predecessor) +
-			mrp_relation_get_lag (relation) - (finish - start);
-
-		break;
-		
-	case MRP_RELATION_SF:
-		/* start-to-finish */
-		start = mrp_task_get_start (task);
-		finish = mrp_task_get_finish (task);
-
-		time = mrp_task_get_start (predecessor) +
-			mrp_relation_get_lag (relation) - (finish - start);
-		break;
-#endif	
-	case MRP_RELATION_SS:
-		/* start-to-start */
-		time = mrp_task_get_start (predecessor) +
-			mrp_relation_get_lag (relation);
-		break;
-			
-	case MRP_RELATION_FS:
-	case MRP_RELATION_NONE:
-	default:
-		/* finish-to-start */
-		time = mrp_task_get_finish (predecessor) +
-			mrp_relation_get_lag (relation);
-		break;
-	}
-
-	return time;
-}
-
 /* Calculate the start time of the task by finding the latest finish of it's
  * predecessors (plus any lag). Also take constraints into consideration.
  */
 static mrptime
 task_manager_calculate_task_start (MrpTaskManager *manager,
-				   MrpTask        *task)
+				   MrpTask        *task, 
+				   gint           *duration)
 {
 	MrpTaskManagerPriv *priv;
 	MrpTask            *tmp_task;
 	GList              *predecessors, *l;
 	MrpRelation        *relation;
+	MrpRelationType     type;
 	MrpTask            *predecessor;
 	mrptime             project_start;
 	mrptime             start;
+	mrptime             finish;
 	mrptime             dep_start;
 	MrpConstraint       constraint;
 
@@ -1185,10 +1136,51 @@
 			relation = l->data;
 			predecessor = mrp_relation_get_predecessor (relation);
 
-			dep_start = task_manager_calc_relation (task,
-								relation,
-								predecessor);
+			type = mrp_relation_get_relation_type (relation);
 			
+			switch (type) {
+			case MRP_RELATION_FF:
+				/* finish-to-finish */
+				/* predecessor must finish before successor can finish */
+				finish = mrp_task_get_finish (predecessor) + mrp_relation_get_lag (relation);
+				start =  task_manager_calculate_task_start_from_finish (manager,
+											task,
+											finish,
+											duration);
+				dep_start = start;
+
+				break;
+				
+			case MRP_RELATION_SF:
+				/* start-to-finish */
+				/* predecessor must start before successor can finish */
+				finish = mrp_task_get_start (predecessor);
+				start =  task_manager_calculate_task_start_from_finish (manager,
+											task,
+											finish,
+											duration);
+
+				dep_start = mrp_task_get_start (predecessor) +
+					    mrp_relation_get_lag (relation) - (finish - start);
+				break;
+
+			case MRP_RELATION_SS:
+				/* start-to-start */
+				/* predecessor must start before successor can start */
+				dep_start = mrp_task_get_start (predecessor) +
+					    mrp_relation_get_lag (relation);
+				break;
+
+			case MRP_RELATION_FS:
+			case MRP_RELATION_NONE:
+			default:
+				/* finish-to-start */
+				/* predecessor must finish before successor can start */
+				dep_start = mrp_task_get_finish (predecessor) +
+					mrp_relation_get_lag (relation);
+				break;
+			}
+
 			start = MAX (start, dep_start);
 		}
 
@@ -1196,7 +1188,7 @@
 	}
 
 	/* Take constraint types in consideration. */
-	constraint = impr_task_get_constraint (task);
+	constraint = imrp_task_get_constraint (task);
 	switch (constraint.type) {
 	case MRP_CONSTRAINT_SNET:
 		/* Start-no-earlier-than. */
@@ -1619,6 +1611,146 @@
 	return finish;
 }
 
+/* Calculate the start time from the work needed for the task, and the effort
+ * that the allocated resources add to the task. Uses the project calendar if no
+ * resources are allocated. This function also sets the work_start property of
+ * the task, which is the first time that actually has work scheduled, this can
+ * differ from the start if start is inside a non-work period.
+ */
+static mrptime
+task_manager_calculate_task_start_from_finish (MrpTaskManager *manager,
+				    MrpTask        *task,
+				    mrptime         finish,
+				    gint           *duration)
+{
+	MrpTaskManagerPriv *priv;
+	mrptime             start;
+	mrptime             t;
+	mrptime             t1, t2;
+	mrptime             work_start;
+	gint                work;
+	gint                effort;
+	gint                delta;
+	GList              *unit_ivals, *l;
+	UnitsInterval      *unit_ival;
+	MrpTaskType         type;
+	MrpTaskSched        sched;
+	
+	priv = manager->priv;
+
+	if (task == priv->root) {
+		g_warning ("Tried to get duration of root task.");
+		return 0;
+	}
+
+	effort = 0;
+	start = finish;
+	work_start = -1;
+	t = mrp_time_align_day (start);
+
+
+	/* Milestone tasks can be special cased, no duration. */
+	type = mrp_task_get_task_type (task);
+	if (type == MRP_TASK_TYPE_MILESTONE) {
+		*duration = 0;
+		task_manager_calculate_milestone_work_start (manager, task, start);
+		return start;
+	}
+	
+	work = mrp_task_get_work (task);
+	sched = mrp_task_get_sched (task);
+
+	if (sched == MRP_TASK_SCHED_FIXED_WORK) {
+		*duration = 0;
+	} else {
+		*duration = mrp_task_get_duration (task);
+	}
+
+	while (1) {
+		unit_ivals = g_list_reverse (task_manager_get_task_units_intervals (manager, task, t));
+
+		/* If we don't get anywhere in 100 days, then the calendar must
+		 * be broken, so we abort the scheduling of this task. It's not
+		 * the best solution but fixes the issue for now.
+		 */
+		if (effort == 0 && finish - t > (60*60*24*100)) {
+			break;
+		}
+
+		if (!unit_ivals) {
+			t -= 60*60*24;
+			continue;
+		}
+		
+		for (l = unit_ivals; l; l = l->next) { 
+			unit_ival = l->data;
+
+			t1 = t + unit_ival->start;
+			t2 = t + unit_ival->end;
+			
+			/* Skip any intervals after the task ends. */
+			if (t1 > finish) {
+				continue;
+			}
+
+			/* Don't add time after the finish time of the task. */
+			t2 = MIN (t2, finish);
+
+			if (t1 == t2) {
+				continue;
+			}
+			
+			if (work_start == -1) {
+				work_start = t1;
+			}
+
+			/* Effort added by this interval. */
+			if (sched == MRP_TASK_SCHED_FIXED_WORK) {
+				delta = floor (0.5 + (double) unit_ival->units * (t2 - t1) / 100.0);
+
+				*duration += (t2 - t1);
+				
+				if (effort + delta >= work) {
+					start = t2 - floor (0.5 + (work - effort) / unit_ival->units * 100.0);
+
+					/* Subtract the spill. */
+					*duration -= floor (0.5 + (effort + delta - work) / unit_ival->units * 100.0);
+					goto done;
+				}
+			}
+			else if (sched == MRP_TASK_SCHED_FIXED_DURATION) {
+				delta = t2 - t1;
+				
+				if (effort + delta >= *duration) {
+					/* Done, make sure we don't spill. */
+					start = t2 - (*duration - effort);
+					goto done;
+				}
+			} else {
+				/* Schedule is either fixed work of fixed duration - we should never get here */
+				delta = 0;
+				g_assert_not_reached ();
+			}
+			
+			effort += delta;
+		}
+		
+		t -= 60*60*24;
+	}
+
+ done:
+
+	if (work_start == -1) {
+		work_start = start;
+	}
+	imrp_task_set_work_start (task, work_start);
+
+	g_list_foreach (unit_ivals, (GFunc) g_free, NULL);
+	g_list_free (unit_ivals);
+
+	return start;
+}
+
 static void
 task_manager_do_forward_pass_helper (MrpTaskManager *manager,
 				     MrpTask        *task)
@@ -1687,7 +1819,7 @@
 		imrp_task_set_duration (task, duration);
 	} else {
 		/* Non-summary task. */
-		t1 = task_manager_calculate_task_start (manager, task);
+		t1 = task_manager_calculate_task_start (manager, task, &duration);
 		t2 = task_manager_calculate_task_finish (manager, task, t1, &duration);
 		
 		imrp_task_set_start (task, t1);
Index: planner/libplanner/mrp-task.c
===================================================================
RCS file: /cvs/gnome/planner/libplanner/mrp-task.c,v
retrieving revision 1.14
diff -u -r1.14 mrp-task.c
--- planner/libplanner/mrp-task.c	23 Nov 2005 03:41:43 -0000	1.14
+++ planner/libplanner/mrp-task.c	3 Apr 2006 03:07:36 -0000
@@ -1047,9 +1047,12 @@
 			  glong             lag,
 			  GError          **error)
 {
-	MrpRelation    *relation;
-	MrpProject     *project;
-	MrpTaskManager *manager;
+	MrpRelation		*relation;
+	MrpProject		*project;
+	MrpTaskManager		*manager;
+	GList			*relations;
+	gchar			*tmp;
+	MrpConstraint		 constraint;
 	
 	g_return_val_if_fail (MRP_IS_TASK (task), NULL);
 	g_return_val_if_fail (MRP_IS_TASK (predecessor), NULL);
@@ -1064,6 +1067,43 @@
 		return NULL;
 	}
 	
+	relations = mrp_task_get_predecessor_relations (task);
+
+	/* check for attempt to add SF or FF relation when other relation types already present */
+	if ((type == MRP_RELATION_SF || type == MRP_RELATION_FF) && relations) {
+
+		if (type == MRP_RELATION_SF) {
+			tmp = _("Start to Finish relation type cannot be combined with other relations.");
+		} else {
+			tmp = _("Finish to Finish relation type cannot be combined with other relations.");
+		}
+
+		g_set_error (error,
+			     MRP_ERROR,
+			     MRP_ERROR_TASK_RELATION_FAILED,
+			     tmp);
+		
+		return NULL;
+	}
+
+	/* check for attempt to add SF or FF when a Start No Earlier Than constraint exists */
+	constraint = imrp_task_get_constraint (task);
+	if ((type == MRP_RELATION_SF || type == MRP_RELATION_FF) && 
+	    constraint.type == MRP_CONSTRAINT_SNET) {
+		if (type == MRP_RELATION_SF) {
+			tmp = _("Start to Finish relation type cannot be combined with Start No Earlier Than constraint.");
+		} else {
+			tmp = _("Finish to Finish relation type cannot be combined with Start No Earlier Than constraint.");
+		}
+
+		g_set_error (error,
+			     MRP_ERROR,
+			     MRP_ERROR_TASK_RELATION_FAILED,
+			     tmp);
+		
+		return NULL;
+	}
+
 	project = mrp_object_get_project (MRP_OBJECT (task));
 	manager = imrp_project_get_task_manager (project);
 	if (!mrp_task_manager_check_predecessor (manager, task, predecessor, error)) {
@@ -1766,7 +1806,7 @@
 }
 
 MrpConstraint
-impr_task_get_constraint (MrpTask *task)
+imrp_task_get_constraint (MrpTask *task)
 {
 	MrpConstraint c = { 0 };
 	
@@ -1776,7 +1816,7 @@
 }
 
 void
-impr_task_set_constraint (MrpTask *task, MrpConstraint constraint)
+imrp_task_set_constraint (MrpTask *task, MrpConstraint constraint)
 {
 	g_return_if_fail (MRP_IS_TASK (task));
 
Index: planner/src/planner-task-dialog.c
===================================================================
RCS file: /cvs/gnome/planner/src/planner-task-dialog.c,v
retrieving revision 1.37
diff -u -r1.37 planner-task-dialog.c
--- planner/src/planner-task-dialog.c	23 Apr 2005 10:33:10 -0000	1.37
+++ planner/src/planner-task-dialog.c	3 Apr 2006 03:07:37 -0000
@@ -31,6 +31,7 @@
 #include <gtk/gtk.h>
 #include <libplanner/mrp-object.h>
 #include <libplanner/mrp-project.h>
+#include <libplanner/mrp-private.h>
 #include "libplanner/mrp-paths.h"
 #include "planner-cell-renderer-list.h"
 #include "planner-assignment-model.h"
@@ -1844,13 +1845,52 @@
 task_dialog_predecessor_dialog_new (MrpTask       *task,
 				    PlannerWindow *main_window)
 {
-	MrpProject *project;
-	GladeXML   *glade;
-	GtkWidget  *dialog;
-	GtkWidget  *w;
-	GList      *tasks;
-	gchar      *filename;
-	
+	MrpProject		*project;
+	GladeXML		*glade;
+	GtkWidget		*dialog;
+	GtkWidget  		*w;
+	GList      		*tasks;
+	gchar      		*filename;
+	GList			*relations, *l;
+	MrpRelationType		 rel_type;
+	MrpConstraint		 constraint;
+	
+	/* check for attempt to add relation when Must Start On constraint is present */
+	constraint = imrp_task_get_constraint (task);
+	if (constraint.type == MRP_CONSTRAINT_MSO) {
+		dialog = gtk_message_dialog_new (GTK_WINDOW (main_window),
+						 GTK_DIALOG_DESTROY_WITH_PARENT,
+						 GTK_MESSAGE_ERROR,
+						 GTK_BUTTONS_CLOSE,
+						 "You cannot add a relationship to a task with a Must Start On constraint.");
+		
+		gtk_dialog_run (GTK_DIALOG (dialog));
+		gtk_widget_destroy (dialog);
+
+		return NULL;
+	}
+		
+
+	/* check for attempt to add relation when SF or FF already present */
+	relations = mrp_task_get_predecessor_relations (task);
+	for (l = relations; l; l = l->next) {
+
+		rel_type = mrp_relation_get_relation_type (l->data);
+		if (rel_type == MRP_RELATION_SF || rel_type == MRP_RELATION_FF) {
+ 			dialog = gtk_message_dialog_new (GTK_WINDOW (main_window),
+							 GTK_DIALOG_DESTROY_WITH_PARENT,
+							 GTK_MESSAGE_ERROR,
+							 GTK_BUTTONS_CLOSE,
+				     			 "You cannot add a relationship if a Start to Finish or Finish to Finish relationship already exists.");
+			
+			gtk_dialog_run (GTK_DIALOG (dialog));
+			gtk_widget_destroy (dialog);
+
+			return NULL;
+		}
+	}
+
+
 	mrp_object_get (task, "project", &project, NULL);
 	
 	filename = mrp_paths_get_glade_dir ("add-predecessor.glade");
@@ -1871,16 +1911,13 @@
 	w = glade_xml_get_widget (glade, "type_optionmenu");
 	g_object_set_data (G_OBJECT (dialog), "type_optionmenu", w);
 
-	/* FIXME: FF and SF are disabled for now, since the scheduler doesn't
-	 * handle them.
-	 */
 	task_dialog_setup_option_menu (w,
 				       NULL,
 				       NULL,
 				       _("Finish to start (FS)"), MRP_RELATION_FS,
-				       /*_("Finish to finish (FF)"), MRP_RELATION_FF,*/
+				       _("Finish to finish (FF)"), MRP_RELATION_FF,
 				       _("Start to start (SS)"), MRP_RELATION_SS,
-				       /*_("Start to finish (SF)"), MRP_RELATION_SF,*/
+				       _("Start to finish (SF)"), MRP_RELATION_SF,
 				       NULL);
 
 	w = glade_xml_get_widget (glade, "lag_entry");
@@ -1973,7 +2010,9 @@
 	GtkWidget *dialog;
 	
 	dialog = task_dialog_predecessor_dialog_new (data->task, data->main_window);
-	gtk_widget_show (dialog);
+	if (dialog) {
+		gtk_widget_show (dialog);
+	}
 }
 
 static void  
@@ -2134,7 +2173,11 @@
 	case 0:
 		return MRP_RELATION_FS;
 	case 1:
+		return MRP_RELATION_FF;
+	case 2:
 		return MRP_RELATION_SS;
+	case 3:
+		return MRP_RELATION_SF;
 	default:
 		g_warning ("Unknown relation type index");
 		return MRP_RELATION_FS;
@@ -2170,13 +2213,11 @@
 
 	relation = mrp_task_get_relation (data->task, predecessor);
 
-	/* FIXME: FF and SF are disabled for now. */
-	
 	list = NULL;
 	list = g_list_append (list, g_strdup (_("FS")));
-	/*list = g_list_append (list, g_strdup (_("FF")));*/
+	list = g_list_append (list, g_strdup (_("FF")));
 	list = g_list_append (list, g_strdup (_("SS")));
-	/*list = g_list_append (list, g_strdup (_("SF")));*/
+	list = g_list_append (list, g_strdup (_("SF")));
 	
 	cell->list = list;
 
@@ -2184,18 +2225,15 @@
 	case MRP_RELATION_FS:
 		cell->selected_index = 0;
 		break;
-	case MRP_RELATION_SS:
-		cell->selected_index = 1;
-		break;
-#if 0
-		/* FIXME: FF and SF disabled. Renumber indices when enabling. */
 	case MRP_RELATION_FF:
 		cell->selected_index = 1;
 		break;
+	case MRP_RELATION_SS:
+		cell->selected_index = 2;
+		break;
 	case MRP_RELATION_SF:
 		cell->selected_index = 3;
 		break;
-#endif
 	default:
 		cell->selected_index = 0;
 		break;
@@ -2271,12 +2309,54 @@
 				 gboolean            ok,
 				 DialogData         *data)
 {
-	MrpConstraint constraint;
+	MrpConstraint	 constraint;
+	GList		*relations, *l;
+	GtkWidget	*dialog;
+	MrpRelationType  rel_type;
 	
 	if (ok) {
 		constraint.time = planner_task_date_widget_get_date (PLANNER_TASK_DATE_WIDGET (widget));
 		constraint.type = planner_task_date_widget_get_constraint_type (PLANNER_TASK_DATE_WIDGET (widget));
 		
+		relations = mrp_task_get_predecessor_relations (data->task);
+
+		/* check for attempt to add MSO constraint when relations are present */
+		if (constraint.type == MRP_CONSTRAINT_MSO && relations) {
+			
+			dialog = gtk_message_dialog_new (GTK_WINDOW (data->main_window),
+							 GTK_DIALOG_DESTROY_WITH_PARENT,
+							 GTK_MESSAGE_ERROR,
+							 GTK_BUTTONS_CLOSE,
+							 "You cannot add a Must Start On constraint with predecessor relations defined for this task.");
+			
+			gtk_dialog_run (GTK_DIALOG (dialog));
+			gtk_widget_destroy (dialog);
+			gtk_widget_destroy (widget);
+
+			return;
+		}
+
+		/* check for attempt to add MSO constraint when relations are present */
+		if (constraint.type == MRP_CONSTRAINT_SNET && relations) {
+			for (l = relations; l; l = l->next) {
+
+				rel_type = mrp_relation_get_relation_type (l->data);
+				if (rel_type == MRP_RELATION_SF || rel_type == MRP_RELATION_FF) {
+					dialog = gtk_message_dialog_new (GTK_WINDOW (data->main_window),
+									 GTK_DIALOG_DESTROY_WITH_PARENT,
+									 GTK_MESSAGE_ERROR,
+									 GTK_BUTTONS_CLOSE,
+									 "You cannot add a Start No Earlier Than constraint because a Start to Finish or Finish to Finish predecessor relationship exists for this task.");
+					
+					gtk_dialog_run (GTK_DIALOG (dialog));
+					gtk_widget_destroy (dialog);
+					gtk_widget_destroy (widget);
+
+					return;
+				}
+			}
+		}
+
 		task_cmd_edit_constraint (data->main_window,
 					  data->task,
 					  &constraint);


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]