old backend core lib
This commit is contained in:
@@ -21,6 +21,7 @@ use uuid::Uuid;
|
||||
use crate::{
|
||||
domain::{ComponentMessage, LiveEvent, LiveEventKind},
|
||||
overlay::OverlaySettings,
|
||||
song_request::{SongRequestDefinition, SongRequestProjection},
|
||||
};
|
||||
|
||||
pub const DANMAKU_OVERLAY_KIND: &str = "danmaku_overlay";
|
||||
@@ -180,6 +181,19 @@ pub trait EventProjection: Send + Sync {
|
||||
/// `async-trait` dependency.
|
||||
pub type HandlerFuture<'a> = Pin<Box<dyn Future<Output = Result<(), ComponentError>> + Send + 'a>>;
|
||||
|
||||
/// Future used by component-specific durable state snapshots during WebSocket
|
||||
/// authentication. Snapshot messages share the normal component envelope.
|
||||
pub type SnapshotFuture<'a> =
|
||||
Pin<Box<dyn Future<Output = Result<Vec<ComponentMessage>, ComponentError>> + Send + 'a>>;
|
||||
|
||||
pub trait ComponentSnapshotProvider: Send + Sync {
|
||||
fn snapshot<'a>(
|
||||
&'a self,
|
||||
component: &'a ComponentInstance,
|
||||
room_id: &'a str,
|
||||
) -> SnapshotFuture<'a>;
|
||||
}
|
||||
|
||||
/// An active handler may perform durable side effects (for example recording a
|
||||
/// song request). It runs independently of WebSocket receiver count and should
|
||||
/// implement idempotency in its persistence layer.
|
||||
@@ -280,6 +294,7 @@ pub struct ComponentRuntime {
|
||||
definition: Arc<dyn ComponentDefinition>,
|
||||
projection: Arc<dyn EventProjection>,
|
||||
handlers: Vec<Arc<dyn EventHandler>>,
|
||||
snapshot_provider: Option<Arc<dyn ComponentSnapshotProvider>>,
|
||||
}
|
||||
|
||||
impl ComponentRuntime {
|
||||
@@ -320,6 +335,17 @@ impl ComponentRuntime {
|
||||
pub fn handlers(&self) -> Vec<Arc<dyn EventHandler>> {
|
||||
self.handlers.clone()
|
||||
}
|
||||
|
||||
pub async fn snapshot(
|
||||
&self,
|
||||
component: &ComponentInstance,
|
||||
room_id: &str,
|
||||
) -> Result<Vec<ComponentMessage>, ComponentError> {
|
||||
match &self.snapshot_provider {
|
||||
Some(provider) => provider.snapshot(component, room_id).await,
|
||||
None => Ok(Vec::new()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -347,6 +373,12 @@ impl ComponentRegistry {
|
||||
)
|
||||
.expect("built-in component kinds are unique");
|
||||
registry
|
||||
.register(
|
||||
Arc::new(SongRequestDefinition),
|
||||
Arc::new(SongRequestProjection),
|
||||
)
|
||||
.expect("built-in component kinds are unique");
|
||||
registry
|
||||
}
|
||||
|
||||
pub fn register(
|
||||
@@ -371,6 +403,7 @@ impl ComponentRegistry {
|
||||
definition,
|
||||
projection,
|
||||
handlers: Vec::new(),
|
||||
snapshot_provider: None,
|
||||
},
|
||||
);
|
||||
Ok(())
|
||||
@@ -392,6 +425,22 @@ impl ComponentRegistry {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn register_snapshot_provider(
|
||||
&self,
|
||||
kind: &str,
|
||||
provider: Arc<dyn ComponentSnapshotProvider>,
|
||||
) -> Result<(), ComponentError> {
|
||||
let mut entries = self
|
||||
.entries
|
||||
.write()
|
||||
.unwrap_or_else(|poisoned| poisoned.into_inner());
|
||||
let runtime = entries
|
||||
.get_mut(kind)
|
||||
.ok_or_else(|| ComponentError::NotRegistered(kind.to_owned()))?;
|
||||
runtime.snapshot_provider = Some(provider);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn runtime(&self, kind: &str) -> Result<ComponentRuntime, ComponentError> {
|
||||
self.entries
|
||||
.read()
|
||||
@@ -469,6 +518,33 @@ mod tests {
|
||||
assert!(!subscriptions.contains(LiveEventKind::GiftCombo));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builtin_song_request_is_registered_with_bounded_unlimited_defaults() {
|
||||
let registry = ComponentRegistry::default();
|
||||
let runtime = registry.runtime("song_request").unwrap();
|
||||
let mut settings = runtime.definition().default_settings();
|
||||
settings["scrollSpeedPixelsPerSecond"] = Value::from(999);
|
||||
let validated = registry
|
||||
.validate_settings("song_request", 1, settings)
|
||||
.unwrap();
|
||||
assert_eq!(validated["scrollSpeedPixelsPerSecond"], 200);
|
||||
assert_eq!(validated["maxQueueSize"], 0);
|
||||
assert_eq!(validated["maxRequestsPerViewer"], 0);
|
||||
assert_eq!(validated["requestCooldownSeconds"], 0);
|
||||
|
||||
let instance = ComponentInstance::new(
|
||||
Uuid::new_v4(),
|
||||
Uuid::new_v4(),
|
||||
"song_request",
|
||||
"点歌姬",
|
||||
1,
|
||||
validated,
|
||||
);
|
||||
let subscriptions = runtime.subscriptions(&instance).unwrap();
|
||||
assert!(subscriptions.contains(LiveEventKind::Danmaku));
|
||||
assert!(!subscriptions.contains(LiveEventKind::Gift));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duplicate_component_kinds_are_rejected() {
|
||||
let registry = ComponentRegistry::default();
|
||||
|
||||
Reference in New Issue
Block a user