Skip to content

AudYoFlo: Behavior: The Console Host

jvxgit edited this page Jun 17, 2024 · 19 revisions

The console host is a very special host implementation that is suited to realize different kinds of host realizations. The idea is to embed the host functionality in an application that has no specific UI - instead, the host can be controlled from the command line by issuing text command.

grafik

Once the host is up and running, commands can be issued by entering text expressions. The text expressions must comply to a specific command syntax. A brief overview of all text commands can be displayed by typing help.

grafik

With the text command option, all procedures typically done by clicking in a GUI can be executed to control all features of the AudYoFlo host system.

Technical Background

The console host on its own has a useful functionality. However, the real strength lies in the general architecture: the console host can be extended by defining frontends and backends which are coupled to each other by an event loop. By using this mechanism, e.g., the console host can be extended by a web frontend. This allows the host to be controlled either by text commands or by http commands. This, as a consequence, allows to create a client server architecture that involves a web client running in a web browser, connected to the host control backend to address certain functionalities while at the same time allowing full control as well via command line and text commands. This is really helpful in particular during the development process.

Threading Model

Principally, the AudYoFlo host application involves the control of multiple threads. However, the main functional controls are required to be driven from within a single main thread. When combining different controls from different frontends, typically, the frontends involve their own thread environment. Therefore, the console host involves the event loop which synchronizes inputs from different threads. For example, the console command input operates in the main thread of the application whereas the http control commands arrive in different threads. All commands therefore have to be forwarded to the event main loop.

Frontends, Backend and Event Loop in a Diagram

The interaction of the frontends, the backend and the event loop is depicted in the following diagram:

grafik

There always needs to be a primary frontend and a primary backend. In addition, multiple secondary frontends and backends can be linked. In the example diagram, the class CjvxConsoleHost_fe_console defines the primary frontend - the frontend which accepts the text inputs on the console command prompt. The primary backend is the class CjvxConsoleHost_be_drivehost which forwards all commands to address the involved host. A secondary frontend is linked in the example which is class CjvxWebControl_fe which involves a web server to accepts and process http commands.

Every user interaction enters the system via a frontend. The frontend then forwards the event to the event loop where the user interaction is synchronized to the main thread and forwarded to the addressed backend in the main thread. Typically, a successful command is then reported backwards to the frontend from where a command was issued to give any kind of feedback (console host frontend) or send a response (web control frontend).

Defining and Linking Frontends and Backends

Each application that involves the console host may define its own frontends and backends. Weak function references are involved in the console host library that may be overriden to modify the default behavior.

The allocated frontend and backend singletons are typically stored in global variables, e.g.,

static CjvxConsoleHost_fe_console* theConsole_fe = NULL;
static CjvxWebControl_fe* theWeb_fe = NULL;
static CjvxConsoleHost_be_drivehost* main_be = NULL;

The weak function entries are the following:

Function jvx_manage_frontends

extern "C"
{
   jvxErrorType jvx_manage_frontends(IjvxEventLoop_frontend_ctrl** theFrontend, jvxSize cnt, jvxCBool is_init)
   {
      if (cnt == 0)
      {
         if (is_init == c_true)
         {
            JVX_DSP_SAFE_ALLOCATE_OBJECT(theConsole_fe, CjvxConsoleHost_fe_console);
            *theFrontend = static_cast<IjvxEventLoop_frontend_ctrl*>(theConsole_fe);
         }
         else
         {
            JVX_DSP_SAFE_DELETE_OBJECT(theConsole_fe);
            theConsole_fe = NULL;
            *theFrontend = NULL;
         }
	 return JVX_NO_ERROR;
      }
      else if (cnt == 1)
      {
         if (is_init == c_true)
         {
            JVX_DSP_SAFE_ALLOCATE_OBJECT(theWeb_fe, CjvxWebControl_fe);
            *theFrontend = static_cast<IjvxEventLoop_frontend_ctrl*>(theWeb_fe);
         }
         else
         {
            JVX_DSP_SAFE_DELETE_OBJECT(theWeb_fe);
            *theFrontend = NULL;
         }
         return JVX_NO_ERROR;
      }
      return JVX_ERROR_WRONG_STATE;
   }
}

The function jvx_manage_frontends is called on bootup as well as on shutdown time. The frontend is allocated and deallocated for an increasing value of cnt to allocate frontends and return to the library.

Function jvx_manage_backends

