//----------------------------------------------------------------------- // ----------------------------------------------------------------------- // file="IStaticModule" // Project: StgSharp // AuthorGroup: Nitload Space // Copyright (c) Nitload Space. All rights reserved. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. // // ----------------------------------------------------------------------- // ----------------------------------------------------------------------- using System; using System.Collections; using System.Collections.Generic; namespace StgSharp { /// /// Represents a statically activated module that can be registered and initialized as part of /// the application startup sequence, and gracefully torn down on shutdown. /// public interface IStaticModule { /// /// Gets the display name of this module, used for logging and diagnostics. /// string ModuleName { get; } /// /// Performs all initialization work for this module using the provided profile. /// /// /// The configuration profile supplied at registration time. Use if no configuration is required. /// void InitializeModule(IModuleInitializeProfile profile); /// /// Releases all resources acquired during . Implementations should /// be idempotent: calling this method more than once must not throw. /// void UninitializeModule(); } /// /// Marker interface for module initialization configuration. Implementations are plain data /// carriers; no static factory is required. /// public interface IModuleInitializeProfile { /// /// A shared, allocation-free empty profile for modules that require no configuration. /// static readonly IModuleInitializeProfile Empty = EmptyModuleInitializeProfile.Instance; private sealed class EmptyModuleInitializeProfile : IModuleInitializeProfile { public static readonly EmptyModuleInitializeProfile Instance = new(); private EmptyModuleInitializeProfile() { } } } /// /// Pairs an with its , /// ensuring the two are never stored or iterated independently. /// /// /// Using a avoids the boxing overhead that would occur if /// the pair were stored as two parallel lists of reference types. /// public readonly struct ModuleDescriptor { /// /// Initializes a new using as the profile. /// public ModuleDescriptor(IStaticModule module) : this(module, IModuleInitializeProfile.Empty) { } /// /// Initializes a new with an explicit profile. /// public ModuleDescriptor(IStaticModule module, IModuleInitializeProfile profile) { Module = module; Profile = profile; } /// /// Gets the configuration profile associated with the module. /// public IModuleInitializeProfile Profile { get; } /// /// Gets the module to be initialized. /// public IStaticModule Module { get; } /// /// Calls with the stored profile. /// public void Initialize() { Module.InitializeModule(Profile); } /// /// Calls on the stored module. /// public void Uninitialize() { Module.UninitializeModule(); } } /// /// An ordered collection of modules to be initialized during application startup and /// uninitialized on shutdown. Supports three registration strategies so that the new() /// generic constraint is never a hard requirement. /// public sealed class ModuleToInitializeCollection : IEnumerable, IStaticModule { private readonly List _descriptors = []; internal ModuleToInitializeCollection(World.Initializer i) { _descriptors.Add(new ModuleDescriptor(i)); } // ── IEnumerable ─────────────────────────────────────────── /// public IEnumerator GetEnumerator() { foreach (ModuleDescriptor d in _descriptors) { yield return d.Module; } } // ── Initialization / Uninitialization ──────────────────────────────────── /// /// Initializes all registered modules in the order they were registered. Each module /// receives the profile that was supplied at registration time. /// public void InitializeModule() { for (int i = 0; i < _descriptors.Count; i++) { _descriptors[i].Initialize(); } } /// /// Uninitializes all registered modules in reverse registration order, mirroring /// the typical LIFO teardown convention (e.g., GLFW must be unloaded after all subsystems /// that depend on it). /// public void UninitializeModule() { for (int i = _descriptors.Count - 1; i >= 0; i--) { _descriptors[i].Uninitialize(); } } // ── Registration API ───────────────────────────────────────────────────── /// /// Registers a module of type using its default constructor. /// This is the most concise overload for simple modules that expose a public /// parameterless constructor. /// /// /// A concrete module type that implements and exposes a /// public parameterless constructor. /// /// /// Optional initialization profile. Defaults to when . /// public ModuleToInitializeCollection UseModule(IModuleInitializeProfile? profile = null) where T: IStaticModule, new() { return UseModule(static() => new T(), profile); } /// /// Registers a module produced by a factory delegate. This overload imposes no generic /// constraints and supports any construction strategy, including dependency injection and /// parameterized constructors. /// /// /// A delegate that constructs and returns the module instance. The delegate is invoked /// once, immediately at registration time. If is or returns , the call is silently ignored /// and the collection is returned unchanged. /// /// /// Optional initialization profile. Defaults to when . /// public ModuleToInitializeCollection UseModule( Func factory, IModuleInitializeProfile? profile = null) { if (factory is null) { return this; } IStaticModule? mod = factory(); if (mod is null) { return this; } _descriptors.Add(new ModuleDescriptor( mod, profile ?? IModuleInitializeProfile.Empty)); return this; } /// /// Registers a pre-existing module instance. Use this overload when the module is /// provided externally, such as from a DI container, an object pool, or a singleton. /// /// /// The module instance to register. /// /// /// Optional initialization profile. Defaults to when . /// public ModuleToInitializeCollection UseModule(IStaticModule module, IModuleInitializeProfile? profile = null) { _descriptors.Add(new ModuleDescriptor( module, profile ?? IModuleInitializeProfile.Empty)); return this; } /// IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } // ── IStaticModule explicit implementation ──────────────────────────────── /// /// Explicit implementation. Delegates to , ignoring the supplied profile because each contained module uses /// its own registered profile. /// void IStaticModule.InitializeModule(IModuleInitializeProfile profile) { InitializeModule(); } /// string IStaticModule.ModuleName => "Module Initializer"; } }