Logo Search packages:      
Sourcecode: magic version File versions  Download package

PlowMain.c

/*
 * PlowMain.c --
 *
 * Plowing.
 * Main loop: set everything up, and then repeatedly remove an
 * edge from the queue of pending edges and process it.
 *
 *     ********************************************************************* 
 *     * Copyright (C) 1985, 1990 Regents of the University of California. * 
 *     * Permission to use, copy, modify, and distribute this              * 
 *     * software and its documentation for any purpose and without        * 
 *     * fee is hereby granted, provided that the above copyright          * 
 *     * notice appear in all copies.  The University of California        * 
 *     * makes no representations about the suitability of this            * 
 *     * software for any purpose.  It is provided "as is" without         * 
 *     * express or implied warranty.  Export of this software outside     * 
 *     * of the United States of America may require an export license.    * 
 *     *********************************************************************
 */

#ifndef lint
static char rcsid[] __attribute__ ((unused)) = "$Header: /usr/cvsroot/magic-7.5/plow/PlowMain.c,v 1.1.1.1 2006/04/10 22:03:13 tim Exp $";
#endif  /* not lint */

#include <stdio.h>
#include <string.h>
#include <sys/time.h>
#include <sys/resource.h>

#include "utils/magic.h"
#include "utils/geometry.h"
#include "tiles/tile.h"
#include "utils/hash.h"
#include "database/database.h"
#include "utils/undo.h"
#include "debug/debug.h"
#include "plow/plow.h"
#include "plow/plowInt.h"
#include "textio/textio.h"
#include "windows/windows.h"
#include "dbwind/dbwind.h"
#include "drc/drc.h"
#include "utils/styles.h"
#include "utils/malloc.h"
#include "utils/signals.h"
#include "utils/main.h"
#include "select/select.h"

#if defined(SYSV) || defined(macosx)
# define    NO_RUSAGE
#endif

/* Plowing jog horizon: see PlowExtendJogHorizon() for an explanation */
global int PlowJogHorizon = 0;

/* TRUE if we should straighten jogs automatically after each plow */
global bool PlowDoStraighten = FALSE;

/*
 * Search rule table.  These rules are used to search from a moving
 * edge to find the other edges it causes to move.  The procedure
 * implementing each rule should be of the form:
 *
 *    (*proc)(edge, rules)
 *        Edge *edge;
 *        PlowRule *rules;
 *    {
 *    }
 *
 * It may not modify the Edge pointed to by 'edge'.
 * The edge is in the cell plowYankDef.
 */
RuleTableEntry plowSearchRulesTbl[MAXRULES];
RuleTableEntry *plowSearchRulesPtr = plowSearchRulesTbl;

/*
 * Cell rules.
 * Same as above.
 */
RuleTableEntry plowCellRulesTbl[MAXRULES];
RuleTableEntry *plowCellRulesPtr = plowCellRulesTbl;

    /* Imported rules */
extern int prClearUmbra();
extern int prUmbra();
extern int prPenumbraTop(), prPenumbraBot();
extern int prFixedPenumbraTop(), prFixedPenumbraBot();
extern int prSliverTop(), prSliverBot();
extern int prInSliver();
extern int prIllegalTop(), prIllegalBot();
extern int prCoverTop(), prCoverBot();
extern int prFixedLHS(), prFixedRHS(), prFixedDragStubs();
extern int prContactLHS(), prContactRHS();
extern int prFindCells();
extern int prCell();

    /* Defined elsewhere in this module */
extern CellDef *plowYankDef;
extern CellUse *plowYankUse;
extern CellDef *plowSpareDef;
extern CellUse *plowSpareUse;
extern Rect plowYankedArea;
extern int plowYankHalo;

    /* Plow boundary information */
00106 typedef struct pb
{
    CellDef *pb_editDef;      /* Cell to which this applies */
    Rect     pb_editArea;     /* Area of boundary in edit cell coords */

    /* The following exist for redisplay only */
    CellDef *pb_rootDef;      /* Display in all windows with this root */
    Rect     pb_rootArea;     /* Area of boundary in root cell coords */
    struct pb     *pb_next;   /* Next record in chain */
} PlowBoundary;

    /* If the following is TRUE, enable checking of boundaries */
bool plowCheckBoundary = FALSE;

    /* List of PlowBoundary records above */
PlowBoundary *plowBoundaryList = NULL;

    /* TRUE if labels or cells were changed by plowing */
bool plowLabelsChanged;

    /* Transform information to yank cell coordinates */
Transform plowYankTrans;      /* Transform from original cell to yank cell */
Transform plowInverseTrans;   /* Transform from yank cell to original cell */
Rect plowCellBbox;            /* Transformed initial cell bounding box */
int plowDirection;            /* Direction of plowing (GEO_*) */

    /* Dummy whose cu_def pointer is reset to point to the def being plowed */
CellUse *plowDummyUse = (CellUse *) NULL;

    /* Debugging */
RuleTableEntry *plowCurrentRule;/* Rule currently being applied */
RuleTableEntry plowRuleInitial;     /* Dummy rule for debugging */

    /* Procedure called for each edge affected by the one being moved */
int (*plowPropagateProcPtr)() = (int (*)()) NULL;

    /* Statistics */
int plowQueuedEdges;    /* Number of edges passed to plowQueueAdd */
int plowProcessedEdges; /* Number of times plowProcessEdge called */
int plowMovedEdges;     /* Number of edges actually moved */

    /* Forward declarations */
int plowInitialPaint(), plowInitialCell();
int plowUpdatePaintTile(), plowUpdateCell();
bool plowPastBoundary();
bool plowPropagateSel();
bool plowPropagateRect();
PlowRule *plowBuildWidthRules();

void plowMergeBottom(Tile *, Plane *);
void plowInitRule();

extern void PlowRedrawBound();
extern void PlowClearBound();
extern void plowUpdate();
extern void plowSetTrans();
extern void plowProcessEdge();
extern void plowMoveEdge();
extern void plowMergeTop();
extern void plowYankCreate();


/*
 * ----------------------------------------------------------------------------
 *
 * PlowSetBound --
 *
 * Set the bounding box for plowing.
 *
 * Results:
 *    None.
 *
 * Side effects:
 *    See above.
 *
 * ----------------------------------------------------------------------------
 */

void
PlowSetBound(def, area, rootDef, rootArea)
    CellDef *def; /* Def in which bounding area applies */
    Rect *area;         /* Area in 'def' coordinates */
    CellDef *rootDef;   /* Display bounding area in windows with this root */
    Rect *rootArea;     /* Area in 'rootDef' coordinates */
{
    static bool firstTime = TRUE;
    PlowBoundary *pb;

    /* May eventually support a list, but for now, there's just one */
    PlowClearBound();
    pb = (PlowBoundary *) mallocMagic(sizeof (PlowBoundary));

    pb->pb_rootDef = rootDef;
    pb->pb_rootArea = *rootArea;
    pb->pb_editDef = def;
    pb->pb_editArea = *area;
    pb->pb_next = (PlowBoundary *) NULL;
    plowBoundaryList = pb;
    plowCheckBoundary = TRUE;

    /* Add ourselves as a client of the highlight handler */
    if (firstTime)
      DBWHLAddClient(PlowRedrawBound), firstTime = FALSE;

    /* Redisplay the highlight we just added */
    DBWHLRedraw(rootDef, rootArea, FALSE);
}

/*
 * ----------------------------------------------------------------------------
 *
 * PlowClearBound --
 *
 * Eliminate boundary checking for plowing.
 *
 * Results:
 *    None.
 *
 * Side effects:
 *    Eliminates the highlight of the plowing bounding box.
 *
 * ----------------------------------------------------------------------------
 */

void
PlowClearBound()
{
    PlowBoundary *pb;

    pb = plowBoundaryList;
    plowCheckBoundary = FALSE;
    plowBoundaryList = (PlowBoundary *) NULL;
    for ( ; pb; pb = pb->pb_next)
    {
      DBWHLRedraw(pb->pb_rootDef, &pb->pb_rootArea, TRUE);
      freeMagic((char *) pb);
    }
}

/*
 * ----------------------------------------------------------------------------
 *
 * PlowRedrawBound --
 *
 * This procedure is called by the highlight manager to redisplay
 * plowing highlights.  The window is locked before entry.
 *
 * Results:
 *    None.
 *
 * Side effects:
 *    Plowing highlight information is redrawn, if there is any
 *    that needs redisplaying.
 *
 * ----------------------------------------------------------------------------
 */

void
PlowRedrawBound(window, plane)
    MagWindow *window;        /* Window in which to redraw. */
    Plane *plane;       /* Non-space tiles on this plane indicate
                         * areas where highlights need to be
                         * redisplayed.
                         */
{
    Rect worldArea, screenArea;
    CellDef *windowRoot;
    PlowBoundary *pb;
    extern int plowBoundAlways1();  /* Forward reference. */

    /* Nothing to do if no boundaries */
    if (!plowCheckBoundary)
      return;

    windowRoot = ((CellUse *) (window->w_surfaceID))->cu_def;

    GrSetStuff(STYLE_DOTTEDHIGHLIGHTS);
    WindSurfaceToScreen(window, &window->w_surfaceArea, &worldArea);
    for (pb = plowBoundaryList; pb; pb = pb->pb_next)
    {
      /* Nothing to do if not in the right window */
      if (windowRoot != pb->pb_rootDef)
          continue;

      /* See if the current area needs to be redisplayed */
      if (!DBSrPaintArea((Tile *) NULL, plane, &pb->pb_rootArea,
            &DBAllButSpaceBits, plowBoundAlways1, (ClientData) NULL))
          continue;

      WindSurfaceToScreen(window, &pb->pb_rootArea, &screenArea);
      GeoClip(&screenArea, &worldArea);
      GrFastBox(&screenArea);
    }
}

