Tuesday, January 8, 2019

Pseudo Visual Compiler using Interface to Ada as the OFP - Part 2



Following along with the translation of the C# message framework to Ada of the previous post, I am reporting my progress.

In C# the interface to Windows was available in their (Microsoft's) system classes.  In Ada, via the GNAT libraries, it is necessary to use C interfaces.  This post will describe those used to implement my C# Threads class with its associated PeriodicTimer class - both of which are in the Threads.cs file.  The PeriodicTimer class uses wait handles which is also used by the Disburse class.  Both PeriodicTimer and Disburse use wait handles to signal the end of a wait to periodic components or those components that are to be run when a message is added to their receive queue. 

In this post, the Ada version of the Threads class and the PeriodicTimer class will be presented along with wait events that allow a periodic component to wait until its period interval has elapsed in order to resume the component.  The components will only be test components to allow the wait events to be tested.  The actual component to implement the greatly reduced OFP will be done later after the necessary Ada packages have been translated from their C# classes to allow for the delivery of messages from the remote C# application.

Threads

In the C# application, the Threads Create method is run last to allow the other classes to be instantiated and the user declared components to install themselves into the application.  This allows separate threads to be created for each component that has been registered with the Component class.  Then, for periodic components, an instance of the PeriodicTimer class is instantiated so that it can signal the component when it is to resume.

The Ada package that implements this feature has been coded in approximately the reverse order from the C# class.  This is because Ada has to have the procedure or function declared before it is referenced.  Ada allow a declaration of the routine prior to the code that implements it (called the body) but I chose to just implement the routine without a separate declaration so the implementation has to be placed after code that it references.

And, of course, Ada doesn't know about Windows thread priorities.  So that is a small amount of additional code.  Note: I'm not sure that I have made the correct assignments concerning Windows thread priorities.  I have assumed the normal priority class.  For this application it shouldn't make much difference.


The basic flow of the Threads class is the necessary structure structs and classes and the static variables followed by the methods.  The public Create method is first.  It initializes the threadTable and creates and starts a timing scheduler thread.  Following this is a utility method to create component priorities to Windows priorities.  Then the TimingScheduler method.  For each component that has been registered, it adds to the threadTable and, if not a Receive thread, creates the thread.  The start procedure is denoted by lines such as
threadTable.list[i].threadInstance = new Thread(ComThread2);
where ComThread2 is for the second component thread.  Numerous thread procedures are implemented to have as sufficient number but threads are only created for components that have been registered. 

Each of the thread procedures are similar to
        private static void ComThread1()
        {
            ComponentThread(0);
        }
 doing nothing but calling the common ComponentThread method passing their index into the threadTable.

Following the TimingScheduler method is the ComponentThread method that each of the created component threads will invoke.  It will use separate stack space to retain the variables for each invocation and each invocation will run in its own thread.  After all the component threads have started, ComponentThread obtains the main entry point of the component from the table in the Component class and, if such an entry point has been provided, checks if the component is to run periodically.  If so, it starts a timer for the thread.  In either case it invokes the component's entry point to start its execution.  The component must then do any initialization for its thread and wait for a continue event.  (Of course, a component could be stand alone and execute without the need to receive messages and manage its own delays and such so that there would be no need for it to wait for a PeriodicTimer event or a message received and queued event.)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Threading;

namespace VisualCompiler
{
    public class Threads
    {
        // This framework class has the instances of various threads.  One is a
        // higher priority TimingScheduler thread and the others are threads of
        // a pool of threads for components to run in where a separate thread
        // is assigned to each installed component.  These component threads
        // are assigned the priorities requested by the component except that
        // no component thread is assigned a priority above normal.

        public enum ComponentThreadPriority
        {
            WHATEVER,
            HIGHEST,
            HIGH,
            NORMAL,
            LOWER,
            LOWEST
        };

        // Component thread data
        private struct ThreadDataType
        {
            public string name;
            public Thread threadInstance;
            public ThreadPriority priority;
        }

        // Component thread list
        private class ComponentThreadType
        {
            public int count; // Number of component threads.  Note: This should
                              //  end up equal to the number of components.
            public ThreadDataType[] list = new ThreadDataType[Component.MaxComponents];
                                    // List of component threads
        }

        // Thread pool of component threads
        static private ComponentThreadType threadTable = new ComponentThreadType();

        // To allow Remote to be invoked for the correct instance
        // of the receive thread.
        static int receiveIndex = 0;
       
        // To allow ComponentThread to idle until all component threads have started
        static bool allThreadsStarted = false;