extern "C"
{
   jvxErrorType jvx_manage_backends(IjvxEventLoop_backend_ctrl** theBackend, jvxSize cnt, jvxCBool is_init)
   {
      if (cnt == 0)
      {
         if (is_init == c_true)
         {
            CjvxConsoleHost_be_drivehost* thePrinter = NULL;
            JVX_DSP_SAFE_ALLOCATE_OBJECT(main_be, CjvxConsoleHost_be_drivehost);
            *theBackend = static_cast<IjvxEventLoop_backend_ctrl*>(main_be);
         }
         else
         {
            JVX_DSP_SAFE_DELETE_OBJECT(*theBackend);
            *theBackend = NULL;
         }
         return JVX_NO_ERROR;
      }
      return JVX_ERROR_WRONG_STATE;
   }
}

The function jvx_manage_backends is called in analogie to the allocation of the frontends to allocate backends with an increasing counter cnt.

Function jvx_link_frontends_backends

extern "C"
{
   jvxErrorType jvx_link_frontends_backends(jvxCBool is_init)
   {
      jvxErrorType res = JVX_NO_ERROR;
      if (is_init)
      {
         // Create the cross references
         // Both frontends control the same backend as primary target
         res = theConsole_fe->set_pri_reference_event_backend(main_be);
         res = theWeb_fe->set_pri_reference_event_backend(main_be);

         // The main backend gets references to both frontends
         res = main_be->set_pri_reference_frontend(theConsole_fe);
         res = main_be->add_sec_reference_frontend(theWeb_fe);
      }
      else
      {
         // Unlink the cross references
         res = main_be->clear_sec_reference_frontend(theWeb_fe);
         res = main_be->clear_pri_reference_frontend(theConsole_fe);
         res = theWeb_fe->clear_pri_reference_event_backend(main_be);
         res = theConsole_fe->clear_pri_reference_event_backend(main_be);
      }
      return JVX_NO_ERROR;
   }
};

In this function, the frontends are linked to each other. We define the only backend as the primary backend for both frontends, and we link the console frontend as the primary frontend for the backend, and the web frontend as the secondary frontend for the backend.

grafik

Frontend / Backend Roles

The frontend typically receives user inputs. The user input is then forwarded to the event queue, synchronzed and finally forwarded to the backend. A call to the event loop typically involves a reference to an instance of type IjvxEvenetLoop adressing the member function

JVX_INTERFACE IjvxEventLoop
{
   // ....
   virtual jvxErrorType JVX_CALLINGCONVENTION event_schedule(TjvxEventLoopElement* newElm, jvxErrorType* res_scheduled_event, jvxHandle* blockPrivate) = 0;
   // ...
}

An element of type TjvxEventLoopElement defines how the event loop shall manage the request. For example, a call can be a trigger functionality only where the requested operation is executed sometime after in the main loop, or a trigger with a call to an acknowledge function to report completion of the requested function or even a blocking call in which the thread in which the frontend requests the command is blocked until the command is complete.

Implementations

In the following the available frontends and backends are briefly described.

Frontend CjvxConsoleHost_fe_console

This frontend accepts user inputs from the console. It loops in the start function and waits for single characters. The characters are forwarded to the primary backend.

The module emits the following events towards the primary backend:

grafik

  • Event JVX_EVENTLOOP_EVENT_ACTIVATE provides an event of type JVX_EVENTLOOP_DATAFORMAT_COMMAND_LINE which yields a pointer to IjvxCommandLine.
  • Event JVX_EVENTLOOP_EVENT_TEXT_INPUT to report any kind of user input. The data reaches from single characters to full text token.
  • Event JVX_EVENTLOOP_EVENT_TEXT_SHOW to report a text to be simply shown.
  • Event JVX_EVENTLOOP_EVENT_DEACTIVATE to report that the host shall be deactivated.

The module receives the following repsonses from the connected backend:

grafik

  • Event JVX_EVENTLOOP_EVENT_SELECT to report that the host has been selected.
  • Event JVX_EVENTLOOP_EVENT_ACTIVATE to report that the host has been activated.
  • Event JVX_EVENTLOOP_EVENT_TEXT_INPUT to report a text input from the frontend.
  • Event JVX_EVENTLOOP_EVENT_DEACTIVATE to report that the host has been deactivated.
  • Event JVX_EVENTLOOP_EVENT_UNSELECTED to report that the host has been unselected.

Backend CjvxConsoleHost_be_print

