Whenever I try to use RunSoon(), Magic Cap breaks into the debugger with the message:
User break at TranslateParameters+0066 parameters not set up correctly for New or Each call
I called SetUpParameters() on the parameter block before passing it to RunSoon(). What's going wrong?
You probably have your parameter block allocated on the stack of the routine that calls RunSoon(). When "soon" rolls around and your completion function is called, the routine that called RunSoon() might have exited already, so the location of the parameter block on that local stack is probably being used for something else.
Instead of creating the parameter block on the local stack, allocate a buffer with a call to NewLockedBuffer and use that buffer as your parameter block. You can safely destroy this buffer at the end of your completion function when you are no longer using it:typedef struct MyRunSoonParameters { Parameters header; ObjectID myObject; ... } MyRunSoonParameters; Private void MyRunSoonCompletionFunction(MyRunSoonParameters *params) { ... // Destroy the parameter block when we're done with it DestroyLockedBuffer((void *) params); } Method void MyClass_MyOperation(ObjectID self, ...) { MyRunSoonParameters* myParameters; ... // Allocate the parameter block in transient memory myParameters = (MyRunSoonParameters*) NewLockedBuffer(sizeof(MyRunSoonParameters)); SetUpParameters(&(myParameters->header), ...); // Set up additional parameters ... // Call RunSoon to call my function later RunSoon(true, (CompletionFunction) MyRunSoonCompletionFunction, &(myParameters->header)); ... }
I would like to have a method called periodically, or after a certain amount of time has passed. How can I do this?
There is a method, RunSoonIn(), that lets you do this. One of the parameters to this method is the amount of time in milliseconds that should elapse before your completion function is called. If you want to have a method called periodically, you can simply issue another RunSoonIn() call inside of your completion function.
Method void Timer_RunSoonIn(ObjectID self, ulong relativeMilliseconds, Boolean forUser, CompletionFunction andThen, Parameters *parameters);
One thing to keep in mind is that the relativeMilliseconds parameter means "wait until this long" not "call me within this amount of time".
I want to update a viewable from an actor I created. I set the forUser bit in my actor so I can call RedrawNow() to make a viewable redraw itself, but I always crash. What's the right way to do drawing from another actor?
You're only allowed to do user-interface related tasks on the user actor's thread. Setting the forUser bit will just cause Magic Cap to give you grief. In cases like you describe, the correct thing to do is use RunSoon() to schedule a function to do any user interface actions.
RunSoon(Boolean forUser, CompletionFunction soonProc, Parameters *parameters)
The forUser parameter should be set to true. The completion routine is a function pointer, which should be defined as follows:
Private void soonProc(MyParameters* params) { DoMyOperation(params->myObject); DestroyLockedBuffer(params); }
You should call RunSoon as follows:
paramsPtr = NewLockedBuffer(sizeof(MyParameters); SetUpParameters(¶msPtr->header, 1); paramsPtr->myObject = myObject; RunSoon(true, soonProc, ¶msPtr->header)
Your routine will be called when the user interface actor is current, at which time it is perfectly legal to call RedrawNow().
I'd like to know if data hasn't been sent from the serial server for a certain amount of time. I've called SetDefaultTimeout(outputServer, kTimeOutPeriod) after opening the port, but an exception never seems to get thrown, even when the serialServer doesn't send data for a very long time. What's the right way to do this?
The first thing to note is there's not much flow control in Magic Cap; a write will always seem to succeed. If you're expecting a response back after a write, the subsequent Read() will time out. If you really want to catch writes timing out, the best way to we could think of to do this is to use RunSoonIn(), and have the completion routine call Abort() on the serial server:
typedef struct AbortSerialServerParams { Params header; ObjectID serverToAbort; } AbortSerialServerParams; Private void AbortSerialServer(AbortSerialServerParams *params) { Abort(params->serverToAbort); DestroyTransientBuffer(params); } { AbortSerialServerParams *abortParams; ObjectID writeTimer; ... abortParams = (AbortSerialServerParams *) NewTransientBuffer(sizeof(AbortSerialServerParams)); SetUpParameters(&(abortParams->header), 1); abortParams->serverToAbort = DirectID(iSerialAServer); // Set up a timer to fire in 15 seconds. The write must complete in this // time. writeTimer = NewTransient(Timer_, nil); RunSoonIn(writeTimer, 15 * oneSecond, false, AbortSerialServer, &(abortParams->header)); // Do the write. Once the right is complete, cancel the RunSoonIn so Abort // doesn't get called. ... StopTimerNow(writeTimer); Destroy(writeTimer); ... }
I want to use some objects referred to by package indexicals in a RunSoon completion function. However, every time I use this indexical, I get the message "using direct package ID in system context" from the debugger. What can I do to fix this?
When the completion function executes, you are not necessarily in your package context so you can't use any of your package indexicals or object IDs unless they were passed to the CompletionFunction in the Parameters structure.
The easiest way to get into your package context so you can use all your object IDs and indexicals from your completionFunction is to immediately dispatch against one of your objects passed in the parameters structure.Here is a snippet of sample code that shows a RunSoon call being set up:
typedef struct { Parameters header; ObjectID responder; } HideParameters; Private void HideNow(HideParameters* params) { Hide(params->responder); // dispatch against package object DestroyLockedBuffer(params); } Private void HideSoon(ObjectID responder) { HideParameters* params; params = NewLockedBuffer(sizeof(HideParameters)); // do not allocate on stack! SetUpParameters(¶ms->header, 1); params->responder = responder; RunSoon(true, (CompletionFunction)HideNow, ¶ms->header); }
Don't forget, if you want to do any drawing or other user interface related tasks in your completion function, it has to run on the user actor thread. This is accomplished by passing TRUE as the first parameter to RunSoon().
I want something to happen in my package during idle time. When I override Idle(), though, the task does not execute until I touch the screen. Why is my idle task not being executed as it should be?
It's likely that the problem is caused by not returning a non-zero value in your Idle override. Idle is supposed to return then number of milliseconds within which it wants to be called again, with zero being a special case to indicate that your object doesn't care when it is called again.
If you return zero, and there is no other system activity, your Idle method will not be called until something else happens (like the clock updating). With non-zero values, the system will endeavor to call your Idle method within the time returned, but it is not guaranteed.
Viewable_Idle (which is probably the only implementation of Idle in your class chain) returns the shortest amount reported by the Idle methods of its subviews, with zero as the default.
The recommended incantation for your Idle's return statement is:
return SoonerIdle(InheritedIdle(self), constantMyIdleInterval);
where constantMyIdleInterval is the number of milliseconds you want as your idle interval. SoonerIdle is a utility function that returns the shorter of the two intervals, handling the special case of zero.