        // Create the TimingScheduler thread with above normal priority and start it.
        public static void Create()
        {
            var timingScheduler = new Thread(TimingScheduler);
            timingScheduler.Priority = ThreadPriority.AboveNormal;

            // Set the number of threads in the thread pool to the number of components
            threadTable.count = Component.componentTable.count;

            // Start the TimingScheduler
            timingScheduler.Start();
        } // end Create

        // Convert component thread priority to that of Windows
        private static ThreadPriority ConvertThreadPriority(
            Component.ComponentKind kind,
            ComponentThreadPriority priority)
        { // Only for user component threads.
            if (kind == Component.ComponentKind.USER)
            {
                // No component thread is allowed to have a priority above Normal.
                if (priority == ComponentThreadPriority.LOWER) return ThreadPriority.BelowNormal;
                if (priority == ComponentThreadPriority.LOWEST) return ThreadPriority.Lowest;
                return ThreadPriority.Normal;
            }
            else
            {
                // Framework threads are allowed to have their specified priority.
                if (priority == ComponentThreadPriority.HIGHEST) return ThreadPriority.Highest;
                if (priority == ComponentThreadPriority.HIGH) return ThreadPriority.AboveNormal;
                if (priority == ComponentThreadPriority.LOWER) return ThreadPriority.BelowNormal;
                if (priority == ComponentThreadPriority.LOWEST) return ThreadPriority.Lowest;
                return ThreadPriority.Normal;

            }
        } // end ConvertThreadPriority

