Aztec® Programming Language
Version 1.1 Alpha 2

Copyright © 2010-2017, Aztec Development Group, All Rights Reserved

Download Aztec

Search        Contact Us

She took me to the river...

Where she cast her spell.

- Lowell George, Fred Martin

 

Aztec Events

Events are an integral part of the Aztec Class Framework and Virtual Machine. The core Virtual Machine understands Aztec Event objects, and it provides services to setup event handlers with one or more Aztec VM Threads. The Virtual Machine will automatically wake up a Thread and invoke the appropriate event handler once it is setup. Setting up an event handler is easy, and it is a consistent process across the entire Aztec Class Framework.

The Event Handling page describes the rules and naming conventions for Aztec Events, Exceptions and User Defined Events in much more detail. The goal of this page to show how easy Events are to use, particularly when coupled with some of the powerful communication facilities provided within the Aztec Engine.

Here are some of the key takeaways from that detailed Event Handling page that are useful to help appreciate this page.

♦ There are three ways to setup an event handler to be invoked when a particular event occurs.

♦ Derive a new class from class that provides default handler for the event, and override the virtual OnXXX() method (for XXXEvent).

♦ Register an event handler method with the object, which will be invoked when the event occurs.

♦ Register an event interface object with the object, which has event handling methods for each event that the object supports.

♦ Method can be global, class shared or class instance, and it can be registered to execute in any thread.

♦ There is a consistent naming convention for the set of event handler methods and event handler data type which help support a particular event.

♦ A simple set of core event handling methods is provided with the Base class so that each user defined class can create a similar, consistent event framework.

♦ An event handler or interface object can be registered from any Virtual Machine thread.

♦ The Virtual Machine thread will efficiently wait for one or more applicable events to occur, and wakes up the thread, if necessary, to execute the associated event handler.

♦ Any valid Aztec object can be registered along with an event handler. The registered object is passed to the event handler, along with the event itself, when it is invoked.

The following diagram shows how a single event gets dispatched to multiple threads internally. The beauty is that the Aztec developer does not need to worry about any of these details. Simply register the event handler from a particular thread, and the Virtual Machine automatically routes the event back to that thread when appropriate.

Aztec Event Handling with Multiple Threads

 

Tying a registered event handler to any thread using the above mechanism provides a very flexible mechanism for developing multi-threaded systems. Each Thread can be assigned a particular task, and the thread gets woken up automatically when there is relevant data to be processed. Any type of Aztec Event can be used to trigger a thread to be woken up to process an event, but the following Aztec events are particularly useful for task management in a multi-threaded system.

♦ Message family of events (TextMessage, ObjectMessage, BinaryMessage)

♦ Send a message to the thread, the thread wakes up and the message handler gets invoked automatically.

♦ Message event handlers can be setup for any VM Thread object and for the Script object itself.

♦ The Script Message handler will also automatically receive messages that were sent from remote Aztec Scripts across a network or the internet.

♦ Queue<> Receive events

♦ Add an item to the queue, a registered thread wakes up and invokes the queue receive handler automatically.

♦ Socket Receive events

♦ Write to the socket, a registered thread wakes up and invokes the socket receive handler automatically.

♦ Timer Alarm events

♦ The registered thread wakes up and invokes the timer alarm handler automatically.

♦ Exception event(s)

♦ An exception occurs in one thread and automatically wakes up another registered thread as an "alarm" that the exception occurred.

♦ The exception is "handled" in the thread in which it occurred using the standard exceptions/handle construct, but another thread can be notified in parallel, which of course can perform any necessary processing.

With any of the above event handling models, each "specialized" VM Thread efficiently waits in a sleep-type mode for an applicable event. When an event occurs, the thread wakes up, executes the registered event handler(s) with the applicable event object, and then goes back to sleep, waiting for more events.

Aztec Exceptions

An Aztec Exception is an Aztec Event, by definition. As shown in the following diagram of sample Aztec events and exception classes, the ExceptionEvent class is derived directly from the Event class. An Aztec exception needs to be handled using the "exceptions/handle/cleanup" construct in the thread in which the exception occurred. This "traditional" exception handling is discussed in detail on the Exceptions Handling page. But since an Aztec Exception is an Event, an exception can also be processed by one or more event handlers setup in other VM Threads.

The Thread class provides methods to register handlers for a specific type of Exception or any type of Exception. If registered from a separate thread, this allows the registered event handlers to be processed using the services described above, in parallel with the "traditional" exception handling which occurs in the thread where the exception took place.

Sample of Aztec Event Framework

 

Example Chat Scripts Using Remote Messaging

Remote Messaging provides a simple client/server model for communication between Aztec Scripts across a network or the internet. The Script class contains a StartMessageServer() method which the "server" script calls to listen for incoming connections and messages. The "client" script creates a MessageClient object and uses it to connect to the remote Aztec message server. Once connected, the client sends text or binary messages to the remote server using the MessageClient object. The server script receives the incoming messages and sends them to the default message handler for the script (TextMessageHandler or BinaryMessageHandler). The following diagram shows the path that a message takes from the Client Script to the Server Script.

Aztec Remote Message Communication

 

Two simple Chat programs were created as an example for remote messaging, and for event handling in general, and they are available in the Aztec sample source code download package with the names "MessageChat.aztec" and "MessageSerializationChat.aztec". Both scripts are also shown at the end of this page. The following table describes the tasks necessary on each side of the conversation to perform the chat communication. One side is considered the "Server" and one side is considered the "Client". Note that both sides perform client and server duties, but this simple example is setup so that one side has to initiate the process (client). The client and server scripts both use the same physical code, but the programs need to start differently (hence, the different command line arguments).

One important note is that a separate instance of each script must be run on the client and on the server, and they talk to each other. This is a very different approach than you'll see in the "Remote UI" page.

Remote Message Sampe Flow

 

The simple "MessageChat.aztec" example uses five different events, and sets up event handlers for each of them. The setup is very simple, and is almost identical for each of them. Each of the five events is listed below, followed by two separate lines of code from the source file - 1) where the handler is setup, and 2) where the handler method is defined. Once a handler is defined, the system automatically calls the method when the applicable event occurs for that object.