This backend is very simple as it only prints out the commands from the frontend. However, it defines some functions that can be overridden to forward events to a parent class. It accepts the following events:

  • Event JVX_EVENTLOOP_EVENT_ACTIVATE is accepted if the system is started. this event triggers a call to process_init which starts the host.
  • Event JVX_EVENTLOOP_EVENT_DEACTIVATE is accepted if the system shall be stopped. The event triggers a call to process_shutdown.
  • Event JVX_EVENTLOOP_EVENT_TEXT_SHOW is accepted if a text token shall be shown.
  • Event JVX_EVENTLOOP_EVENT_TEXT_INPUT is the control input event that accepts characters as well as full strings. The event handler composes tokens if characters are passed over from the frontend and runs the function process_full_command in case an end-of-input is received (newline).

In case a command was issued, the output text is a json string. The output is at first passed to the primary frontend: if the origin of the command was the primary frontend, the output is assigned to the primary frontend by means of a call to member function report_assign_output: If the origin was another frontend, the output is only passed to the primary frontend if the configuration is setup for outputting all coammand outputs via the silent out option.

Next, the output is assigned to all secondary frontends via the report_assign_output member function. If the secondary frontend is at the same time the origin of the command, the third argument of the call to report_assign_output is a non-null pointer to hold private data passed when the command was emitted. In case the secondary frontend was another one, the third argument is a null pointer. Typically, secondary frontends only accept assigned output from the same secondary frontend.

Typically, commands are triggered in one thread by a blocking request (thread waits until processing is complete), then synchronized to the main thread and finally reported to the origin. The private data (PV) then helps to map the output from the command back to the thread in which the event was originally triggered. The principle is depicted in the following:

grafik

Backend CjvxConsoleHost_be_drivehost

This backend stacks on top of the backend CjvxConsoleHost_be_print and controls the actual AudYoFlo core host. It receives the following events.

  • Event JVX_EVENTLOOP_EVENT_SPECIFIC is accepted by the backend to run a periodic wake. When this event occurs, the sequencer is triggered to execute one sequencer step.
  • Event JVX_EVENTLOOP_EVENT_SPECIFIC + 1 is accepted to forward a call to report_internals_have_changed to all registered interfaces of type JVX_INTERFACE_REPORT.
  • Event JVX_EVENTLOOP_EVENT_SPECIFIC + 2 is accepted to forward a call to report_command_request_inThread .
  • Event JVX_EVENTLOOP_EVENT_FWD_REQUEST_COMMAND is emitted in case of an asynchronous command request in the core host. The command request is typically synchronized to the main thread. Then, the command request is forwarded to all secondary backends.

Events which are not processed are forwarded to the base class CjvxConsoleHost_be_print.

Frontend CjvxWebControl_fe

The frontend CjvxWebControl_fe is frontend and backend at the same time and involves a web server that accepts http requests and web socket connections. The request typically arrive in worker threads and hence outside the main thread.

Http requests arrive in function synchronizeWebServerCoEvents and are translated into command tokens depending on the actual request. The command token is then forwarded towards the primary backend to address the host.

Web socket events arrive in member function synchronizeWebServerWsEvents. These requests typically need not be translated into a command token since their impact is local to the frontend. However, the requests must first be synchronized to the main thread by emitting the following events to itself - from frontend part to backend part:

  • Event JVX_EVENT_LOOP_EVENT_WS_READY (JVX_EVENTLOOP_EVENT_SPECIFIC + 1).
  • Event JVX_EVENT_LOOP_EVENT_WS_CLOSE (JVX_EVENTLOOP_EVENT_SPECIFIC + 2).
  • Event JVX_EVENT_LOOP_EVENT_WS_TIMEOUT (JVX_EVENTLOOP_EVENT_SPECIFIC + 3).
  • Event JVX_EVENT_LOOP_EVENT_WS_TIMEOUT_RECONF (JVX_EVENTLOOP_EVENT_SPECIFIC + 4).
  • Event JVX_EVENT_LOOP_EVENT_WS_ADD_PROP_LIST (JVX_EVENTLOOP_EVENT_SPECIFIC + 5).
  • Event JVX_EVENT_LOOP_EVENT_WS_REM_PROP_LIST (JVX_EVENTLOOP_EVENT_SPECIFIC + 6).
  • Event JVX_EVENT_LOOP_EVENT_WS_FLOW_EVENT (JVX_EVENTLOOP_EVENT_SPECIFIC + 7).

grafik

Back

Clone this wiki locally