        // The framework TimingScheduler thread
        private static void TimingScheduler() // thread to manage component threads
        {
            DateTime start = DateTime.Now;
            var stopWatch = Stopwatch.StartNew();

            // Create the component thread pool/factory; one thread for each
            // component.  Wait until all are created before starting the threads.

            if (threadTable.count > 0)
            {
                int i = 0;
                Component.ComponentKind kind =
                     Component.componentTable.list[i].kind;
                ComponentThreadPriority reqPriority =
                    Component.componentTable.list[i].priority;
                ThreadPriority threadPriority;
                threadPriority = ConvertThreadPriority(kind, reqPriority);
                threadTable.list[i].name = "ComThread1";
                threadTable.list[i].priority = threadPriority;
                if (kind == Component.ComponentKind.RECEIVE)
                {
                    threadTable.list[i].threadInstance = Remote.ReceiveThread(receiveIndex);
                    receiveIndex++;
                }
                else
                {
                    threadTable.list[i].threadInstance = new Thread(ComThread1);
                }
                threadTable.list[i].threadInstance.Priority = threadPriority;
            }
            if (threadTable.count > 1)
            {
                int i = 1;
                threadTable.list[i].name = "ComThread2";
                Component.ComponentKind kind =
                    Component.componentTable.list[i].kind;
                ComponentThreadPriority reqPriority =
                    Component.componentTable.list[i].priority;
                ThreadPriority threadPriority;
                threadPriority = ConvertThreadPriority(kind, reqPriority);
                threadTable.list[i].priority = threadPriority;
                if (kind == Component.ComponentKind.RECEIVE)
                {
                    threadTable.list[i].threadInstance = Remote.ReceiveThread(receiveIndex);
                    receiveIndex++;
                }
                else
                {
                    threadTable.list[i].threadInstance = new Thread(ComThread2);
                }

                threadTable.list[i].threadInstance.Priority = threadPriority;
            }
            if (threadTable.count > 2)
            {
                int i = 2;
                threadTable.list[i].name = "ComThread3";
                Component.ComponentKind kind =
                    Component.componentTable.list[i].kind;
                ComponentThreadPriority reqPriority =
                    Component.componentTable.list[i].priority;
                ThreadPriority threadPriority;
                threadPriority = ConvertThreadPriority(kind,reqPriority);
                threadTable.list[i].priority = threadPriority;
                if (kind == Component.ComponentKind.RECEIVE)
                {
                    threadTable.list[i].threadInstance = Remote.ReceiveThread(receiveIndex);
                    receiveIndex++;
                }
                else
                {
                    threadTable.list[i].threadInstance = new Thread(ComThread3);
                }
                threadTable.list[i].threadInstance.Priority = threadPriority;
            }
            if (threadTable.count > 3)
            {
                int i = 3;
                threadTable.list[i].name = "ComThread4";
                Component.ComponentKind kind =
                    Component.componentTable.list[i].kind;
                ComponentThreadPriority reqPriority =
                    Component.componentTable.list[i].priority;
                ThreadPriority threadPriority;
                threadPriority = ConvertThreadPriority(kind,reqPriority);
                threadTable.list[i].priority = threadPriority;
                if (kind == Component.ComponentKind.RECEIVE)
                {
                    threadTable.list[i].threadInstance = Remote.ReceiveThread(receiveIndex);
                    receiveIndex++;
                }
                else
                {
                    threadTable.list[i].threadInstance = new Thread(ComThread4);
                }
                threadTable.list[i].threadInstance.Priority = threadPriority;
            }
            if (threadTable.count > 4)
            {
                int i = 4;
                threadTable.list[i].name = "ComThread5";
                Component.ComponentKind kind =
                    Component.componentTable.list[i].kind;
                ComponentThreadPriority reqPriority =
                    Component.componentTable.list[i].priority;
                ThreadPriority threadPriority;
                threadPriority = ConvertThreadPriority(kind, reqPriority);
                threadTable.list[i].priority = threadPriority;
                if (kind == Component.ComponentKind.RECEIVE)
                {
                    threadTable.list[i].threadInstance = Remote.ReceiveThread(receiveIndex);
                    receiveIndex++;
                }
                else
                {
                    threadTable.list[i].threadInstance = new Thread(ComThread5);
                }
                threadTable.list[i].threadInstance.Priority = threadPriority;
            }
            if (threadTable.count > 5)
            {
                int i = 5;
                threadTable.list[i].name = "ComThread6";
                Component.ComponentKind kind =
                    Component.componentTable.list[i].kind;
                ComponentThreadPriority reqPriority =
                    Component.componentTable.list[i].priority;
                ThreadPriority threadPriority;
                threadPriority = ConvertThreadPriority(kind, reqPriority);
                threadTable.list[i].priority = threadPriority;
                if (kind == Component.ComponentKind.RECEIVE)
                {
                    threadTable.list[i].threadInstance = Remote.ReceiveThread(receiveIndex);
                    receiveIndex++;
                }
                else
                {
                    threadTable.list[i].threadInstance = new Thread(ComThread6);
                }
                threadTable.list[i].threadInstance.Priority = threadPriority;
            }
            if (threadTable.count > 6)
            {
                int i = 6;
                threadTable.list[i].name = "ComThread7";
                 Component.ComponentKind kind =
                    Component.componentTable.list[i].kind;
                ComponentThreadPriority reqPriority =
                    Component.componentTable.list[i].priority;
                ThreadPriority threadPriority;
                threadPriority = ConvertThreadPriority(kind,reqPriority);
                threadTable.list[i].priority = threadPriority;
                if (kind == Component.ComponentKind.RECEIVE)
                {
                    threadTable.list[i].threadInstance = Remote.ReceiveThread(receiveIndex);
                    receiveIndex++;
                }
                else
                {
                    threadTable.list[i].threadInstance = new Thread(ComThread7);
                }
                threadTable.list[i].threadInstance.Priority = threadPriority;
            }
            if (threadTable.count > 7)
            {
                int i = 7;
                threadTable.list[i].name = "ComThread8";
                Component.ComponentKind kind =
                    Component.componentTable.list[i].kind;
                ComponentThreadPriority reqPriority =
                    Component.componentTable.list[i].priority;
                ThreadPriority threadPriority;
                threadPriority = ConvertThreadPriority(kind,reqPriority);
                threadTable.list[i].priority = threadPriority;
                if (kind == Component.ComponentKind.RECEIVE)
                {
                    threadTable.list[i].threadInstance = Remote.ReceiveThread(receiveIndex);
                    receiveIndex++;
                }
                else
                {
                    threadTable.list[i].threadInstance = new Thread(ComThread8);
                }
                threadTable.list[i].threadInstance.Priority = threadPriority;
            }
            if (threadTable.count > 8)
            {
                int i = 8;
                threadTable.list[i].name = "ComThread9";
                Component.ComponentKind kind =
                    Component.componentTable.list[i].kind;
                ComponentThreadPriority reqPriority =
                    Component.componentTable.list[i].priority;
                ThreadPriority threadPriority;
                threadPriority = ConvertThreadPriority(kind, reqPriority);
                threadTable.list[i].priority = threadPriority;
                if (kind == Component.ComponentKind.RECEIVE)
                {
                    threadTable.list[i].threadInstance = Remote.ReceiveThread(receiveIndex);
                    receiveIndex++;
                }
                else
                {
                    threadTable.list[i].threadInstance = new Thread(ComThread9);
                }
                threadTable.list[i].threadInstance.Priority = threadPriority;
            }
            if (threadTable.count > 9)
            {
                int i = 9;
                threadTable.list[i].name = "ComThread10";
                Component.ComponentKind kind =
                    Component.componentTable.list[i].kind;
                ComponentThreadPriority reqPriority =
                    Component.componentTable.list[i].priority;
                ThreadPriority threadPriority;
                threadPriority = ConvertThreadPriority(kind, reqPriority);
                threadTable.list[i].priority = threadPriority;
                if (kind == Component.ComponentKind.RECEIVE)
                {
                    threadTable.list[i].threadInstance = Remote.ReceiveThread(receiveIndex);
                    receiveIndex++;
                }
                else
                {
                    threadTable.list[i].threadInstance = new Thread(ComThread10);
                }
                threadTable.list[i].threadInstance.Priority = threadPriority;
            }
            if (threadTable.count > 10)
            {
                int i = 10;
                threadTable.list[i].name = "ComThread11";
                Component.ComponentKind kind =
                    Component.componentTable.list[i].kind;
                ComponentThreadPriority reqPriority =
                    Component.componentTable.list[i].priority;
                ThreadPriority threadPriority;
                threadPriority = ConvertThreadPriority(kind, reqPriority);
                threadTable.list[i].priority = threadPriority;
                if (kind == Component.ComponentKind.RECEIVE)
                {
                    threadTable.list[i].threadInstance = Remote.ReceiveThread(receiveIndex);
                    receiveIndex++;
                }
                else
                {
                    threadTable.list[i].threadInstance = new Thread(ComThread11);
                }
                threadTable.list[i].threadInstance.Priority = threadPriority;
            }
            if (threadTable.count > 11)
            {
                int i = 11;
                threadTable.list[i].name = "ComThread12";
                Component.ComponentKind kind =
                    Component.componentTable.list[i].kind;
                ComponentThreadPriority reqPriority =
                    Component.componentTable.list[i].priority;
                ThreadPriority threadPriority;
                threadPriority = ConvertThreadPriority(kind, reqPriority);
                threadTable.list[i].priority = threadPriority;
                if (kind == Component.ComponentKind.RECEIVE)
                {
                    threadTable.list[i].threadInstance = Remote.ReceiveThread(receiveIndex);
                    receiveIndex++;
                }
                else
                {
                    threadTable.list[i].threadInstance = new Thread(ComThread12);
                }
                threadTable.list[i].threadInstance.Priority = threadPriority;
            }
            if (threadTable.count > 12)
            {
                int i = 12;
                threadTable.list[i].name = "ComThread13";
                Component.ComponentKind kind =
                    Component.componentTable.list[i].kind;
                ComponentThreadPriority reqPriority =
                    Component.componentTable.list[i].priority;
                ThreadPriority threadPriority;
                threadPriority = ConvertThreadPriority(kind, reqPriority);
                threadTable.list[i].priority = threadPriority;
                if (kind == Component.ComponentKind.RECEIVE)
                {
                    threadTable.list[i].threadInstance = Remote.ReceiveThread(receiveIndex);
                    receiveIndex++;
                }
                else
                {
                    threadTable.list[i].threadInstance = new Thread(ComThread13);
                }
                threadTable.list[i].threadInstance.Priority = threadPriority;
            }
            if (threadTable.count > 13)
            {
                int i = 13;
                threadTable.list[i].name = "ComThread14";
                Component.ComponentKind kind =
                    Component.componentTable.list[i].kind;
                ComponentThreadPriority reqPriority =
                    Component.componentTable.list[i].priority;
                ThreadPriority threadPriority;
                threadPriority = ConvertThreadPriority(kind, reqPriority);
                threadTable.list[i].priority = threadPriority;
                if (kind == Component.ComponentKind.RECEIVE)
                {
                    threadTable.list[i].threadInstance = Remote.ReceiveThread(receiveIndex);
                    receiveIndex++;
                }
                else
                {
                    threadTable.list[i].threadInstance = new Thread(ComThread14);
                }
                threadTable.list[i].threadInstance.Priority = threadPriority;
            }

            // Start the created threads of the component thread pool.
            for (int tIndex = 0; tIndex < threadTable.count; tIndex++)
            {
                string name = Component.componentTable.list[tIndex].name;
                Console.WriteLine("TimingScheduler Start {0} {1} {2}", tIndex,
                    threadTable.list[tIndex].name,
                    name);
                threadTable.list[tIndex].threadInstance.Start();
            }
            allThreadsStarted = true;

            // Run the TimingScheduler thread every half a second.
            // What to have this thread do has yet to be decided.
            while (true)
            { // forever loop
                Thread.Sleep(500); // one-half second
            }

        } // end TimingScheduler