♦ TextMessageEvent for Script - Client and Server both receive messages from other side using this event handler

Script().AddTextMessageHandler(MainTextMessageHandler)
method unique MainTextMessageHandler(TextMessageEvent Event,Base ExtraRef) { }

♦ WindowCloseEvent for main Frame window - to shutdown the communication and the dialog

MainFrame.AddWindowCloseHandler(MainFrameCloseHandler)
method unique MainFrameCloseHandler(WindowCloseEvent CloseEvent1,Base ExtraRef1) { }

♦ WindowResizeEvent for main Frame window - to resize and reposition controls inside the dialog

MainFrame.AddWindowResizeHandler(MainFrameResizeHandler)
method unique MainFrameResizeHandler(WindowResizeEvent ResizeEvent1,Base ExtraRef1) { }

♦ TextChangedEvent for Editor control - to track the state of the control

MessageEditor.AddTextChangedHandler(MessageUpdateHandler)
method unique MessageUpdateHandler(TextChangedEvent ChangedEvent1,Base ExtraRef1) { }

♦ ButtonClickEvent for Send and Close buttons - to send message and to shutdown the chat dialog

SendButton.AddButtonClickHandler(SendHandler)
method unique SendHandler(ButtonClickEvent ButtonEvent1,Base ExtraRef1) { }
ExitButton.AddButtonClickHandler(ExitHandler)
method unique ExitHandler(ButtonClickEvent ButtonEvent1,Base ExtraRef1) { }

All six of the event handler methods registered above are class instance methods in the example script. The object reference associated with the event (Script, MainFrame, MessageEditor and SendButton) is stored internally with the method reference passed into the "AddXXXHandler()" method. When the system invokes the event handler, it automatically pushes the appropriate object reference onto the stack prior to calling the method.

The "MessageSerializationChat" script is very similar to "MessageChat" except for the format of the information it sends across the network. MessageChat uses a simple SendTextMessage() method to send the data to the server, while MessageSerializationChat shows a more advanced, yet simple approach. It embeds the data in an Aztec object, serializes the contents of the object to a MemoryStream, and then sends the memory stream to the server. The server code then serializes from the memory stream back into an Aztec object. This is all done with just a few lines of code. This code loads a separate Aztec source module named 'SerializeManager.aztec', which is also available in the download package. It must be available when running this serialization script. This is a simple yet effective I/O class used for performing serialization of Aztec objects to a memory or file stream, and it could easily be extended to stream the object directly over a socket stream.

♦ On client, serialize the message contents to a new memory stream object and send the message to the server

RemoteMessage = new<AztecRemoteMessage(Message)>
RemoteMessage.SerializeOut()
RemoteMessageClient.SendBinaryMessage(RemoteMessage.OutgoingMessage())

♦ On server, serialize the message contents from memory stream to a new Aztec object

RemoteMessage = new<AztecRemoteMessage>
RemoteMessage.SerializeIn(Event.Message())

These remote messaging chat scripts are part of a set of five separate chat programs provided in the download package. From a UI standpoint, they are all pretty much identical. The main difference between the five scripts is how they communicate. This list contains all five of the chat programs, with a short description of the communication mechanism for each.

♦ MessageChat - Both sides of the conversation use Aztec message server technology

♦ MessageSerializationChat - Both sides of the conversation use Aztec message server technology and serialize an Aztec object across the network

♦ SocketChat - Both sides of the conversation use Aztec socket communcations

♦ HybridChat - Client to Server uses Aztec message server and Server to Client uses Aztec sockets

♦ RemoteUIChat - Entire conversation is driven by the client using Aztec remote UI technology

The following two pictures show the server script and the client script for the remote message chat program. With the simplicity of the Aztec event handling system, together with features like the remote message server, this type of functionality is achieved with just a few hundred lines of code.

Remote Message Chat Example Server    Remote Message Chat Example Client

 

The Aztec source code for the remote messaging chat script MessageChat is shown below.


    
#===============================================================================================
# Example Script: MessageChat
# Demonstrates the use of Aztec remote messaging to create a network chat program.
#===============================================================================================
 
