lumiera_documentation/doc/design/application/ApplicationStart.txt
Ichthyostega 8e250839f4 Application: Adjustments to the Layer structure
During the last months, a lot of gradual changes to policies and guidelines
had been finalised and documented. Moreover, recent clarifications regarding
memory management in the render engine will lead to a clearer definition
of the »Vault« Layer's role, with some ''engine code'' expected to move down.

On this occasion, I reworked relevant sections in the documentation to
confirm the role that now falls to central parts of the overall architecture.
Notably I decided to rename those parts related to the application backbone
as »Vessel« — to underpin the more active and defining role they assumed.
2026-03-03 22:16:09 +01:00

154 lines
9.2 KiB
Text

Application : Start-up and Subsystems
=====================================
:Date: 2009
:Author: Ichthyo
//MENU: label Start-up
.the purpose of »Application-main«
Lumiera is envisaged as a heavyweight yet coherent »**Application**« -- which implies
it is not so much seen as ``a platform'', framework or operating system. And, in line
with this understanding, we place no emphasis on possible configuration changes and state
transitions within the running application _as a whole._ The ability to react ``life'' to
some global setting changes can be limited to relevant special cases. And, while it is
indeed possible to close and load a new Session from a running application instance, in
most parts of the code base we assume the application just to be ``up and running'' -- all
required services are assumed to be available and interfaces to be accessible. If some of
these assumptions break, an exception is raised and the application unwinds.
Without doubt, these decisions represent a forcible simplification, and might be limiting
at times, yet serve us well to cut down complexity in large parts of the code base --
however, there needs to be _one dedicated realm_ to deal with all these specific concerns
related to configuration, interdependency and lifecyle.
The Vessel
----------
So we treat all these concerns within a small and self contained structure, called the
»**Vessel**«, and clearly set apart from all the other layers, services and subsystems.
This dedicated _Application Realm_ is organised around the ``Application main object''.footnote:[
This is the singleton `vessel::Voyage`, which is triggered by the `main` function
of the Lumiera Application. The sourcecode is kept in a separate folder 'src/vessel'
and linked into the shared library 'liblumieravessel.so']
Its purpose is to bootstrap and tear-down the application in a controlled way,
which includes to provide the backbone services, evaluate the start-up parameters
and to connect and start the necessary core components. The beginning and end of
this vessel's voyage therefore marks the *Lifecycle* of the application, as a
succession of ``lifecycle phases'' -- where almost any activity happens within
the _operational phase,_ when everything is considered ``up and running''.
Subsystems
~~~~~~~~~~
However, before becoming operational, the application must be bootstrapped, and at the end,
all parts need to be unwound cleanly. To organise this process, we identify a limited number
of *Subsystems* within the Application, which are self-sufficient and operate independently,
at least to some degree.
Each link:{ldoc}/design/architecture/Subsystems.html[Subsystem] is self contained
and groups some other parts and services, which typically work together and may
be mutually dependent. These subsystems represent a grouping, applied for the purpose
of starting and stopping the application in a regular way; they rather do not
correspond 1:1 to a layer, an interface, a class or a plugin. As a matter of fact,
they are _rather irrelevant_ outside the mentioned »Application realm«. A subsystem
may depend on other subsystems, which comprises a clear startup- and shutdown-ordering.
However, once the application is in normal operational mode, the subsystems turn
into a passive, guarding and managing role; the activities relevant for the
application's purpose rather rely on components, interfaces, services, all
aggregated into the three Layers »Stage«, »Steam« and »Vault«.
__We expect the following subsystems to be built eventually:__ +
Engine, Session, PlayOut, GUI, Script runner, Renderfarm node.
Organisation of Subsystems
^^^^^^^^^^^^^^^^^^^^^^^^^^
Not all subsystems need to be started for any use of the application. A script-driven
use, or a renderfarm node does not need a GUI. So there is an overall global operation
mode of the application, controlled through the launching options, and determined during
the startup phase. It is the responsibility of the _Application main object_ to
pull up required functionality, which in turn might result in pulling up
further subsystems as dependencies.
Subsystems are defined by implementing the interface `vessel::Subsys`, which acts
as façade to conduct the lifecycle, find out about dependencies and shut down
the subsystem in the end. So this interface, together with the _Subsystem Runner,_
define a lifecycle protocol; each subsystem is free to implement this as it
sees fit. Typically, this façade will load plugins, register and provide further
_business interfaces,_ and especially set up the _Layer separation interfaces_
which canalise any communication going on between the layers.
The *GUI Façade* is special, while in compliance with this protocol. The actual
UI is loaded from a plug-in at runtime,footnote:[This corresponds to the vision
to allow for different Lumiera UI's -- maybe to support different working styles
or target audiences. If such is actually feasible remains to be clarified as of
2020; even while decoupled on a technical level, the session still needs to make
a lot of assumptions regarding the UI's capabilities and needs.]
and so the implementation of this façade needs to reside in the application core
realm; it will start a `GuiRunner` to load and activate the GUI plug-in, which
then in turn has to open the public _GUI Notification_ façade. The latter is
one of the _Layer separation interfaces_ and comprises the actual way for the
lower layers to activate and interact with the user interface.
Parallelism
~~~~~~~~~~~
Actually this scheme builds on the assumption that starting each subsystem will
not block the overall start/main/shutdown thread. Any subsystem is supposed
to spawn its own control/event threads if necessary. The Lumiera application
works fundamentally asynchronous. The user interface operates single threaded,
in accordance to long established best practices of UI programming. However,
any user interaction is translated into commands, sent down into the session
and handled there one by one. The result of processing such commands will be
pushed back up into the UI later and detached from the immediate interaction.
Likewise, the re-rendering caused by changes in the session is carried out
within the engine independently, relying on worker jobs and a thread pool.
Initialisation and Lifecycle
----------------------------
After some discussion,footnote:[
See the link:{rfc}/GlobalInitialization.html[GlobalInitialisation] RfC
from spring 2008. In the beginning, we all agreed to ``keep matters simple''
and build an `init()` function within one central piece of code everyone knows
and hooks into. However, while the outline of the application emerged, there
was a tension between the concern about _over-engineering_ versus the concern
about _tangled and unmanageable complexity._ At some point, an alternative
implementation based on lifecycle callbacks was elaborated, which then turned
into the solution described here. At that point, Lumiera ceased to be the
typical UI based application started-up by GTK-main, and the existing GTK
code was retrofitted to be launched from within a plug-in.
]
the design leaned toward loosely coupled parts and a formal lifecycle; which
saves us from investigating and coding up the various interdependencies
explicitly. Rather, all parts of the application have to comply to
link:{ldoc}/design/architecture/Subsystems.html#lifecycle[Lifecycle Phases],
and each part has to care for its own state transitions, invoked through
_lifecycle callbacks._ We can distinguish two distinct models how to deal
with lifecycle, and both are equally acceptable:
- Assuming that operations happen in response to some client's request,
this activation should go through a _service interface._ Interfaces
can be opened and closed in Lumiera, and this is accomplished by
hooking them up below some subsystem.
- However, some parts carry out continuous activities, and in that case
a _lifecycle hook_ should be registered, to limit activities to the
appropriate lifecycle phase.
Application Start
~~~~~~~~~~~~~~~~~
* some fundamental language-level facilities will be prepared during
_static initialisation._ At some point, execution enters `main(argc,arvv)`.
* `Voyage::init()` brings up the plugin loader and opens the config-interface.
* ...followed by triggering *ON_GLOBAL_INIT*
* the main thread then pulls up the subsystems (`Voyage::maybeStart(subsystem)`),
according to the command line options.
* within each subsystem, façade interfaces will be opened through the
interface/plug-in system.
* At this point, the GUI plug-in is loaded and launched, the windows created,
the UI event loop starts and the application becomes live.
* shutdown or failure of _any given subsystem_ initiates the shutdown sequence
by requesting all other running subsystems to terminate. In the typical case,
the UI subsystem will trigger this shutdown sequence, in response to closing
the main window.
* there is an *ON_GLOBAL_SHUTDOWN* event, which can be used for normal cleanup;
In case of an emergency exit, the *ON_EMERGENCY_EXIT* event is triggered alternatively.
* the Voyage destructor tears down the core systems (config-interface and pluginloader).
* we establish a policy to _prohibit any non-local and non-trivial activities_ during the
tear-down phase after leaving the `main()` function.