        // The common component thread code.  This code runs in the thread
        // of the invoking component thread.  The input parameter is its
        // location in the component table.
        // Note: There are multiple "copies" of this function running; one
        //       for each component as called by that component's ComThread.
        //       Therefore, the data (such as stopWatch) is on the stack in
        //       a different location for each such thread.
        private static void ComponentThread(int location)
        {
            // Get initial milliseconds; adjust Sleep period to be used below
            Stopwatch stopWatch = new Stopwatch();
            stopWatch.Start();
            int cycleInterval = Component.componentTable.list[location].period;
            if (cycleInterval < 1) // no period supplied
            {
                cycleInterval = 100; // msec
            }
            int delayInterval = cycleInterval; // initial delay
            Disburse queue = Component.componentTable.list[location].queue;

            // Wait until all component threads have been started
            while (!allThreadsStarted)
            {
                Thread.Sleep(500);
            }

            // Create a Timer to signal the periodic components to resume
            // when the timeout has been reached. 
            Console.WriteLine("Threads ComponentThread {0}", location);
            MainEntry fMain = Component.componentTable.list[location].fMain;
            if (fMain != null)
            { // component with periodic entry point
                // Create instance of Timer for periodic components
                if (Component.componentTable.list[location].period > 0)
                {
                    PeriodicTimer executeTimer = new PeriodicTimer(location);
                    int period = Component.componentTable.list[location].period;
                    executeTimer.StartTimer(period, period); // milliseconds
                }
                // enter the component's callback to await a resume event
                fMain();
               
            }

        } // end ComponentThread

