[Planner Dev] patch to add support for FF and SF relationships
- From: Kurt Maute <kurt maute us>
- To: Planner-Dev <planner-dev lists imendio com>
- Subject: [Planner Dev] patch to add support for FF and SF relationships
- Date: Sun, 02 Apr 2006 23:30:40 -0400
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]