int
plowBoundAlways1()
{
    return 1;
}

/*
 * ----------------------------------------------------------------------------
 *
 * PlowStraighten --
 *
 * Straighten all jogs in the interior of the Rect 'area' in the CellDef 'def'
 * by pulling them all in 'direction' (one of GEO_NORTH, GEO_SOUTH, GEO_EAST,
 * or GEO_WEST) in such a way as to avoid moving any edges other than the jogs.
 *
 * Results:
 *    None.
 *
 * Side effects:
 *    Modifies the geometry of def in the area 'area'.
 *
 * ----------------------------------------------------------------------------
 */

void
PlowStraighten(def, area, direction)
    CellDef *def; /* Def whose jogs we should straighten */
    Rect *area;         /* Area in which jogs are to be straightened */
    int direction;      /* Pull all jogs in this direction to straighten them */
{
    Rect changedArea, changedUserArea, yankArea;
    bool saveCheckBoundary;
    int saveJogHorizon;
    SearchContext scx;
    PaintUndoInfo ui;

    /* Make sure the yank buffers exist */
    plowYankCreate();

    /* Set the yank transforms plowYankTrans and plowInverseTrans */
    plowSetTrans(direction);

    /* Set the bounding box of this cell in yanked coordinates */
    GeoTransRect(&plowYankTrans, &def->cd_bbox, &plowCellBbox);

    /* Transform the straightening area into yank def coordinates */
    GeoTransRect(&plowYankTrans, area, &yankArea);

    /*
     * Yank into yank buffer.
     * Yank at least a tech halo around the area to make sure
     * we detect all potential design-rule violations.
     */
    plowDummyUse->cu_def = def;
    UndoDisable();
    DBCellClearDef(plowYankDef);
    plowYankedArea.r_xbot = yankArea.r_xbot - DRCTechHalo;
    plowYankedArea.r_ybot = yankArea.r_ybot - DRCTechHalo;
    plowYankedArea.r_xtop = yankArea.r_xtop + DRCTechHalo;
    plowYankedArea.r_ytop = yankArea.r_ytop + DRCTechHalo;
    scx.scx_use = plowDummyUse;
    scx.scx_trans = plowYankTrans;
    GeoTransRect(&plowInverseTrans, &plowYankedArea, &scx.scx_area);
    (void) DBCellCopyPaint(&scx, &DBAllButSpaceAndDRCBits, 0, plowYankUse);
    (void) DBCellCopyCells(&scx, plowYankUse, (Rect *) NULL);
    DBReComputeBbox(plowYankDef);
    UndoEnable();

    /* Temporarily disable boundary checking and jog horizon for the plow */
    saveCheckBoundary = plowCheckBoundary;
    saveJogHorizon = PlowJogHorizon;
    plowCheckBoundary = FALSE;
    PlowJogHorizon = 0;

    /* Reduce jogs */
    UndoDisable();
    changedArea.r_xbot = changedArea.r_xtop = 0;
    changedArea.r_ybot = changedArea.r_ytop = 0;
    plowCleanupJogs(&yankArea, &changedArea);
    UndoEnable();

    /* Debugging */
    DBWAreaChanged(plowYankDef,&TiPlaneRect,DBW_ALLWINDOWS,&DBAllButSpaceBits);
    DBReComputeBbox(plowYankDef);

    /* Restore previous state of boundary checking and jog horizon */
    plowCheckBoundary = saveCheckBoundary;
    PlowJogHorizon = saveJogHorizon;

    /* Done if nothing was changed */
    if (GEO_RECTNULL(&changedArea))
      return;

    /* Erase area in original def */
    ui.pu_def = def;
    GeoTransRect(&plowInverseTrans, &changedArea, &changedUserArea);
    GeoClip(&changedUserArea, &TiPlaneRect);
    for (ui.pu_pNum = PL_TECHDEPBASE; ui.pu_pNum < DBNumPlanes; ui.pu_pNum++)
      DBPaintPlane(def->cd_planes[ui.pu_pNum], &changedUserArea,
                  DBWriteResultTbl[TT_SPACE], &ui);

    /* Stuff from yank buffer back into original def */
    scx.scx_area = changedArea;
    scx.scx_use = plowYankUse;
    scx.scx_trans = plowInverseTrans;
    (void) DBCellCopyPaint(&scx, &DBAllButSpaceAndDRCBits, 0, plowDummyUse);
    DBReComputeBbox(def);
    DBWAreaChanged(def, &changedUserArea, DBW_ALLWINDOWS, &DBAllButSpaceBits);
    DRCCheckThis(def, TT_CHECKPAINT, &changedUserArea);
}

/*
 * ----------------------------------------------------------------------------
 *
 * PlowSelection --
 *
 * Plow the entire selection by the distance indicated.
 *
 * Results:
 *    Returns FALSE if *pdistance had to be modified in order to keep the
 *    effects of plowing entirely within the limits specified by
 *    plowBoundaryList, or TRUE otherwise.
 *
 * Side effects:
 *    Plows.
 *    If we return FALSE, then *pdistance will be modified as described above.
 *
 * ----------------------------------------------------------------------------
 */

bool
PlowSelection(def, pdistance, direction)
    CellDef *def; /* Cell being plowed */
    int *pdistance;     /* Distance to plow */
    int direction;      /* One of GEO_NORTH, GEO_SOUTH, GEO_WEST, or GEO_EAST */
{
    Rect changedArea;
    bool firstTime;

    /* Create the dummy yank buffers if they don't already exist */
    plowYankCreate();

    /* Set plowYankTrans and plowInverseTrans */
    plowSetTrans(direction);

    /* Set the bounding box of this cell in yanked coordinates */
    GeoTransRect(&plowYankTrans, &def->cd_bbox, &plowCellBbox);

    /*
     * If boundary checking is enabled, the following loop may be
     * executed several times because the original plow affected too
     * much of the circuit.  In this case, userRect gets updated to
     * the amount the plow finally did move.
     */
    firstTime = TRUE;
    while (plowPropagateSel(def, pdistance, &changedArea))
      firstTime = FALSE;

    if (!GEO_RECTNULL(&changedArea))
      plowUpdate(def, direction, &changedArea);

    return (firstTime);
}

/*
 * ----------------------------------------------------------------------------
 *
 * Plow --
 *
 * Plow a given collection of layers.
 *
 * Results:
 *    Returns FALSE if userRect had to be modified in order to keep the
 *    effects of plowing entirely within the limits specified by
 *    plowBoundaryList, or TRUE otherwise.
 *
 * Side effects:
 *    Plows.
 *    If we return FALSE, then userRect will be modified as described above.
 *
 * ----------------------------------------------------------------------------
 */

bool
Plow(def, userRect, layers, direction)
    CellDef *def;       /* Cell being plowed */
    Rect *userRect;           /* The plow.  Interpreted as per direction
                         * below.
                         */
    TileTypeBitMask layers;   /* The initial plow only sees these layers */
    int direction;            /* One of GEO_NORTH, GEO_SOUTH, GEO_WEST,
                         * or GEO_EAST.
                         */
{
#ifdef      COUNTWIDTHCALLS
    extern int plowWidthNumCalls;
    extern int plowWidthNumChoices;
#endif      /* COUNTWIDTHCALLS */
    TileTypeBitMask lc;
    Rect changedArea;
    bool firstTime;

    /* Create the dummy yank buffers if they don't already exist */
    plowYankCreate();

    /* Set plowYankTrans and plowInverseTrans */
    plowSetTrans(direction);

    /* Set the bounding box of this cell in yanked coordinates */
    GeoTransRect(&plowYankTrans, &def->cd_bbox, &plowCellBbox);

    /*
     * If boundary checking is enabled, the following loop may be
     * executed several times because the original plow affected too
     * much of the circuit.  In this case, userRect gets updated to
     * the amount the plow finally did move.
     */
    firstTime = TRUE;
    TTMaskCom2(&lc, &layers);
    while (plowPropagateRect(def, userRect, lc, &changedArea))
      firstTime = FALSE;

    if (!GEO_RECTNULL(&changedArea))
      plowUpdate(def, direction, &changedArea);

#ifdef      COUNTWIDTHCALLS
    TxPrintf("Choices = %d Calls = %d Choices/Call = %.1f\n",
      plowWidthNumChoices, plowWidthNumCalls,
      ((double) plowWidthNumChoices) / ((double) plowWidthNumCalls));
#endif      /* COUNTWIDTHCALLS */
    return (firstTime);
}

/*
 * ----------------------------------------------------------------------------
 *
 * plowUpdate --
 *
 * Update the original def after plowing.
 *
 * Results:
 *    None.
 *
 * Side effects:
 *    Updates the layout from plowYankDef.
 *
 * ----------------------------------------------------------------------------
 */

