Hello World

By this stage you should have a rudimentary understanding of how the Pandora Engine is structured, so it's time to look at an example program before we dive into the more technical side of the system. If you've seen a Hello World program before, you might be familiar with examples such as:

   #include <stdio.h>
   #include <stdlib.h>

   int main(int argc, char **argv)
   {
      printf("Hello World.\n");
      return(0);
   }

And even:

   10 PRINT "Hello World"

For the purposes of this example, we are going to make the exercise a little more interesting and open an Athene window that has "Hello World" printed inside of it. This will give you some working knowledge of the system in general, as well as the construction of a basic program interface.

Hello World as a Script

If you're using the Pandora Engine to write programs within Athene, the only realistic option for writing a Hello World equivalent is to use DML. While we're going to show you how to write a Hello World executable in the next part of this section, for the amount of time it takes to write the same thing in DML, there is no practical reason for writing Hello World as an executable program. In this case we will make an exception for the sake of comparison and the fact that we want to show you how to write Pandora executables.

Here is a Hello World program written in DML:

   <?xml version="1.0"?>
   <!DOCTYPE dml PUBLIC "-//ROCKLYTE//DTD DML 1.0//EN" "http://www.rocklyte.com/dtd/dml_1_0.xml">

   <dml type="program">
     <window title="Hello World" insideborder="1" insideheight="100"
       insidewidth="180">
       <render x="[container.leftmargin]" y="[container.topmargin]"
          xoffset="[container.rightmargin]" yoffset="[container.bottommargin]"
          colour="#ffffff">
         <text align="center" string="Hello World" colour="#000000"/>
       </render>
     </window>
   </dml>

The primary reason why we can write this program so quickly in DML is not due to the simplicity of the language, but because all the basic elements for creating the program (such as printing text) are already available as dynamic instructions. If we were creating something more complex, then using a combination of DML and class code would be necessary, which would make the process far more time consuming.

For the purposes of this part of the manual we will not discuss the intricacies of DML, but you can refer to the DML Whitepaper if you need further information on how DML works.

Hello World as an Executable

Writing a Pandora based executable from scratch is a fairly straight-forward affair and does not require a great deal of startup code. The following code illustrates a working program that displays a window containing the words "Hello World". When the user clicks on the window's close gadget, the program ends. Here's the code:

   #include <pandora/system/all.h>
   #include <pandora/graphics/all.h>
   #include <pandora/functions/set.c>
   
   STRING ProgName      = "Hello World";
   STRING ProgAuthor    = "Rocklyte Systems";
   STRING ProgDate      = "July 2001";
   STRING ProgCopyright = "Copyright Rocklyte Systems (c) 2001.  All rights reserved.";
   
   extern struct KernelBase *KernelBase;
   
   ERROR PROGRAM_ActionNotify(OBJECTPTR, struct acActionNotify *);
   
   FIELD FID_PrimaryObject, FID_Align, FID_Face, FID_String, FID_Colour;
   FIELD FID_TopMargin, FID_LeftMargin, FID_RightMargin, FID_BottomMargin;
   OBJECTID WindowID = 0;

   ERROR program(void)
   {
      struct Message message;
      struct NextMsg next;
      struct Render *render;
      APTR *actions;
      OBJECTPTR script, window, text, task;
      OBJECTID RenderID;
      FLOAT topmargin, bottommargin, rightmargin, leftmargin;
   
      if (ResolveFields("PrimaryObject",  &FID_PrimaryObject,
                        "LeftMargin",     &FID_LeftMargin,
                        "TopMargin",      &FID_TopMargin,
                        "BottomMargin",   &FID_BottomMargin,
                        "RightMargin",    &FID_RightMargin,
                        "Align",          &FID_Align,
                        "Face",           &FID_Face,
                        "String",         &FID_String,
                        "Colour",         &FID_Colour,
                        TAGEND) != ERR_Okay) return(ERR_ResolveFields);
   
      /*** Intercept some actions that are required for our program ***/
   
      if (AttemptExclusive(CurrentTask(), 5000, &task) IS ERR_Okay) {
         if (GetField(task, FID_Actions, FT_POINTER, &actions) IS ERR_Okay) {
            actions[AC_ActionNotify] = PROGRAM_ActionNotify;
         }
         else {
            FreeExclusive(task);
            return(ERR_GetField);
         }
         FreeExclusive(task);
      }
      else return(ERR_ExclusiveDenied);
   
      /*** Open a window ***/
   
      if (NewObject(ID_SCRIPT, NULL, &script, NULL) IS ERR_Okay) {
   
         SetField(script, FID_Location, FT_POINTER, "templates:window.dml");
   
         SetUnlistedFields(script, "title",        ProgName,
                                   "insideborder", "1",
                                   "container",    "desktop",
                                   "insidewidth",  "200",
                                   "insideheight", "100",
                                   TAGEND);
   
         if (Action(AC_Init, script, NULL) IS ERR_Okay) {
   
            if (Action(AC_Activate, script, NULL) IS ERR_Okay) {
   
               GetField(script, FID_PrimaryObject, FT_LONG, &WindowID);
               Action(AC_Free, script, NULL);
               script = NULL;
   
               /*** Get the window margins ***/
   
               if (AttemptExclusive(WindowID, 10000, &window) IS ERR_Okay) {
                  GetFields(window, FID_LeftMargin|TFLOAT,   &leftmargin,
                                    FID_TopMargin|TFLOAT,    &topmargin,
                                    FID_BottomMargin|TFLOAT, &bottommargin,
                                    FID_RightMargin|TFLOAT,  &rightmargin,
                                    TAGEND);

                  if (AttemptExclusive(CurrentTask(), 3000, &task) IS ERR_Okay) {
                     SubscribeAction(window, AC_Free, task);
                     FreeExclusive(task);
                  }
   
                  FreeExclusive(window);
               }
   
               /*** Create a render area for drawing graphics ***/
   
               if (CreateObject(ID_RENDER, NULL, &render, &RenderID,
                  FID_Container|TLONG, WindowID,
                  FID_XCoord|TFLOAT,   leftmargin,
                  FID_YCoord|TFLOAT,   topmargin,
                  FID_XOffset|TFLOAT,  rightmargin,
                  FID_YOffset|TFLOAT,  bottommargin,
                  FID_Colour|TSTRING,  "#ffffff",
                  TAGEND) IS ERR_Okay) {
   
                  /*** Create the Hello World text ***/
   
                  CreateObject(ID_TEXT, NULL, &text, NULL,
                     FID_Container|TLONG, RenderID,
                     FID_String|TSTRING,  "Hello World",
                     FID_Align|TLONG,     ALIGN_CENTER,
                     FID_Face|TSTRING,    "Sans Serif",
                     FID_Colour|TSTRING,  "#000000",
                     TAGEND);
   
                  /*** Show the Render object ***/
   
                  Action(AC_Show, render, NULL);
                  FreeExclusive(render);
   
                  /*** Main loop ***/
   
                  while (ProcessMessages(&next, MF_WAIT) IS ERR_Okay) {
                     if (next.Count > 0) {
                        if (GetMessage(NULL, NULL, NULL, &message, sizeof(struct Message)) IS ERR_Okay) {
                           if (message.Type IS MSGID_QUIT) {
                              ActionMsg(AC_Free, WindowID, NULL);
                              break;
                           }
                           else DPrintF("@Program:","Received unknown message type #%d.", message.Type);
                        }
                     }
                  }
               }
            }
         }
      }
   
      if (script) Action(AC_Free, script, NULL);
      return(ERR_Okay);
   }

   ERROR PROGRAM_ActionNotify(OBJECTPTR Self, struct acActionNotify *Args)
   {
      if ((Args->ActionID IS AC_Free) AND (Args->ObjectID IS WindowID)) {
         SendMessage(NULL, MSGID_QUIT, NULL, NULL, NULL);
      }
      return(ERR_Okay);
   }

