Building Event-Driven Applications

An event-driven application begins work and then reacts to completions, timers, input, or service notifications. The default Application::main() supplies the main event loop, so the derived class only needs to arrange the first event and call quit() when the process should finish.

Entering the Main Event Loop

The following application schedules its first task during initialization. After command-line parsing succeeds, the unchanged base main() enters the event loop and dispatches the queued work. The second callback calls quit(), which records the exit code and ends managed event processing in an orderly way.

/// Derive from `Application` when the program is driven by asynchronous work on the main event loop.
///
/// The default `Application::main()` enters the event loop.
/// Calling `quit()` from an event callback finishes the loop and returns the requested exit code.
class MaintenanceApplication final : public el::Application {
public:
    using Application::Application;

protected: // implement Application
    void initialize() override {
        info().setApplicationName("Maintenance Queue"_el);
        info().setApplicationVersion(el::Version{1, 0, 0});
        events()->invoke([this]() -> void { runMaintenance(); });
    }

private:
    void runMaintenance() {
        el::io::printLine("maintenance job started"_el);
        events()->invoke([this]() -> void {
            el::io::printLine("maintenance job completed"_el);
            quit();
        });
    }
};

/// Create the application and leave event dispatch to its default main implementation.
auto main(const int argc, char *argv[]) -> int {
    auto app = MaintenanceApplication{argc, argv};
    return app.run();
}

$ core/core_event_application

maintenance job started
maintenance job completed

$ core/core_event_application --help

Usage:
core_event_application [options]
Options:
-h, --help[=<boolean>]    Display this help.
    --version[=<boolean>] Display version information.

Starting Work at the Right Time

initialize() is useful for metadata, terminal setup, shared services, and work that must exist before parsing. registerCommandLineOptions() describes accepted arguments. When asynchronous work depends on parsed values, start it after the base parser has succeeded, for example from an overridden parseCommandLine() that first calls Application::parseCommandLine() and checks optionValues().

Queued initialization events are harmless when the user requests help or version output because the application never enters the event loop in that case. Avoid starting unmanaged background work during initialization, because it would need separate cancellation.

Finishing and Reporting Failures

The first call to quit() chooses the process exit code. It also asks registered application parts and managed event threads to stop before the main event system exits.

An Erbsland Core exception thrown by a callback on the automatically managed main event loop crosses the same reporting boundary as an exception from initialize() or main(). Use explicit result handling when a failure is expected and recoverable; throw ApplicationError when a callback must terminate the whole process with a diagnostic and exit code.

Overriding main() disables the automatic event-loop entry. An application that needs custom synchronous preparation before dispatch can finish its override with runEventLoop().