
/* `TTalk' - Talking Tasks
* -programming example using messages and ports
* (C)1987 Transactor Publishing Inc.
* From Transactor Magazine, written by Chris Zamara, May 1987
* ---->>This program may be freely distributed<<----
*
* This code shows you how to create and find message ports, and how
* to send, receive, and reply to messages.
*
* This program, `TTalk', lets you create several named DOS windows, and send
* messages between them. Just give each task its name when you run it
* from the CLI, e.g. "run TTalk Fred". If you start another task,
* like "run TTalk Edna", you can send Edna a message from Fred by typing
* into Fred's window something like, "Edna, you look lovely today!"
* Edna will receive the  message and print it in her window. Any number
* of these tasks can be started, and any one can talk to any other.
*
* How it works: A message port is created and given the name that the user
* supplies (the name in the window title). To send a message to another task,
* its port is found with FindPort(), a message is sent to the port with
* PutMsg(), and a reply is waited for with WaitPort(). Within the message is
* a pointer to the text that the user wanted to send. The receiving task
* uses GetMsg() in between waiting for the keypresses to receive the message.
* All talking tasks also have access to a public port called "Joe's Cafe",
* where they read a message saying how many talking tasks are running, and
* update the message when they are started and ended. By talking at Joe"s
* Cafe, the talking tasks can determine a good place to put their window so
* that the user doesn't have to always move around overlapping windows. The
* first task started creates the "Joe's Cafe" port and the first message
* there, and the last task ended deletes them.
*
* compiled with Manx Aztec 3.40a, should work with Lattice as well.
*/

#include <exec/types.h>
#include <exec/memory.h>
#include <exec/ports.h>
#include <libraries/dos.h>

/* Print macro used to send a string to the output window */
#define Print(s) Write(IOfile,(s),(long)strlen(s))

#define BUFLEN 200 /* length of input buffer for user text entry */

/* this is the structure for the message we will be sending */
struct MyMessage {
   struct Message Msg;     /* for Exec message routines */
   char *NameOfSender;     /* sender puts his name here */
   char *text;             /* the text we want to send */
};

/* this is the kind of message we will use to
 * determine how many talking tasks are currently running
 */
 struct CountMsg {
   struct Message Msg;
      int Count;
};

/* external function declarations */
extern BPTR Open();
extern ULONG Read();
extern UBYTE *AllocMem();
extern struct MsgPort *CreatePort(), *FindPort();
extern struct MyMessage *GetMsg();

/* global variables */
char *MyName;                   /* ptr to name given to this `talking task'*/ 
struct MsgPort *MyPort = NULL;  /* message port for sending/receiving msgs */
struct MsgPort *TTport;         /* port shared by all Talking Tasks        */
char *TTportName="Joe's Cafe";  /* name of TTport, where they all hang out */
struct CountMsg *TTmsg;         /* the message we'll leave at Joe's Cafe   */
BPTR IOfile = NULL;             /* DOS file handle for the terminal window */


/******** start of main  **************************************************/
main(argc,argv)
int argc;
char **argv;
{
/* give user instructions and exit if invalid args passed */
  if (argc!=2||strlen(argv[1])>30)
  {
    printf("Run me with a name, like `run %s Ernie'\n",argv[0]);
    exit(0);
  }
  MyName = argv[1]; /* first argument is name given to this `talking task' */
  /* open DOS window,create ports, etc. and return TRUE if successful */
  if(OpenStuff())
    HandleInput();  /* get input, send and read messages until user exits */
  CloseStuff();
}