# The "Main" class is derived from 'Thread'. System automatically creates it and invokes Run().
public class Main from<Thread>
{
# Constructor for Main class. Even with no initialization, still need so Thread() is called for base class.
public method Main()
{
}

#--------------------------------------------------------------------------
# This is the main Run method for the Script. Aztec automatically invokes
# this method in order to start execution of the Script. It builds and
# displays the main UI for the script. We also set up the Script Message
# listening port and also connect to the message server for 'client' mode.
# We then go into a perpetual event waiting mode, never to return.
#--------------------------------------------------------------------------
public virtual method Run()
{
data<bool> Success = true
data<int> RemotePort
data<string> Argument
data<string> DialogMessage

# Determine whether we are the "Server" or the "Client", based on command line argument.
Argument = Script().GetArg(1)
if ( Argument.Lwr() == "server")
{
IsServer = true
IncomingPort = 1081
OutgoingPort = 1082
}
else
{
IsServer = false
IncomingPort = 1082
OutgoingPort = 1081

# Set the server name from the second argument.
RemoteName = Script().GetArg(2)
}

# Setup text message event handler for client and server.
Script().AddTextMessageHandler(MainTextMessageHandler)

# Setup the message server to receive messages from the other side (done for client and server).
Success = Script().StartMessageServer(IncomingPort)
if ( Success )
{
#-------------------------------------------------------------------------------
# For client only, create the message client and try to connect to the server.
# Then display the chat dialog which can talk or display connection error.
#-------------------------------------------------------------------------------
if ( !IsServer )
{
# Reset success flag to false for client processing within this block.
Success = false

if ( RemoteName.Len() > 0 )
{
RemoteMessageClient = new<MessageClient>
Success = RemoteMessageClient.Connect(RemoteName,OutgoingPort)
if ( Success )
{
#--------------------------------------------------------------------------------
# Send message with start command to remote message server to establish "chat".
# The message Id is Start, and the text doesn't matter.
#--------------------------------------------------------------------------------
RemoteMessageClient.SendTextMessage("",StartCommand)
DialogMessage = "Client successfully connected to " + RemoteName + " server."
}
else
{
DialogMessage = "Error connecting to " + RemoteName
}
}
else
{
DialogMessage = "Server name is empty"
}
}
else
{
DialogMessage = "Waiting for connection request from client..."
}
}
else
{
DialogMessage = "Error starting message server on Port " + IncomingPort.Str()
}

# Create Chat Dialog object (client and server) and then go into event mode (success or failure).
LocalChatDialog = new<ChatDialog(self,IsServer,Success,DialogMessage)>

EventMode()
}

#---------------------------------------------------------------------------
# This method receives incoming Text messages from remote machine. We also
# support a couple "commmands" in addition to the text bound for the UI.
# The MessageId() of MessageEvent class is used to send several commands
# here - "start" and "stop". The remote name that is connected is in the
# text for the start command.
#---------------------------------------------------------------------------
method unique MainTextMessageHandler(TextMessageEvent Event,Base ExtraRef)
{
data<bool> Success
data<string> MessageString
data<string[]> ClientNames
data<int> shared TotalClientCount = 0

#------------------------------------------------------------------------
# Look at the message ID for special commands, start and stop. The Start
# will only come from the 'client', but Stop can come from either side.
#------------------------------------------------------------------------
MessageString = Event.Text()
if ( Event.MessageId() == StopCommand )
{
if ( IsServer )
LocalChatDialog.DisableMessageInput("Disabled chat - client has shut down.")
else
LocalChatDialog.DisableMessageInput("Disabled chat - server has shut down.")
}
else if ( Event.MessageId() == StartCommand )
{
# Only happens on Server. The client name can be gotten from the Message Client Name list.
ClientNames = Script().GetMessageClients()
if ( ClientNames != null )
{
# Simple logic assumes no conflict with multiple clients connecting simultaneously and conflicting.
TotalClientCount.Inc()
RemoteName = ClientNames[TotalClientCount]

# Connect to the message server on the 'client' machine.
RemoteMessageClient = new<MessageClient>
Success = RemoteMessageClient.Connect(RemoteName,OutgoingPort)
if ( Success )
LocalChatDialog.EnableMessageInput("Established connection with " + RemoteName + " client.")
else
LocalChatDialog.DisableMessageInput("Error connecting to " + RemoteName)
}
else
{
LocalChatDialog.DisableMessageInput("Unable to determine name of message client.")
}
}
else
{
# This is a standard text message and process accordingly.
ProcessMessage(MessageString,false)
}
}

#------------------------------------------------------------------------
# Message to be called from the ChatDialog object when the Send button
# is pressed. It handles it for client and server. We massage the text
# and then send it back to the Chat Dialog. We then also send it to the
# remote connection using Script.SendTextMessage() method.
#------------------------------------------------------------------------
method ProcessMessage(string Message,bool IsLocal)
{
data<string> LocalSource = "Local: "
data<string> RemoteSource = "Remote: "
data<string> MassagedMessage

# Create the message for the local ChatDialog object and send it to conversation window.
if ( IsLocal )
MassagedMessage = LocalSource + Message
else
MassagedMessage = RemoteSource + Message

LocalChatDialog.UpdateConversation(MassagedMessage)

#---------------------------------------------------------------------------
# Send the text to the remote server (client or server), if the local flag
# is on. If remote, put up message saying we received the message.
#---------------------------------------------------------------------------
if ( IsLocal )
RemoteMessageClient.SendTextMessage(Message)
else
LocalChatDialog.SetDialogMessage("Received new message.")
}

# Function to be called from within the script to shut down the program.
method ShutdownScript(string Message)
{
# If we're connected to remote message server, send message to server that we're shutting down.
if ( RemoteMessageClient != null )
{
RemoteMessageClient.SendTextMessage("",StopCommand)
}

Script().WriteLog(Message)
exit
}

# Data items for the 'Main' Class - 'private' by default.
data<int> IncomingPort
data<int> OutgoingPort
data<bool> IsServer
data<string> RemoteName
data<int> const StopCommand = 2
data<int> const StartCommand = 1
data<ChatDialog> LocalChatDialog
data<MessageClient> RemoteMessageClient
}

