use crate::context::{ResourceContext, ServiceContext};
use crate::event::EventBus;
use crate::system::System;
use async_trait::async_trait;
use std::any::Any;
use std::sync::Arc;
use super::events::*;
use super::factions::Factions;
use super::hook::FactionHook;
use super::state::FactionState;
use super::types::*;
#[derive(Clone)]
pub struct FactionSystem {
hook: Arc<dyn FactionHook>,
next_operation_id: u64,
}
impl FactionSystem {
pub fn new(hook: Arc<dyn FactionHook>) -> Self {
Self {
hook,
next_operation_id: 1,
}
}
fn generate_operation_id(&mut self) -> OperationId {
let id = OperationId::new(format!("op-{:06}", self.next_operation_id));
self.next_operation_id += 1;
id
}
pub async fn process_operation_launches(
&mut self,
_services: &ServiceContext,
resources: &mut ResourceContext,
) {
let requests = {
if let Some(mut bus) = resources.get_mut::<EventBus>().await {
let reader = bus.reader::<OperationLaunchRequested>();
reader.iter().cloned().collect::<Vec<_>>()
} else {
Vec::new()
}
};
for request in requests {
let faction = {
if let Some(factions) = resources.get::<Factions>().await {
match factions.get(&request.faction_id) {
Some(f) => f.clone(),
None => continue, }
} else {
continue;
}
};
let operation_id = self.generate_operation_id();
let operation = Operation::new(
operation_id.as_str(),
request.faction_id.clone(),
request.operation_name.clone(),
)
.with_metadata(request.metadata.clone());
let cost = {
let resources_ref = resources as &ResourceContext;
match self
.hook
.calculate_operation_cost(&faction, &operation, resources_ref)
.await
{
Ok(cost) => cost,
Err(_) => continue, }
};
let _ = cost;
{
if let Some(mut state) = resources.get_mut::<FactionState>().await {
if state.launch_operation(operation.clone()).is_err() {
continue; }
} else {
continue;
}
}
self.hook
.on_operation_launched(&faction, &operation, resources)
.await;
if let Some(mut bus) = resources.get_mut::<EventBus>().await {
bus.publish(OperationLaunchedEvent {
operation_id: operation.id.clone(),
faction_id: operation.faction_id.clone(),
operation_name: operation.name.clone(),
});
}
}
}
pub async fn process_operation_resolutions(
&mut self,
_services: &ServiceContext,
resources: &mut ResourceContext,
) {
let requests = {
if let Some(mut bus) = resources.get_mut::<EventBus>().await {
let reader = bus.reader::<OperationResolveRequested>();
reader.iter().cloned().collect::<Vec<_>>()
} else {
Vec::new()
}
};
for request in requests {
let operation = {
if let Some(state) = resources.get::<FactionState>().await {
match state.get_operation(&request.operation_id) {
Some(op) => op.clone(),
None => continue, }
} else {
continue;
}
};
let faction = {
if let Some(factions) = resources.get::<Factions>().await {
match factions.get(&operation.faction_id) {
Some(f) => f.clone(),
None => continue, }
} else {
continue;
}
};
let status = if request.outcome.success {
OperationStatus::Completed
} else {
OperationStatus::Failed
};
{
if let Some(mut state) = resources.get_mut::<FactionState>().await {
if state
.update_operation_status(&request.operation_id, status)
.is_err()
{
continue; }
} else {
continue;
}
}
if request.outcome.success {
self.hook
.on_operation_completed(&faction, &operation, &request.outcome, resources)
.await;
if let Some(mut bus) = resources.get_mut::<EventBus>().await {
bus.publish(OperationCompletedEvent {
operation_id: request.operation_id.clone(),
faction_id: operation.faction_id.clone(),
success: true,
metrics: request.outcome.metrics.clone(),
});
}
} else {
self.hook
.on_operation_failed(&faction, &operation, resources)
.await;
if let Some(mut bus) = resources.get_mut::<EventBus>().await {
bus.publish(OperationFailedEvent {
operation_id: request.operation_id.clone(),
faction_id: operation.faction_id.clone(),
reason: request
.outcome
.metadata
.get("reason")
.and_then(|v| v.as_str())
.unwrap_or("Unknown failure")
.to_string(),
});
}
}
}
}
pub async fn process_events(
&mut self,
services: &ServiceContext,
resources: &mut ResourceContext,
) {
self.process_operation_launches(services, resources).await;
self.process_operation_resolutions(services, resources)
.await;
}
}
#[async_trait]
impl System for FactionSystem {
fn name(&self) -> &'static str {
"faction_system"
}
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::event::EventBus;
use crate::plugin::faction::{DefaultFactionHook, Faction};
use serde_json::json;
#[tokio::test]
async fn test_faction_system_launch_operation() {
let mut resources = ResourceContext::new();
let mut factions = Factions::new();
factions.add(Faction::new("crimson", "Crimson Syndicate"));
resources.insert(factions);
resources.insert(FactionState::new());
resources.insert(EventBus::new());
let services = ServiceContext::new();
let hook = Arc::new(DefaultFactionHook);
let mut system = FactionSystem::new(hook);
{
let mut bus = resources.get_mut::<EventBus>().await.unwrap();
bus.publish(OperationLaunchRequested {
faction_id: FactionId::new("crimson"),
operation_name: "Test Operation".to_string(),
metadata: json!({ "test": "data" }),
});
bus.dispatch();
}
system.process_events(&services, &mut resources).await;
let state = resources.get::<FactionState>().await.unwrap();
assert_eq!(state.operation_count(), 1);
{
let mut bus = resources.get_mut::<EventBus>().await.unwrap();
bus.dispatch();
}
let mut bus = resources.get_mut::<EventBus>().await.unwrap();
let reader = bus.reader::<OperationLaunchedEvent>();
let events: Vec<_> = reader.iter().collect();
assert_eq!(events.len(), 1);
assert_eq!(events[0].operation_name, "Test Operation");
}
#[tokio::test]
async fn test_faction_system_complete_operation() {
let mut resources = ResourceContext::new();
let mut factions = Factions::new();
factions.add(Faction::new("crimson", "Crimson Syndicate"));
resources.insert(factions);
let mut state = FactionState::new();
let op = Operation::new("op-001", FactionId::new("crimson"), "Test");
state.launch_operation(op).unwrap();
resources.insert(state);
resources.insert(EventBus::new());
let services = ServiceContext::new();
let hook = Arc::new(DefaultFactionHook);
let mut system = FactionSystem::new(hook);
{
let mut bus = resources.get_mut::<EventBus>().await.unwrap();
bus.publish(OperationResolveRequested {
operation_id: OperationId::new("op-001"),
outcome: Outcome::new("op-001", true).with_metric("test", 1.0),
});
bus.dispatch();
}
system.process_events(&services, &mut resources).await;
let state = resources.get::<FactionState>().await.unwrap();
let op = state.get_operation(&OperationId::new("op-001")).unwrap();
assert!(op.is_completed());
{
let mut bus = resources.get_mut::<EventBus>().await.unwrap();
bus.dispatch();
}
let mut bus = resources.get_mut::<EventBus>().await.unwrap();
let reader = bus.reader::<OperationCompletedEvent>();
let events: Vec<_> = reader.iter().collect();
assert_eq!(events.len(), 1);
assert!(events[0].success);
}
#[tokio::test]
async fn test_faction_system_fail_operation() {
let mut resources = ResourceContext::new();
let mut factions = Factions::new();
factions.add(Faction::new("crimson", "Crimson Syndicate"));
resources.insert(factions);
let mut state = FactionState::new();
let op = Operation::new("op-001", FactionId::new("crimson"), "Test");
state.launch_operation(op).unwrap();
resources.insert(state);
resources.insert(EventBus::new());
let services = ServiceContext::new();
let hook = Arc::new(DefaultFactionHook);
let mut system = FactionSystem::new(hook);
{
let mut bus = resources.get_mut::<EventBus>().await.unwrap();
bus.publish(OperationResolveRequested {
operation_id: OperationId::new("op-001"),
outcome: Outcome::new("op-001", false)
.with_metadata(json!({ "reason": "Test failure" })),
});
bus.dispatch();
}
system.process_events(&services, &mut resources).await;
let state = resources.get::<FactionState>().await.unwrap();
let op = state.get_operation(&OperationId::new("op-001")).unwrap();
assert!(op.is_failed());
{
let mut bus = resources.get_mut::<EventBus>().await.unwrap();
bus.dispatch();
}
let mut bus = resources.get_mut::<EventBus>().await.unwrap();
let reader = bus.reader::<OperationFailedEvent>();
let events: Vec<_> = reader.iter().collect();
assert_eq!(events.len(), 1);
assert_eq!(events[0].reason, "Test failure");
}
#[tokio::test]
async fn test_faction_system_operation_not_found() {
let mut resources = ResourceContext::new();
resources.insert(Factions::new());
resources.insert(FactionState::new());
resources.insert(EventBus::new());
let services = ServiceContext::new();
let hook = Arc::new(DefaultFactionHook);
let mut system = FactionSystem::new(hook);
{
let mut bus = resources.get_mut::<EventBus>().await.unwrap();
bus.publish(OperationResolveRequested {
operation_id: OperationId::new("nonexistent"),
outcome: Outcome::new("nonexistent", true),
});
bus.dispatch();
}
system.process_events(&services, &mut resources).await;
{
let mut bus = resources.get_mut::<EventBus>().await.unwrap();
bus.dispatch();
}
let mut bus = resources.get_mut::<EventBus>().await.unwrap();
let reader = bus.reader::<OperationCompletedEvent>();
let events: Vec<_> = reader.iter().collect();
assert_eq!(events.len(), 0);
}
}