use std::path::PathBuf;
use crate::plugin_host::{load_plugins_from_directories, platform_plugin_directories};
use crate::{
FilterRegistry, LoadedPlugin, Logger, OrderedRender, RenderEngine, RenderExecutorMap,
RenderOptions, Result,
};
#[derive(Clone)]
pub struct CoreConfig {
auto_load_plugins: bool,
plugin_directories: Vec<PathBuf>,
logger: Logger,
worker_threads: usize,
}
impl Default for CoreConfig {
fn default() -> Self {
Self {
auto_load_plugins: true,
plugin_directories: Vec::new(),
logger: Logger::default(),
worker_threads: std::thread::available_parallelism().map_or(1, usize::from),
}
}
}
impl CoreConfig {
#[must_use]
pub const fn with_auto_load_plugins(mut self, enabled: bool) -> Self {
self.auto_load_plugins = enabled;
self
}
#[must_use]
pub fn with_logger(mut self, logger: Logger) -> Self {
self.logger = logger;
self
}
#[must_use]
pub fn with_worker_threads(mut self, worker_threads: usize) -> Self {
self.worker_threads = worker_threads.max(1);
self
}
#[must_use]
pub const fn plugin_directories_mut(&mut self) -> &mut Vec<PathBuf> {
&mut self.plugin_directories
}
pub(crate) const fn auto_load_plugins(&self) -> bool {
self.auto_load_plugins
}
pub(crate) fn plugin_directories(&self) -> &[PathBuf] {
&self.plugin_directories
}
pub(crate) const fn logger(&self) -> &Logger {
&self.logger
}
#[must_use]
pub const fn worker_threads(&self) -> usize {
self.worker_threads
}
}
pub struct Core {
registry: FilterRegistry,
config: CoreConfig,
loaded_plugins: Vec<LoadedPlugin>,
}
impl Core {
pub fn new() -> Result<Self> {
Self::with_config(CoreConfig::default())
}
pub fn with_config(config: CoreConfig) -> Result<Self> {
let mut registry = FilterRegistry::new();
let mut directories = config.plugin_directories().to_vec();
if config.auto_load_plugins() {
directories.extend(platform_plugin_directories());
}
let loaded_plugins =
load_plugins_from_directories(&directories, &mut registry, config.logger());
Ok(Self {
registry,
config,
loaded_plugins,
})
}
#[must_use]
pub const fn registry(&self) -> &FilterRegistry {
&self.registry
}
pub const fn registry_mut(&mut self) -> &mut FilterRegistry {
&mut self.registry
}
#[must_use]
pub const fn config(&self) -> &CoreConfig {
&self.config
}
#[must_use]
pub fn loaded_plugins(&self) -> &[LoadedPlugin] {
&self.loaded_plugins
}
#[must_use]
pub const fn render_engine(&self) -> RenderEngine {
RenderEngine::new(crate::WorkerPoolConfig::new(self.config.worker_threads()))
}
pub fn render_ordered(
&self,
graph: crate::Graph,
executors: RenderExecutorMap,
options: RenderOptions,
) -> Result<OrderedRender> {
self.render_engine()
.render_ordered(graph, executors, options)
}
}
#[cfg(test)]
mod tests {
use std::path::PathBuf;
use crate::{Core, CoreConfig, FilterDescriptor};
#[test]
fn core_can_disable_plugin_auto_load_for_deterministic_tests() {
let core = Core::with_config(CoreConfig::default().with_auto_load_plugins(false))
.expect("core should construct without scanning plugin directories");
assert!(core.registry().filter_names().is_empty());
}
#[test]
fn core_preserves_pre_registered_filters_when_plugin_load_fails() {
let mut config = CoreConfig::default().with_auto_load_plugins(false);
config
.plugin_directories_mut()
.push(PathBuf::from("/path/that/does/not/exist"));
let mut core = Core::with_config(config).expect("core should construct");
core.registry_mut()
.register_filter(FilterDescriptor::new("crop", "pixelflow", "crop"))
.expect("built-in filter should register");
assert!(core.registry().contains_filter("crop"));
}
#[test]
fn core_config_defaults_to_available_worker_threads() {
let config = CoreConfig::default();
assert!(config.worker_threads() >= 1);
}
#[test]
fn core_config_accepts_explicit_worker_count_for_cli() {
let config = CoreConfig::default().with_worker_threads(3);
assert_eq!(config.worker_threads(), 3);
}
#[test]
fn core_config_clamps_zero_workers_to_one() {
let config = CoreConfig::default().with_worker_threads(0);
assert_eq!(config.worker_threads(), 1);
}
#[test]
fn core_render_ordered_uses_configured_worker_count() {
let core = Core::with_config(
CoreConfig::default()
.with_auto_load_plugins(false)
.with_worker_threads(1),
)
.expect("core should construct");
assert_eq!(core.render_engine().worker_threads(), 1);
}
}