#-------------------------------------------------------------------------------------------------
# The "ChatDialog" class is responsible for creating a chat window, and it works for the "client"
# side of the link and for the "server" side of the link. The script creates one instance of
# this object. The client creates it right away. The server holds off until it gets a message
# successfully sent to it.
#-------------------------------------------------------------------------------------------------
public class ChatDialog
{
# Constructor for the Main class. Receives main thread object and several other startup flags.
public method ChatDialog(Main MainThread, bool IsServer, bool RemoteConnectionSuccessful = true,
string StartupMessage = "")
{
MainScriptThread = MainThread
IsServerDialog = IsServer

# Build and display the chat dialog.
BuildChatDialog(RemoteConnectionSuccessful,StartupMessage)
}

# This method is used to create the Chat Dialog for local and remote windows.
method BuildChatDialog(bool RemoteConnectionSuccessful, string StartupMessage)
{
data<bool> ReadOnly = true
data<string> FrameTitle

if ( IsServerDialog )
FrameTitle = "Message Server Chat Dialog"
else
FrameTitle = "Message Client Chat Dialog"

# Create main window and the controls for the run-time options. Add Close and Resize handlers.
MainFrame = new<Frame(TargetDisplay,0,0,FrameWidth,FrameHeight,FrameTitle,true)>
MainFrame.AddWindowCloseHandler(MainFrameCloseHandler)
MainFrame.AddWindowResizeHandler(MainFrameResizeHandler)

# Create the "Conversation" box and associated text box (read-only). Use arbitrary size.
ConversationBox = new<GroupBox(MainFrame,TopBoxPosX,TopBoxPosY,10,10," Conversation ")>
ConversationEditor = new<Editor(ConversationBox,1,1,10,10,ReadOnly)>

# Create the "Message" box and associated text box (arbitrary size) and "Send" button (disable at startup).
MessageBox = new<GroupBox(MainFrame,TopBoxPosX,TopBoxPosY,10,10," Outgoing Message ")>
MessageEditor = new<Editor(MessageBox,1,1,10,10)>
MessageEditor.AddTextChangedHandler(MessageUpdateHandler)

SendButton = new<PushButton(MessageBox,1,1,ButtonWidth,ButtonHeight,"Send",null)>
SendButton.AddButtonClickHandler(SendHandler)
SendButton.Disable()

# Finally create the "Alert" text and the "Exit" button and attach the appropriate event handler.
AlertText = new<Text(MainFrame,1,1,10,10,"")>
ExitButton = new<PushButton(MainFrame,1,1,ButtonWidth,ButtonHeight,"Close",null)>
ExitButton.AddButtonClickHandler(ExitHandler)

#-----------------------------------------------------------------------------
# If error connecting to remote display, display error message on local
# window and gray out messaging controls. Otherwise, display startup message.
#-----------------------------------------------------------------------------
if ( !RemoteConnectionSuccessful )
DisableMessageInput(StartupMessage)
else
AlertText.SetWindowText(StartupMessage)

# For server, disable input into the message box until we have an active connection.
if ( IsServerDialog )
{
DisableMessageInput()
}

#--------------------------------------------------------------------
# Display the dialog and and all controls below it. Works the same
# way for the local and the remote UI. The UI framework handles all
# of the details.
#--------------------------------------------------------------------
SetUISizesAndPositions()
MainFrame.Show()
}

# Method to disable messaging controls in Local when error in Remote, or at start of program.
method DisableMessageInput(string ErrorMessage = "")
{
if ( ErrorMessage.Len() > 0 )
{
AlertText.SetWindowText(ErrorMessage)
}

MessageBox.Disable()
ConversationBox.Disable()
}

# Method to turn on the message boxes once we get contacted from client.
method EnableMessageInput(string ErrorMessage = "")
{
if ( ErrorMessage.Len() > 0 )
{
AlertText.SetWindowText(ErrorMessage)
}

MessageBox.Enable()
ConversationBox.Enable()
}

method SetDialogMessage(string Message)
{
AlertText.SetWindowText(Message)
}

#--------------------------------------------------------------------------------------
# Takes a massaged message from the Main object and adds it to the Conversation
# editor control. This will be slightly different based on the source of the message
# and which ChatDialog is receiving it.
#--------------------------------------------------------------------------------------
method UpdateConversation(string Message)
{
# Simply add the string to the conversation editor control.
ConversationEditor.AddLine(Message)
}

#--------------------------------------------------------------------------------------
# Sends a message to the "other" chat dialog. We send it to the Main Script object and
# let it handle the details. It massages it based on who sent it, and then it gets
# sent back out to both ChatDialog objects to be embedded in the Conversation editor.
# Finally, we clear out the Message editor control within this dialog.
#--------------------------------------------------------------------------------------
method unique SendHandler(ButtonClickEvent ButtonEvent1,Base ExtraRef1)
{
data<bool> IsLocal = true
data<string> Message

# Extract the message and send it to the main object for processing.
Message = MessageEditor.WindowText()
MainScriptThread.ProcessMessage(Message,IsLocal)

# Clear the text in the Message editor control and disable Send button.
MessageEditor.SetWindowText("")
SendButton.Disable()
}

#-----------------------------------------------------------------------
# Event handler to get control when the Message field has been updated.
# We simply turn on the Send button. We will also clear out Alert text.
#-----------------------------------------------------------------------
method unique MessageUpdateHandler(TextChangedEvent ChangedEvent1,Base ExtraRef1)
{
SendButton.Enable()
UpdateAlertText("")
}

# Updates the "Alert" text box near the bottom of the window.
method UpdateAlertText(string AlertMessage)
{
AlertText.SetWindowText(AlertMessage)
}

# Event handler to get control when the Chat Dialog is closed.
method unique MainFrameCloseHandler(WindowCloseEvent CloseEvent1,Base ExtraRef1)
{
ShutdownChatDialog()
}

# Event handler to get control when the Chat Dialog is resized.
method unique MainFrameResizeHandler(WindowResizeEvent ResizeEvent1,Base ExtraRef1)
{
# Resize and/or reposition all of the controls in the window.
SetUISizesAndPositions()
}

# Event handler to get control when the Chat Dialog is closed via the 'Close' button.
method unique ExitHandler(ButtonClickEvent ButtonEvent1,Base ExtraRef1)
{
ShutdownChatDialog()
}

#---------------------------------------------------------------------------------
# This method calls the associated shut down method from the Main Script Thread.
# if we're the Local dialog. If we're the remote dialog, then we'll hide ourself
# and then tell the Local dialog to disable messaging controls.
#---------------------------------------------------------------------------------
method ShutdownChatDialog()
{
data<string> Message

if ( IsServerDialog )
{
Message = "Shutting down chat script - cancelled from server side."
MainScriptThread.ShutdownScript(Message)
}
else
{
Message = "Shutting down chat script - cancelled from client side."
MainScriptThread.ShutdownScript(Message)
}

}

#-----------------------------------------------------------------------
# Dynamically sets the position and size of the controls in the frame.
# Invoked when dialog is first displayed and also as a resize handler.
#-----------------------------------------------------------------------
method SetUISizesAndPositions()
{
data<int> ButtonX
data<int> ButtonY
data<int> BoxWidth
data<int> BoxHeight
data<int> FrameWidth
data<int> FrameHeight
data<int> TestFrameWidth
data<int> TestFrameHeight

data<int> const InnerGapX = 5
data<int> const InnerGapY = 5
data<int> const OuterGapX = 10
data<int> const OuterGapY = 10
data<int> const ButtonGapX = 10
data<int> const ButtonGapY = 7
data<int> const MinFrameWidth = 250
data<int> const MinFrameHeight = 250
data<int> const TextToButtonAdjustY = 3

#------------------------------------------------------------------------------
# First, determine width and height of frame to use for sizing other controls.
# We will only let the entire thing get so small, and will fix the dimension
# at the min value, regardless of actual size of the window.
#------------------------------------------------------------------------------
TestFrameWidth = MainFrame.Width()
TestFrameHeight = MainFrame.Height()

# Determine if we use the actual width or the min width for our "virtual frame".
if ( TestFrameWidth > MinFrameWidth )
FrameWidth = TestFrameWidth
else
FrameWidth = MinFrameWidth

# Determine if we use the actual height or the min height for our "virtual frame".
if ( TestFrameHeight > MinFrameHeight )
FrameHeight = TestFrameHeight
else
FrameHeight = MinFrameHeight

#----------------------------------------------------------------------------------------------
# Now we know the size that we're going to work with, so lay out the controls appropriately.
# Some will be moved (most controls and all buttons) and some will be resized. The group boxes
# and the Editor controls will grow in both directions as necessary.
#----------------------------------------------------------------------------------------------

# First, determine the width and height of the two group boxes. They will have same height.
BoxWidth = FrameWidth - (2 * OuterGapX)
BoxHeight = (FrameHeight - ((2 * ButtonGapY) + ButtonHeight + OuterGapY + TopBoxPosY)) / 2

# Do the top box first and its internal editor control.
ConversationBox.SetPos(TopBoxPosX,TopBoxPosY)
ConversationBox.SetSize(BoxWidth,BoxHeight)
ConversationEditor.SetSize(ConversationBox.Width(),ConversationBox.Height())

# Do the bottom box next, its internal editor control and the Send button.
MessageBox.SetPos(TopBoxPosX,TopBoxPosY + BoxHeight + OuterGapY)
MessageBox.SetSize(BoxWidth,BoxHeight)
MessageEditor.SetSize(MessageBox.Width(),MessageBox.Height() - (ButtonHeight + (ButtonGapY *2)))
SendButton.SetPos(1,MessageEditor.Height() + ButtonGapY + 1)

# Finally, modify the position of the Alert text (and size) and the Exit button.
ButtonX = FrameWidth - OuterGapX - ButtonWidth
ButtonY = TopBoxPosY + (BoxHeight * 2) + OuterGapY + ButtonGapY
AlertText.SetPos(OuterGapX,ButtonY + TextToButtonAdjustY)
AlertText.SetSize(ButtonX - (OuterGapX + ButtonGapX),ControlHeight)
ExitButton.SetPos(ButtonX,ButtonY)
}

# Data items for the 'Main' Class - 'private' by default.
data<Main> MainScriptThread
data<Display> TargetDisplay

data<Frame> MainFrame
data<GroupBox> ConversationBox
data<GroupBox> MessageBox
data<Editor> ConversationEditor
data<Editor> MessageEditor
data<Text> AlertText
data<Button> SendButton
data<Button> ExitButton

data<bool> IsClosed
data<bool> IsServerDialog

data<int> const FrameWidth = 600
data<int> const FrameHeight = 500
data<int> const TopBoxPosX = 10
data<int> const TopBoxPosY = 15
data<int> const ButtonWidth = 75
data<int> const ButtonHeight = 25
data<int> const ControlHeight = 25
}