        // Component thread factory -- one thread for each possible component.
        // Only those for the components in the Component componentTable will
        // be run.
        private static void ComThread1()
        {
            ComponentThread(0);
        }
        private static void ComThread2()
        {
            ComponentThread(1);
        }
        private static void ComThread3()
        {
            ComponentThread(2);
        }
        private static void ComThread4()
        {
            ComponentThread(3);
        }
        private static void ComThread5()
        {
            ComponentThread(4);
        }
        private static void ComThread6()
        {
            ComponentThread(5);
        }
        private static void ComThread7()
        {
            ComponentThread(6);
        }
        private static void ComThread8()
        {
            ComponentThread(7);
        }
        private static void ComThread9()
        {
            ComponentThread(8);
        }
        private static void ComThread10()
        {
            ComponentThread(9);
        }
        private static void ComThread11()
        {
            ComponentThread(10);
        }
        private static void ComThread12()
        {
            ComponentThread(11);
        }
        private static void ComThread13()
        {
            ComponentThread(12);
        }
        private static void ComThread14()
        {
            ComponentThread(13);
        }

    } // end class Threads

    // Periodic Component Timer
    public class PeriodicTimer
    {
        private int location; // index into componentTable
        private int iterations = 0;
        Stopwatch stopWatch = new Stopwatch();

        public PeriodicTimer(int index) // constructor
        {
            Console.WriteLine("PeriodicTimer {0}", index);
            location = index;

        } // end constructor

        public void StartTimer(int dueTime, int period)
        {
            Timer periodicTimer = new Timer(new TimerCallback(TimerProcedure));
            periodicTimer.Change(dueTime, period);
            stopWatch.Start();
        }

        public void ResetInvokedTimer()
        {
            Console.WriteLine("ResetInvokedTimer entered");
            StartTimer(0, 0);
        } // end ResetInvokedTimer

        private void TimerProcedure(object state)
        {
            // The state object is the Timer object.
            Timer periodicTimer = (Timer)state;
            stopWatch.Stop();
            TimeSpan ts = stopWatch.Elapsed;
            stopWatch.Start();
            iterations++;
            Console.WriteLine("TimerProcedure {0} {1} {2}",
                Component.componentTable.list[location].name,
                ts, iterations);

            // Invoke component's Signal.
            Component.componentTable.list[location].waitHandle.Set();

        } // end TimerProcedure

    } // end PeriodicTimer

} // end namespace

Note that in the C# application the Receive threads are created before Threads is run (via the Create method).  Therefore, if a particular entry into the threadTable is for a receive thread the thread is not created in Threads since it has already been created.  The receive threads are not periodic since they wait for a message to be received - one thread for each possible remote application. 

Ada Threads package

The Threads package to be shown does not, as yet, concern itself with the Receive threads since the implementation of the packages necessary for the remote application have yet to be implemented.

The package spec (visible part) and body (implementation part) follow.  The implementation is only to create threads for debug/test components so is not complete.  The version of the Component package is only to be able to test.