void
plowUpdate(def, direction, pChangedArea)
    CellDef *def;
    int direction;
    Rect *pChangedArea;
{
    Rect changedUserArea;
    TileTypeBitMask *m;
    PaintUndoInfo ui;

    if (SigInterruptPending)
      goto done;

      /* Mark cell as modified */
    def->cd_flags |= CDMODIFIED|CDGETNEWSTAMP;

      /* Bloat the changed area to catch edges on the LHS */
    pChangedArea->r_xbot--, pChangedArea->r_ybot--;
    pChangedArea->r_xtop++, pChangedArea->r_ytop++;
    GeoTransRect(&plowInverseTrans, pChangedArea, &changedUserArea);
    GeoClip(&changedUserArea, &TiPlaneRect);    /* SANITY */
    plowLabelsChanged = FALSE;

    /*
     * Update the cells.  Find whether each cell in the plowed
     * planes has moved, and if so, move the corresponding cell
     * in the original def.
     */
    (void) DBCellEnum(plowYankDef, plowUpdateCell, (ClientData) def);

    /*
     * Update the labels.  This consists of changing the positions
     * of each label that was dragged along with its paint.  The
     * labels come from the original def, since they didn't have
     * to be yanked.
     */
    plowUpdateLabels(plowYankDef, def, &changedUserArea);

    /*
     * Update the paint.  Erase the changed area from the original
     * layout, and then paint back from the new layout.  Use the
     * transform plowInverseTrans to transform back from the yanked
     * planes into coordinates of the original def.
     */
    ui.pu_def = def;
    for (ui.pu_pNum = PL_TECHDEPBASE; ui.pu_pNum < DBNumPlanes; ui.pu_pNum++)
    {
      /* Erase area in original def */
      DBPaintPlane(def->cd_planes[ui.pu_pNum], &changedUserArea,
                  DBWriteResultTbl[TT_SPACE], &ui);

      /* Update from yanked def */
      (void) DBSrPaintArea((Tile *) NULL, plowYankDef->cd_planes[ui.pu_pNum],
                  pChangedArea, &DBAllButSpaceBits,
                  plowUpdatePaintTile, (ClientData) &ui);
    }

    /* Ashes to ashes */
done:
    DBAdjustLabels(def, &changedUserArea);
    DBReComputeBbox(plowYankDef);
    DBReComputeBbox(def);
    m = &DBAllButSpaceBits;
    if (plowLabelsChanged) m = (TileTypeBitMask *) NULL;
    DBWAreaChanged(def, &changedUserArea, DBW_ALLWINDOWS, m);
    DRCCheckThis(def, TT_CHECKSUBCELL, &changedUserArea);

    /*
     * Final postpass: straighten any jogs in the area
     * affected by this plow operation.
     */
    if (PlowDoStraighten && !SigInterruptPending)
      PlowStraighten(def, &changedUserArea, direction);
}

/*
 * ----------------------------------------------------------------------------
 *
 * plowPropagateRect --
 *
 * Do the actual work of plowing a single plow, propagating the
 * effects of the initial plow to all geometry eventually affected by it.
 *
 * If the global bool plowCheckBoundary is TRUE, then we check against
 * the boundaries in the list plowBoundaryList for edges that violate
 * the limits set by those boundaries.  If the effects of plowing extend
 * outside of this area, we adjust userRect to the largest plowing rectangle
 * that will not cause propagation into illegal areas.  If userRect had to
 * be adjusted, we return TRUE.
 *
 * Results:
 *    Returns TRUE if we had to adjust userRect, FALSE if not.
 *    If plowCheckBoundary is FALSE, we never return TRUE.
 *
 * Side effects:
 *    See above.
 *    Sets the rectangle pointed to by 'changedArea' to be a
 *    bounding box around the area modified by plowing.
 *
 * ----------------------------------------------------------------------------
 */

bool
plowPropagateRect(def, userRect, lc, changedArea)
    CellDef *def; /* Def being plowed */
    Rect *userRect;     /* User-specified plow (we transform this) */
    TileTypeBitMask lc; /* Complement of set of layers to plow */
    Rect *changedArea;  /* Set to bounding box around area modified */
{
    Rect cellPlowRect, plowRect, r;
#ifndef     NO_RUSAGE
    struct rusage t1, t2;
#endif
    int tooFar, pNum;
    SearchContext scx;
    Edge edge;

    changedArea->r_xbot = changedArea->r_xtop = 0;
    changedArea->r_ybot = changedArea->r_ytop = 0;

    /*
     * Back off by one lambda to catch edges underneath the plow.
     * If no work to do, then we return FALSE.
     */
    GeoTransRect(&plowYankTrans, userRect, &plowRect);
    if (plowRect.r_xbot == plowRect.r_xtop)
      return (FALSE);

    cellPlowRect = plowRect;
    plowRect.r_xbot--;

      /* Clear the yank buffer for this iteration */
    DBCellClearDef(plowYankDef);

    /*
     * Part 0.
     * Yank the area of the plow, plus plowYankHalo, into a separate
     * set of tile planes.
     */
    plowDummyUse->cu_def = def;
    UndoDisable();
    scx.scx_use = plowDummyUse;
    scx.scx_trans = plowYankTrans;
    if (DebugIsSet(plowDebugID, plowDebYankAll))
    {
      scx.scx_area.r_xbot = def->cd_bbox.r_xbot - 1;
      scx.scx_area.r_ybot = def->cd_bbox.r_ybot - 1;
      scx.scx_area.r_xtop = def->cd_bbox.r_xtop + 1;
      scx.scx_area.r_ytop = def->cd_bbox.r_ytop + 1;
      GeoTransRect(&plowYankTrans, &scx.scx_area, &plowYankedArea);
    }
    else
    {
      plowYankedArea.r_xbot = plowRect.r_xbot - plowYankHalo;
      plowYankedArea.r_xtop = plowRect.r_xtop + plowYankHalo;
      plowYankedArea.r_ybot = plowRect.r_ybot - plowYankHalo;
      plowYankedArea.r_ytop = plowRect.r_ytop + plowYankHalo;
      GeoTransRect(&plowInverseTrans, &plowYankedArea, &scx.scx_area);
    }
    (void) DBCellCopyPaint(&scx, &DBAllButSpaceAndDRCBits, 0, plowYankUse);
    (void) DBCellCopyCells(&scx, plowYankUse, (Rect *) NULL);
    UndoEnable();

    /*
     * Part 1.
     * Searching.  This finds all the edges in the layout that have
     * to move, and marks them with the distance they must move.
     * Everything here works with the transformed cell.  We initialize
     * plowCurrentRule to null here so the debugging output for plowQueueAdd
     * will reflect the fact that the edges found below are "initial" edges
     * (those found by the plow itself, as opposed to by other edges).
     */
#ifndef NO_RUSAGE
    if (DebugIsSet(plowDebugID, plowDebTime))
      getrusage(RUSAGE_SELF, &t1);
#endif
    plowMovedEdges = plowProcessedEdges = plowQueuedEdges = 0;
    plowQueueInit(&plowCellBbox, plowRect.r_xtop - plowRect.r_xbot);

      /* Queue each edge found by the plowing rules */
    plowPropagateProcPtr = plowQueueAdd;

      /* Debugging */
    plowCurrentRule = &plowRuleInitial;

      /* Add the initial edges */
    for (pNum = PL_TECHDEPBASE; pNum < DBNumPlanes; pNum++)
      (void) plowSrShadowInitial(pNum, &plowRect,
                lc, plowInitialPaint, (ClientData) plowRect.r_xtop);

      /* Find any subcells crossed by the plow */
    (void) TiSrArea((Tile *) NULL, plowYankDef->cd_planes[PL_CELL],
                &cellPlowRect, plowInitialCell, (ClientData) &cellPlowRect);

      /* While edges remain, process them */
    tooFar = 0;
    while (plowQueueLeftmost(&edge))
    {
      /* Ignore edges that don't move (sanity check) */
      if (edge.e_x == edge.e_newx)
          continue;

      /*
       * If we are doing boundary checking, don't add edges to the right of
       * the boundary; just record how far they move.  Edges whose original
       * position is to the left of the boundary but cross it must still be
       * queued for processing, since they can affect edges on the right of
       * the boundary.
       */
      if (plowCheckBoundary && plowPastBoundary(def, &edge, &tooFar))
          continue;
      if (!SigInterruptPending)
          plowProcessEdge(&edge, changedArea);
    }

      /* Clean up */
    plowQueueDone();
#ifndef NO_RUSAGE
    if (DebugIsSet(plowDebugID, plowDebTime))
    {
      getrusage(RUSAGE_SELF, &t2);
      plowShowTime(&t1, &t2, plowQueuedEdges,
                  plowProcessedEdges, plowMovedEdges);
    }
#endif
    /*
     * If geometry to the right of the boundary moved, adjust
     * the user's plow.
     */
    if (tooFar)
    {
      GeoTransRect(&plowYankTrans, userRect, &r);
      r.r_xtop -= tooFar;
      GeoTransRect(&plowInverseTrans, &r, userRect);
      return (TRUE);
    }

    /* Successful plow */
    return (FALSE);
}

/*
 * ----------------------------------------------------------------------------
 *
 * plowPropagateSel --
 *
 * Do the actual work of plowing the selection, propagating the
 * effects of each initial plow to all geometry eventually affected
 * by them.
 *
 * If the global bool plowCheckBoundary is TRUE, then we check against
 * the boundaries in the list plowBoundaryList for edges that violate
 * the limits set by those boundaries.  If the effects of plowing extend
 * outside of this area, we adjust *pdistance to the largest plowing distance
 * that will not cause propagation into illegal areas.  If *pdistance had to
 * be adjusted, we return TRUE.
 *
 * Results:
 *    Returns TRUE if we had to adjust *pdistance, FALSE if not.
 *    If plowCheckBoundary is FALSE, we never return TRUE.
 *
 * Side effects:
 *    See above.
 *    Sets the rectangle pointed to by 'changedArea' to be a
 *    bounding box around the area modified by plowing.
 *
 * ----------------------------------------------------------------------------
 */

