/* * Copyright (c) 2024 Vaughn Nugent * * Library: VNLib * Package: VNLib.Plugins.Runtime * File: PluginController.cs * * PluginController.cs is part of VNLib.Plugins.Runtime which is part of the larger * VNLib collection of libraries and utilities. * * VNLib.Plugins.Runtime 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 2 of the License, * or (at your option) any later version. * * VNLib.Plugins.Runtime 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 * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with VNLib.Plugins.Runtime. If not, see http://www.gnu.org/licenses/. */ using System; using System.Linq; using System.Reflection; using System.Collections.Generic; using VNLib.Utils.IO; using VNLib.Utils.Extensions; using VNLib.Plugins.Runtime.Services; namespace VNLib.Plugins.Runtime { /// /// Manages the lifetime of a collection of instances, /// and their dependent event listeners /// public sealed class PluginController : IPluginEventRegistrar { /* * Lock must be held any time the internals lists are read/written * to avoid read/write enumeration issues. * * This can happen when a manual unload is called duiring an automatic * reload, or a runtime is tearing down the plugin environment * when an automatic reload is happening. * * This also allows thread safe register/unregister event listeners */ private readonly object _stateLock = new(); private readonly List _plugins = []; private readonly List> _listeners = []; private readonly PluginServicePool _servicePool = new(); /// /// The current collection of plugins. Valid before the unload event. /// public IEnumerable Plugins => _plugins; /// /// public void Register(IPluginEventListener listener, object? state = null) { ArgumentNullException.ThrowIfNull(listener); lock (_stateLock) { _listeners.Add(new(listener, state)); } } /// public bool Unregister(IPluginEventListener listener) { lock(_stateLock) { //Remove listener return _listeners.RemoveAll(p => ReferenceEquals(p.Key, listener)) > 0; } } /// /// Returns an array of all exported services from all loaded plugins /// public PluginServiceExport[] GetExportedServices() => _servicePool.GetServices(); internal void InitializePlugins(Assembly asm) { lock (_stateLock) { //get all Iplugin types Type[] types = asm.GetTypes().Where(static type => !type.IsAbstract && typeof(IPlugin).IsAssignableFrom(type)).ToArray(); //Initialize the new plugin instances IPlugin[] plugins = types.Select(static t => (IPlugin)Activator.CreateInstance(t)!).ToArray(); //Crate new containers LivePlugin[] lps = plugins.Select(p => new LivePlugin(p, asm)).ToArray(); //Store containers _plugins.AddRange(lps); } } internal void ConfigurePlugins(VnMemoryStream configData, string[] cliArgs) { lock (_stateLock) { _plugins.ForEach(lp => lp.InitConfig(configData.AsSpan())); _plugins.ForEach(lp => lp.InitLog(cliArgs)); } } internal void LoadPlugins() { lock( _stateLock) { //Load all plugins _plugins.TryForeach(static p => p.LoadPlugin()); //Load all services into the service pool _plugins.ForEach(p => p.GetServices(_servicePool)); //Notify event handlers _listeners.ForEach(l => l.Key.OnPluginLoaded(this, l.Value)); } } internal void UnloadPlugins() { lock (_stateLock) { try { //Notify event handlers _listeners.ForEach(l => l.Key.OnPluginUnloaded(this, l.Value)); //Unload plugin instances _plugins.TryForeach(static p => p.UnloadPlugin()); } finally { //Always clear plugins _plugins.Clear(); //always make sure service pool is clear _servicePool.Clear(); } } } internal void Dispose() { _plugins.Clear(); _listeners.Clear(); _servicePool.Clear(); } } }