The Aztec source code for the remote messaging chat script MessageSerializationChat is shown below.


    
#=====================================================================================================
# Example Script: MessageSerializationChat
# Demonstrates the use of Aztec message server technology to create a network chat program. This code
# also uses serialization to transfer contents of an Aztec object across the network.
#=====================================================================================================

# Uses the serialization services which are in a separate Aztec source file.
CompilerLoadModule('SerializeManager')

# The "Main" class is derived from 'Thread'. System automatically creates it and invokes Run().
public class Main from<Thread>
{
# Constructor for Main class. Even with no initialization, still need so Thread() is called for base class.
public method Main()
{
}

#--------------------------------------------------------------------------
# This is the main Run method for the Script. Aztec automatically invokes
# this method in order to start execution of the Script. It builds and
# displays the main UI for the script. We also set up the Script Message
# listening port and also connect to the message server for 'client' mode.
# We then go into a perpetual event waiting mode, never to return.
#--------------------------------------------------------------------------
public virtual method Run()
{
data<bool> Success = true
data<int> RemotePort
data<string> Argument
data<string> DialogMessage

# Determine whether we are the "Server" or the "Client", based on command line argument.
Argument = Script().GetArg(1)
if ( Argument.Lwr() == "server")
{
IsServer = true
IncomingPort = 1081
OutgoingPort = 1082
}
else
{
IsServer = false
IncomingPort = 1082
OutgoingPort = 1081

# Set the server name from the second argument.
RemoteName = Script().GetArg(2)
}

# Setup binary message event handler for client and server.
Script().AddBinaryMessageHandler(MainBinaryMessageHandler)

# Setup the message server to receive messages from the other side (done for client and server).
Success = Script().StartMessageServer(IncomingPort)
if ( Success )
{
#-------------------------------------------------------------------------------
# For client only, create the message client and try to connect to the server.
# Then display the chat dialog which can talk or display connection error.
#-------------------------------------------------------------------------------
if ( !IsServer )
{
# Reset success flag to false for client processing within this block.
Success = false
if ( RemoteName.Len() > 0 )
{
RemoteMessageClient = new<MessageClient>
Success = RemoteMessageClient.Connect(RemoteName,OutgoingPort)
if ( Success )
{
# Create a memory buffer to hold just the remote name.
data<MemoryStream> NameStream = new<MemoryStream>

#--------------------------------------------------------------------------------
# Send message with start command to remote message server to establish "chat".
# The message Id is Start, and send memory buffer message with it.
#--------------------------------------------------------------------------------
RemoteMessageClient.SendBinaryMessage(NameStream,0,StartCommand)
DialogMessage = "Client successfully connected to " + RemoteName + " server."
}
else
{
DialogMessage = "Error connecting to " + RemoteName
}
}
else
{
DialogMessage = "Server name is empty"
}
}
else
{
DialogMessage = "Waiting for connection request from client..."
}
}
else
{
DialogMessage = "Error starting message server on Port " + IncomingPort.Str()
}

# Create Chat Dialog object (client and server) and then go into event mode (success or failure).
LocalChatDialog = new<ChatDialog(self,IsServer,Success,DialogMessage)>

EventMode()
}

#----------------------------------------------------------------------------
# This method receives incoming binary messages from remote machine. We also
# support a couple "commmands" in addition to the object message bound for
# the UI. The MessageId() of MessageEvent class is used to send several
# commands here - "start" and "stop". Otherwise, it is used to indicate a
# a real binary message (which is an "AztecRemoteMessage" object).
#----------------------------------------------------------------------------
method unique MainBinaryMessageHandler(BinaryMessageEvent Event,Base ExtraRef)
{
data<bool> Success
data<string[]> ClientNames
data<int> shared TotalClientCount = 0
data<AztecRemoteMessage> RemoteMessage

#------------------------------------------------------------------------
# Look at the message ID for special commands, start and stop. For the
# start command, the remote name is contained in the text. For the stop
# command, there is no text. The Start will only come from the 'client',
# but the Stop can come from either side.
#------------------------------------------------------------------------
if ( Event.MessageId() == StopCommand )
{
if ( IsServer )
LocalChatDialog.DisableMessageInput("Disabled chat - client has shut down.")
else
LocalChatDialog.DisableMessageInput("Disabled chat - server has shut down.")
}
else if ( Event.MessageId() == StartCommand )
{
# Only happens on Server. The client name can be gotten from the Message Client Name list.
ClientNames = Script().GetMessageClients()
if ( ClientNames != null )
{
# Simple logic assumes no conflict with multiple clients connecting simultaneously and conflicting.
TotalClientCount.Inc()
RemoteName = ClientNames[TotalClientCount]

# Connect to the message server on the 'client' machine.
RemoteMessageClient = new<MessageClient>
Success = RemoteMessageClient.Connect(RemoteName,OutgoingPort)
if ( Success )
LocalChatDialog.EnableMessageInput("Established connection with " + RemoteName + " client.")
else
LocalChatDialog.DisableMessageInput("Error connecting to " + RemoteName)
}
else
{
LocalChatDialog.DisableMessageInput("Unable to determine name of message client.")
}
}
else
{
# This is a standard binary message. Create message object and serialize binary message into it.
RemoteMessage = new<AztecRemoteMessage>
RemoteMessage.SerializeIn(Event.Message())

# Process the text portion of the message and send it to the UI.
ProcessMessage(RemoteMessage.MessageText(),false)
}
}

#------------------------------------------------------------------------
# Message to be called from the ChatDialog object when the Send button
# is pressed. It handles it for client and server. We massage the text
# and then send it back to the Chat Dialog. We then also send it to the
# remote connection using Script.SendTextMessage() method.
#------------------------------------------------------------------------
method ProcessMessage(string Message,bool IsLocal)
{
data<string> LocalSource = "Local: "
data<string> RemoteSource = "Remote: "
data<string> MassagedMessage
data<AztecRemoteMessage> RemoteMessage

# Create the message for the local ChatDialog object and send it to conversation window.
if ( IsLocal )
MassagedMessage = LocalSource + Message
else
MassagedMessage = RemoteSource + Message

LocalChatDialog.UpdateConversation(MassagedMessage)

#---------------------------------------------------------------------------
# Send the text to the remote server (client or server), if the local flag
# is on. If remote, put up message saying we received the message.
#---------------------------------------------------------------------------
if ( IsLocal )
{
# Create a message object and serialize it to a memory buffer and then reset position to 1.
RemoteMessage = new<AztecRemoteMessage(Message)>
RemoteMessage.SerializeOut()
RemoteMessage.OutgoingMessage().SetPos(1)

# Get the memory stream from the message and send it to the server.
RemoteMessageClient.SendBinaryMessage(RemoteMessage.OutgoingMessage())
}
else
{
LocalChatDialog.SetDialogMessage("Received new message.")
}
}

# Function to be called from within the script to shut down the program.
method ShutdownScript(string Message)
{
# If we're connected to remote message server, send message to server that we're shutting down.
if ( RemoteMessageClient != null )
{
RemoteMessageClient.SendBinaryMessage(null,0,StopCommand)
}

Script().WriteLog(Message)
exit
}

# Data items for the 'Main' Class - 'private' by default.
data<int> IncomingPort
data<int> OutgoingPort
data<bool> IsServer
data<string> RemoteName
data<int> const StopCommand = 2
data<int> const StartCommand = 1
data<ChatDialog> LocalChatDialog
data<MessageClient> RemoteMessageClient
}