bool
plowPropagateSel(def, pdistance, changedArea)
    CellDef *def; /* Def being plowed */
    int *pdistance;     /* Distance to plow */
    Rect *changedArea;  /* Set to bounding box around area modified */
{
#ifndef NO_RUSAGE
    struct rusage t1, t2;
#endif
    int plowSelPaintBox(), plowSelCellBox();
    int plowSelPaintPlow(), plowSelCellPlow();
    Rect selBox;
    int tooFar;
    SearchContext scx;
    bool dummy;
    Edge edge;

    changedArea->r_xbot = changedArea->r_xtop = 0;
    changedArea->r_ybot = changedArea->r_ytop = 0;

    if (*pdistance <= 0)
      return (FALSE);

    /*
     * Find the bounding box for all material in the selection
     * that lies in the edit cell.
     */
    selBox.r_xbot = selBox.r_ybot = INFINITY;
    selBox.r_xtop = selBox.r_ytop = MINFINITY;
    SelEnumPaint(&DBAllButSpaceBits, TRUE, &dummy,
            plowSelPaintBox, (ClientData) &selBox);
    SelEnumCells(TRUE, &dummy, (SearchContext *) NULL,
            plowSelCellBox, (ClientData) &selBox);

    if (GEO_RECTNULL(&selBox))
      return (FALSE);

      /* Clear the yank buffer for this iteration */
    DBCellClearDef(plowYankDef);

    /*
     * Yank the area of the selection, plus plowYankHalo,
     * into a separate set of tile planes.
     */
    plowDummyUse->cu_def = def;
    UndoDisable();
    scx.scx_use = plowDummyUse;
    scx.scx_trans = plowYankTrans;
    if (DebugIsSet(plowDebugID, plowDebYankAll))
    {
      scx.scx_area.r_xbot = def->cd_bbox.r_xbot - 1;
      scx.scx_area.r_ybot = def->cd_bbox.r_ybot - 1;
      scx.scx_area.r_xtop = def->cd_bbox.r_xtop + 1;
      scx.scx_area.r_ytop = def->cd_bbox.r_ytop + 1;
      GeoTransRect(&plowYankTrans, &scx.scx_area, &plowYankedArea);
    }
    else
    {
      /* Note selBox is in parent def coordinates */
      GeoTransRect(&plowYankTrans, &selBox, &plowYankedArea);
      plowYankedArea.r_xtop += *pdistance + plowYankHalo;
      plowYankedArea.r_xbot -= plowYankHalo;
      plowYankedArea.r_ybot -= plowYankHalo;
      plowYankedArea.r_ytop += plowYankHalo;
      GeoTransRect(&plowInverseTrans, &plowYankedArea, &scx.scx_area);
    }
    (void) DBCellCopyPaint(&scx, &DBAllButSpaceAndDRCBits, 0, plowYankUse);
    (void) DBCellCopyCells(&scx, plowYankUse, (Rect *) NULL);
    UndoEnable();

    /*
     * Searching.  This finds all the edges in the layout that have
     * to move, and marks them with the distance they must move.
     * Everything here works with the transformed cell.  We initialize
     * plowCurrentRule to null here so the debugging output for plowQueueAdd
     * will reflect the fact that the edges found below are "initial" edges
     * (those found by the plow itself, as opposed to by other edges).
     */
#ifndef NO_RUSAGE
    if (DebugIsSet(plowDebugID, plowDebTime))
      getrusage(RUSAGE_SELF, &t1);
#endif
    plowMovedEdges = plowProcessedEdges = plowQueuedEdges = 0;
    plowQueueInit(&plowCellBbox, *pdistance);

      /* Queue each edge found by the plowing rules */
    plowPropagateProcPtr = plowQueueAdd;

      /* Debugging */
    plowCurrentRule = &plowRuleInitial;

      /* Add everything in the selection */
    SelEnumPaint(&DBAllButSpaceBits, TRUE, &dummy,
            plowSelPaintPlow, (ClientData) *pdistance);
    SelEnumCells(TRUE, &dummy, (SearchContext *) NULL,
            plowSelCellPlow, (ClientData) *pdistance);

      /* While edges remain, process them */
    tooFar = 0;
    while (plowQueueLeftmost(&edge))
    {
      /* Ignore edges that don't move (sanity check) */
      if (edge.e_x == edge.e_newx)
          continue;

      /*
       * If we are doing boundary checking, don't add edges to the right of
       * the boundary; just record how far they move.  Edges whose original
       * position is to the left of the boundary but cross it must still be
       * queued for processing, since they can affect edges on the right of
       * the boundary.
       */
      if (plowCheckBoundary && plowPastBoundary(def, &edge, &tooFar))
          continue;
      if (!SigInterruptPending)
          plowProcessEdge(&edge, changedArea);
    }

      /* Clean up */
    plowQueueDone();
#ifndef NO_RUSAGE
    if (DebugIsSet(plowDebugID, plowDebTime))
    {
      getrusage(RUSAGE_SELF, &t2);
      plowShowTime(&t1, &t2, plowQueuedEdges,
                  plowProcessedEdges, plowMovedEdges);
    }
#endif
    /*
     * If geometry to the right of the boundary moved,
     * adjust the plow distance.
     */
    if (tooFar)
    {
      *pdistance -= tooFar;
      return (TRUE);
    }

    /* Successful plow */
    return (FALSE);
}

/*
 * ----------------------------------------------------------------------------
 *
 * plowSelPaintBox --
 * plowSelCellBox --
 *
 * Called on behalf of plowPropagateSel() above to find the bounding
 * box for the material in the selection.  Each is called for an element
 * in the selection: a paint tile for plowSelPaintBox, or a subcell for
 * plowSelCellBox.  The bounding box *pSelBox is updated to include the
 * area of the element.
 *
 * Results:
 *    Both return 0 always.
 *
 * Side effects:
 *    Both adjust *pSelBox;
 *
 * ----------------------------------------------------------------------------
 */

int
plowSelPaintBox(rect, type, pSelBox)
    Rect *rect;
    TileType type;
    Rect *pSelBox;
{
    Rect editRect;

    GeoTransRect(&RootToEditTransform, rect, &editRect);
    GeoInclude(&editRect, pSelBox);
    return (0);
}

int
plowSelCellBox(selUse, realUse, transform, pSelBox)
    CellUse *selUse;
    CellUse *realUse;
    Transform *transform;
    Rect *pSelBox;
{
    GeoInclude(&realUse->cu_bbox, pSelBox);
    return (0);
}

/*
 * ----------------------------------------------------------------------------
 *
 * plowSelPaintPlow --
 *
 * Called on behalf of plowPropagateSel() above to queue the initial
 * edges belonging to material in the selection.  For paint, we create
 * a plow at both the right and left edges of the tile.
 *
 * Results:
 *    Returns 0 always.
 *
 * Side effects:
 *    Queues edges marked as initial (e_flags has E_ISINITIAL set).
 *
 * ----------------------------------------------------------------------------
 */

int
plowSelPaintPlow(rect, type, distance)
    Rect *rect;
    TileType type;
    int distance;
{
    int plowSelPaintAdd();
    Rect editRect, plowRect, plowLHS, plowRHS;
    TileTypeBitMask mask;

    GeoTransRect(&RootToEditTransform, rect, &editRect);
    GeoTransRect(&plowYankTrans, &editRect, &plowRect);
    plowLHS = plowRHS = plowRect;

    /* Queue the LHS */
    plowLHS.r_xtop = plowLHS.r_xbot + distance;
#ifdef      notdef
    plowAtomize(DBPlane(type), &plowLHS, plowSelPaintAdd, (ClientData) NULL);
#endif      /* notdef */
    plowLHS.r_xbot--;
    plowSrShadow(DBPlane(type), &plowLHS, DBZeroTypeBits,
                  plowInitialPaint, (ClientData) plowLHS.r_xtop);

    /* Queue the RHS */
    plowRHS.r_xbot = plowRHS.r_xtop;
    plowRHS.r_xtop += distance;
#ifdef      notdef
    plowAtomize(DBPlane(type), &plowRHS, plowSelPaintAdd, (ClientData) NULL);
#endif      /* notdef */
    plowRHS.r_xbot--;
    TTMaskSetOnlyType(&mask, type);
    plowSrShadow(DBPlane(type), &plowRHS, mask,
                  plowInitialPaint, (ClientData) plowRHS.r_xtop);

    return (0);
}

int
plowSelPaintAdd(edge)
    Edge *edge;
{
    int saveFlags = edge->e_flags;

    edge->e_flags |= E_ISINITIAL;
    plowQueueAdd(edge);
    edge->e_flags = saveFlags;
    return (0);
}

/*
 * ----------------------------------------------------------------------------
 *
 * plowSelCellPlow --
 *
 * Called on behalf of plowPropagateSel() above to queue the initial
 * edges belonging to material in the selection.  For subcells, we
 * have to look for the subcell with the same use-id in our yank
 * buffer, and then move it by its leading edge.
 *
 * Results:
 *    Returns 0 always.
 *
 * Side effects:
 *    Queues edges.
 *
 * ----------------------------------------------------------------------------
 */

int
plowSelCellPlow(selUse, realUse, transform, distance)
    CellUse *selUse;          /* Cell in selection */
    CellUse *realUse;         /* Corresponding cell in def being plowed */
    Transform *transform;     /* UNUSED */
    int distance;       /* Plow distance */
{
    int plowFindSelCell();
    ClientData save;

    /* Find the cell in the yanked def that has the same use-id as this one */
    save = realUse->cu_client;
    realUse->cu_client = (ClientData)distance;
    (void) DBCellEnum(plowYankDef, plowFindSelCell, (ClientData)realUse);
    realUse->cu_client = save;

    return (0);
}