/* OpenStuff() ************************************************************
* create `MyPort' message port, call HowMany() and open DOS window
*/
OpenStuff()
{
static char windowName[50]; /* holds filename for DOS `con' window */
/* this array ia used to choose an appropriate window position */
static char *conNames[] = {"con:0/0/319/65/","con:320/0/319/65/",
                           "con:0/67/319/65/","con:320/67/319/65/",
                           "con:0/134/319/65/","con:320/134/319/65/"
                          };
/* see if a message port with the given name already exists */
  if(FindPort(MyName))
  {
    printf("Hey, there's already someone here called %s!\n",MyName);
    return (int)FALSE;
  }
/* set up a message port with the given name and get a pointer to it */
  MyPort = CreatePort(MyName,0L);
  if(MyPort==NULL)
  {
    printf("can't open %s' port!\n",MyName);
    return(int)FALSE;
  }
  /* the number of talking tasks running determines where to put the window */
  strcpy(windowName,conNames[HowMany()%6]);
  strcat(windowName,MyName); /* MyName is title for the DOS window */
  IOfile = Open(windowName,MODE_NEWFILE); /* open DOS window for user I/O */
  if(IOfile==NULL)    /* file didn't open for some reason */
    return (int)FALSE;
  /* print some instructions */
  Print("(send message with <name,message...>)");
  return (int)TRUE;     /* everything opened OK */
}

/* CloseStuff() **********************************************************
* Undo what OpenStuff() and HowMany() did
*/
CloseStuff()
{
  if(IOfile)
    Close(IOfile);      /* close DOS window if open */
  /* decrease count in TTport message, remove port if count is zero */
  if(TTport = FindPort(TTportName))
  {
    TTmsg = (struct CountMsg *)GetMsg(TTport);
    if(TTmsg->Count--)  /* still more talking tasks, don't remove port */
      PutMsg(TTport,TTmsg); /* put bacxk message with decreased count */
    else
    {  /* we're the last talking task, remove TTport and TTmsg */
      RemPort(TTport);  /* remove the port */
      FreeMem(TTmsg,(ULONG)sizeof(*TTmsg));
      FreeMem(TTport->mp_Node.ln_Name,(ULONG)(strlen(TTportName)+1));
      FreeMem(TTport,(ULONG)sizeof(*TTport));
    }
  }
  if(MyPort)
    DeletePort(MyPort);   /* delete our main message port */
}


/* HandleInput() *********************************************************
* Get user input from window and any messages arriving at MyPort
* Text from the user is passed to SendMessage().
* Print text field of incloming messages, then reply to message.
* Returns when user inputs a null text line.
*/
HandleInput()
{
char InputBuffer[BUFLEN];
BOOL exit_flag = FALSE;
struct MyMessage *msg;

/* We want to get keyboard input from the user AND get messages arriving
* at our message port, and we don't want to waste much CPU time.
* So we WAitForChar() and if no character is received within 1/10
* second, we read the message port, process any messages there, and try
* again. This way we only GetMsg() every 1/10 second, which is cheap.
*/
  while (exit_flag == FALSE)
  {
  if(WaitForChar(IOfile,100000))  /* wait for 1/10 second (in micros) */
  {
    /* read an input line and send the message */
    if(Read(IOfile,InputBuffer,(long)BUFLEN)>1)
      SendString(InputBuffer);
    else
      exit_flag = TRUE; /* newline by itself means user exit */
    }
    /* now handle any messages for us at the port */
    while(msg=GetMsg(MyPort)) /* loop until all messages processed */
    {
      Print("A message from ");
      Print(msg->NameOfSender);
      Print(":\n\"");
      Print(msg->text);
      Print("\"\n");
      /* We took care of the message, now reply to it */
      ReplyMsg(msg);
    }
  }
}

/* SendString(text) ****************************************************
* Split the given screen into two strings at first comma
* and call SendMessage() with the resultant strings.
* Print error message if no comma found.
*/
SendString(text)
char *text;
{
int NamePos;
  NamePos = SearchChar(text,',',BUFLEN);  /* check for comma */
  if(NamePos==BUFLEN) /* no comma */
    Print("(send message with <name,message...>)");
  else
  {   /* split strings into two and give strings to SendMessage() */
    text[NamePos] = '\0';
    text[SearchChar(text,'\n',BUFLEN)] = '\0';
    SendMessage(text,text + NamePos +1);
  }
}


