#![warn(missing_docs)]
#![cfg_attr(doc, allow(unknown_lints))]
#![deny(rustdoc::all)]
#[doc(inline)]
pub use bones_ecs as ecs;
pub mod prelude {
pub use crate::{
ecs::prelude::*, instant::Instant, time::*, Game, GamePlugin, Session, SessionCommand,
SessionOptions, SessionPlugin, SessionRunner, Sessions,
};
}
pub use instant;
pub mod time;
use std::{collections::VecDeque, fmt::Debug, sync::Arc};
use crate::prelude::*;
#[derive(Deref, DerefMut)]
pub struct Session {
pub world: World,
#[deref]
pub stages: SystemStages,
pub active: bool,
pub visible: bool,
pub priority: i32,
pub runner: Box<dyn SessionRunner>,
}
impl std::fmt::Debug for Session {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Session")
.field("world", &self.world)
.field("stages", &self.stages)
.field("active", &self.active)
.field("visible", &self.visible)
.field("priority", &self.priority)
.field("runner", &"SessionRunner")
.finish()
}
}
impl Session {
pub fn new() -> Self {
Self {
world: {
let mut w = World::default();
w.init_resource::<Time>();
w
},
stages: default(),
active: true,
visible: true,
priority: 0,
runner: Box::<DefaultSessionRunner>::default(),
}
}
pub fn install_plugin(&mut self, plugin: impl SessionPlugin) -> &mut Self {
plugin.install(self);
self
}
pub fn snapshot(&self) -> World {
self.world.clone()
}
pub fn restore(&mut self, world: &mut World) {
std::mem::swap(&mut self.world, world)
}
pub fn set_session_runner(&mut self, runner: Box<dyn SessionRunner>) {
self.runner = runner;
}
pub fn reset_internals(
&mut self,
reset_components: bool,
reset_entities: bool,
reset_systems: bool,
) {
self.world.reset_internals(reset_components, reset_entities);
if reset_systems {
self.reset_remove_all_systems();
}
}
}
impl Default for Session {
fn default() -> Self {
Self::new()
}
}
pub trait SessionPlugin {
fn install(self, session: &mut Session);
}
impl<F: FnOnce(&mut Session)> SessionPlugin for F {
fn install(self, session: &mut Session) {
(self)(session)
}
}
pub trait GamePlugin {
fn install(self, game: &mut Game);
}
impl<F: FnOnce(&mut Game)> GamePlugin for F {
fn install(self, game: &mut Game) {
(self)(game)
}
}
pub trait SessionRunner: Sync + Send + 'static {
fn step(&mut self, now: Instant, world: &mut World, stages: &mut SystemStages);
fn restart_session(&mut self);
fn disable_local_input(&mut self, input_disabled: bool);
}
#[derive(Default)]
pub struct DefaultSessionRunner;
impl SessionRunner for DefaultSessionRunner {
fn step(&mut self, now: instant::Instant, world: &mut World, stages: &mut SystemStages) {
world.resource_mut::<Time>().update_with_instant(now);
stages.run(world)
}
#[allow(clippy::default_constructed_unit_structs)]
fn restart_session(&mut self) {
*self = DefaultSessionRunner::default();
}
fn disable_local_input(&mut self, _input_disabled: bool) {}
}
#[derive(Default)]
pub struct Game {
pub sessions: Sessions,
pub systems: GameSystems,
pub sorted_session_keys: Vec<Ustr>,
pub shared_resources: Vec<AtomicUntypedResource>,
}
impl Game {
pub fn new() -> Self {
Self::default()
}
pub fn install_plugin<P: GamePlugin>(&mut self, plugin: P) -> &mut Self {
plugin.install(self);
self
}
#[track_caller]
pub fn shared_resource<T: HasSchema>(&self) -> Option<Ref<T>> {
let res = self
.shared_resources
.iter()
.find(|x| x.schema() == T::schema())?;
let borrow = res.borrow();
if borrow.is_some() {
Some(Ref::map(borrow, |b| unsafe {
b.as_ref().unwrap().as_ref().cast_into_unchecked()
}))
} else {
None
}
}
#[track_caller]
pub fn shared_resource_mut<T: HasSchema>(&self) -> Option<RefMut<T>> {
let res = self
.shared_resources
.iter()
.find(|x| x.schema() == T::schema())?;
let borrow = res.borrow_mut();
if borrow.is_some() {
Some(RefMut::map(borrow, |b| unsafe {
b.as_mut().unwrap().as_mut().cast_into_mut_unchecked()
}))
} else {
None
}
}
pub fn shared_resource_cell<T: HasSchema>(&self) -> Option<AtomicResource<T>> {
let res = self
.shared_resources
.iter()
.find(|x| x.schema() == T::schema())?;
Some(AtomicResource::from_untyped(res.clone()).unwrap())
}
pub fn init_shared_resource<T: HasSchema + Default>(&mut self) -> RefMut<T> {
if !self
.shared_resources
.iter()
.any(|x| x.schema() == T::schema())
{
self.insert_shared_resource(T::default());
}
self.shared_resource_mut::<T>().unwrap()
}
pub fn insert_shared_resource<T: HasSchema>(&mut self, resource: T) {
for r in &mut self.shared_resources {
if r.schema() == T::schema() {
let mut borrow = r.borrow_mut();
if let Some(b) = borrow.as_mut().as_mut() {
*b.cast_mut() = resource;
} else {
*borrow = Some(SchemaBox::new(resource))
}
return;
}
}
self.shared_resources
.push(Arc::new(UntypedResource::new(SchemaBox::new(resource))));
}
pub fn step(&mut self, now: instant::Instant) {
let mut game_systems = std::mem::take(&mut self.systems);
if !game_systems.has_run_startup {
for system in &mut game_systems.startup {
system(self);
}
game_systems.has_run_startup = true;
}
for system in &mut game_systems.before {
system(self)
}
self.sorted_session_keys.clear();
self.sorted_session_keys.extend(self.sessions.map.keys());
self.sorted_session_keys
.sort_by_key(|name| self.sessions.map.get(name).unwrap().priority);
for session_name in self.sorted_session_keys.clone() {
let Some(mut current_session) = self.sessions.map.remove(&session_name) else {
continue;
};
let options = if current_session.active {
if let Some(systems) = game_systems.before_session.get_mut(&session_name) {
for system in systems {
system(self)
}
}
for r in &self.shared_resources {
if !current_session
.world
.resources
.untyped()
.contains_cell(r.schema().id())
{
current_session
.world
.resources
.untyped()
.insert_cell(r.clone())
.unwrap();
}
}
current_session.world.resources.insert(SessionOptions {
active: true,
delete: false,
visible: current_session.visible,
});
{
let mut sessions = current_session.world.resource_mut::<Sessions>();
std::mem::swap(&mut *sessions, &mut self.sessions);
}
current_session.runner.step(
now,
&mut current_session.world,
&mut current_session.stages,
);
{
let mut sessions = current_session.world.resource_mut::<Sessions>();
std::mem::swap(&mut *sessions, &mut self.sessions);
}
*current_session.world.resource::<SessionOptions>()
} else {
SessionOptions {
active: false,
visible: current_session.visible,
delete: false,
}
};
if options.delete {
let session_idx = self
.sorted_session_keys
.iter()
.position(|x| x == &session_name)
.unwrap();
self.sorted_session_keys.remove(session_idx);
} else {
current_session.active = options.active;
current_session.visible = options.visible;
self.sessions.map.insert(session_name, current_session);
}
if let Some(systems) = game_systems.after_session.get_mut(&session_name) {
for system in systems {
system(self)
}
}
}
{
let mut session_commands: VecDeque<Box<SessionCommand>> = default();
std::mem::swap(&mut session_commands, &mut self.sessions.commands);
for command in session_commands.drain(..) {
command(&mut self.sessions);
}
}
for system in &mut game_systems.after {
system(self)
}
self.systems = game_systems;
self.sorted_session_keys
.retain(|x| self.sessions.iter().any(|(id, _)| id == x));
}
}
pub type GameSystem = Box<dyn FnMut(&mut Game) + Sync + Send>;
#[derive(Default)]
pub struct GameSystems {
pub has_run_startup: bool,
pub startup: Vec<GameSystem>,
pub before: Vec<GameSystem>,
pub after: Vec<GameSystem>,
pub after_session: HashMap<Ustr, Vec<GameSystem>>,
pub before_session: HashMap<Ustr, Vec<GameSystem>>,
}
impl GameSystems {
pub fn add_startup_system<F>(&mut self, system: F) -> &mut Self
where
F: FnMut(&mut Game) + Sync + Send + 'static,
{
self.startup.push(Box::new(system));
self
}
pub fn add_before_system<F>(&mut self, system: F) -> &mut Self
where
F: FnMut(&mut Game) + Sync + Send + 'static,
{
self.before.push(Box::new(system));
self
}
pub fn add_after_system<F>(&mut self, system: F) -> &mut Self
where
F: FnMut(&mut Game) + Sync + Send + 'static,
{
self.after.push(Box::new(system));
self
}
pub fn add_before_session_system<F>(&mut self, session: &str, system: F) -> &mut Self
where
F: FnMut(&mut Game) + Sync + Send + 'static,
{
self.before_session
.entry(session.into())
.or_default()
.push(Box::new(system));
self
}
pub fn add_after_session_system<F>(&mut self, session: &str, system: F) -> &mut Self
where
F: FnMut(&mut Game) + Sync + Send + 'static,
{
self.after_session
.entry(session.into())
.or_default()
.push(Box::new(system));
self
}
}
pub type SessionCommand = dyn FnOnce(&mut Sessions) + Sync + Send;
#[derive(HasSchema, Default)]
pub struct Sessions {
map: UstrMap<Session>,
commands: VecDeque<Box<SessionCommand>>,
}
#[derive(HasSchema, Default, Debug, Clone, Copy)]
#[repr(C)]
pub struct SessionOptions {
pub active: bool,
pub visible: bool,
pub delete: bool,
}
impl Sessions {
#[track_caller]
pub fn create<K: TryInto<Ustr>>(&mut self, name: K) -> &mut Session
where
<K as TryInto<Ustr>>::Error: Debug,
{
let name = name.try_into().unwrap();
let mut session = Session::new();
session.world.init_resource::<Sessions>();
self.map.insert(name, session);
self.map.get_mut(&name).unwrap()
}
#[track_caller]
pub fn delete<K: TryInto<Ustr>>(&mut self, name: K)
where
<K as TryInto<Ustr>>::Error: Debug,
{
self.map.remove(&name.try_into().unwrap());
}
#[track_caller]
pub fn get<K: TryInto<Ustr>>(&self, name: K) -> Option<&Session>
where
<K as TryInto<Ustr>>::Error: Debug,
{
self.map.get(&name.try_into().unwrap())
}
#[track_caller]
pub fn get_mut<K: TryInto<Ustr>>(&mut self, name: K) -> Option<&mut Session>
where
<K as TryInto<Ustr>>::Error: Debug,
{
self.map.get_mut(&name.try_into().unwrap())
}
pub fn iter_mut(&mut self) -> std::collections::hash_map::IterMut<Ustr, Session> {
self.map.iter_mut()
}
pub fn iter(&self) -> std::collections::hash_map::Iter<Ustr, Session> {
self.map.iter()
}
pub fn add_command(&mut self, command: Box<SessionCommand>) {
self.commands.push_back(command);
}
}
impl Clone for Sessions {
fn clone(&self) -> Self {
Self::default()
}
}