package Threads is

  type ComponentThreadPriority
  is ( WHATEVER,
       HIGHEST,
       HIGH,
       NORMAL,
       LOWER,
       LOWEST
     );

  procedure Create;

end Threads;

with Component;
with ExecItf;
with PeriodicTimer;
with System;
with Text_IO;
with Topic;
with Unchecked_Conversion;

package body Threads is

  type ThreadPriorityType
  is ( Lowest,
       BelowNormal,
       Normal,
       AboveNormal,
       Highest
     );
  for ThreadPriorityType use
    ( Lowest      => 6,
      BelowNormal => 7,
      Normal      => 8,
      AboveNormal => 9,
      Highest     => 10
    );

  type ThreadDataType
  is record
    Name           : String(1..10);
    ThreadInstance : ExecItf.HANDLE;
    Priority       : ThreadPriorityType;
  end record;

  type ThreadDataArrayType
  is array (1..Component.MaxComponents) of ThreadDataType;

  -- Component thread list
  type ComponentThreadType
  is record
    Count : Integer; -- Number of component threads.  Note: This should
                     -- end up equal to the number of components.
    List  : ThreadDataArrayType; -- List of component threads
  end record;

  -- Thread pool of component threads
  ThreadTable : ComponentThreadType;

  -- To allow ComponentThread to idle until all component threads have started
  AllThreadsStarted : Boolean := False;

  SchedulerThread
  -- Handle returned by Create_Thread for the Scheduler thread
  : ExecItf.HANDLE;

  function to_Void_Ptr is new Unchecked_Conversion
                              ( Source => System.Address,
                                Target => ExecItf.Void_Ptr );

  -- Convert component thread priority to that of Windows
  function ConvertThreadPriority
  ( Kind     : in Component.ComponentKind;
    Priority : in ComponentThreadPriority
  ) return ThreadPriorityType is
    use type Component.ComponentKind;
  begin -- Only for user component threads.
    if Kind = Component.USER then
      -- No component thread is allowed to have a priority above Normal.
      if Priority = LOWER then
        return BelowNormal;
      elsif Priority = LOWEST then
        return Lowest;
      else
        return Normal;
      end if;
    else
      -- Framework threads are allowed to have their specified priority.
      if Priority = HIGHEST then
        return Highest;
      elsif Priority = HIGH then
        return AboveNormal;
      elsif Priority = LOWER then
        return BelowNormal;
      elsif Priority = LOWEST then
        return Lowest;
      end if;
      return Normal;
    end if;
  end ConvertThreadPriority;

  -- Convert Windows priority to an integer
  function Priority_to_Int
  ( Priority : in ThreadPriorityType
  ) return Integer is
  begin -- Priority_to_Int
    case Priority is
      when Lowest      => return 6;
      when BelowNormal => return 7;
      when Normal      => return 8;
      when AboveNormal => return 9;
      when Highest     => return 10;
      when others      => return 8;
    end case;
  end Priority_to_Int;

  -- The common component thread code.  This code runs in the thread
  -- of the invoking component thread.  The input parameter is its
  -- location in the component table.
  -- Note: There are multiple "copies" of this function running; one
  --       for each component as called by that component's ComThread.
  --       Therefore, the data (such as stopWatch) is on the stack in
  --       a different location for each such thread.
  procedure ComponentThread
  ( Location : in Integer
  ) is

    CycleInterval : Integer;
    DelayInterval : Integer;
    Callback      : Topic.CallbackType;
    Period        : Integer;
    use type Topic.CallbackType;

  begin -- ComponentThread

    CycleInterval := Component.ComponentTable.List(Location).Period;
    if CycleInterval < 1 then -- no period supplied
      CycleInterval := 500; -- msec
    end if;
    DelayInterval := CycleInterval; -- initial delay

    -- Wait until all component threads have been started
    while AllThreadsStarted = False loop
      Delay(Duration(DelayInterval/100)); -- using Periods in seconds
    end loop;

    -- Create a Timer to signal the periodic components to resume
    -- when the timeout has been reached.
    Text_IO.Put_Line("Threads ComponentThread ");
    Callback := Component.ComponentTable.List(Location).fMain;
    if Callback /= null then -- component with periodic entry point

      -- Create instance of Timer for periodic components
      if Component.ComponentTable.List(Location).Period > 0 then

        -- Instantiate PeriodicTimer for this component
        declare
          package ExecuteTimer is new PeriodicTimer( Index => Location );
        begin
          Period := Component.ComponentTable.List(Location).Period;
          ExecuteTimer.StartTimer
          ( DueTime  => Period,
            Period   => Period, -- milliseconds
            Priority => 8, -- Normal
            Wait     => Component.ComponentTable.List(Location).WaitEvent );
        end;
      end if;

      -- Enter the component's callback to await a resume event from Timer or
      -- from receive of a message.
      Callback( TopicId => Topic.Empty );

    end if;

    Delay(Duration(DelayInterval/100)); -- using Periods in seconds

  end ComponentThread;

  -- Component thread factory -- one thread for each possible component.
  -- Only those for the components in the Component componentTable will
  -- be run.
  procedure ComThread1 is
  begin
    ComponentThread(1);
  end ComThread1;
  procedure ComThread2 is
  begin
    ComponentThread(2);
  end ComThread2;

  -- The TimingScheduler thread
  procedure TimingScheduler is -- thread to manage component threads

    I    : Integer;
    Kind : Component.ComponentKind;
    ReqPriority    : ComponentThreadPriority;
    ThreadPriority : ThreadPriorityType;

  begin -- TimingScheduler

    -- Create the component thread pool/factory; one thread for each
    -- component. 
    if ThreadTable.Count > 0 then
      I := 1;
      Kind := Component.ComponentTable.List(I).Kind;
      ReqPriority := Component.ComponentTable.List(I).Priority;
      ThreadPriority := ConvertThreadPriority(Kind, ReqPriority);
      ThreadTable.List(I).Name := "ComThread1";
      ThreadTable.List(I).Priority := ThreadPriority;
      ThreadTable.List(I).ThreadInstance :=
        ExecItf.Create_Thread
        ( Start      => ComThread1'Address,
          Parameters => to_Void_Ptr(System.Null_Address),
          Stack_Size => 0,
          Priority   => Priority_to_Int(ThreadPriority) );
    end if;
    if ThreadTable.Count > 1 then
      I := 2;
      Kind := Component.ComponentTable.List(I).Kind;
      ReqPriority := Component.ComponentTable.List(I).Priority;
      ThreadPriority := ConvertThreadPriority(Kind, ReqPriority);
      ThreadTable.List(I).Name := "ComThread2";
      ThreadTable.List(I).Priority := ThreadPriority;
      ThreadTable.List(I).ThreadInstance :=
        ExecItf.Create_Thread
        ( Start      => ComThread2'Address,
          Parameters => to_Void_Ptr(System.Null_Address),
          Stack_Size => 0,
          Priority   => Priority_to_Int(ThreadPriority) );
    end if;

    AllThreadsStarted := True; -- since started as created

    while True loop -- forever
      Delay 10.0;
    end loop;
  end TimingScheduler;

  procedure Create is

  begin -- Create

    ThreadTable.Count := Component.ComponentTable.Count;

    SchedulerThread := ExecItf.Create_Thread
                       ( Start      => TimingScheduler'address,
                         Parameters => to_Void_Ptr(System.Null_Address),
                         Stack_Size => 0,
                         Priority   => Priority_to_Int(Normal) ); -- use AboveNormal? );

  end Create;