#-------------------------------------------------------------------------------------------
# This class contains the "message" to be sent to the message server. We will create an
# instance, serialize it to a memory stream (buffer) and send it to the server. The server
# then automatically creates the message object from the binary buffer. The serialization
# methods for input and output to a memory buffer are included in the class.
#-------------------------------------------------------------------------------------------
class AztecRemoteMessage
{
# Constructor used when creating the message on client to send to server.
method AztecRemoteMessage(string Text)
{
# Save text message and record time this message is created.
MessageText = Text
CreationTime = new<Time>
}

# Constructor used when creating empty message on server to serialize in message from client.
method AztecRemoteMessage()
{
}

method<Time> CreationTime()
{
return(CreationTime)
}

method<string> MessageText()
{
return(MessageText)
}

# Balance the I/O in this output method with the I/O in the input method below.
method SerializeOut()
{
data<SerializeManager> IOManager

# Create a serialization object with memory and then write contents of the class.
IOManager = new<SerializeManager>

# Write out all of the items in this object. Write time as seconds and milliseconds.
IOManager.Out(MessageText)
IOManager.Out(CreationTime.TotalSeconds())
IOManager.Out(CreationTime.MilliSecond())

# Set the outgoing buffer with the memory stream inside the serialize manager.
OutgoingMessage = IOManager.SerializeStream() as type<MemoryStream>
}

# Balance the I/O in this input method with the I/O in the output method above.
method SerializeIn(MemoryStream Buffer)
{
data<int> Seconds
data<int> MilliSeconds
data<SerializeManager> IOManager

# Create a serialization object with memory and then read contents of this class from the buffer.
IOManager = new<SerializeManager(Buffer)>

# Read in all of the items in this object. Read time as seconds and milliseconds.
IOManager.In(@MessageText)
IOManager.In(@Seconds)
IOManager.In(@MilliSeconds)

# Create the original time using the seconds/milliseconds from the message.
CreationTime = new<Time(Seconds,MilliSeconds)>

# Let's add in time to incoming message automatically.
MessageText.Add(" (" + CreationTime.TimeStr(TimeFormat.HHMMSSms) + ")")
}

method<MemoryStream> OutgoingMessage()
{
return(OutgoingMessage)
}

# Internal data items visible only in the class.
data<Time> CreationTime
data<string> MessageText
data<MemoryStream> OutgoingMessage
}