Please note that if you need exact detail on the functionality of this program, you can click on the hyperlinks to read the function documentation. Step-by-step, this is an overview of how the program works:

  1. The ResolveFields() function is called to convert field names into their relative ID's. We need to perform this process as functions such as CreateObject() and GetField() operate on field ID's for speed. By resolving the field names early on, an overall speed increase is gained further on in the program.
  2. The next section hooks into the ActionNotify routine, which is an important part of the program's connection to the Pandora Engine. By hooking into this particular routine we can use the SubscribeAction() function later on in the program so that we can listen to object activity in the system.
  3. In order to create a window for displaying our text, we need to run the window script which is stored in "templates:window.dml". Unlike other systems that may employ specific interface functions such as 'OpenWindow()', the GUI for Athene is entirely script driven, which means that interface functions do not need to be employed. To run the script we use NewObject() to create a Script object, then set the Location field so that the script knows what file to run. Take note of the SetUnlistedField() function - this is used to send the window special arguments, such as the title that it should display and the window's position within the user interface. If you need to know the arguments that a particular script provides, open the script in a text editor. Scripts are self-documenting and will give you a detailed overview of how each argument affects the running of the script. Read the 'templates:' directory for a list of all available templates.
  4. Once the window script is initialised, activating it will cause it to show up on the desktop. We then need to put the "Hello World" text inside of it. To do this we get the PrimaryObject field from the script - this will tell us the object ID of the window itself. After freeing the script (by this stage we no longer need it), we get the margins of the window using the object ID that we acquired earlier. This is enough information to create and position our main drawing area, so we then create a Render object inside the window (Render objects are basically 2-Dimensional areas used for graphics drawing). Following this, the "Hello World" text is drawn inside the graphics area by creating a Text object within the rendered area.
  5. At this point the program interface and graphics are all set-up, so the last necessary step is to make a call to the Show action to actually make the Render object visible. At this point, the program is basically complete.
  6. The final procedure requires that we wait for the user to close the program. We do this simply by waiting for the window to disappear due to user interaction. You will notice the ProcessMessages() function - this vital call must be made continuously in order to process incoming messages from other tasks, especially messages coming directly from Athene itself. The message process is handled almost entirely behind the scenes, but the ProcessMessages() function must be called in order to process and clear messages as they arrive. Failing to call the function will cause a back-log to be generated that will eventually cause your program to cease functioning correctly.
  7. The ActionNotify routine in our program is used to listen to the Free action of the window that we created. When that window is destroyed, the Pandora Engine will send a message to our program to notify us about its destruction. Because we want our program to exit when our window is destroyed, we send a message type of MSGID_QUIT back to our program so that the message loop breaks and the program terminates.

That's all there is to it - not quite a walk in the park for creating Hello World, but for a program that opens a window and draws graphics, it could have been a lot more difficult. This is the only working example of an executable program in this manual, so if you want to see more, please refer to the source code that is provided with the Pandora Engine to learn more from other programs.

Next: Object Management In Depth

Table of Contents


Copyright (c) 2000-2001 Rocklyte Systems. All rights reserved. Contact us at feedback@rocklyte.com