end Threads;

Note how the Ada Threads package follows the C# class except for the reordering of its procedures from the methods of the C# class and the much reduced set of component threads since only accommodating the debug/test case.  Hidden in ExecItf is the interface to the C libraries of GNAT to interface to Windows.

Ada PeriodicTimer package

This package is an example of an Ada generic.  It works somewhat similar to "new" in instantiating a C class.  That is, there can be multiple instances of the package.  With PeriodicTimer this is necessary since there needs to be a timer for each periodic thread in order to notify the thread's component when it needs to execute.

In order for the ComponentThread procedure of the Threads package to both start the timer and enter the component's callback each timer has to run in its own separate thread.  This is because ComponentThread is running in the component's thread.  When the timer is started there won't be a return to the component's thread and when the component's callback is entered, it won't be returning to ComponentThread of the Threads package.  Therefore, each timer needs to have its own thread.  Therefore, ComponentThread passed what the new thread's priority should be to timer's StartTimer procedure. 

When a component registers itself, the code creates a WaitEvent handle for the component and returns it to the component for its use.  This handle is also passed to PeriodicTimer when ComponentThread invokes StartTimer to create the timer so that it can signal the component when to continue.

The public/visible part
with ExecItf;

generic
-- generic PeriodicTimer

  Index
  -- Instantiation index
  : Integer;

package PeriodicTimer is

  procedure StartTimer
  ( DueTime  : in Integer;
    Period   : in Integer;
    Priority : in Integer;
    Wait     : in ExecItf.HANDLE
  );