#-------------------------------------------------------------------------------------------------
# The "ChatDialog" class is responsible for creating a chat window, and it works for the "client"
# side of the link and for the "server" side of the link. The script creates one instance of
# this object. The client creates it right away. The server holds off until it gets a message
# successfully sent to it.
#-------------------------------------------------------------------------------------------------
public class ChatDialog
{
# Constructor for the Main class. Receives main thread object and several other startup flags.
public method ChatDialog(Main MainThread, bool IsServer, bool RemoteConnectionSuccessful = true,
string StartupMessage = "")
{
MainScriptThread = MainThread
IsServerDialog = IsServer

# Build and display the chat dialog.
BuildChatDialog(RemoteConnectionSuccessful,StartupMessage)
}

# This method is used to create the Chat Dialog for local and remote windows.
method BuildChatDialog(bool RemoteConnectionSuccessful, string StartupMessage)
{
data<bool> ReadOnly = true
data<string> FrameTitle

if ( IsServerDialog )
FrameTitle = "Message Server Chat Dialog"
else
FrameTitle = "Message Client Chat Dialog"

# Create main window and the controls for the run-time options. Add Close and Resize handlers.
MainFrame = new<Frame(TargetDisplay,0,0,FrameWidth,FrameHeight,FrameTitle,true)>
MainFrame.AddWindowCloseHandler(MainFrameCloseHandler)
MainFrame.AddWindowResizeHandler(MainFrameResizeHandler)

# Create the "Conversation" box and associated text box (read-only). Use arbitrary size.
ConversationBox = new<GroupBox(MainFrame,TopBoxPosX,TopBoxPosY,10,10," Conversation ")>
ConversationEditor = new<Editor(ConversationBox,1,1,10,10,ReadOnly)>

# Create the "Message" box and associated text box (arbitrary size) and "Send" button (disable at startup).
MessageBox = new<GroupBox(MainFrame,TopBoxPosX,TopBoxPosY,10,10," Outgoing Message ")>
MessageEditor = new<Editor(MessageBox,1,1,10,10)>
MessageEditor.AddTextChangedHandler(MessageUpdateHandler)

SendButton = new<PushButton(MessageBox,1,1,ButtonWidth,ButtonHeight,"Send",null)>
SendButton.AddButtonClickHandler(SendHandler)
SendButton.Disable()

# Finally create the "Alert" text and the "Exit" button and attach the appropriate event handler.
AlertText = new<Text(MainFrame,1,1,10,10,"")>
ExitButton = new<PushButton(MainFrame,1,1,ButtonWidth,ButtonHeight,"Close",null)>
ExitButton.AddButtonClickHandler(ExitHandler)

#-----------------------------------------------------------------------------
# If error connecting to remote display, display error message on local
# window and gray out messaging controls. Otherwise, display startup message.
#-----------------------------------------------------------------------------
if ( !RemoteConnectionSuccessful )
DisableMessageInput(StartupMessage)
else
AlertText.SetWindowText(StartupMessage)

# For server, disable input into the message box until we have an active connection.
if ( IsServerDialog )
{
DisableMessageInput()
}

#--------------------------------------------------------------------
# Display the dialog and and all controls below it. Works the same
# way for the local and the remote UI. The UI framework handles all
# of the details.
#--------------------------------------------------------------------
SetUISizesAndPositions()
MainFrame.Show()
}

# Method to disable messaging controls in Local when error in Remote, or at start of program.
method DisableMessageInput(string ErrorMessage = "")
{
if ( ErrorMessage.Len() > 0 )
{
AlertText.SetWindowText(ErrorMessage)
}

MessageBox.Disable()
ConversationBox.Disable()
}

# Method to turn on the message boxes once we get contacted from client.
method EnableMessageInput(string ErrorMessage = "")
{
if ( ErrorMessage.Len() > 0 )
{
AlertText.SetWindowText(ErrorMessage)
}

MessageBox.Enable()
ConversationBox.Enable()
}

method SetDialogMessage(string Message)
{
AlertText.SetWindowText(Message)
}

#--------------------------------------------------------------------------------------
# Takes a massaged message from the Main object and adds it to the Conversation
# editor control. This will be slightly different based on the source of the message
# and which ChatDialog is receiving it.
#--------------------------------------------------------------------------------------
method UpdateConversation(string Message)
{
# Simply add the string to the conversation editor control.
ConversationEditor.AddLine(Message)
}

#--------------------------------------------------------------------------------------
# Sends a message to the "other" chat dialog. We send it to the Main Script object and
# let it handle the details. It massages it based on who sent it, and then it gets
# sent back out to both ChatDialog objects to be embedded in the Conversation editor.
# Finally, we clear out the Message editor control within this dialog.
#--------------------------------------------------------------------------------------
method unique SendHandler(ButtonClickEvent ButtonEvent1,Base ExtraRef1)
{
data<bool> IsLocal = true
data<string> Message

# Extract the message and send it to the main object for processing.
Message = MessageEditor.WindowText()
MainScriptThread.ProcessMessage(Message,IsLocal)

# Clear the text in the Message editor control and disable Send button.
MessageEditor.SetWindowText("")
SendButton.Disable()
}

#-----------------------------------------------------------------------
# Event handler to get control when the Message field has been updated.
# We simply turn on the Send button. We will also clear out Alert text.
#-----------------------------------------------------------------------
method unique MessageUpdateHandler(TextChangedEvent ChangedEvent1,Base ExtraRef1)
{
SendButton.Enable()
UpdateAlertText("")
}

# Updates the "Alert" text box near the bottom of the window.
method UpdateAlertText(string AlertMessage)
{
AlertText.SetWindowText(AlertMessage)
}

# Event handler to get control when the Chat Dialog is closed.
method unique MainFrameCloseHandler(WindowCloseEvent CloseEvent1,Base ExtraRef1)
{
ShutdownChatDialog()
}

# Event handler to get control when the Chat Dialog is resized.
method unique MainFrameResizeHandler(WindowResizeEvent ResizeEvent1,Base ExtraRef1)
{
# Resize and/or reposition all of the controls in the window.
SetUISizesAndPositions()
}

# Event handler to get control when the Chat Dialog is closed via the 'Close' button.
method unique ExitHandler(ButtonClickEvent ButtonEvent1,Base ExtraRef1)
{
ShutdownChatDialog()
}

#---------------------------------------------------------------------------------
# This method calls the associated shut down method from the Main Script Thread.
# if we're the Local dialog. If we're the remote dialog, then we'll hide ourself
# and then tell the Local dialog to disable messaging controls.
#---------------------------------------------------------------------------------
method ShutdownChatDialog()
{
data<string> Message

if ( IsServerDialog )
{
Message = "Shutting down chat script - cancelled from server side."
MainScriptThread.ShutdownScript(Message)
}
else
{
Message = "Shutting down chat script - cancelled from client side."
MainScriptThread.ShutdownScript(Message)
}

}

#-----------------------------------------------------------------------
# Dynamically sets the position and size of the controls in the frame.
# Invoked when dialog is first displayed and also as a resize handler.
#-----------------------------------------------------------------------
method SetUISizesAndPositions()
{
data<int> ButtonX
data<int> ButtonY
data<int> BoxWidth
data<int> BoxHeight
data<int> FrameWidth
data<int> FrameHeight
data<int> TestFrameWidth
data<int> TestFrameHeight

data<int> const InnerGapX = 5
data<int> const InnerGapY = 5
data<int> const OuterGapX = 10
data<int> const OuterGapY = 10
data<int> const ButtonGapX = 10
data<int> const ButtonGapY = 7
data<int> const MinFrameWidth = 250
data<int> const MinFrameHeight = 250
data<int> const TextToButtonAdjustY = 3

#------------------------------------------------------------------------------
# First, determine width and height of frame to use for sizing other controls.
# We will only let the entire thing get so small, and will fix the dimension
# at the min value, regardless of actual size of the window.
#------------------------------------------------------------------------------
TestFrameWidth = MainFrame.Width()
TestFrameHeight = MainFrame.Height()

# Determine if we use the actual width or the min width for our "virtual frame".
if ( TestFrameWidth > MinFrameWidth )
FrameWidth = TestFrameWidth
else
FrameWidth = MinFrameWidth

# Determine if we use the actual height or the min height for our "virtual frame".
if ( TestFrameHeight > MinFrameHeight )
FrameHeight = TestFrameHeight
else
FrameHeight = MinFrameHeight

#----------------------------------------------------------------------------------------------
# Now we know the size that we're going to work with, so lay out the controls appropriately.
# Some will be moved (most controls and all buttons) and some will be resized. The group boxes
# and the Editor controls will grow in both directions as necessary.
#----------------------------------------------------------------------------------------------

# First, determine the width and height of the two group boxes. They will have same height.
BoxWidth = FrameWidth - (2 * OuterGapX)
BoxHeight = (FrameHeight - ((2 * ButtonGapY) + ButtonHeight + OuterGapY + TopBoxPosY)) / 2

# Do the top box first and its internal editor control.
ConversationBox.SetPos(TopBoxPosX,TopBoxPosY)
ConversationBox.SetSize(BoxWidth,BoxHeight)
ConversationEditor.SetSize(ConversationBox.Width(),ConversationBox.Height())

# Do the bottom box next, its internal editor control and the Send button.
MessageBox.SetPos(TopBoxPosX,TopBoxPosY + BoxHeight + OuterGapY)
MessageBox.SetSize(BoxWidth,BoxHeight)
MessageEditor.SetSize(MessageBox.Width(),MessageBox.Height() - (ButtonHeight + (ButtonGapY *2)))
SendButton.SetPos(1,MessageEditor.Height() + ButtonGapY + 1)

# Finally, modify the position of the Alert text (and size) and the Exit button.
ButtonX = FrameWidth - OuterGapX - ButtonWidth
ButtonY = TopBoxPosY + (BoxHeight * 2) + OuterGapY + ButtonGapY
AlertText.SetPos(OuterGapX,ButtonY + TextToButtonAdjustY)
AlertText.SetSize(ButtonX - (OuterGapX + ButtonGapX),ControlHeight)
ExitButton.SetPos(ButtonX,ButtonY)
}

# Data items for the 'Main' Class - 'private' by default.
data<Main> MainScriptThread
data<Display> TargetDisplay

data<Frame> MainFrame
data<GroupBox> ConversationBox
data<GroupBox> MessageBox
data<Editor> ConversationEditor
data<Editor> MessageEditor
data<Text> AlertText
data<Button> SendButton
data<Button> ExitButton

data<bool> IsClosed
data<bool> IsServerDialog

data<int> const FrameWidth = 600
data<int> const FrameHeight = 500
data<int> const TopBoxPosX = 10
data<int> const TopBoxPosY = 15
data<int> const ButtonWidth = 75
data<int> const ButtonHeight = 25
data<int> const ControlHeight = 25
}

 

Page UpPage DownCopyright © 2010-2017
Aztec Development Group
All Rights Reserved

Download Aztec