use core::default::Default;
use core::iter::Iterator;
use core::option::Option;
use crate::types::{SokrResult, SokrSubstratePlugin};
pub const MAX_SUBSTRATES: usize = 16;
pub struct Registry {
substrates: [Option<SokrSubstratePlugin>; MAX_SUBSTRATES],
next_substrate_id: u64,
}
impl Registry {
#[must_use]
pub const fn new() -> Self {
const EMPTY: Option<SokrSubstratePlugin> = None;
Self {
substrates: [EMPTY; MAX_SUBSTRATES],
next_substrate_id: 1,
}
}
#[must_use]
pub fn len(&self) -> usize {
self.substrates.iter().filter(|s| s.is_some()).count()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.substrates.iter().all(Option::is_none)
}
#[must_use]
pub fn is_full(&self) -> bool {
self.substrates.iter().all(Option::is_some)
}
fn allocate_substrate_id(&mut self) -> u64 {
loop {
let candidate = self.next_substrate_id;
self.next_substrate_id = self.next_substrate_id.wrapping_add(1);
if self.next_substrate_id == 0 {
self.next_substrate_id = 1;
}
if candidate != 0 && self.find_by_substrate_id(candidate).is_none() {
return candidate;
}
}
}
pub fn register_with_id(&mut self, mut plugin: SokrSubstratePlugin) -> Result<u64, SokrResult> {
if self.is_full() {
return Err(SokrResult::RegistryFull);
}
let assigned_id = self.allocate_substrate_id();
plugin.substrate_id = assigned_id;
for slot in &mut self.substrates {
if slot.is_none() {
*slot = Some(plugin);
return Ok(assigned_id);
}
}
unreachable!("is_full() returned false but no empty slot found")
}
pub fn register(&mut self, plugin: SokrSubstratePlugin) -> SokrResult {
match self.register_with_id(plugin) {
Ok(_) => SokrResult::Ok,
Err(err) => err,
}
}
pub fn deregister(&mut self, substrate_id: u64) -> SokrResult {
for slot in &mut self.substrates {
if let Some(plugin) = slot.as_ref() {
if plugin.substrate_id == substrate_id {
let destroy_fn = plugin.destroy_fn;
destroy_fn();
*slot = None;
return SokrResult::Ok;
}
}
}
SokrResult::NotFound
}
#[must_use]
pub fn get(&self, index: usize) -> Option<&SokrSubstratePlugin> {
self.substrates.get(index)?.as_ref()
}
pub fn iter(&self) -> impl Iterator<Item = &SokrSubstratePlugin> {
self.substrates.iter().filter_map(|s| s.as_ref())
}
#[must_use]
pub fn find_by_substrate_id(&self, id: u64) -> Option<&SokrSubstratePlugin> {
self.iter().find(|p| p.substrate_id == id)
}
}
impl Default for Registry {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use core::sync::atomic::{AtomicUsize, Ordering};
use super::*;
use crate::types::{SokrResult, SokrSubstratePlugin, SokrVersion};
static DESTROY_CALLS: AtomicUsize = AtomicUsize::new(0);
fn reset_destroy_calls() -> std::sync::MutexGuard<'static, ()> {
static TEST_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
let guard = TEST_LOCK.lock().unwrap_or_else(|e| e.into_inner());
DESTROY_CALLS.store(0, Ordering::SeqCst);
guard
}
extern "C" fn dummy_capability(
_version: *const crate::types::SokrVersion,
_query: *const crate::types::SokrCapabilityQuery,
_response: *mut crate::types::SokrCapabilityResponse,
) -> SokrResult {
SokrResult::Ok
}
extern "C" fn dummy_dispatch(
_request: *const crate::types::SokrDispatchRequest,
_response: *mut crate::types::SokrDispatchResponse,
) -> SokrResult {
SokrResult::Ok
}
extern "C" fn dummy_completion(
_query: *const crate::types::SokrCompletionQuery,
_signal: *mut crate::types::SokrCompletionSignal,
) -> SokrResult {
SokrResult::Ok
}
extern "C" fn dummy_destroy() {
DESTROY_CALLS.fetch_add(1, Ordering::SeqCst);
}
fn dummy_plugin() -> SokrSubstratePlugin {
SokrSubstratePlugin {
version: SokrVersion::CURRENT,
capability_fn: dummy_capability,
dispatch_fn: dummy_dispatch,
completion_fn: dummy_completion,
destroy_fn: dummy_destroy,
substrate_id: 1,
padding: [0; 8],
}
}
#[test]
fn new_registry_is_empty() {
let reg = Registry::new();
assert!(reg.is_empty());
assert_eq!(reg.len(), 0);
}
#[test]
fn register_increases_count() {
let mut reg = Registry::new();
let result = reg.register(dummy_plugin());
assert!(result.is_ok());
assert_eq!(reg.len(), 1);
assert!(!reg.is_empty());
}
#[test]
fn register_with_id_returns_assigned_non_zero_id() {
let mut reg = Registry::new();
let assigned = reg
.register_with_id(dummy_plugin())
.expect("registration should succeed");
assert_ne!(assigned, 0);
assert!(reg.find_by_substrate_id(assigned).is_some());
}
#[test]
fn register_full_returns_error() {
let mut reg = Registry::new();
for _ in 0..MAX_SUBSTRATES {
assert!(reg.register(dummy_plugin()).is_ok());
}
assert!(reg.is_full());
assert!(reg.register(dummy_plugin()).is_err());
}
#[test]
fn get_returns_registered() {
let mut reg = Registry::new();
assert!(reg.register(dummy_plugin()).is_ok());
assert!(reg.get(0).is_some());
assert!(reg.get(1).is_none());
}
#[test]
fn iter_visits_all() {
let mut reg = Registry::new();
assert!(reg.register(dummy_plugin()).is_ok());
assert!(reg.register(dummy_plugin()).is_ok());
let count = reg.iter().count();
assert_eq!(count, 2);
}
#[test]
fn get_out_of_bounds_returns_none() {
let mut reg = Registry::new();
assert!(reg.register(dummy_plugin()).is_ok());
assert!(reg.get(MAX_SUBSTRATES).is_none());
assert!(reg.get(usize::MAX).is_none());
}
#[test]
fn deregister_existing_calls_destroy() {
let _guard = reset_destroy_calls();
let mut reg = Registry::new();
let assigned = reg
.register_with_id(dummy_plugin())
.expect("registration should succeed");
assert_eq!(reg.deregister(assigned), SokrResult::Ok);
assert_eq!(DESTROY_CALLS.load(Ordering::SeqCst), 1);
}
#[test]
fn deregister_unknown_returns_not_found() {
let mut reg = Registry::new();
assert_eq!(reg.deregister(777), SokrResult::NotFound);
}
#[test]
fn deregister_then_reregister_works() {
let mut reg = Registry::new();
let first = reg
.register_with_id(dummy_plugin())
.expect("registration should succeed");
assert_eq!(reg.deregister(first), SokrResult::Ok);
let second = reg
.register_with_id(dummy_plugin())
.expect("registration should succeed");
assert_ne!(second, 0);
assert!(reg.find_by_substrate_id(second).is_some());
}
}