int
plowFindSelCell(yankUse, editUse)
    CellUse *yankUse;   /* Cell in the plow yank buffer */
    CellUse *editUse;   /* Cell from the original cell def */
{
    Edge edge;

    if (strcmp(yankUse->cu_id, editUse->cu_id) != 0)
      return (0);

    edge.e_flags = 0;
    edge.e_pNum = PL_CELL;
    edge.e_use = yankUse;
    edge.e_ytop = yankUse->cu_bbox.r_ytop;
    edge.e_ybot = yankUse->cu_bbox.r_ybot;
    edge.e_x = yankUse->cu_bbox.r_xtop;
    edge.e_newx = yankUse->cu_bbox.r_xtop + (int)editUse->cu_client;
    edge.e_ltype = PLOWTYPE_CELL;
    edge.e_rtype = PLOWTYPE_CELL;
    (void) plowQueueAdd(&edge);
    return (1);
}

/*
 * ----------------------------------------------------------------------------
 *
 * PlowExtendJogHorizon --
 *
 * Search above and below 'edge' for the closest "natural" jogs within
 * plowJogHorizon of the top or bottom of the edge, and extend 'edge'
 * to these jogs if they are found.  If no jog is found in a particular
 * direction, we leave that end of 'edge' (top/bottom) alone.
 *
 * A "natural" jog is nothing more than a change in the direction of
 * an edge being followed.  The following diagram gives an example, with
 * the vertical arrows indicating the jog horizons.  Note that the edge
 * is extended up, because there is a natural jog there, but not down
 * because the jog on the bottom is outside the horizon.
 *
 *          +--------   ^           E--------
 *          |         |       E
 *          |         |       E
 *          |         v       E
 *          E                 E
 *          E                 E
 *          E                 E
 *          |         ^       |
 *          |         |       |
 *          |         |       |
 *          |         v       |
 *          |                 |
 *    --------+         --------+
 *
 * Results:
 *    None.
 *
 * Side effects:
 *    May modify its argument 'edge' by extending it vertically.
 *    May also add additional edges via plowAtomizeEdge().
 *
 * ----------------------------------------------------------------------------
 */

int
PlowExtendJogHorizon(edge)
    Edge *edge;               /* Edge being moved */
{
    int horizonTop, horizonBot, eTop, eBot;
    Tile *tpR, *tpL;
    Point startPoint;
    bool rhsChanged;
    Rect r, newEdgeR;

    if (PlowJogHorizon == 0)
      return;

    horizonTop = edge->e_ytop + PlowJogHorizon;
    horizonBot = edge->e_ybot - PlowJogHorizon;
    r.r_xbot = edge->e_x - 1;
    r.r_xtop = edge->e_x + 1;
    newEdgeR = edge->e_rect;

    /* Extend to the top */
restarttop:
    startPoint.p_x = edge->e_x - 1;
    startPoint.p_y = edge->e_ytop;
    tpL = TiSrPointNoHint(plowYankDef->cd_planes[edge->e_pNum], &startPoint);
    r.r_ybot = r.r_ytop = edge->e_ytop;

    /*
     * Walk upwards.
     * The loop terminates with r.r_ytop equal to the Y coordinate
     * of the closest jog to our top, and eTop equal to the smaller
     * of r.r_ytop and the Y coordinate of the closest point at which
     * the RHS of the edge changed type.
     */
    rhsChanged = FALSE;
    while (RIGHT(tpL) == edge->e_x
          && TiGetTypeExact(tpL) == edge->e_ltype
          && BOTTOM(tpL) < horizonTop)
    {
      r.r_ytop = TOP(tpL);
      if (plowYankMore(&r, 1, 1))
          goto restarttop;

      /*
       * Walk along RHS to see if its type changed.
       * If so, make eTop record the lowest point
       * at which this happened.
       */
      if (!rhsChanged)
          for (tpR = TR(tpL); TOP(tpR) > r.r_ybot; tpR = LB(tpR))
            if (TiGetTypeExact(tpR) != edge->e_rtype)
                rhsChanged = TRUE, eTop = BOTTOM(tpR);
      tpL = RT(tpL);
      r.r_ybot = r.r_ytop;
    }

    /* Update if within the jog horizon */
    if (r.r_ytop <= horizonTop && r.r_ytop > edge->e_ytop)
    {
      newEdgeR.r_ytop = r.r_ytop;
      edge->e_ytop = (rhsChanged) ? eTop : r.r_ytop;
    }


    /* Extend to the bottom */
restartbot:
    startPoint.p_x = edge->e_x;
    startPoint.p_y = edge->e_ybot - 1;
    tpR = TiSrPointNoHint(plowYankDef->cd_planes[edge->e_pNum], &startPoint);
    r.r_ybot = r.r_ytop = edge->e_ybot;

    /*
     * Walk down.
     * The loop terminates with r.r_ybot equal to the Y coordinate
     * of the closest jog to our bottom, and eBot equal to the larger
     * of r.r_ytop and the Y coordinate of the closest point at which
     * the RHS of the edge changed type.
     */
    rhsChanged = FALSE;
    while (LEFT(tpR) == edge->e_x && TOP(tpR) > horizonBot)
    {
      r.r_ybot = BOTTOM(tpR);
      if (plowYankMore(&r, 1, 1))
          goto restartbot;

      /* Record where the RHS type changed if it did */
      if (!rhsChanged && TiGetTypeExact(tpR) != edge->e_rtype)
          rhsChanged = TRUE, eBot = TOP(tpR);

      /* Walk up the LHS */
      for (tpL = BL(tpR); BOTTOM(tpL) < r.r_ytop; tpL = RT(tpL))
          if (TiGetTypeExact(tpL) != edge->e_ltype)
            r.r_ybot = TOP(tpL);

      if (r.r_ybot > BOTTOM(tpR))
          break;

      tpR = LB(tpR);
      r.r_ytop = r.r_ybot;
    }

    /* Update if within the jog horizon */
    if (r.r_ybot >= horizonBot && r.r_ybot < edge->e_ybot)
    {
      newEdgeR.r_ybot = r.r_ybot;
      edge->e_ybot = (rhsChanged) ? eBot : r.r_ybot;
    }

    if (newEdgeR.r_ytop > edge->e_ytop)
    {
      r = newEdgeR;
      r.r_ybot = edge->e_ytop;
      (void) plowAtomize(edge->e_pNum, &r, plowQueueAdd, (ClientData) NULL);
    }
    if (newEdgeR.r_ybot < edge->e_ybot)
    {
      r = newEdgeR;
      r.r_ytop = edge->e_ybot;
      (void) plowAtomize(edge->e_pNum, &r, plowQueueAdd, (ClientData) NULL);
    }
}

/*
 * ----------------------------------------------------------------------------
 *
 * plowSetTrans --
 *
 * Set up the transforms based on the direction we will be plowing.
 * Just use simple rotations, since we always have the inverse
 * transform around for copying stuff back.
 *
 * Results:
 *    None.
 *
 * Side effects:
 *    Sets plowYankTrans and plowInverseTrans.
 *
 * ----------------------------------------------------------------------------
 */

void
plowSetTrans(direction)
    int direction;
{
    plowDirection = direction;
    switch (direction)
    {
      case GEO_NORTH:
          plowYankTrans = Geo90Transform;
          break;
      case GEO_SOUTH:
          plowYankTrans = Geo270Transform;
          break;
      case GEO_WEST:
          plowYankTrans = Geo180Transform;
          break;
      case GEO_EAST:
          plowYankTrans = GeoIdentityTransform;
          break;
    }
    GeoInvertTrans(&plowYankTrans, &plowInverseTrans);
}

/*
 * ----------------------------------------------------------------------------
 *
 * plowPastBoundary --
 *
 * Check to see if the edge is in an illegal region according to the
 * boundaries on the list plowBoundaryList.
 *
 * Results:
 *    TRUE if we should not even bother to process this edge (because
 *    its initial position was in an invalid area), FALSE otherwise.
 *
 * Side effects:
 *    Updates *pmove to the farthest distance by which anything in an
 *    illegal area moves.
 *
 * ----------------------------------------------------------------------------
 */

bool
plowPastBoundary(def, edge, pmove)
    CellDef *def;       /* Def being plowed */
    Edge *edge;   /* Edge being moved */
    int *pmove;   /* Updated to be the maximum distance by
                         * which something moves in an illegal area.
                         */
{
    PlowBoundary *pb;
    int delta;
    bool ret;
    Rect r;

    ret = FALSE;
    delta = 0;
    for (pb = plowBoundaryList; pb; pb = pb->pb_next)
    {
      if (pb->pb_editDef != def)
          continue;

      GeoTransRect(&plowYankTrans, &pb->pb_editArea, &r);
      if (edge->e_x < r.r_xbot)
      {
          /* To the left of the boundary */
          delta = MAX(edge->e_newx, r.r_xbot) - edge->e_x;
      }
      else if (edge->e_newx > r.r_xtop)
      {
          /* To the right of the boundary */
          delta = edge->e_newx - MAX(edge->e_x, r.r_xtop);
          if (edge->e_x > r.r_xtop) ret = TRUE;
      }
      else if (edge->e_ytop > r.r_ytop || edge->e_ybot < r.r_ybot)
      {
          /* Above or below the boundary */
          delta = edge->e_newx - edge->e_x;
      }

      if (delta > *pmove) *pmove = delta;
    }

    return (ret);
}

/*
 * ----------------------------------------------------------------------------
 *
 * plowInitialPaint --
 *
 * Add one of the edges found initially by the plow to the queue
 * of edges to move.  The edge will move as far as 'xnew'.
 *
 * Results:
 *    Always returns 0.
 *
 * Side effects:
 *    Adds the edge to the queue of edges to move via plowQueueAdd().
 *
 * ----------------------------------------------------------------------------
 */

int
plowInitialPaint(edge, xnew)
    Edge *edge;
    int xnew;
{
    edge->e_newx = xnew;
    edge->e_flags = E_ISINITIAL;
    (void) plowQueueAdd(edge);
    return (0);
}

/*
 * ----------------------------------------------------------------------------
 *
 * plowInitialCell --
 *
 * Add a cell to the queue of edges to move.  The cell will move as far
 * as 'plowRect->r_xtop'.
 *
 * Results:
 *    Always returns 0.
 *
 * Side effects:
 *    Adds an edge to the queue of edges to move via plowQueueAdd().
 *
 * ----------------------------------------------------------------------------
 */

int
plowInitialCell(cellTile, plowRect)
    Tile *cellTile;
    Rect *plowRect;
{
    CellTileBody *ctb;
    CellUse *use;
    int xmove;
    Edge edge;

    edge.e_pNum = PL_CELL;
    for (ctb = (CellTileBody *) TiGetBody(cellTile); ctb; ctb = ctb->ctb_next)
    {
      use = ctb->ctb_use;
      if (use->cu_bbox.r_xbot < plowRect->r_xbot)
      {
          if (use->cu_bbox.r_xtop >= plowRect->r_xtop)
            continue;

          /* Dragging this cell by its front edge */
          xmove = plowRect->r_xtop - use->cu_bbox.r_xtop;
      }
      else
      {
          /* Pushing this cell by its back edge */
          xmove = plowRect->r_xtop - use->cu_bbox.r_xbot;
      }

      edge.e_use = use;
      edge.e_flags = E_ISINITIAL;
      edge.e_ytop = use->cu_bbox.r_ytop;
      edge.e_ybot = use->cu_bbox.r_ybot;
      edge.e_x = use->cu_bbox.r_xtop;
      edge.e_newx = use->cu_bbox.r_xtop + xmove;
      edge.e_ltype = PLOWTYPE_CELL;
      edge.e_rtype = PLOWTYPE_CELL;
      (void) plowQueueAdd(&edge);
    }

    return (0);
}

/*
 * ----------------------------------------------------------------------------
 *
 * plowProcessEdge --
 *
 * Process a single edge from the queue.
 * Plowing is rule-based, so processing an edge consists of applying
 * a sequence of rules.  The overall algorithm is:
 *
 *    - Yank more of the original cell if necessary.
 *    - Check to see if the edge has already moved; if so, we don't do
 *      anything further.
 *    - Apply extension rules.  These are allowed to extend the edge
 *      but shouldn't search for new edges to be added to the edge queue.
 *    - Compute the real width rules for this material.
 *    - Apply search rules to each remaining segment of the clipped edge.
 *      These look for other edges to be added to the edge queue.
 *    - Update the edge's position.  This consists of modifying the
 *      LEADING coordinates in the tiles along the edge, possibly
 *      splitting the tiles at the top and bottom of the edge.
 *
 * Results:
 *    None.
 *
 * Side effects:
 *    May split and merge tiles.
 *    May cause additional area to be yanked from the original cell.
 *    Updates changedArea to include any additional area modified.
 *
 * ----------------------------------------------------------------------------
 */

void
plowProcessEdge(edge, changedArea)
    Edge *edge;         /* Edge to be processed (in plowYankDef) */
    Rect *changedArea;  /* Include any additional area changed in this area */
{
    int amountToMove = edge->e_newx - edge->e_x;
    RuleTableEntry *rte;
    Tile *tp;
    Point p;
    Rect r;

    /* Debugging */
    if ((plowWhenTop && edge->e_x == plowWhenTopPoint.p_x
                 && edge->e_ytop == plowWhenTopPoint.p_y)
      || (plowWhenBot && edge->e_x == plowWhenBotPoint.p_x
                  && edge->e_ybot == plowWhenBotPoint.p_y))
    {
      plowDebugEdge(edge, (RuleTableEntry *) NULL, "matched edge");
    }

    /*
     * Process cells specially.
     * These don't get clipped, but are either processed
     * completely or not at all.
     */
    plowProcessedEdges++;
    if (edge->e_use)
    {
      if (amountToMove > (int)edge->e_use->cu_client)
      {
          /* Update area modified by plowing */
          (void) GeoInclude(&edge->e_rect, changedArea);

          /*
           * See if the cell's bbox plus the distance to move
           * forces us to yank more from the original cell.
           */
          r = edge->e_use->cu_bbox;
          r.r_xtop = edge->e_newx;
          (void) plowYankMore(&r, plowYankHalo, 1);

          /*
           * Update the cell's position.
           * We do this here so we don't see the cell a
           * second time.  The whole cell moves, so we have
           * to update the area changed by the area of the
           * cell PLUS the area swept out by its RHS.
           */
          edge->e_use->cu_client = (ClientData)amountToMove;
          r = edge->e_use->cu_bbox;
          r.r_xbot += amountToMove;
          r.r_xtop += amountToMove;
          (void) GeoInclude(&r, changedArea);

          /* Apply cell rules */
          for (rte = plowCellRulesTbl; rte < plowCellRulesPtr; rte++)
          {
            if (TTMaskHasType(&rte->rte_ltypes, edge->e_ltype)
                  && TTMaskHasType(&rte->rte_rtypes, edge->e_rtype))
            {
                plowCurrentRule = rte;
                (*rte->rte_proc)(edge, (PlowRule *) NULL);
            }
          }
          plowMovedEdges++;
      }
      return;
    }

    /*
     * Check to see if any of this edge needs to move.  If it has
     * already moved far enough, we can just return; otherwise,
     * we process the entire edge as a whole.  This check is necessary
     * to avoid infinite loops.
     */
    p.p_x = edge->e_x, p.p_y = edge->e_ytop - 1;
    tp = TiSrPointNoHint(plowYankDef->cd_planes[edge->e_pNum], &p);
    for ( ; TOP(tp) > edge->e_ybot; tp = LB(tp))
      if (TRAILING(tp) < edge->e_newx)
          goto worktodo;
    return;

    /*
     * Some or all of this edge must be moved.
     * Extend it if necessary.
     * Yank more if necessary.
     * Compute its width.
     * Apply the search rules.
     * Update the coordinates of all tiles along this edge.
     */
worktodo:

    /*
     * Extend this edge if we're reducing jogs.
     * May cause other edges to be added.
     */
    plowMovedEdges++;
    if (PlowJogHorizon > 0)
      PlowExtendJogHorizon(edge);

    /*
     * Update area modified by plowing.
     * This must happen after applying the extension rules above.
     */
    (void) GeoInclude(&edge->e_rect, changedArea);

    /* Apply search rules */
    plowApplySearchRules(edge);

    /* Update edge position */
    plowMoveEdge(edge);
}

/*
 * ----------------------------------------------------------------------------
 *
 * plowApplySearchRules --
 *
 * Apply the search rules to an edge.
 *
 * Results:
 *    None.
 *
 * Side effects:
 *    May call (*plowPropagateProcPtr)() if the search rules
 *    determine that edges must move.  May yank more.
 *    May cause additional area to be yanked from the original cell.
 *
 * ----------------------------------------------------------------------------
 */

int
plowApplySearchRules(edge)
    Edge *edge;
{
    PlowRule *widthRules, *rules;
    RuleTableEntry *rte;
    int halo;

    /*
     * Build list of width rules using computed (instead of min) width.
     * This can also yank more.
     */
    halo = plowYankHalo;
    widthRules = plowBuildWidthRules(edge, &plowCellBbox, &halo);

    /* Yank more if necessary */
    (void) plowYankMore(&edge->e_rect, halo, 1);

    /*
     * Search rules.
     * These generally won't need to yank more unless they deal
     * with entire tiles.
     */
    for (rte = plowSearchRulesTbl; rte < plowSearchRulesPtr; rte++)
    {
      if (TTMaskHasType(&rte->rte_ltypes, edge->e_ltype)
            && TTMaskHasType(&rte->rte_rtypes, edge->e_rtype))
      {
          plowCurrentRule = rte;
          switch (rte->rte_whichRules)
          {
            /* Apply no rules */
            case RTE_NULL:
                rules = (PlowRule *) NULL;
                break;

            /* Apply width rules, but use actual widths */
            case RTE_REALWIDTH:
                rules = widthRules;
                break;

            /* Apply minimum-width rules */
            case RTE_MINWIDTH:
                rules = plowWidthRulesTbl[edge->e_ltype][edge->e_rtype];
                break;

            /* Apply spacing rules */
            case RTE_SPACING:
                rules = plowSpacingRulesTbl[edge->e_ltype][edge->e_rtype];
                break;

            /* Only apply rule if no spacing rules apply */
            case RTE_NOSPACING:
                rules = plowSpacingRulesTbl[edge->e_ltype][edge->e_rtype];
                if (rules)
                  continue;
                break;
          }
          (*rte->rte_proc)(edge, rules);
      }
    }
}

/*
 * ----------------------------------------------------------------------------
 *
 * plowBuildWidthRules --
 *
 * Find the real width of the material being plowed, and construct
 * a list of width rules that is the same as the normal list for
 * this type of edge, but with the minimum distance replaced by the
 * actual width if the actual width is greater than the minimum
 * distance.  (If the actual width is less than or equal to the
 * minimum distance, we use the minimum distance instead).
 *
 * If the minimum-width rectangle about the starting edge touches the
 * boundary of the yanked area, yank more and retry.
 *
 * Results:
 *    Returns a pointer to the statically constructed rules list,
 *    or NULL if no width rules apply.
 *
 * Side effects:
 *    Since the width rules list is statically allocated, subsequent
 *    calls to plowBuildWidthRules() will trash the results of previous
 *    calls.
 *
 *    May cause more of the original cell to be yanked.
 *
 * ----------------------------------------------------------------------------
 */

PlowRule *
plowBuildWidthRules(edge, bbox, phalo)
    Edge *edge;         /* Edge being moved */
    Rect *bbox;         /* Bounding box of def being plowed */
    int *phalo;         /* Update *phalo to be the max of its initial value
                   * and each of the widths we compute for the rules
                   * we return.
                   */
{
    extern char *maskToPrint();
    static PlowRule widthRuleList[MAXRULES];
    PlowRule *prMin, *prReal;
    Rect maxBox;
    int dist;

retry:
    prMin = plowWidthRulesTbl[edge->e_ltype][edge->e_rtype];
    if (prMin == NULL)
      return ((PlowRule *) NULL);

    /* At this point, know there will be at least one rule in the list */
    for (prReal = widthRuleList;
          prMin && prReal < &widthRuleList[MAXRULES];
          prMin = prMin->pr_next, prReal++)
    {
      *prReal = *prMin;
      prReal->pr_next = prReal + 1;
      dist = plowFindWidth(edge, prMin->pr_oktypes, bbox, &maxBox);

      /* Conservative test of whether we need to yank more */
      if (plowYankMore(&maxBox, 1, 1))
      {
          if (DebugIsSet(plowDebugID, plowDebWidth))
            TxPrintf("width: yank more and retry\n");
          goto retry;
      }
      prReal->pr_dist = MAX(dist, prReal->pr_dist);
      *phalo = MAX(*phalo, dist);
      if (DebugIsSet(plowDebugID, plowDebWidth))
          TxPrintf("width: %d types: %s\n",
                  prReal->pr_dist, maskToPrint(&prReal->pr_oktypes));
    }

    (--prReal)->pr_next = (PlowRule *) NULL;
    if (DebugIsSet(plowDebugID, plowDebWidth))
      plowDebugEdge(edge, (RuleTableEntry *) NULL, "find width");

    return (widthRuleList);
}

/*
 * ----------------------------------------------------------------------------
 *
 * plowMoveEdge --
 *
 * Do the actual work of updating the coordinates of an edge.  In general,
 * the edge may have more than one tile on either side, as below:
 *
 *          |
 *          |
 *    --------+
 *          +---------
 *          |
 *          |
 *    --------+
 *          |---------
 *          |
 *    --------+
 *          |
 *
 * Updating the coordinates consists of first clipping the tiles on
 * either side so they do not extend vertically past the edge, then
 * updating the TRAILING coordinates of all tiles along the RHS, and
 * then merging vertically where possible.
 *
 * We only update TRAILING coordinates if they are not already far
 * enough to the right.
 *
 * Results:
 *    None.
 *
 * Side effects:
 *    May split and merge tiles.
 *
 * ----------------------------------------------------------------------------
 */

void
plowMoveEdge(edge)
    Edge *edge;   /* Edge to be moved */
{
    Plane *plane = plowYankDef->cd_planes[edge->e_pNum];
    Tile *tp, *tpL;
    Point p;

    /*
     * Find topmost tile along LHS of edge.
     * The goal is to clip the topmost LHS and topmost RHS tiles
     * so their tops are equal to the top of the edge.
     */
    p.p_x = edge->e_x - 1;
    p.p_y = edge->e_ytop - 1;
    tp = TiSrPointNoHint(plane, &p);
    ASSERT(RIGHT(tp) == edge->e_x, "plowMoveEdge");

    /* Only clip top tiles if we must update their coordinates */
    if (LEADING(tp) < edge->e_newx)
    {
      if (TOP(tp) > edge->e_ytop)
          (void) plowSplitY(tp, edge->e_ytop);  /* Tp is bottom tile */
      tp = TR(tp);                              /* Top tile on RHS */
      if (TOP(tp) > edge->e_ytop)
          (void) plowSplitY(tp, edge->e_ytop);  /* Tp is bottom tile */
    }
    else for (tp = TR(tp); BOTTOM(tp) >= edge->e_ytop; tp = LB(tp))
      /* Nothing */;

    /*
     * Now 'tp' is the top-right tile.  Walk down the RHS, updating TRAILING
     * coordinates if necessary, and merging each tile with its upper neighbor.
     * Stop one short of the last tile.
     */
    for (; BOTTOM(tp) > edge->e_ybot; tp = LB(tp))
    {
      if (TRAILING(tp) < edge->e_newx)
          plowSetTrailing(tp, edge->e_newx);
      plowMergeTop(tp, plane);
    }

    /*
     * Now 'tp' is the bottom-right tile.
     * Clip it if necessary, and update its TRAILING coordinate.  Merge it
     * with both its upper and lower neighbors.  If tp is the only tile on
     * the RHS, its TRAILING coordinate doesn't get updated until here.
     */
    if (TRAILING(tp) < edge->e_newx)
    {
      if (BOTTOM(tp) < edge->e_ybot)
      {
          /* Clip (no merging will be necessary) */
          tp = plowSplitY(tp, edge->e_ybot);
          plowSetTrailing(tp, edge->e_newx);
          tpL = BL(tp);
      }
      else  /* BOTTOM(tp) == edge->e_ybot */
      {
          /* Merge (no clipping was necessary) */
          tpL = BL(tp);
          plowSetTrailing(tp, edge->e_newx);
          plowMergeBottom(tp, plane);
      }

      /* Split the bottom-left tile if necessary; otherwise, merge down */
      if (BOTTOM(tpL) < edge->e_ybot)
          tpL = plowSplitY(tpL, edge->e_ybot);  /* TpL is upper tile */
      else
          plowMergeBottom(tpL, plane);
    }
    else for (tpL = BL(tp); TOP(tpL) <= edge->e_ybot; tpL = RT(tpL))
      /* Nothing */;
    plowMergeTop(tp, plane);

    /*
     * Now 'tpL' is the bottom-left tile, which has already been merged
     * with its lower neighbor.  Walk up the rest of the LHS, merging
     * each tile with its lower neighbor.
     */
    for (tp = RT(tpL); BOTTOM(tp) < edge->e_ytop; tp = RT(tp))
      plowMergeBottom(tp, plane);

    /*
     * If tp now extends above edge->e_ytop, then it must not have been split
     * way up in the first step in this procedure, which means that its LEADING
     * coordinate was already far enough to the right, which means that it was
     * not changed.  Hence, we needn't try to merge to its bottom.
     */
    if (BOTTOM(tp) == edge->e_ytop)
      plowMergeBottom(tp, plane);

    if (DebugIsSet(plowDebugID, plowDebMove))
      plowDebugEdge(edge, (RuleTableEntry *) NULL, "move");
}

/*
 * ----------------------------------------------------------------------------
 *
 * plowSplitY --
 *
 * Split a tile vertically in two, returning a pointer to
 * the newly created upper tile.
 *
 * The new tile has its ti_client edge positions set to that
 * of the original tile.
 *
 * Results:
 *    Returns a pointer to the newly created tile.
 *
 * Side effects:
 *    Splits the tile 'tp'.
 *
 * ----------------------------------------------------------------------------
 */

Tile *
plowSplitY(tp, y)
    Tile *tp;
{
    Tile *newTile;

    newTile = TiSplitY(tp, y);
    newTile->ti_client = tp->ti_client;
    TiSetBody(newTile, TiGetBody(tp));

    return (newTile);
}

/*
 * ----------------------------------------------------------------------------
 *
 * plowMergeTop --
 * plowMergeBottom --
 *
 * Merge the given tile with its upper/lower neighbor if appropriate:
 * plowMergeTop merges with the upper neighbor, plowMergeBottom with
 * the lower.  This may happen only if the types are the same, the
 * LEADING/TRAILING coordinate the same, and the LEFT and RIGHT
 * coordinates the same.
 *
 * Guarantees that the tile pointer passed it as an argument will
 * remain valid after the call.
 *
 * Results:
 *    None.
 *
 * Side effects:
 *    May cause two tiles to be joined.
 *
 * ----------------------------------------------------------------------------
 */

void
plowMergeTop(tp, plane)
    Tile *tp;
    Plane *plane;
{
    Tile *tpRT = RT(tp);

    if (TiGetTypeExact(tp) == TiGetTypeExact(tpRT)
          && LEFT(tp) == LEFT(tpRT) && RIGHT(tp) == RIGHT(tpRT)
          && LEADING(tp) == LEADING(tpRT) && TRAILING(tp) == TRAILING(tpRT))
    {
      TiJoinY(tp, tpRT, plane);
    }
}

void
plowMergeBottom(tp, plane)
    Tile *tp;
    Plane *plane;
{
    Tile *tpLB = LB(tp);

    if (TiGetTypeExact(tp) == TiGetTypeExact(tpLB)
          && LEFT(tp) == LEFT(tpLB) && RIGHT(tp) == RIGHT(tpLB)
          && LEADING(tp) == LEADING(tpLB) && TRAILING(tp) == TRAILING(tpLB))
    {
      TiJoinY(tp, tpLB, plane);
    }
}

