diff --git a/base/src/main/kotlin/de/staropensource/engine/base/Engine.kt b/base/src/main/kotlin/de/staropensource/engine/base/Engine.kt index 0ae957abd..2b1e2c96a 100644 --- a/base/src/main/kotlin/de/staropensource/engine/base/Engine.kt +++ b/base/src/main/kotlin/de/staropensource/engine/base/Engine.kt @@ -20,6 +20,7 @@ package de.staropensource.engine.base import de.staropensource.engine.base.exception.EngineInitializationFailureException +import de.staropensource.engine.base.implementable.Subsystem import de.staropensource.engine.base.logging.Logger import de.staropensource.engine.base.utility.Environment import de.staropensource.engine.base.utility.FileAccess @@ -70,6 +71,14 @@ class Engine private constructor() { */ var bootstrapping: Boolean? = null + /** + * Contains a set of all registered [Subsystem]s. + * + * @see Subsystem + * @since v1-alpha10 + */ + private val subsystems: MutableSet = mutableSetOf() + /** * Contains a [BuildInformation] instance * providing information about the running @@ -81,6 +90,51 @@ class Engine private constructor() { var info: BuildInformation? = null + // -----> Subsystems + /** + * Returns an array of + * registered [Subsystem]s. + * + * @return array of registered [Subsystem]s + * @since v1-alpha10 + */ + fun getSubsystems(): Array = subsystems.toTypedArray() + + /** + * Registers the specified subsystem. + * + * Does not work if the engine is + * initializing, shutting down, + * has already shut down for good + * or crashed fatally. + * + * @param subsystem [Subsystem] to register + * @see Subsystem + * @since v1-alpha10 + */ + fun registerSubsystem(subsystem: Subsystem) { + // Check for state + if (bootstrapping == true) + return + when (state) { + State.INITIALIZING, State.SHUTTING_DOWN, State.SHUT_DOWN_FINAL, State.CRASHED -> return + else -> {} + } + + subsystems.add(subsystem) + + when (state) { + State.UNINITIALIZED -> {} + State.INITIALIZED -> { + subsystem.bootstrap() + subsystem.initialize() + } + State.SHUT_DOWN -> subsystem.bootstrap() + else -> logger.crash("Engine changed state during subsystem registration") + } + } + + // -----> Lifecycle /** * Bootstraps the engine. @@ -107,6 +161,20 @@ class Engine private constructor() { // Run bootstrapping code // *none yet* + // Bootstrap subsystems + logger.verb("Bootstrapping subsystems") + for (subsystem: Subsystem in subsystems) + try { + logger.diag("Bootstrapping subsystem '${subsystem.getName()}' [${subsystem::class.qualifiedName ?: ""}]") + subsystem.bootstrap() + } catch (throwable: Throwable) { + logger.crash( + "Failed to bootstrap subsystem '${subsystem.getName()}' [${subsystem::class.qualifiedName ?: ""}]", + throwable = throwable, + fatal = true + ) + } + bootstrapping = false return true } catch (exception: Exception) { @@ -150,6 +218,20 @@ class Engine private constructor() { FileAccess.updateDefaultPaths() info = BuildInformation(loadPrefix = "sosengine-base") + // Initialize subsystems + logger.verb("Initializing subsystems") + for (subsystem: Subsystem in subsystems) + try { + logger.diag("Initializing subsystem '${subsystem.getName()}' [${subsystem::class.qualifiedName ?: ""}]") + subsystem.initialize() + } catch (throwable: Throwable) { + logger.crash( + "Failed to initialize subsystem '${subsystem.getName()}' [${subsystem::class.qualifiedName ?: ""}]", + throwable = throwable, + fatal = true + ) + } + state = State.INITIALIZED // Print initialization message @@ -300,6 +382,20 @@ class Engine private constructor() { FileAccess.unsetDefaultPaths() info = null + // Initialize subsystems + logger.verb("Shutting subsystems down") + for (subsystem: Subsystem in subsystems) + try { + logger.diag("Shutting subsystem '${subsystem.getName()}' [${subsystem::class.qualifiedName ?: ""}] down") + subsystem.shutdown(final = final, fatalCrash = crashed) + } catch (throwable: Throwable) { + logger.crash( + "Failed to shutdown subsystem '${subsystem.getName()}' [${subsystem::class.qualifiedName ?: ""}]", + throwable = throwable, + fatal = true + ) + } + // Print shutdown message if (final) logger.info(""" diff --git a/base/src/main/kotlin/de/staropensource/engine/base/implementable/Subsystem.kt b/base/src/main/kotlin/de/staropensource/engine/base/implementable/Subsystem.kt new file mode 100644 index 000000000..f94131d4f --- /dev/null +++ b/base/src/main/kotlin/de/staropensource/engine/base/implementable/Subsystem.kt @@ -0,0 +1,103 @@ +/* + * STAROPENSOURCE ENGINE SOURCE FILE + * Copyright (c) 2024 The StarOpenSource Engine Authors + * Licensed under the GNU General Public License v3. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.staropensource.engine.base.implementable + +/** + * Provides an API for the engine to talk to subsystems. + * + * @since v1-alpha10 + */ +abstract class Subsystem { + /** + * Returns the name of this subsystem. + * + * @return name + * @since v1-alpha10 + */ + abstract fun getName(): String + + /** + * Returns the version of this subsystem. + * + * Not called by the engine + * before [bootstrap]. + * + * @return version + * @since v1-alpha10 + */ + abstract fun getVersion(): String + + /** + * Bootstraps this subsystem. + * + * Invoked during the engine's bootstrapping + * phase or after this subsystem has been + * registered, if registered after the + * engine has already bootstrapped itself. + * + * Always run before [initialize]. + * + * @throws Throwable on error + * @since v1-alpha10 + */ + @Throws(Throwable::class) + open fun bootstrap() = Unit + + /** + * Initializes this subsystem. + * + * Invoked during the engine's initialization + * phase or after this subsystem has been + * registered, if registered after the engine + * has already initialized itself. + * + * @throws Throwable on error + * @since v1-alpha10 + */ + @Throws(Throwable::class) + open fun initialize() = Unit + + /** + * Reloads this subsystem's configuration + * and data. + * + * Invoked during the engine's reloading phase. + * + * @throws Throwable on error + * @since v1-alpha10 + */ + @Throws(Throwable::class) + open fun reload() = Unit + + /** + * Shuts this subsystem down. + * + * Invoked during the engine shutdown phase. + * The supplied booleans indicate which + * shutdown method was used. + * + * @param final indicates if the engine will shutdown for good + * @param fatalCrash indicates if the engine crashed fatally + * @throws Throwable on error + * @since v1-alpha10 + */ + @Throws(Throwable::class) + open fun shutdown(final: Boolean, fatalCrash: Boolean) = Unit +}