use crate::resources::Resources;
use crate::service::Service;
use crate::state::States;
use crate::system::System;
use std::any::{Any, TypeId};
use std::collections::HashMap;
use std::marker::PhantomData;
use std::ops::{Deref, DerefMut};
use std::sync::Arc;
use tokio::sync::{OwnedRwLockReadGuard, OwnedRwLockWriteGuard, RwLock};
pub trait GameContext: Send + Sync {
}
pub struct Context {
data: HashMap<String, Box<dyn Any + Send + Sync>>,
services: HashMap<&'static str, Box<dyn Service>>,
resources: Resources,
states: States,
}
impl Default for Context {
fn default() -> Self {
Self::new()
}
}
impl Context {
pub fn new() -> Self {
Self {
data: HashMap::new(),
services: HashMap::new(),
resources: Resources::new(),
states: States::new(),
}
}
pub fn insert<T: Any + Send + Sync>(&mut self, key: impl Into<String>, value: T) {
self.data.insert(key.into(), Box::new(value));
}
pub fn get<T: Any + Send + Sync>(&self, key: &str) -> Option<&T> {
self.data.get(key)?.downcast_ref()
}
pub fn get_mut<T: Any + Send + Sync>(&mut self, key: &str) -> Option<&mut T> {
self.data.get_mut(key)?.downcast_mut()
}
pub fn remove(&mut self, key: &str) -> bool {
self.data.remove(key).is_some()
}
pub fn contains(&self, key: &str) -> bool {
self.data.contains_key(key)
}
pub fn register_service(&mut self, service: Box<dyn Service>) {
let name = service.name(); self.services.insert(name, service);
}
pub fn service(&self, name: &str) -> Option<&dyn Service> {
self.services.get(name).map(|s| s.as_ref())
}
pub fn service_mut(&mut self, name: &str) -> Option<&mut dyn Service> {
self.services.get_mut(name).map(|s| s.as_mut())
}
pub fn service_as<T: Service + 'static>(&self, name: &str) -> Option<&T> {
self.service(name)?.as_any().downcast_ref::<T>()
}
pub fn service_as_mut<T: Service + 'static>(&mut self, name: &str) -> Option<&mut T> {
self.service_mut(name)?.as_any_mut().downcast_mut::<T>()
}
pub fn service_count(&self) -> usize {
self.services.len()
}
pub fn service_names(&self) -> Vec<&'static str> {
self.services.keys().copied().collect()
}
pub fn resources(&self) -> &Resources {
&self.resources
}
pub fn resources_mut(&mut self) -> &mut Resources {
&mut self.resources
}
pub fn states(&self) -> &States {
&self.states
}
pub fn states_mut(&mut self) -> &mut States {
&mut self.states
}
}
impl GameContext for Context {}
#[cfg(test)]
mod tests {
use super::*;
use async_trait::async_trait;
#[derive(Clone)]
struct MockCombatService {
damage_multiplier: f32,
}
impl MockCombatService {
fn new() -> Self {
Self {
damage_multiplier: 1.0,
}
}
fn calculate(&self, base: i32) -> i32 {
(base as f32 * self.damage_multiplier) as i32
}
}
#[async_trait]
impl Service for MockCombatService {
fn name(&self) -> &'static str {
"mock_combat"
}
fn clone_box(&self) -> Box<dyn Service> {
Box::new(self.clone())
}
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
}
#[test]
fn test_service_registration() {
let mut ctx = Context::new();
assert_eq!(ctx.service_count(), 0);
let service = Box::new(MockCombatService::new());
ctx.register_service(service);
assert_eq!(ctx.service_count(), 1);
assert_eq!(ctx.service_names(), vec!["mock_combat"]);
}
#[test]
fn test_service_access() {
let mut ctx = Context::new();
ctx.register_service(Box::new(MockCombatService::new()));
let service = ctx.service("mock_combat");
assert!(service.is_some());
assert_eq!(service.unwrap().name(), "mock_combat");
let missing = ctx.service("nonexistent");
assert!(missing.is_none());
}
#[test]
fn test_service_type_safe_access() {
let mut ctx = Context::new();
ctx.register_service(Box::new(MockCombatService::new()));
let service = ctx.service_as::<MockCombatService>("mock_combat");
assert!(service.is_some());
let damage = service.unwrap().calculate(100);
assert_eq!(damage, 100);
}
#[test]
fn test_service_mutable_access() {
let mut ctx = Context::new();
ctx.register_service(Box::new(MockCombatService::new()));
let service = ctx.service_as_mut::<MockCombatService>("mock_combat");
assert!(service.is_some());
let service = service.unwrap();
service.damage_multiplier = 2.0;
let service = ctx.service_as::<MockCombatService>("mock_combat").unwrap();
let damage = service.calculate(100);
assert_eq!(damage, 200);
}
#[test]
fn test_multiple_services() {
let mut ctx = Context::new();
#[derive(Clone)]
struct ServiceA;
#[derive(Clone)]
struct ServiceB;
#[async_trait]
impl Service for ServiceA {
fn name(&self) -> &'static str {
"service_a"
}
fn clone_box(&self) -> Box<dyn Service> {
Box::new(self.clone())
}
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
}
#[async_trait]
impl Service for ServiceB {
fn name(&self) -> &'static str {
"service_b"
}
fn clone_box(&self) -> Box<dyn Service> {
Box::new(self.clone())
}
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
}
ctx.register_service(Box::new(ServiceA));
ctx.register_service(Box::new(ServiceB));
assert_eq!(ctx.service_count(), 2);
assert!(ctx.service("service_a").is_some());
assert!(ctx.service("service_b").is_some());
}
}
type Resource = Arc<RwLock<Box<dyn Any + Send + Sync>>>;
pub struct ResourceReadGuard<T: 'static> {
guard: OwnedRwLockReadGuard<Box<dyn Any + Send + Sync>>,
_marker: PhantomData<T>,
}
impl<T: 'static> Deref for ResourceReadGuard<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.guard
.downcast_ref::<T>()
.expect("Resource type mismatch - this is a bug")
}
}
pub struct ResourceWriteGuard<T: 'static> {
guard: OwnedRwLockWriteGuard<Box<dyn Any + Send + Sync>>,
_marker: PhantomData<T>,
}
impl<T: 'static> Deref for ResourceWriteGuard<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.guard
.downcast_ref::<T>()
.expect("Resource type mismatch - this is a bug")
}
}
impl<T: 'static> DerefMut for ResourceWriteGuard<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.guard
.downcast_mut::<T>()
.expect("Resource type mismatch - this is a bug")
}
}
pub struct ResourceContext {
resources: HashMap<TypeId, Resource>,
}
impl ResourceContext {
pub fn new() -> Self {
Self {
resources: HashMap::new(),
}
}
pub fn insert<T: 'static + Send + Sync>(&mut self, resource: T) {
self.resources
.insert(TypeId::of::<T>(), Arc::new(RwLock::new(Box::new(resource))));
}
pub(crate) fn insert_boxed(
&mut self,
type_id: TypeId,
boxed: Box<dyn std::any::Any + Send + Sync>,
) {
self.resources.insert(type_id, Arc::new(RwLock::new(boxed)));
}
pub async fn get<T: 'static + Send + Sync>(&self) -> Option<ResourceReadGuard<T>> {
let resource = self.resources.get(&TypeId::of::<T>())?.clone();
let guard = resource.read_owned().await;
Some(ResourceReadGuard {
guard,
_marker: PhantomData,
})
}
pub async fn get_mut<T: 'static + Send + Sync>(&self) -> Option<ResourceWriteGuard<T>> {
let resource = self.resources.get(&TypeId::of::<T>())?.clone();
let guard = resource.write_owned().await;
Some(ResourceWriteGuard {
guard,
_marker: PhantomData,
})
}
pub fn try_get<T: 'static + Send + Sync>(&self) -> Option<ResourceReadGuard<T>> {
let resource = self.resources.get(&TypeId::of::<T>())?.clone();
let guard = resource.try_read_owned().ok()?;
Some(ResourceReadGuard {
guard,
_marker: PhantomData,
})
}
pub fn try_get_mut<T: 'static + Send + Sync>(&self) -> Option<ResourceWriteGuard<T>> {
let resource = self.resources.get(&TypeId::of::<T>())?.clone();
let guard = resource.try_write_owned().ok()?;
Some(ResourceWriteGuard {
guard,
_marker: PhantomData,
})
}
pub fn contains<T: 'static>(&self) -> bool {
self.resources.contains_key(&TypeId::of::<T>())
}
pub fn remove<T: 'static>(&mut self) -> bool {
self.resources.remove(&TypeId::of::<T>()).is_some()
}
pub fn len(&self) -> usize {
self.resources.len()
}
pub fn is_empty(&self) -> bool {
self.resources.is_empty()
}
}
impl Default for ResourceContext {
fn default() -> Self {
Self::new()
}
}
pub struct ServiceContext {
services: HashMap<&'static str, Box<dyn Service>>,
}
impl ServiceContext {
pub fn new() -> Self {
Self {
services: HashMap::new(),
}
}
pub fn register(&mut self, service: Box<dyn Service>) {
let name = service.name();
self.services.insert(name, service);
}
pub fn get(&self, name: &str) -> Option<&dyn Service> {
self.services.get(name).map(|boxed| boxed.as_ref())
}
pub fn get_mut(&mut self, name: &str) -> Option<&mut dyn Service> {
self.services.get_mut(name).map(|boxed| boxed.as_mut())
}
pub fn get_as<T: Service + 'static>(&self, name: &str) -> Option<&T> {
self.get(name)?.as_any().downcast_ref::<T>()
}
pub fn get_as_mut<T: Service + 'static>(&mut self, name: &str) -> Option<&mut T> {
self.get_mut(name)?.as_any_mut().downcast_mut::<T>()
}
pub fn len(&self) -> usize {
self.services.len()
}
pub fn is_empty(&self) -> bool {
self.services.is_empty()
}
pub fn service_names(&self) -> Vec<&'static str> {
self.services.keys().copied().collect()
}
}
impl Default for ServiceContext {
fn default() -> Self {
Self::new()
}
}
pub struct SystemContext {
systems: HashMap<TypeId, Box<dyn System>>,
}
impl SystemContext {
pub fn new() -> Self {
Self {
systems: HashMap::new(),
}
}
pub fn register<T: System + 'static>(&mut self, system: T) {
self.systems.insert(TypeId::of::<T>(), Box::new(system));
}
pub fn register_boxed(&mut self, system: Box<dyn System>) {
let type_id = system.as_any().type_id();
self.systems.insert(type_id, system);
}
pub fn get<T: System + 'static>(&self) -> Option<&T> {
self.systems
.get(&TypeId::of::<T>())?
.as_any()
.downcast_ref::<T>()
}
pub fn get_mut<T: System + 'static>(&mut self) -> Option<&mut T> {
self.systems
.get_mut(&TypeId::of::<T>())?
.as_any_mut()
.downcast_mut::<T>()
}
pub fn contains<T: System + 'static>(&self) -> bool {
self.systems.contains_key(&TypeId::of::<T>())
}
pub fn remove<T: System + 'static>(&mut self) -> bool {
self.systems.remove(&TypeId::of::<T>()).is_some()
}
pub fn len(&self) -> usize {
self.systems.len()
}
pub fn is_empty(&self) -> bool {
self.systems.is_empty()
}
}
impl Default for SystemContext {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod new_context_tests {
use super::*;
use async_trait::async_trait;
#[derive(Debug, Clone, PartialEq)]
struct Player {
name: String,
hp: i32,
max_hp: i32,
}
impl Player {
fn new(name: &str) -> Self {
Self {
name: name.to_string(),
hp: 100,
max_hp: 100,
}
}
}
#[derive(Debug, Clone, PartialEq)]
struct Score(i32);
#[derive(Clone)]
struct TestCombatService;
impl TestCombatService {
fn calculate_damage(&self, attack: i32, defense: i32) -> i32 {
(attack - defense).max(1)
}
}
#[async_trait]
impl Service for TestCombatService {
fn name(&self) -> &'static str {
"test_combat"
}
fn clone_box(&self) -> Box<dyn Service> {
Box::new(self.clone())
}
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
}
struct TestCombatSystem {
turn_count: u32,
}
impl TestCombatSystem {
fn new() -> Self {
Self { turn_count: 0 }
}
fn increment_turn(&mut self) {
self.turn_count += 1;
}
}
#[async_trait]
impl System for TestCombatSystem {
fn name(&self) -> &'static str {
"test_combat_system"
}
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
}
#[tokio::test]
async fn test_resource_context_insert_and_get() {
let mut resources = ResourceContext::new();
resources.insert(Player::new("Hero"));
let player = resources.get::<Player>().await.unwrap();
assert_eq!(player.name, "Hero");
assert_eq!(player.hp, 100);
}
#[tokio::test]
async fn test_resource_context_get_mut() {
let mut resources = ResourceContext::new();
resources.insert(Player::new("Hero"));
{
let mut player = resources.get_mut::<Player>().await.unwrap();
player.hp -= 30;
}
let player = resources.get::<Player>().await.unwrap();
assert_eq!(player.hp, 70);
}
#[tokio::test]
async fn test_resource_context_multiple_resources() {
let mut resources = ResourceContext::new();
resources.insert(Player::new("Hero"));
resources.insert(Score(100));
assert_eq!(resources.len(), 2);
let player = resources.get::<Player>().await.unwrap();
let score = resources.get::<Score>().await.unwrap();
assert_eq!(player.name, "Hero");
assert_eq!(score.0, 100);
}
#[tokio::test]
async fn test_resource_context_contains() {
let mut resources = ResourceContext::new();
resources.insert(Player::new("Hero"));
assert!(resources.contains::<Player>());
assert!(!resources.contains::<Score>());
}
#[tokio::test]
async fn test_resource_context_remove() {
let mut resources = ResourceContext::new();
resources.insert(Player::new("Hero"));
assert!(resources.contains::<Player>());
assert!(resources.remove::<Player>());
assert!(!resources.contains::<Player>());
}
#[tokio::test]
async fn test_resource_context_concurrent_reads() {
let mut resources = ResourceContext::new();
resources.insert(Player::new("Hero"));
let reader1 = resources.get::<Player>().await.unwrap();
let reader2 = resources.get::<Player>().await.unwrap();
assert_eq!(reader1.name, "Hero");
assert_eq!(reader2.name, "Hero");
}
#[test]
fn test_service_context_register_and_get() {
let mut services = ServiceContext::new();
services.register(Box::new(TestCombatService));
let service = services.get("test_combat").unwrap();
assert_eq!(service.name(), "test_combat");
}
#[test]
fn test_service_context_get_as() {
let mut services = ServiceContext::new();
services.register(Box::new(TestCombatService));
let combat = services.get_as::<TestCombatService>("test_combat").unwrap();
let damage = combat.calculate_damage(100, 30);
assert_eq!(damage, 70);
}
#[test]
fn test_service_context_service_names() {
let mut services = ServiceContext::new();
services.register(Box::new(TestCombatService));
let names = services.service_names();
assert_eq!(names, vec!["test_combat"]);
}
#[test]
fn test_service_context_len_and_is_empty() {
let mut services = ServiceContext::new();
assert!(services.is_empty());
assert_eq!(services.len(), 0);
services.register(Box::new(TestCombatService));
assert!(!services.is_empty());
assert_eq!(services.len(), 1);
}
#[test]
fn test_system_context_register_and_get() {
let mut systems = SystemContext::new();
systems.register(TestCombatSystem::new());
let system = systems.get::<TestCombatSystem>().unwrap();
assert_eq!(system.turn_count, 0);
}
#[test]
fn test_system_context_get_mut() {
let mut systems = SystemContext::new();
systems.register(TestCombatSystem::new());
{
let system = systems.get_mut::<TestCombatSystem>().unwrap();
system.increment_turn();
system.increment_turn();
}
let system = systems.get::<TestCombatSystem>().unwrap();
assert_eq!(system.turn_count, 2);
}
#[test]
fn test_system_context_contains() {
let mut systems = SystemContext::new();
systems.register(TestCombatSystem::new());
assert!(systems.contains::<TestCombatSystem>());
assert_eq!(systems.len(), 1);
}
#[test]
fn test_system_context_remove() {
let mut systems = SystemContext::new();
systems.register(TestCombatSystem::new());
assert!(systems.contains::<TestCombatSystem>());
assert!(systems.remove::<TestCombatSystem>());
assert!(!systems.contains::<TestCombatSystem>());
}
#[test]
fn test_system_context_len_and_is_empty() {
let mut systems = SystemContext::new();
assert!(systems.is_empty());
assert_eq!(systems.len(), 0);
systems.register(TestCombatSystem::new());
assert!(!systems.is_empty());
assert_eq!(systems.len(), 1);
}
}