-
Notifications
You must be signed in to change notification settings - Fork 2
AudYoFlo: Behavior: The Console Host
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.

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.

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.
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.
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.
The interaction of the frontends, the backend and the event loop is depicted in the following diagram:

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).
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:
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.
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.
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.

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.
In the following the available frontends and backends are briefly described.
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:

- Event
JVX_EVENTLOOP_EVENT_ACTIVATEprovides an event of typeJVX_EVENTLOOP_DATAFORMAT_COMMAND_LINEwhich yields a pointer toIjvxCommandLine. - Event
JVX_EVENTLOOP_EVENT_TEXT_INPUTto report any kind of user input. The data reaches from single characters to full text token. - Event
JVX_EVENTLOOP_EVENT_TEXT_SHOWto report a text to be simply shown. - Event
JVX_EVENTLOOP_EVENT_DEACTIVATEto report that the host shall be deactivated.
The module receives the following repsonses from the connected backend:

- Event
JVX_EVENTLOOP_EVENT_SELECTto report that the host has been selected. - Event
JVX_EVENTLOOP_EVENT_ACTIVATEto report that the host has been activated. - Event
JVX_EVENTLOOP_EVENT_TEXT_INPUTto report a text input from the frontend. - Event
JVX_EVENTLOOP_EVENT_DEACTIVATEto report that the host has been deactivated. - Event
JVX_EVENTLOOP_EVENT_UNSELECTEDto report that the host has been unselected.
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_ACTIVATEis accepted if the system is started. this event triggers a call toprocess_initwhich starts the host. - Event
JVX_EVENTLOOP_EVENT_DEACTIVATEis accepted if the system shall be stopped. The event triggers a call toprocess_shutdown. - Event
JVX_EVENTLOOP_EVENT_TEXT_SHOWis accepted if a text token shall be shown. - Event
JVX_EVENTLOOP_EVENT_TEXT_INPUTis 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 functionprocess_full_commandin 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:

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_SPECIFICis 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 + 1is accepted to forward a call toreport_internals_have_changedto all registered interfaces of typeJVX_INTERFACE_REPORT. - Event
JVX_EVENTLOOP_EVENT_SPECIFIC + 2is accepted to forward a call toreport_command_request_inThread. - Event
JVX_EVENTLOOP_EVENT_FWD_REQUEST_COMMANDis 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.
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).
