/* This little demo show how to write classes which need a long time to render their contents. In this case, we take a little fractal algorithm as example. The actual calculations are done in a separate task, the display is updated from time to time. */ #include "demo.h" #include #include /* Pixel dimensions of our fractal */ #define FRACTALWIDTH 300 #define FRACTALHEIGHT 300 /* Fractal Description */ struct FractalDesc { float left; float right; float top; float bottom; }; #define MaxIterations 60 /* Attributes and methods for the custom class */ #define MUISERIALNR_STUNTZI 1 #define TAGBASE_STUNTZI (TAG_USER | ( MUISERIALNR_STUNTZI << 16)) #define MUIM_Class4_Update (TAGBASE_STUNTZI | 0x0001) struct MUIP_Class4_Update { ULONG id; LONG percent; }; #define MUIM_Class4_Calc (TAGBASE_STUNTZI | 0x0002) struct MUIP_Class4_Calc { ULONG id; struct FractalDesc *fd; }; #define MUIA_Class4_Percent (TAGBASE_STUNTZI | 0x0003) #define STC_START 0 #define STC_STOP 1 /* Instance Data for the fractal class */ struct Data { struct SignalSemaphore sema; /* data item protection */ Object *app; /* pointer to application */ Object *self; /* pointer to ourselves */ struct SubTask *subtask; /* our sub task */ struct RastPort rp; /* rastport for the sub task */ BYTE *udlines; /* line update flags array */ }; /**************************************************************/ /* Functions for easy and secure spawning/killing of subtasks */ /**************************************************************/ struct SubTaskMsg { struct Message stm_Message; WORD stm_Command; APTR stm_Parameter; LONG stm_Result; }; struct SubTask { struct Task *st_Task; /* sub task pointer */ struct MsgPort *st_Port; /* allocated by sub task */ struct MsgPort *st_Reply; /* allocated by main task */ APTR st_Data; /* more initial data to pass to the sub task */ struct SubTaskMsg st_Message; /* Message buffer */ }; #define STC_STARTUP -2 #define STC_SHUTDOWN -1 LONG SendSubTaskMsg(struct SubTask *st,WORD command,APTR params) { st->st_Message.stm_Message.mn_ReplyPort = st->st_Reply; st->st_Message.stm_Message.mn_Length = sizeof(struct SubTaskMsg); st->st_Message.stm_Command = command; st->st_Message.stm_Parameter = params; st->st_Message.stm_Result = 0; PutMsg(command==STC_STARTUP ? &((struct Process *)st->st_Task)->pr_MsgPort : st->st_Port,(struct Message *)&st->st_Message); WaitPort(st->st_Reply); GetMsg(st->st_Reply); return(st->st_Message.stm_Result); } struct SubTask *SpawnSubTask(char *name,VOID (*func)(VOID),APTR data) { struct SubTask *st; if (st = AllocVec(sizeof(struct SubTask),MEMF_PUBLIC|MEMF_CLEAR)) { if (st->st_Reply = CreateMsgPort()) { st->st_Data = data; if (st->st_Task = (struct Task *)CreateNewProcTags(NP_Entry,func,NP_Name,name,TAG_DONE)) { if (SendSubTaskMsg(st,STC_STARTUP,st)) { return(st); } } DeleteMsgPort(st->st_Reply); } FreeVec(st); } return(NULL); } VOID KillSubTask(struct SubTask *st) { SendSubTaskMsg(st,STC_SHUTDOWN,st); DeleteMsgPort(st->st_Reply); FreeVec(st); } /* the following functions are called from the sub task */ VOID ExitSubTask(struct SubTask *st,struct SubTaskMsg *stm) { /* ** We reply after a Forbid() to make sure we're really gone ** when the main task continues. */ if (st->st_Port) DeleteMsgPort(st->st_Port); Forbid(); stm->stm_Result = FALSE; ReplyMsg((struct Message *)stm); } struct SubTask *InitSubTask(void) { struct Task *me = FindTask(NULL); struct SubTask *st; struct SubTaskMsg *stm; /* ** Wait for our startup message from the SpawnSubTask() function. */ WaitPort(&((struct Process *)me)->pr_MsgPort); stm = (struct SubTaskMsg *)GetMsg(&((struct Process *)me)->pr_MsgPort); st = (struct SubTask *)stm->stm_Parameter; if (st->st_Port = CreateMsgPort()) { /* ** Reply startup message, everything ok. ** Note that if the initialization fails, the code falls ** through and replies the startup message with a stm_Result ** of 0 after a Forbid(). This tells SpawnSubTask() that the ** sub task failed to run. */ stm->stm_Result = TRUE; ReplyMsg((struct Message *)stm); return(st); } else { ExitSubTask(st,stm); return(NULL); } } /*******************************************************/ /* Subtask which does all the time-consuming rendering */ /*******************************************************/ VOID __asm __saveds RenderFunc(VOID) { struct SubTask *st; struct Library *GfxBase; if (GfxBase = OpenLibrary("graphics.library",37)) /* dont share library pointers! */ { if (st = InitSubTask()) { struct Data *data = st->st_Data; BOOL running = TRUE; BOOL worktodo = FALSE; LONG x,y; struct SubTaskMsg *stm; /* ** after the sub task is up and running, we go into ** a loop and process the messages from the main task. */ for (;;) { float left,top,right,bottom; while (stm = (struct SubTaskMsg *)GetMsg(st->st_Port)) { switch (stm->stm_Command) { case STC_SHUTDOWN: /* ** This is the shutdown message from KillSubTask(). */ running = FALSE; break; case STC_START: /* ** we received a start message with a fractal description. ** clear the rastport and the line update array and start ** rendering. */ SetRast(&data->rp,1); memset(data->udlines,0,FRACTALHEIGHT); left = ((struct FractalDesc *)stm->stm_Parameter)->left ; top = ((struct FractalDesc *)stm->stm_Parameter)->top ; right = ((struct FractalDesc *)stm->stm_Parameter)->right ; bottom = ((struct FractalDesc *)stm->stm_Parameter)->bottom; y = 0; worktodo = TRUE; break; case STC_STOP: /* this message is not used in this example */ worktodo = FALSE; break; } /* ** If we received a shutdown message, we do not reply it ** immediately. First, we need to free our resources. */ if (!running) break; ReplyMsg((struct Message *)stm); } if (!running) break; if (worktodo) { /* if there is work to do, i.e. if the fractal is not ** finished yet, we calculate the next line and draw ** it to the offscreen rastport. */ for (x=0;x= 4.0) { /* ** set the pixel in the offscreen rastport. ** this demo is kind of dirty, as it does no ** nice color allocation and palette stuff. ** dont be so dirty in your own programs! :-) */ SetAPen(&data->rp,1+counter); WritePixel(&data->rp,x,y); break; } if (++counter==MaxIterations) { break; } } } /* ** after the line is finished, we set the corresponding ** flag in the line update array to FALSE. This shows the ** main task that this line needs to be redrawn the next ** time it gets the chance. */ ObtainSemaphore(&data->sema); data->udlines[y] = FALSE; if (data->app) { /* ** if our class is attached to an application, we send ourselves ** an update method. Note that because we are in a separate task, ** we cannot send this method directly but instead have to use ** the MUIM_Application_PushMethod call. This is the only method ** that you may send to a MUI object from a separate task. What it ** does is to copy the method to a private buffer and wait until ** the next time the main task calls the input method. Then, our ** update method will be executed under the main tasks context. ** ** If our class is not attached to an application ** (i.e. we are outside of MUIM_Setup/MUIM_Cleanup), there is ** nobody who could render something anyway so we just skip ** the update method and continue to render in our private ** buffer. */ DoMethod(data->app,MUIM_Application_PushMethod,data->self,2,MUIM_Class4_Update,100*(y+1)/FRACTALHEIGHT); } ReleaseSemaphore(&data->sema); if (++y==FRACTALHEIGHT) { /* check if we are finished to draw our fractal */ worktodo = FALSE; } /* Since we are very busy working, we do not Wait() for signals. */ } else { /* We have nothing to do, just sit quietly and wait for something to happen */ WaitPort(st->st_Port); } } ExitSubTask(st,stm); } CloseLibrary(GfxBase); } } /***************************************************************************/ /* Here is the beginning of our new class... */ /***************************************************************************/ SAVEDS ULONG mNew(struct IClass *cl,Object *obj,Msg msg) { struct Data *data; if (!(obj = (Object *)DoSuperMethodA(cl,obj,msg))) return(0); data = INST_DATA(cl,obj); /* store a pointer to ourselves so the subtask knows about us */ data->self = obj; /* ** initialization and allocation of data structures. ** note that if something fails here, we *must* do a ** CoerceMethod(cl,obj,OM_DISPOSE) to give ourselves ** (and MUI!) a chance to clean up. */ InitSemaphore(&data->sema); InitRastPort(&data->rp); if (data->udlines = AllocVec(FRACTALHEIGHT,MEMF_CLEAR)) { if (data->rp.BitMap = AllocBitMap(FRACTALWIDTH,FRACTALHEIGHT,8,BMF_CLEAR,NULL)) { SetRast(&data->rp,1); /* the following call starts the sub task */ if (data->subtask = SpawnSubTask("Class4-Render-Task",RenderFunc,data)) { SetTaskPri(data->subtask->st_Task,-1); return((ULONG)obj); } } } CoerceMethod(cl,obj,OM_DISPOSE); return(0); } SAVEDS ULONG mDispose(struct IClass *cl,Object *obj,Msg msg) { struct Data *data = INST_DATA(cl,obj); if (data->subtask) KillSubTask(data->subtask); if (data->rp.BitMap) FreeBitMap(data->rp.BitMap); if (data->udlines) FreeVec(data->udlines); return(DoSuperMethodA(cl,obj,msg)); } /* ** AskMinMax method will be called before the window is opened ** and before layout takes place. We need to tell MUI the ** minimum, maximum and default size of our object. */ SAVEDS ULONG mAskMinMax(struct IClass *cl,Object *obj,struct MUIP_AskMinMax *msg) { /* ** let our superclass first fill in what it thinks about sizes. ** this will e.g. add the size of frame and inner spacing. */ DoSuperMethodA(cl,obj,msg); /* ** now add the values specific to our object. note that we ** indeed need to *add* these values, not just set them! */ msg->MinMaxInfo->MinWidth += 10; msg->MinMaxInfo->DefWidth += 100; msg->MinMaxInfo->MaxWidth += FRACTALWIDTH; msg->MinMaxInfo->MinHeight += 10; msg->MinMaxInfo->DefHeight += 100; msg->MinMaxInfo->MaxHeight += FRACTALHEIGHT; return(0); } /* ** Draw method is called whenever MUI feels we should render ** our object. This usually happens after layout is finished ** or when we need to refresh in a simplerefresh window. ** Note: You may only render within the rectangle ** _mleft(obj), _mtop(obj), _mwidth(obj), _mheight(obj). */ SAVEDS ULONG mDraw(struct IClass *cl,Object *obj,struct MUIP_Draw *msg) { struct Data *data = INST_DATA(cl,obj); /* ** let our superclass draw itself first, area class would ** e.g. draw the frame and clear the whole region. What ** it does exactly depends on msg->flags. ** ** Note: You *must* call the super method prior to do ** anything else, otherwise msg->flags will not be set ** properly! */ DoSuperMethodA(cl,obj,msg); if (msg->flags & MADF_DRAWUPDATE) { /* ** This flag indicates that we were called from our ** update method. We needn't render the complete ** image, we only need to update the lines that ** were changed. So what we do is to browse through ** the line flag array and blit each changed line ** from the offscreen buffer into the display. ** We could do a better and more efficient job ** by collecting subsequent changed lines to blit ** larger rectangles, but hey... this is only a demo! :-) */ int l; /* ** note the usage of semaphores to protect access ** to variables use by both tasks. */ ObtainSemaphore(&data->sema); for (l=0;l<_mheight(obj);l++) { if (!data->udlines[l]) { /* ** once we copied the line, we set the corresponding line flag ** to indicate that this line is uptodate and does not need ** to be redrawn the next time. When our sub task gets the message ** to calculate a new fractal, it will reset the flag to FALSE again. */ BltBitMapRastPort(data->rp.BitMap,0,l,_rp(obj),_mleft(obj),_mtop(obj)+l,_mwidth(obj),1,0xc0); data->udlines[l] = TRUE; } } ReleaseSemaphore(&data->sema); } else if (msg->flags & MADF_DRAWOBJECT) { /* ** we were called directly from MUI because the window needs refresh. ** no need to care about our line array here, we just copy the complete ** offscreen buffer to our display. */ ObtainSemaphore(&data->sema); BltBitMapRastPort(data->rp.BitMap,0,0,_rp(obj),_mleft(obj),_mtop(obj),_mwidth(obj),_mheight(obj),0xc0); ReleaseSemaphore(&data->sema); } return(0); } SAVEDS ULONG mSetup(struct IClass *cl,Object *obj,struct MUIP_HandleInput *msg) { struct Data *data = INST_DATA(cl,obj); if (!(DoSuperMethodA(cl,obj,msg))) return(FALSE); /* ** set a pointer to our application in our instance data. ** this indicates the sub task that we should be notified ** when a new line is calculated. */ ObtainSemaphore(&data->sema); get(obj,MUIA_ApplicationObject,&data->app); ReleaseSemaphore(&data->sema); return(TRUE); } SAVEDS ULONG mCleanup(struct IClass *cl,Object *obj,struct MUIP_HandleInput *msg) { struct Data *data = INST_DATA(cl,obj); ObtainSemaphore(&data->sema); data->app = NULL; ReleaseSemaphore(&data->sema); return(DoSuperMethodA(cl,obj,msg)); } /* ** a simple method that sends a START msg with ** fractal description packet to the sub task. */ SAVEDS ULONG mCalc(struct IClass *cl,Object *obj,struct MUIP_Class4_Calc *msg) { struct Data *data = INST_DATA(cl,obj); SendSubTaskMsg(data->subtask,STC_START,msg->fd); return(0); } /* ** thats the method that is called through MUIM_Application_PushMethod ** from the subtask. */ SAVEDS ULONG mUpdate(struct IClass *cl,Object *obj,struct MUIP_Class4_Update *msg) { /* Tell MUI to redraw our object. Set the update flag ** so we know that only the changed lines are subject ** to render. */ MUI_Redraw(obj,MADF_DRAWUPDATE); /* ** Also the the percentage attribute. The class itself doesnt ** have any use for this, but if we set it, its possible ** for other objects (e.g. a gauge) to receive notifications */ set(obj,MUIA_Class4_Percent,msg->percent); return(0); } /* ** Here comes the dispatcher for our custom class. ** Unknown/unused methods are passed to the superclass immediately. */ SAVEDS ASM ULONG MyDispatcher(REG(a0) struct IClass *cl,REG(a2) Object *obj,REG(a1) Msg msg) { switch (msg->MethodID) { case OM_NEW : return(mNew (cl,obj,(APTR)msg)); case OM_DISPOSE : return(mDispose (cl,obj,(APTR)msg)); case MUIM_AskMinMax : return(mAskMinMax(cl,obj,(APTR)msg)); case MUIM_Draw : return(mDraw (cl,obj,(APTR)msg)); case MUIM_Setup : return(mSetup (cl,obj,(APTR)msg)); case MUIM_Cleanup : return(mCleanup (cl,obj,(APTR)msg)); case MUIM_Class4_Update: return(mUpdate (cl,obj,(APTR)msg)); case MUIM_Class4_Calc : return(mCalc (cl,obj,(APTR)msg)); } return(DoSuperMethodA(cl,obj,msg)); } /****************************************************************************/ /* Misc Help Functions */ /****************************************************************************/ LONG xget(Object *obj,ULONG attribute) { LONG x; get(obj,attribute,&x); return(x); } char *getstr(Object *obj) { return((char *)xget(obj,MUIA_String_Contents)); } /***************************************************************************/ /* Thats all there is about it. Now lets see how things are used... */ /***************************************************************************/ int main(int argc,char *argv[]) { Object *app,*window,*MyObj; Object *strleft,*strtop,*strright,*strbottom,*start,*gauge; struct MUI_CustomClass *mcc; ULONG signals; BOOL running = TRUE; if (((struct Library *)SysBase)->lib_Version < 39) { fprintf(stderr,"runs only with V39 and up\n"); exit(20); } init(); /* Create the new custom class with a call to MUI_CreateCustomClass(). */ /* Caution: This function returns not a struct IClass, but a */ /* struct MUI_CustomClass which contains a struct IClass to be */ /* used with NewObject() calls. */ /* Note well: MUI creates the dispatcher hook for you, you may */ /* *not* use its h_Data field! If you need custom data, use the */ /* cl_UserData of the IClass structure! */ if (!(mcc = MUI_CreateCustomClass(NULL,MUIC_Area,NULL,sizeof(struct Data),MyDispatcher))) fail(NULL,"Could not create custom class."); app = ApplicationObject, MUIA_Application_Title , "Class4", MUIA_Application_Version , "$VER: Class4 19.5 (12.02.97)", MUIA_Application_Copyright , "©1993, Stefan Stuntz", MUIA_Application_Author , "Stefan Stuntz", MUIA_Application_Description, "Demonstrate rendering from sub tasks.", MUIA_Application_Base , "Class4", SubWindow, window = WindowObject, MUIA_Window_Title, "Subtask rendering", MUIA_Window_ID , MAKE_ID('C','L','S','4'), WindowContents, VGroup, Child, HGroup, GroupSpacing(8), Child, ColGroup(2), Child, Label2("_Left:" ), Child, strleft = MUI_MakeObject(MUIO_String,"_L",30), Child, Label2("_Right:" ), Child, strright = MUI_MakeObject(MUIO_String,"_R",30), End, Child, ColGroup(2), Child, Label2("_Top:" ), Child, strtop = MUI_MakeObject(MUIO_String,"_T",30), Child, Label2("_Bottom:"), Child, strbottom = MUI_MakeObject(MUIO_String,"_B",30), End, Child, MUI_MakeObject(MUIO_VBar,2), Child, start = VGroup, GroupSpacing(0), MUIA_Weight, 0, ButtonFrame, MUIA_InputMode , MUIV_InputMode_RelVerify, MUIA_Background, MUII_ButtonBack, Child, VSpace(0), Child, TextObject, MUIA_Text_Contents, "\33c Start ", End, Child, VSpace(0), End, End, Child, gauge = GaugeObject, GaugeFrame, MUIA_Gauge_Horiz, TRUE, MUIA_Gauge_Max, 100, MUIA_FixHeight, 8, End, Child, MyObj = NewObject(mcc->mcc_Class,NULL, TextFrame, MUIA_Background, MUII_BACKGROUND, TAG_DONE), End, End, End; if (!app) fail(app,"Failed to create Application."); set(window,MUIA_Window_DefaultObject, MyObj); DoMethod(window,MUIM_Notify,MUIA_Window_CloseRequest,TRUE, app,2,MUIM_Application_ReturnID,MUIV_Application_ReturnID_Quit); DoMethod(start,MUIM_Notify,MUIA_Pressed,FALSE, app,2,MUIM_Application_ReturnID,1); DoMethod(MyObj,MUIM_Notify,MUIA_Class4_Percent,MUIV_EveryTime, gauge,3,MUIM_Set,MUIA_Gauge_Current,MUIV_TriggerValue); set(strleft ,MUIA_String_Contents,"-2.0"); set(strright ,MUIA_String_Contents,"1.0"); set(strtop ,MUIA_String_Contents,"1.5"); set(strbottom,MUIA_String_Contents,"-1.5"); /* ** Input loop... */ set(window,MUIA_Window_Open,TRUE); while (running) { switch (DoMethod(app,MUIM_Application_Input,&signals)) { case MUIV_Application_ReturnID_Quit: running = FALSE; break; case 1: { struct FractalDesc fd; fd.left = atof(getstr(strleft )); fd.right = atof(getstr(strright )); fd.top = atof(getstr(strtop )); fd.bottom = atof(getstr(strbottom)); if (fd.right > fd.left && fd.top > fd.bottom) DoMethod(MyObj,MUIM_Class4_Calc,&fd); else DisplayBeep(0); } break; } if (running && signals) Wait(signals); } set(window,MUIA_Window_Open,FALSE); /* ** Shut down... */ MUI_DisposeObject(app); /* dispose all objects. */ MUI_DeleteCustomClass(mcc); /* delete the custom class. */ fail(NULL,NULL); /* exit, app is already disposed. */ }