end PeriodicTimer;

The implementation part
with ExecItf;
with System;
with Unchecked_Conversion;

package body PeriodicTimer is

  -- Handles of a particular timer
  TimerThread : ExecItf.HANDLE;
  TimerHandle : ExecItf.HANDLE;
  WaitHandle  : ExecItf.HANDLE;

  procedure TimerProcedure is

    InfiniteValue : Integer := -1;

    Result : Boolean;
    Rtrn   : ExecItf.WaitReturnType;

  begin -- TimerProcedure

    while (True) loop
      Rtrn := ExecItf.WaitForSingleObject(TimerHandle,InfiniteValue);

      Result := ExecItf.Set_Event( Event => WaitHandle );

    end loop;

  end TimerProcedure;

  procedure StartTimer
  ( DueTime  : in Integer;
    Period   : in Integer;
    Priority : in Integer;
    Wait     : in ExecItf.HANDLE
  ) is

    Rtrn : Boolean;

    function to_Void_Ptr is new Unchecked_Conversion
                                ( Source => System.Address,
                                  Target => ExecItf.Void_Ptr );
    begin -- StartTimer

    WaitHandle := Wait;

    TimerThread := ExecItf.Create_Thread
                   ( Start      => TimerProcedure'Address,
                     Parameters => to_Void_Ptr(System.Null_Address),
                     Stack_Size => 0,
                     Priority   => Priority );
    TimerHandle := ExecItf.CreateWaitableTimer( ManualReset => False );

    Rtrn := ExecItf.SetWaitableTimer
            ( Timer   => TimerHandle,
              DueTime => DueTime,
              Period  => Period,
              Resume  => False );

  end StartTimer;

end PeriodicTimer;

Each instance of the PeriodicTimer package has its own copy of the data such as the static data
  TimerThread : ExecItf.HANDLE;
  TimerHandle : ExecItf.HANDLE;
  WaitHandle  : ExecItf.HANDLE;
Of course, like anything else, the stack data declared inside a procedure or function is only available while inside the routine.  Therefore, the TimerThread handle, for instance, is different for the timer used with the first component from that used for the second.

StartTimer is passed the Wait handle which is saved in the static data for use by the TimerProcedure.  The same for the timer handle that is returned from the create of a waitable timer and used to set the timer.

The created thread for the timer starts immediately entering the TimerProcedure.  The TimerProcedure loops forever waiting for the timer to trip.  When it does it sends the component's wait event to signal the component to continue.

A portion of a sample test component is
    procedure Main -- callback
    ( Topic : in Boolean := False
    ) is

      WaitResult  : ExecItf.WaitReturnType;
      ResetResult : Boolean;

    begin -- Main
      Text_IO.Put_Line("in Component1 callback");
      loop -- wait for event

        WaitResult  := ExecItf.WaitForSingleObject(Result.Event, -1);
        ResetResult := ExecItf.Reset_Event(Result.Event);

      end loop;
    end Main;
The callback was entered when the Threads package ComponentThread procedure invoked it.  It then enters its forever loop and does a wait for its event.  When the wait is satisfied – that is, the PeriodicTimer TimerProcedure invokes Set_Event the Reset_Event function is invoked to reset the wait so that the next wait will not immediately return.

An actual component would do something in the loop after the wait was satisfied.  In the debug version, the current time is obtained and the seconds value is output with an identifier of C1 or C2 depending upon which test component it is.  The same is done in the PeriodicTimer when the timer has tripped with an identifier of T1 or T2 depending upon which instance of the timer has tripped.

A sample of this output is
Threads ComponentThread
in TimerProcedure for 2
in Component2 callback
T2 34
C2 34
Threads ComponentThread
in TimerProcedure for 1
in Component1 callback
T1 36
C1 36
T2 38
T1 38
C2 38
C1 38
T1 40
C1 40
T1 42
C1 42
T2 44
T1 44
C1 44
C2 44
T1 46
C1 46
T1 48
C1 48
T2 50
C2 50
T1 50
C1 50
T1 52
C1 52
T1 54
C1 54
The "in Component1/2 callback" output is when Threads invoked the callback as shown in the first line of the Main procedure.  Then the T1 and C1 pairs, for instance, show that the timer tripped at 36 seconds and the component had its wait satisfied in the same second.  It can be observed that the first component (C1) receives a wait event signal at 2 second intervals and that the second component (C2) receives its signals at 6 second intervals.  These are the periodic intervals that were specified when the dummy components were registered with the Component package.


No comments: