Corrected the other issue with timers in magic, which is that the

"progress report" of percent completion on certain long-running
processes (namely extraction, DEF reads, and GDS/CIF writes) was
allowing Tcl/Tk events to be processed so that the display would
be repainted and the console window updated to show the progress
instead of hanging.  But that was allowing any key macros or
commands to be entered and executed, potentially corrupting the
database while the process was running.  I have used
Tk_RestrictEvents() to prevent key and button events from being
processed until afterward.  This preserves the display updates
(which do not alter the database) while preventing commands from
being run during one of these long-running processes.  Also:
Previously, whenever a long-running process printed the status,
it would not update at the end, leaving output like "52% complete"
being the last output, leaving the impression that the process
never finished.  Changed the code so that if any partial progress
is printed, then it will always finish up with the output "100%
complete" so that it is clear to the user that everything went
according to plan.
This commit is contained in:
R. Timothy Edwards 2026-06-23 10:08:31 -04:00
parent 5cf1a46061
commit d8046fba2d
8 changed files with 111 additions and 64 deletions

View File

@ -1 +1 @@
8.3.665
8.3.666

View File

@ -719,6 +719,20 @@ CIFGenSubcells(
SearchContext scx;
int cuts, totcuts;
float pdone, plast;
bool longTime = FALSE;
#ifdef MAGIC_WRAPPER
/* This routine may be long-running and events to refresh the
* console will be periodically run to allow the progress status
* to be displayed. Force a restriction on event processing to
* exclude key and button events so that it is not possible to
* corrupt the database during GDS/CIF writes.
*/
Tk_RestrictProc *oldProc;
ClientData oldArg;
oldProc = Tk_RestrictEvents(RestrictInputProc, (ClientData)NULL, &oldArg);
#endif /* MAGIC_WRAPPER */
UndoDisable();
CIFInitCells();
@ -847,6 +861,7 @@ CIFGenSubcells(
/* Only print something if the 5-second timer has expired */
if (GrDisplayStatus == DISPLAY_BREAK_PENDING)
{
longTime = TRUE;
TxPrintf("Completed %d%%\n", (int)(pdone + 0.5));
plast = pdone;
TxFlushOut();
@ -865,6 +880,12 @@ CIFGenSubcells(
GrDisplayStatus = savedDisplayStatus;
SigRemoveTimer();
if (longTime) TxPrintf("Completed 100%%\n");
#ifdef MAGIC_WRAPPER
/* Restore full event access */
Tk_RestrictEvents(oldProc, oldArg, &oldArg);
#endif /* MAGIC_WRAPPER */
UndoEnable();
}

View File

@ -177,6 +177,20 @@ extSubtree(parentUse, reg, f)
float pdone, plast;
SearchContext scx;
int savedDisplayStatus;
bool longTime = FALSE;
#ifdef MAGIC_WRAPPER
/* This routine may be long-running and events to refresh the
* console will be periodically run to allow the progress status
* to be displayed. Force a restriction on event processing to
* exclude key and button events so that it is not possible to
* corrupt the database during extraction.
*/
Tk_RestrictProc *oldProc;
ClientData oldArg;
oldProc = Tk_RestrictEvents(RestrictInputProc, (ClientData)NULL, &oldArg);
#endif /* MAGIC_WRAPPER */
/* Use the display timer to force a 5-second progress check */
savedDisplayStatus = GrDisplayStatus;
@ -299,6 +313,7 @@ extSubtree(parentUse, reg, f)
/* Only print something if the 5-second timer has expired */
if (GrDisplayStatus == DISPLAY_BREAK_PENDING)
{
longTime = TRUE;
TxPrintf("Completed %d%%\n", (int)(pdone + 0.5));
plast = pdone;
TxFlushOut();
@ -309,7 +324,7 @@ extSubtree(parentUse, reg, f)
#ifdef MAGIC_WRAPPER
/* We need to let Tk paint the console display */
while (Tcl_DoOneEvent(TCL_DONT_WAIT) != 0);
while (Tcl_DoOneEvent(TCL_WINDOW_EVENTS | TCL_DONT_WAIT) != 0);
#endif
}
}
@ -352,11 +367,27 @@ done:
/* Output connections and node adjustments */
extOutputConns(&ha.ha_connHash, f);
HashKill(&ha.ha_connHash);
GrDisplayStatus = savedDisplayStatus;
SigRemoveTimer();
/* Clear the CU_SUB_EXTRACTED flag from all children instances */
DBCellEnum(def, extClearUseFlags, (ClientData)NULL);
/* If this cell took more than 5 seconds to extract and a
* progress update was made, then complete the progress
* update with a 100% complete message.
*/
GrDisplayStatus = savedDisplayStatus;
SigRemoveTimer();
if (longTime)
{
TxPrintf("Completed 100%%\n");
TxFlushOut();
}
#ifdef MAGIC_WRAPPER
/* Restore full event access */
Tk_RestrictEvents(oldProc, oldArg, &oldArg);
#endif /* MAGIC_WRAPPER */
}
#ifdef exactinteractions

View File

@ -2498,6 +2498,11 @@ DefRead(
NULL
};
#ifdef MAGIC_WRAPPER
Tk_RestrictProc *oldProc;
ClientData oldArg;
#endif /* MAGIC_WRAPPER */
/* "annotate" implies "dolabels" whether set or not */
if (annotate) dolabels = TRUE;
@ -2519,6 +2524,17 @@ DefRead(
return NULL;
}
#ifdef MAGIC_WRAPPER
/* This routine may be long-running and events to refresh the
* console will be periodically run to allow the progress status
* to be displayed. Force a restriction on event processing to
* exclude key and button events so that it is not possible to
* corrupt the database during DEF reads.
*/
oldProc = Tk_RestrictEvents(RestrictInputProc, (ClientData)NULL, &oldArg);
#endif /* MAGIC_WRAPPER */
/* Initialize */
TxPrintf("Reading DEF data from file %s.\n", filename);
@ -2739,5 +2755,11 @@ DefRead(
if (f != NULL) fclose(f);
UndoEnable();
#ifdef MAGIC_WRAPPER
/* Restore full event access */
Tk_RestrictEvents(oldProc, oldArg, &oldArg);
#endif /* MAGIC_WRAPPER */
return rootDef;
}

View File

@ -132,66 +132,6 @@ LefEstimate(
}
}
/* This is the original version, which doesn't use the system itimer */
/* and which is not very good at maintaining constant intervals. */
#if 0
void
LefEstimate(
int processed,
int total,
const char *item_name)
{
static int check_interval, partition;
static struct timeval tv_start;
static float last_time;
struct timeval tv;
struct timezone tz;
float cur_time, time_left;
if (!total) return;
if (processed == 0) /* Initialization */
{
GrDisplayStatus = DISPLAY_IN_PROGRESS;
check_interval = 100;
gettimeofday(&tv_start, &tz);
last_time = 0.0;
}
if (processed > check_interval)
{
gettimeofday(&tv, &tz);
cur_time = (float)(tv.tv_sec - tv_start.tv_sec)
+ ((float)(tv.tv_usec - tv_start.tv_usec) / 1.0e6);
time_left = (((float)total / (float)processed) - 1) * cur_time;
/* not likely to happen, but we don't want a divide-by-0 error */
if (cur_time == 0.0) cur_time = 1.0e-6;
partition = (int)((float)processed * (float)PRINT_INTERVAL / cur_time);
/* partition is never less than 1 nor greater than 5% of the total */
if (partition == 0) partition = 1;
if (partition > (total / 20)) partition = (total / 20);
check_interval += partition;
/* Don't print anything in intervals faster than 1 second */
if ((cur_time - last_time) < 1.0) return;
last_time = cur_time;
TxPrintf(" Processed %d of %d %s (%2.1f%%).", processed, total,
item_name, (float)(100 * processed) / (float)total);
TxPrintf(" Est. time remaining: %2.1fs\n", time_left);
TxFlushOut();
}
}
#endif /* 0 */
/*
*------------------------------------------------------------
*

View File

@ -1515,3 +1515,32 @@ Tclmagic_SafeInit(interp)
{
return Tclmagic_Init(interp);
}
/* Procedure to set up restricted Tk event processing to allow window
* exposure and updates to occur while deferring key and button press
* events. This allows window updates during potentially long-running
* processes like extraction or GDS writes but prevents commands from
* being executed while the process is still running.
*/
Tk_RestrictAction
RestrictInputProc(
ClientData clientData,
XEvent *eventPtr)
{
switch (eventPtr->type)
{
case KeyPress:
case KeyRelease:
case ButtonPress:
case ButtonRelease:
return TK_DEFER_EVENT;
case MotionNotify:
return TK_DISCARD_EVENT;
default:
return TK_PROCESS_EVENT;
}
}

View File

@ -34,5 +34,8 @@ extern const char *Tclmagic_InitStubsVersion;
extern void TclmagicRegisterCommands(Tcl_Interp *interp);
extern Tk_RestrictAction RestrictInputProc(ClientData, XEvent *);
#endif /* MAGIC_WRAPPER */
#endif /* _MAGIC__TCLTK__TCLMAGIC_H */

View File

@ -724,3 +724,4 @@ sigSetAction(int signo, sigRetVal (*handler)(int))
sigvec(signo, &sv, (struct sigvec *)NULL);
#endif
}