RunSoon() & Timers


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.


If I use RunSoon() from an Actor subclass to have some code execute on the user actor thread, when will that routine get called? Will Magic Cap pre-empt my actor to execute that function?


Magic Cap uses cooperative multitasking instead of preemptive multitasking, so usually, you'll need to call RunNext(Scheduler_) to explicitly give other actors a chance to run. Keep in mind that many Magic Cap operations also call RunNext(), so other actors might get some time even though you haven't called RunNext() explicitly. The user actor is a special case because it's responsible for handling touch inputs. When the screen is tapped, an interrupt is generated. This interrupt tells Magic Cap to make the user actor the current actor the next time an operation is called.

When you call RunSoon() from another actor, you will put the user actor into a pending state. The next time RunNext() is called, the scheduler will notice that the user actor has a pending action and will make that the current actor until the action is complete.

What this means is that if you have long stretches of code in your actor where you don't call Magic Cap operations, your actor will not get preempted at all. If you call Magic Cap operations fairly frequently, chances are, RunNext() will get called which allows the user actor to run. This is especially true if you're using the Magic Cap communications API: RunNext() is called often in these methods.


What does the SharedBuffer class do?


Shared buffers allow "concurrent" reads and writes. You read data from a shared buffer into a pointer-specified block of memory, and write data from a pointer-specified block into a shared buffer. If you want to read more data from a shared buffer than is currently in the buffer, the read operation will wait for writers to put more data into the buffer to fulfill the read request. Similarly, if you write to a shared buffer and there's not enough room to complete the write, the write operation will wait for readers to take data out so that the write can complete.

Shared buffers are circular. If you are reading data out fairly frequently, you wouldn't need to make the buffer exceedingly large. You specify the size of the buffer in the initialSize field of the NewBufferParameters block.

You can abort reads with the SetEndOfTransfer() method. Passing a non-zero value as the parameter to SetEndOfTransfer() will cause an errorConnectionTerminated exception to get thrown. Passing a zero to SetEndOfTransfer() will just make the read operation end early, returning the number of bytes that have been read so far.

You can abort reads with an exception other than errorConnectionTerminated by calling ReleaseAndFail(). If no reads are pending when you call ReleaseAndFail(), the exception will be thrown the next time ByteCount() is called.