/*
 * ----------------------------------------------------------------------------
 *
 * PlowInit --
 *
 * Initialize the rule tables for plowing.  This gets called after
 * the technology file has been read, since we need to know about
 * fixed-width objects.
 *
 * Also initialize the debugging information.
 *
 * Sets plowYankHalo to be the size of the halo around each edge that
 * we use when checking to see if plowProcessEdge must yank more area
 * from the original cell.  If this halo extends outside plowYankedArea
 * or touches it, we yank more.  The size of the halo should be such
 * that most of the plowing rules are guaranteed not to visit any area
 * outside of it.  Currently, DRCTechHalo is sufficient; only the rules
 * moving whole tiles need anything bigger than this, and they are
 * responsible for doing their own yanking if they need it.
 *
 * Results:
 *    None.
 *
 * Side effects:
 *    See above.
 *
 * ----------------------------------------------------------------------------
 */

void
PlowInit()
{
    RuleTableEntry *rp, *re;
    TileTypeBitMask allButSpace, allBits;
    TileTypeBitMask cellTypes;
    TileTypeBitMask widthL, widthR;
    TileTypeBitMask spaceL, spaceR;
    TileTypeBitMask mask;
    TileType i, j;

    /* Set the masks we will use for all the rules below */
    allButSpace = DBAllButSpaceAndDRCBits;
    allBits = DBAllTypeBits;
    TTMaskSetOnlyType(&cellTypes, PLOWTYPE_CELL);
    TTMaskZero(&widthL);
    TTMaskZero(&widthR);
    TTMaskZero(&spaceL);
    TTMaskZero(&spaceR);
    for (i = 0; i < DBNumTypes; i++)
    {
      for (j = 0; j < DBNumTypes; j++)
      {
          if (plowWidthRulesTbl[i][j])
          {
            TTMaskSetType(&widthL, i);
            TTMaskSetType(&widthR, j);
          }
          if (plowSpacingRulesTbl[i][j])
          {
            TTMaskSetType(&spaceL, i);
            TTMaskSetType(&spaceR, j);
          }
      }
    }

    /* Dummy rule for debugging */
    plowInitRule(&plowRuleInitial, (&plowRuleInitial) + 1, RTE_NULL,
                        (int (*)()) NULL,
                        "initial edge",
                        DBZeroTypeBits, DBZeroTypeBits);

    /* Cell rules */
    rp = plowCellRulesPtr;
    re = &plowCellRulesTbl[MAXRULES];
      /* Drag geometry with cells */
    plowInitRule(rp++, re, RTE_NULL, prCell,
                        "drag paint with cells",
                        allBits, cellTypes);
    if (rp >= re) rp = re;
    plowCellRulesPtr = rp;

    /* Search rules */
    rp = plowSearchRulesPtr;
    re = &plowSearchRulesTbl[MAXRULES];
      /* Clear the umbra */
    plowInitRule(rp++, re, RTE_NULL, prClearUmbra, "clear umbra",
                        allBits, allButSpace);
    plowInitRule(rp++, re, RTE_REALWIDTH, prUmbra, "umbra width",
                        widthL, widthR);
    plowInitRule(rp++, re, RTE_SPACING, prUmbra, "umbra spacing",
                        spaceL, spaceR);

      /* Clear the penumbra */
    plowInitRule(rp++, re, RTE_REALWIDTH, prPenumbraTop,
                        "top penumbra width",
                        widthL, widthR);
    plowInitRule(rp++, re, RTE_SPACING, prPenumbraTop,
                        "top penumbra spacing",
                        spaceL, spaceR);
    plowInitRule(rp++, re, RTE_REALWIDTH, prPenumbraBot,
                        "bottom penumbra width",
                        widthL, widthR);
    plowInitRule(rp++, re, RTE_SPACING, prPenumbraBot,
                        "bottom penumbra spacing",
                        spaceL, spaceR);

      /* Special penumbra searching when RHS is fixed */
    plowInitRule(rp++, re, RTE_NOSPACING, prFixedPenumbraTop,
                        "top penumbra spacing (RHS fixed-width)",
                        allBits, PlowFixedTypes);
    plowInitRule(rp++, re, RTE_NOSPACING, prFixedPenumbraBot,
                        "bottom penumbra spacing (RHS fixed-width)",
                        allBits, PlowFixedTypes);

      /* Avoid introducing slivers */
    plowInitRule(rp++, re, RTE_MINWIDTH, prSliverTop,
                        "top width slivers",
                        widthL, widthR);
    plowInitRule(rp++, re, RTE_SPACING, prSliverTop,
                        "top spacing slivers",
                        spaceL, spaceR);
    plowInitRule(rp++, re, RTE_MINWIDTH, prSliverBot,
                        "bottom width slivers",
                        widthL, widthR);
    plowInitRule(rp++, re, RTE_SPACING, prSliverBot,
                        "bottom spacing slivers",
                        spaceL, spaceR);

      /* Inside slivers (plow too small) */
    TTMaskCom2(&mask, &PlowFixedTypes);
    plowInitRule(rp++, re, RTE_NULL, prInSliver,
                        "inside slivers",
                        mask, mask);

      /* Avoid introducing illegal edges */
    plowInitRule(rp++, re, RTE_NULL, prIllegalTop,
                        "top illegal edges",
                        allBits, allBits);
    plowInitRule(rp++, re, RTE_NULL, prIllegalBot,
                        "bottom illegal edges",
                        allBits, allBits);

      /* Avoid uncovering "covered" materials (e.g, fets) */
    plowInitRule(rp++, re, RTE_NULL, prCoverTop,
                        "top covering",
                        PlowCoveredTypes, allBits);
    plowInitRule(rp++, re, RTE_NULL, prCoverBot,
                        "bottom covering",
                        PlowCoveredTypes, allBits);

      /* Preserve fixed-width objects */
    plowInitRule(rp++, re, RTE_NULL, prFixedLHS,
                        "LHS is fixed",
                        PlowFixedTypes, allBits);
    plowInitRule(rp++, re, RTE_NULL, prFixedRHS,
                        "RHS is fixed",
                        allBits, PlowFixedTypes);

      /* Fixed-width objects drag trailing stubs */
    TTMaskCom2(&mask, &PlowDragTypes);
    TTMaskClearType(&mask, TT_SPACE);
    plowInitRule(rp++, re, RTE_NULL, prFixedDragStubs,
                        "RHS fixed dragging stubs",
                        mask, PlowDragTypes);

      /* Couple contacts */
    plowInitRule(rp++, re, RTE_NULL, prContactLHS,
                        "LHS is contact",
                        PlowContactTypes, allBits);
    plowInitRule(rp++, re, RTE_NULL, prContactRHS,
                        "RHS is contact",
                        allBits, PlowContactTypes);

      /* Move cells out of the way */
    plowInitRule(rp++, re, RTE_NULL, prFindCells,
                        "find cells",
                        allBits, allBits);

    if (rp >= re) rp = re;
    plowSearchRulesPtr = rp;

    /* Initialize debugging flags */
    plowDebugInit();

    /* Initialize the yank halo */
    plowYankHalo = DRCTechHalo;
}

void
plowInitRule(rtePtr, rteEnd, whichRules, proc, name, ltypes, rtypes)
    RuleTableEntry *rtePtr;   /* Pointer to entry to be added */
    RuleTableEntry *rteEnd;   /* Pointer to one past last entry in table */
    int whichRules;           /* Which rules to use (RTE_* from earlier) */
    int (*proc)();            /* Procedure implementing the rule */
    char *name;               /* Name of this rule */
    TileTypeBitMask ltypes, rtypes;
{
    if (rtePtr >= rteEnd)
    {
      TxError("Too many rules in PlowMain.c (maximum %d)\n", MAXRULES);
      return;
    }
    rtePtr->rte_whichRules = whichRules;
    rtePtr->rte_proc = proc;
    rtePtr->rte_name = name;
    rtePtr->rte_ltypes = ltypes;
    rtePtr->rte_rtypes = rtypes;
}

/*
 * ----------------------------------------------------------------------------
 *
 * plowYankCreate --
 *
 * Create the yank buffers used for plowing.
 *
 * Results:
 *    None.
 *
 * Side effects:
 *    Creates the cells __PLOWYANK__ and __PLOWINCR__ if they
 *    don't already exist, and initializes plowYankDef, plowYankUse,
 *    and plowDummyUse.
 *
 * ----------------------------------------------------------------------------
 */

void
plowYankCreate()
{
    if (plowYankDef == NULL)
    {
      DBNewYank("__PLOWYANK__", &plowYankUse, &plowYankDef);
      DBNewYank("__PLOWYANK__", &plowDummyUse, &plowYankDef);
      DBNewYank("__PLOWINCR__", &plowSpareUse, &plowSpareDef);
    }
}

#ifndef     NO_RUSAGE
plowShowTime(t1, t2, nqueued, nprocessed, nmoved)
    struct rusage *t1, *t2;
    int nqueued, nprocessed, nmoved;
{
    double secs, usecs;

    secs = t2->ru_utime.tv_sec - t1->ru_utime.tv_sec;
    usecs = (secs * 1000000.) + (t2->ru_utime.tv_usec - t1->ru_utime.tv_usec);

    printf("%.2f sec, %d queued, %d processed, %d moved\n",
            usecs/1000000., nqueued, nprocessed, nmoved);
}
#endif

Generated by  Doxygen 1.6.0   Back to index