summaryrefslogtreecommitdiffstats
path: root/generic
diff options
context:
space:
mode:
authorsebres <sebres@users.sourceforge.net>2017-07-03 13:23:45 (GMT)
committersebres <sebres@users.sourceforge.net>2017-07-03 13:23:45 (GMT)
commitdf949554f991f8fb4a399bdd248437012deebe40 (patch)
tree81ee1cffcddb95744155eaf3ba12e8e4941e3588 /generic
parentf629f9d07d2b465bba72bc71df2d2c9d3856bca8 (diff)
downloadtcl-df949554f991f8fb4a399bdd248437012deebe40.zip
tcl-df949554f991f8fb4a399bdd248437012deebe40.tar.gz
tcl-df949554f991f8fb4a399bdd248437012deebe40.tar.bz2
[performance] large performance increase by event servicing cycles (3x - 5x faster now);
[win] prevent listen using PeekMessage twice, and no wait anymore for too short timeouts (because windows can wait too long), compare 0µs with up-to 100µs overhead within MsgWaitForMultipleObjectsEx; [bad behavior] process idle events only as long as no other events available (now TclPeekEventQueued will be used to check new events are available in service idle cycle); [enhancement] new option "noidletasks" for command "update", so "update noidle" means "process all events but not idle";
Diffstat (limited to 'generic')
-rw-r--r--generic/tclEvent.c14
-rw-r--r--generic/tclInt.h4
-rw-r--r--generic/tclNotify.c119
-rw-r--r--generic/tclTimer.c214
4 files changed, 207 insertions, 144 deletions
diff --git a/generic/tclEvent.c b/generic/tclEvent.c
index 4db524c..85c564a 100644
--- a/generic/tclEvent.c
+++ b/generic/tclEvent.c
@@ -1410,26 +1410,28 @@ Tcl_UpdateObjCmd(
Tcl_Obj *CONST objv[]) /* Argument objects. */
{
int optionIndex;
- int flags = 0; /* Initialized to avoid compiler warning. */
- static CONST char *updateOptions[] = {"idletasks", NULL};
- enum updateOptions {REGEXP_IDLETASKS};
+ int flags = TCL_ALL_EVENTS|TCL_DONT_WAIT;
+ static CONST char *updateOptions[] = {"idletasks", "noidletasks", NULL};
+ enum updateOptions {UPDATE_IDLETASKS, UPDATE_NOIDLETASKS};
if (objc == 1) {
- flags = TCL_ALL_EVENTS|TCL_DONT_WAIT;
} else if (objc == 2) {
if (Tcl_GetIndexFromObj(interp, objv[1], updateOptions,
"option", 0, &optionIndex) != TCL_OK) {
return TCL_ERROR;
}
switch ((enum updateOptions) optionIndex) {
- case REGEXP_IDLETASKS:
+ case UPDATE_IDLETASKS:
flags = TCL_WINDOW_EVENTS|TCL_IDLE_EVENTS|TCL_DONT_WAIT;
break;
+ case UPDATE_NOIDLETASKS:
+ flags &= ~TCL_IDLE_EVENTS;
+ break;
default:
Tcl_Panic("Tcl_UpdateObjCmd: bad option index to UpdateOptions");
}
} else {
- Tcl_WrongNumArgs(interp, 1, objv, "?idletasks?");
+ Tcl_WrongNumArgs(interp, 1, objv, "?option?");
return TCL_ERROR;
}
diff --git a/generic/tclInt.h b/generic/tclInt.h
index a0bab62..4151f83 100644
--- a/generic/tclInt.h
+++ b/generic/tclInt.h
@@ -2484,6 +2484,7 @@ MODULE_SCOPE Tcl_ObjType tclByteCodeType;
MODULE_SCOPE Tcl_ObjType tclDoubleType;
MODULE_SCOPE Tcl_ObjType tclEndOffsetType;
MODULE_SCOPE Tcl_ObjType tclIntType;
+MODULE_SCOPE Tcl_ObjType tclIndexType;
MODULE_SCOPE Tcl_ObjType tclListType;
MODULE_SCOPE Tcl_ObjType tclDictType;
MODULE_SCOPE Tcl_ObjType tclProcBodyType;
@@ -2919,6 +2920,8 @@ MODULE_SCOPE int Tcl_ConcatObjCmd(ClientData clientData,
MODULE_SCOPE int Tcl_ContinueObjCmd(ClientData clientData,
Tcl_Interp *interp, int objc,
Tcl_Obj *const objv[]);
+MODULE_SCOPE void TclSetTimerEventMarker(void);
+MODULE_SCOPE int TclServiceTimerEvents(void);
MODULE_SCOPE Tcl_TimerToken TclCreateAbsoluteTimerHandler(
Tcl_Time *timePtr, Tcl_TimerProc *proc,
ClientData clientData);
@@ -2930,6 +2933,7 @@ MODULE_SCOPE TimerEntry* TclCreateTimerEntryEx(
Tcl_TimerProc *proc, Tcl_TimerDeleteProc *deleteProc,
size_t extraDataSize, int flags);
MODULE_SCOPE void TclDeleteTimerEntry(TimerEntry *entryPtr);
+MODULE_SCOPE int TclPeekEventQueued(int flags);
MODULE_SCOPE int TclDefaultBgErrorHandlerObjCmd(
ClientData clientData, Tcl_Interp *interp,
int objc, Tcl_Obj *const objv[]);
diff --git a/generic/tclNotify.c b/generic/tclNotify.c
index b45539a..e883071 100644
--- a/generic/tclNotify.c
+++ b/generic/tclNotify.c
@@ -49,6 +49,8 @@ typedef struct ThreadSpecificData {
Tcl_Event *lastEventPtr; /* Last pending event, or NULL if none. */
Tcl_Event *markerEventPtr; /* Last high-priority event in queue, or NULL
* if none. */
+ Tcl_Event *timerMarkerPtr; /* Weak pointer to last event in the queue,
+ * before timer event generation */
Tcl_Mutex queueMutex; /* Mutex to protect access to the previous
* three fields. */
int serviceMode; /* One of TCL_SERVICE_NONE or
@@ -612,7 +614,7 @@ Tcl_ServiceEvent(
{
Tcl_Event *evPtr, *prevPtr;
Tcl_EventProc *proc;
- int result;
+ int result = 0;
ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
/*
@@ -635,6 +637,13 @@ Tcl_ServiceEvent(
}
/*
+ * If timer marker reached, process timer events now.
+ */
+ if (tsdPtr->timerMarkerPtr == INT2PTR(-1) || !tsdPtr->firstEventPtr) {
+ goto timer;
+ }
+
+ /*
* Loop through all the events in the queue until we find one that can
* actually be handled.
*/
@@ -642,6 +651,14 @@ Tcl_ServiceEvent(
Tcl_MutexLock(&(tsdPtr->queueMutex));
for (evPtr = tsdPtr->firstEventPtr; evPtr != NULL;
evPtr = evPtr->nextPtr) {
+
+ /*
+ * If timer marker reached, next cycle will process timer events.
+ */
+ if (evPtr == tsdPtr->timerMarkerPtr) {
+ tsdPtr->timerMarkerPtr = INT2PTR(-1);
+ }
+
/*
* Call the handler for the event. If it actually handles the event
* then free the storage for the event. There are two tricky things
@@ -721,7 +738,85 @@ Tcl_ServiceEvent(
}
}
Tcl_MutexUnlock(&(tsdPtr->queueMutex));
- return 0;
+
+timer:
+ /*
+ * Process timer queue, if alloved and timers are enabled.
+ */
+ if ((flags & TCL_TIMER_EVENTS) && tsdPtr->timerMarkerPtr) {
+ /* reset marker */
+ tsdPtr->timerMarkerPtr = NULL;
+
+ result = TclServiceTimerEvents();
+ if (result <= 0) {
+ /* events processed, but marker to process still pending timers */
+ tsdPtr->timerMarkerPtr = INT2PTR(-1);
+ result = 1;
+ }
+ }
+
+
+ return result;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TclPeekEventQueued --
+ *
+ * Check whether some event (except idle) available (async, queued, timer).
+ *
+ * This will be used e. g. in TclServiceIdle to stop the processing of the
+ * the idle events if some "normal" event occurred.
+ *
+ * Results:
+ * Returns 1 if some event queued, 0 otherwise.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+int
+TclPeekEventQueued(
+ int flags)
+{
+ ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
+
+ return Tcl_AsyncReady()
+ || (tsdPtr->firstEventPtr)
+ || ((flags & TCL_TIMER_EVENTS) && tsdPtr->timerMarkerPtr);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TclSetTimerEventMarker --
+ *
+ * Set timer event marker to the last pending event in the queue.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+void
+TclSetTimerEventMarker(void)
+{
+ ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
+
+ if (tsdPtr->timerMarkerPtr == NULL) {
+ /* marker to last event in the queue */
+ if (!(tsdPtr->timerMarkerPtr = tsdPtr->lastEventPtr)) {
+ /* marker as "now" - queue is empty, so timers events are first */
+ tsdPtr->timerMarkerPtr = INT2PTR(-1);
+ };
+ }
}
/*
@@ -855,21 +950,12 @@ Tcl_DoOneEvent(
* TCL_TIMER_EVENTS, TCL_IDLE_EVENTS, or
* others defined by event sources. */
{
- int result = 0, oldMode;
+ int result = 0, oldMode, i = 0;
EventSource *sourcePtr;
Tcl_Time *timePtr;
ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
/*
- * The first thing we do is to service any asynchronous event handlers.
- */
-
- if (Tcl_AsyncReady()) {
- (void) Tcl_AsyncInvoke(NULL, 0);
- return 1;
- }
-
- /*
* No event flags is equivalent to TCL_ALL_EVENTS.
*/
@@ -904,7 +990,8 @@ Tcl_DoOneEvent(
}
/*
- * Ask Tcl to service a queued event, if there are any.
+ * Ask Tcl to service any asynchronous event handlers or
+ * queued event, if there are any.
*/
if (Tcl_ServiceEvent(flags)) {
@@ -921,6 +1008,8 @@ Tcl_DoOneEvent(
tsdPtr->blockTime.sec = 0;
tsdPtr->blockTime.usec = 0;
tsdPtr->blockTimeSet = 1;
+ timePtr = &tsdPtr->blockTime;
+ goto wait; /* for notifier resp. system events */
} else {
tsdPtr->blockTimeSet = 0;
}
@@ -939,7 +1028,7 @@ Tcl_DoOneEvent(
}
tsdPtr->inTraversal = 0;
- if ((flags & TCL_DONT_WAIT) || tsdPtr->blockTimeSet) {
+ if (tsdPtr->blockTimeSet) {
timePtr = &tsdPtr->blockTime;
} else {
timePtr = NULL;
@@ -949,7 +1038,7 @@ Tcl_DoOneEvent(
* Wait for a new event or a timeout. If Tcl_WaitForEvent returns -1,
* we should abort Tcl_DoOneEvent.
*/
-
+ wait:
result = Tcl_WaitForEvent(timePtr);
if (result < 0) {
result = 0;
diff --git a/generic/tclTimer.c b/generic/tclTimer.c
index 7eee89e..d3aa5aa 100644
--- a/generic/tclTimer.c
+++ b/generic/tclTimer.c
@@ -121,26 +121,13 @@ static void FreeAfterPtr(ClientData clientData);
static AfterInfo * GetAfterEvent(AfterAssocData *assocPtr, Tcl_Obj *objPtr);
static ThreadSpecificData *InitTimer(void);
static void TimerExitProc(ClientData clientData);
-static int TimerHandlerEventProc(Tcl_Event *evPtr, int flags);
-#if 0
static void TimerCheckProc(ClientData clientData, int flags);
-#endif
static void TimerSetupProc(ClientData clientData, int flags);
static void AfterObj_DupInternalRep(Tcl_Obj *, Tcl_Obj *);
static void AfterObj_FreeInternalRep(Tcl_Obj *);
static void AfterObj_UpdateString(Tcl_Obj *);
-static inline void
-QueueTimerHandlerEvent()
-{
- Tcl_Event *timerEvPtr;
-
- timerEvPtr = (Tcl_Event *) ckalloc(sizeof(Tcl_Event));
- timerEvPtr->proc = TimerHandlerEventProc;
- Tcl_QueueEvent(timerEvPtr, TCL_QUEUE_TAIL);
-}
-
/*
* Type definition.
*/
@@ -272,7 +259,7 @@ InitTimer(void)
if (tsdPtr == NULL) {
tsdPtr = TCL_TSD_INIT(&dataKey);
- Tcl_CreateEventSource(TimerSetupProc, NULL, tsdPtr);
+ Tcl_CreateEventSource(TimerSetupProc, TimerCheckProc, tsdPtr);
Tcl_CreateThreadExitHandler(TimerExitProc, NULL);
}
return tsdPtr;
@@ -303,7 +290,7 @@ TimerExitProc(
TclThreadDataKeyGet(&dataKey);
if (tsdPtr != NULL) {
- Tcl_DeleteEventSource(TimerSetupProc, NULL, tsdPtr);
+ Tcl_DeleteEventSource(TimerSetupProc, TimerCheckProc, tsdPtr);
while ((tsdPtr->lastPromptPtr) != NULL) {
TclDeleteTimerEntry(tsdPtr->lastPromptPtr);
@@ -596,7 +583,7 @@ TclDeleteTimerEntry(
static void
TimerSetupProc(
- ClientData data, /* Not used. */
+ ClientData data, /* Specific data. */
int flags) /* Event flags as passed to Tcl_DoOneEvent. */
{
Tcl_Time blockTime, *firstTime;
@@ -604,10 +591,11 @@ TimerSetupProc(
if (tsdPtr == NULL) { tsdPtr = InitTimer(); };
- if (((flags & TCL_IDLE_EVENTS) && tsdPtr->idleList )
- || ((flags & TCL_TIMER_EVENTS) && tsdPtr->timerPending)) {
+ if ( ((flags & TCL_TIMER_EVENTS) && tsdPtr->timerPending)
+ || ((flags & TCL_IDLE_EVENTS) && tsdPtr->idleList )
+ ) {
/*
- * There is an idle handler or a pending timer event, so just poll.
+ * There is a pending timer event or an idle handler, so just poll.
*/
blockTime.sec = 0;
@@ -630,15 +618,6 @@ TimerSetupProc(
blockTime.sec = 0;
blockTime.usec = 0;
}
-
- /*
- * If the first timer has expired, stick an event on the queue.
- */
-
- if (blockTime.sec == 0 && blockTime.usec == 0) {
- tsdPtr->timerPending = 1;
- QueueTimerHandlerEvent();
- }
} else {
return;
@@ -647,15 +626,13 @@ TimerSetupProc(
Tcl_SetMaxBlockTime(&blockTime);
}
-#if 0
/*
*----------------------------------------------------------------------
*
* TimerCheckProc --
*
* This function is called by Tcl_DoOneEvent to check the timer event
- * source for events. This routine checks both the idle and after timer
- * lists.
+ * source for events. This routine checks the first timer in the list.
*
* Results:
* None.
@@ -668,66 +645,63 @@ TimerSetupProc(
static void
TimerCheckProc(
- ClientData data, /* Not used. */
+ ClientData data, /* Specific data. */
int flags) /* Event flags as passed to Tcl_DoOneEvent. */
{
Tcl_Time blockTime, *firstTime;
- ThreadSpecificData *tsdPtr = InitTimer();
-
- if ((flags & TCL_TIMER_EVENTS)
- && !tsdPtr->timerPending
- ) {
- /*
- * Compute the timeout for the next timer on the list.
- */
+ ThreadSpecificData *tsdPtr = (ThreadSpecificData *)data;
- if (tsdPtr->promptList) {
- goto queuetmr;
- }
+ if (!(flags & TCL_TIMER_EVENTS)) {
+ return;
+ }
- if (!tsdPtr->timerList) {
- return;
- }
- Tcl_GetTime(&blockTime);
- firstTime = &(TimerEntry2TimerHandler(tsdPtr->timerList)->time);
- blockTime.sec = firstTime->sec - blockTime.sec;
- blockTime.usec = firstTime->usec - blockTime.usec;
- if (blockTime.usec < 0) {
- blockTime.sec -= 1;
- blockTime.usec += 1000000;
- }
- if (blockTime.sec < 0) {
- blockTime.sec = 0;
- blockTime.usec = 0;
- }
+ if (tsdPtr == NULL) { tsdPtr = InitTimer(); };
- /*
- * If the first timer has expired, stick an event on the queue.
- */
+ /* If already pending */
+ if (!tsdPtr->timerList || tsdPtr->timerPending) {
+ return;
+ }
- if (blockTime.sec == 0 && blockTime.usec == 0) {
- queuetmr:
- tsdPtr->timerPending = 1;
- QueueTimerHandlerEvent();
- }
+ /*
+ * Verify the first timer on the queue.
+ */
+ Tcl_GetTime(&blockTime);
+ firstTime = &(TimerEntry2TimerHandler(tsdPtr->timerList)->time);
+ blockTime.sec = firstTime->sec - blockTime.sec;
+ blockTime.usec = firstTime->usec - blockTime.usec;
+ if (blockTime.usec < 0) {
+ blockTime.sec -= 1;
+ blockTime.usec += 1000000;
+ }
+ if (blockTime.sec < 0) {
+ blockTime.sec = 0;
+ blockTime.usec = 0;
+ }
+
+ /*
+ * If the first timer has expired, stick an event on the queue.
+ */
+ if (blockTime.sec == 0 && blockTime.usec == 0) {
+ TclSetTimerEventMarker();
+ tsdPtr->timerPending = 1;
}
}
-#endif
/*
*----------------------------------------------------------------------
*
- * TimerHandlerEventProc --
+ * TclServiceTimerEvents --
*
- * This function is called by Tcl_ServiceEvent when a timer event reaches
- * the front of the event queue. This function handles the event by
+ * This function is called by Tcl_ServiceEvent when a timer events should
+ * be processed. This function handles the event by
* invoking the callbacks for all timers that are ready.
*
* Results:
* Returns 1 if the event was handled, meaning it should be removed from
- * the queue. Returns 0 if the event was not handled, meaning it should
- * stay on the queue. The only time the event isn't handled is if the
- * TCL_TIMER_EVENTS flag bit isn't set.
+ * the queue.
+ * Returns 0 if the event was not handled (no timer events).
+ * Returns -1 if pending timer events available, meaning the marker should
+ * stay on the queue.
*
* Side effects:
* Whatever the timer handler callback functions do.
@@ -735,25 +709,17 @@ TimerCheckProc(
*----------------------------------------------------------------------
*/
-static int
-TimerHandlerEventProc(
- Tcl_Event *evPtr, /* Event to service. */
- int flags) /* Flags that indicate what events to handle,
- * such as TCL_FILE_EVENTS. */
+int
+TclServiceTimerEvents(void)
{
TimerEntry *entryPtr, *nextPtr;
Tcl_Time time;
size_t currentGeneration, currentEpoch;
ThreadSpecificData *tsdPtr = InitTimer();
- /*
- * Do nothing if timers aren't enabled. This leaves the event on the
- * queue, so we will get to it as soon as ServiceEvents() is called with
- * timers enabled.
- */
- if (!(flags & TCL_TIMER_EVENTS)) {
- return 0;
+ if (!tsdPtr->timerPending) {
+ return 0; /* no timer events */
}
/*
@@ -798,10 +764,10 @@ TimerHandlerEventProc(
ckfree((char *) entryPtr);
}
- /* if pending prompt events (new generation) - repeat event cycle right now */
+ /* if stil pending prompt events (new generation) - repeat event cycle as
+ * soon as possible */
if (tsdPtr->promptList) {
- tsdPtr->timerPending = 1;
- return 0; /* leave handler event in the queue */
+ return -1;
}
/* Hereafter all timer events with time before now */
@@ -859,10 +825,10 @@ TimerHandlerEventProc(
}
done:
- /* don't need to queue event again by pending timer events */
+ /* pending timer events, so mark (queue) timer events */
if (tsdPtr->timerPending > 1) {
tsdPtr->timerPending = 1;
- return 0; /* leave handler event in the queue */
+ return -1;
}
/* Reset generation if both timer queue are empty */
@@ -873,7 +839,7 @@ done:
/* Compute the next timeout (later via TimerSetupProc using the first timer). */
tsdPtr->timerPending = 0;
- return 1; /* processing done, again later via TimerSetupProc */
+ return 1; /* processing done, again later via TimerCheckProc */
}
/*
@@ -925,11 +891,9 @@ TclCreateTimerEntryEx(
/* attach to the prompt queue */
TclSpliceTailEx(entryPtr, tsdPtr->promptList, tsdPtr->lastPromptPtr);
- /* execute immediately: queue handler event right now */
- if (!tsdPtr->timerPending) {
- QueueTimerHandlerEvent();
- }
- tsdPtr->timerPending++; /* queued and TimerSetupProc knows about */
+ /* execute immediately: signal pending and set timer marker */
+ tsdPtr->timerPending++;
+ TclSetTimerEventMarker();
} else {
/* idle generation */
entryPtr->generation = tsdPtr->idleGeneration;
@@ -1018,7 +982,7 @@ Tcl_CancelIdleCall(
/*
*----------------------------------------------------------------------
*
- * TclServiceIdle --
+ * TclServiceIdle -- , TclServiceIdleEx --
*
* This function is invoked by the notifier when it becomes idle. It will
* invoke all idle handlers that are present at the time the call is
@@ -1035,14 +999,14 @@ Tcl_CancelIdleCall(
*/
int
-TclServiceIdle(void)
+TclServiceIdleEx(
+ int count)
{
TimerEntry *idlePtr;
size_t currentGeneration;
- Tcl_Time blockTime;
ThreadSpecificData *tsdPtr = InitTimer();
- if (tsdPtr->idleList == NULL) {
+ if ((idlePtr = tsdPtr->idleList) == NULL) {
return 0;
}
@@ -1065,9 +1029,7 @@ TclServiceIdle(void)
* during the call.
*/
- while ((idlePtr = tsdPtr->idleList) != NULL
- && idlePtr->generation <= currentGeneration
- ) {
+ while (idlePtr->generation <= currentGeneration) {
/* detach entry from the owner's list */
TclSpliceOutEx(idlePtr, tsdPtr->idleList, tsdPtr->lastIdlePtr);
@@ -1079,18 +1041,28 @@ TclServiceIdle(void)
(*idlePtr->deleteProc)(idlePtr->clientData);
}
ckfree((char *) idlePtr);
+
+ /* stop processing idle if no more idle, count reached or other queued */
+ if ( (idlePtr = tsdPtr->idleList) == NULL
+ || !--count
+ || TclPeekEventQueued(TCL_ALL_EVENTS)
+ ) {
+ break;
+ }
}
- if (tsdPtr->idleList) {
- blockTime.sec = 0;
- blockTime.usec = 0;
- Tcl_SetMaxBlockTime(&blockTime);
- }
+
/* Reset generation */
if (!tsdPtr->idleList) {
tsdPtr->idleGeneration = 0;
}
return 1;
}
+
+int
+TclServiceIdle(void)
+{
+ return TclServiceIdleEx(INT_MAX);
+}
/*
*----------------------------------------------------------------------
@@ -1152,21 +1124,17 @@ Tcl_AfterObjCmd(
* First lets see if the command was passed a number as the first argument.
*/
- if (objv[1]->typePtr == &tclIntType
-#ifndef NO_WIDE_TYPE
- || objv[1]->typePtr == &tclWideIntType
-#endif
- || objv[1]->typePtr == &tclBignumType
- || ( Tcl_GetIndexFromObj(NULL, objv[1], afterSubCmds, "", 0,
- &index) != TCL_OK )) {
- index = -1;
- if (Tcl_GetWideIntFromObj(NULL, objv[1], &ms) != TCL_OK) {
- Tcl_AppendResult(interp, "bad argument \"",
- Tcl_GetString(objv[1]),
- "\": must be cancel, idle, info, or an integer",
- NULL);
- return TCL_ERROR;
- }
+ index = -1;
+ if ( ( objv[1]->typePtr == &tclIndexType
+ || Tcl_GetWideIntFromObj(NULL, objv[1], &ms) != TCL_OK
+ )
+ && Tcl_GetIndexFromObj(NULL, objv[1], afterSubCmds, "", 0,
+ &index) != TCL_OK
+ ) {
+ Tcl_AppendResult(interp, "bad argument \"",
+ Tcl_GetString(objv[1]),
+ "\": must be cancel, idle, info, or an integer", NULL);
+ return TCL_ERROR;
}
/*