/* SendMessage(name,msgstring) *****************************************
* Given a port name and a text string, find the port and send a
* message containing the string to it, and wait for a reply.
*/
SendMessage(name,msgstring)
char *name,*msgstring;
{
struct MsgPort *HisPort;
struct MyMessage message;

  HisPort = FindPort(name); /* look for the other fellow's message port */
  if(HisPort==NULL)  /* NULL means port couldn't be found */
  {
    Print("Can't find ");
    Print(name);
    Print("!\n");
  }
  /* error if message being sent to ourselves */
  else if (strcmp(name,MyName)==0)
    Print("Talking to myself...OK!\n");
  else if(strcmp(name,TTportName)==0)
  { /* don't send to Joe's cafe!! */
    Print("Oh no you don't!\nHumans aren't allowed at ");
    Print(TTportName);
    Print(".\n");
  }
  else  /* everything's OK, prepare the message and send it to his port */
  {
    message.Msg.mn_Node.ln_Type = NT_MESSAGE; /* for Exec list handling */
    message.Msg.mn_Length = sizeof(message);  /* number of bytes in msg */
    message.Msg.mn_ReplyPort = MyPort;        /* so receiver can reply  */
    message.NameOfSender = MyName;            /* tell him who sent it   */
    message.text = msgstring;                 /* our text string to send*/
    PutMsg(HisPort,&message);                 /* send the message       */
    WaitPort(MyPort);                         /* wait for reply         */
    GetMsg(MyPort);                           /* remove reply from port */
    Print("<Got acknowledgement from ");      /* tell user we got reply */
    Print(name);
    Print(">\n");
  }
}


/* SearchChar(string,chr,n) *********************************************
* find character `chr' in `string', searching up to n characters
* return n if character not found
*/
SearchChar(string,chr,n)
char *string;
int chr, n;
{
int i;
  for(i=0;i<n&&string[i]!=chr;i++)
    ;
  return(i);
}


/* HowMany() ***********************************************************
* Determines how many `talking tasks' are currently in the system. Look for
* aport named TTportName (Joe's Cafe). If it exists, get a `CountMsg'
* message from it, read the count, increment it and put the message back. If
* the port doesn't exist, create it and put a message there with the count
* field set to zero. Return the value of the count.
*/
HowMany()
{
int count;
  if(TTport = FindPort(TTportName))
  {
    TTmsg = (struct CountMsg *)GetMsg(TTport);  /* get message... */
    count = ++TTmsg->Count;                     /* bump count...  */
    PutMsg(TTport,TTmsg);                       /* and put it back*/
  }
  else
  { /* port not there, we are first talking task - create the port */
    TTport=(struct MsgPort *)AllocMem((ULONG)sizeof(*TTport),MEMF_PUBLIC);
    TTport->mp_Node.ln_Name = (char *)
      AllocMem((ULONG)(strlen(TTportName)+1),MEMF_PUBLIC);
    strcpy(TTport->mp_Node.ln_Name,TTportName);
    TTport->mp_Node.ln_Pri = 0;
    TTport->mp_Node.ln_Type = NT_MSGPORT;
    TTport->mp_Flags = PA_IGNORE;
    AddPort(TTport);  /* make the port public so all tasks have access */
    
    /* now create the message to put in the port (Joe's Cafe) */
    TTmsg=(struct CountMsg *)AllocMem((ULONG)sizeof(*TTmsg),MEMF_PUBLIC);
    TTmsg->Msg.mn_Node.ln_Type = NT_MESSAGE;  /* for Exec list handling */
    TTmsg->Msg.mn_Length = sizeof(*TTmsg);    /* number of byte in msg  */
    TTmsg->Msg.mn_ReplyPort = NULL;           /* no reply port required */
    TTmsg->Count = count = 0;                 /* start count at zero    */
    PutMsg(TTport,TTmsg); /* leave a message at Joe's Cafe for everyone */
  }
  return count;
}

/*********  end of ttasks.c  ********************************************/
