Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new version of modor_physics #289

Merged
merged 7 commits into from
Apr 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,6 @@ jobs:
- name: Install wasm-pack
run: cargo install wasm-pack --debug
if: matrix.target == 'wasm32-unknown-unknown'
- name: Prepare compilation tests
run: bash -x .github/workflows/scripts/prepare_compilation_tests.sh
- name: Test WASM
run: for crate_path in crates/*; do wasm-pack test --node "$crate_path"; done
if: matrix.target == 'wasm32-unknown-unknown'
Expand Down Expand Up @@ -149,8 +147,6 @@ jobs:
run: bash -x .github/workflows/scripts/install_graphic_dependencies_linux.sh
- name: Install grcov
run: cargo install grcov --debug
- name: Prepare compilation tests
run: bash -x .github/workflows/scripts/prepare_compilation_tests.sh
- name: Run unit tests
run: xvfb-run --server-args="-screen 0 1920x1080x24" cargo test --lib --tests
env:
Expand Down
6 changes: 0 additions & 6 deletions .github/workflows/scripts/prepare_compilation_tests.sh

This file was deleted.

10 changes: 5 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
### Added

- Node definition
- Physics module
- Graphics module for 2D rendering
- Text module
- Input module with support of keyboard, mouse, touch and gamepads
- Resources module to easily load and access resources like textures, font, sounds, ...
- Physics for 2D objects
- Graphics for 2D rendering
- Text rendering
- Input support for keyboard, mouse, touch and gamepads
- Resources handling to easily load and access resources like textures, font, sounds, ...
- Asynchronous job utilities
- Support for Windows, Linux, macOS, Android and WebAssembly
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ pico-args = "0.5"
pretty_env_logger = "0.5"
proc-macro-crate = "3.0"
proc-macro2 = "1.0"
rapier2d = "0.18"
quote = "1.0"
syn = { version = "2.0", features = ["full"] }
wasm-bindgen-test = "0.3"
Expand All @@ -34,6 +35,7 @@ modor = { version = "0.1.0", path = "crates/modor" }
modor_derive = { version = "0.1.0", path = "crates/modor_derive" }
modor_internal = { version = "0.1.0", path = "crates/modor_internal" }
modor_math = { version = "0.1.0", path = "crates/modor_math" }
modor_physics = { version = "0.1.0", path = "crates/modor_physics" }

[workspace.lints.rust]
anonymous_parameters = "warn"
Expand Down
1 change: 1 addition & 0 deletions PUBLISHED-CRATES
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ modor_derive
modor_internal
modor
modor_math
modor_physics
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,11 @@ For example: `cargo run --example rendering_2d --release`

## Behind the scene

Here are the main libaries used behind the modules of modor:
Here are the main libraries used to implement Modor:

- Graphics module is backed by [winit](https://github.com/rust-windowing/winit)
- Graphics crate is backed by [winit](https://github.com/rust-windowing/winit)
and [wgpu](https://wgpu.rs/).
- Physics module is backed by [rapier](https://rapier.rs/).
- Physics crate is backed by [rapier](https://rapier.rs/).

## License

Expand Down
2 changes: 1 addition & 1 deletion crates/modor/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
name = "modor"
description = "Core library of Modor game engine"
readme = "../../README.md"
keywords = ["game", "engine", "modular", "object", "framework"]
keywords = ["game", "engine", "modular", "node", "object"]
categories = ["game-engines"]
exclude = [".github", "README.md"]
version.workspace = true
Expand Down
89 changes: 50 additions & 39 deletions crates/modor/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ use fxhash::FxHashMap;
use log::{debug, Level};
use std::any;
use std::any::{Any, TypeId};
use std::cell::{RefCell, RefMut};
use std::rc::Rc;

/// The entrypoint of the engine.
///
Expand All @@ -13,7 +11,8 @@ use std::rc::Rc;
/// See [`modor`](crate).
#[derive(Debug)]
pub struct App {
roots: FxHashMap<TypeId, RootNodeData>,
root_indexes: FxHashMap<TypeId, usize>,
roots: Vec<RootNodeData>, // ensures deterministic update order
}

impl App {
Expand All @@ -33,7 +32,8 @@ impl App {
platform::init_logging(log_level);
debug!("initialize app...");
let mut app = Self {
roots: FxHashMap::default(),
root_indexes: FxHashMap::default(),
roots: vec![],
};
app.root::<T>();
debug!("app initialized");
Expand All @@ -44,64 +44,72 @@ impl App {
///
/// [`Node::update`] method is called for each registered root node.
///
/// Note that update order is predictable inside a root node, but it is not between root nodes.
#[allow(clippy::needless_collect)]
/// Root nodes are updated in the order in which they are created.
pub fn update(&mut self) {
debug!("run update app...");
let roots = self.roots.values().cloned().collect::<Vec<_>>();
let mut ctx = Context { app: self };
for root in roots {
(root.update_fn)(root.value, &mut ctx);
for root_index in 0..self.roots.len() {
let root = &mut self.roots[root_index];
let mut value = root
.value
.take()
.expect("internal error: root node already borrowed");
let update_fn = root.update_fn;
update_fn(&mut *value, &mut self.ctx());
self.roots[root_index].value = Some(value);
}
debug!("app updated");
}

/// Returns an update context.
///
/// This method is generally used for testing purpose.
pub fn ctx(&mut self) -> Context<'_> {
Context { app: self }
}

/// Returns a mutable reference to a root node.
///
/// The root node is created using [`RootNode::on_create`] if it doesn't exist.
pub fn root<T>(&mut self) -> RefMut<'_, T>
pub fn root<T>(&mut self) -> &mut T
where
T: RootNode,
{
let type_id = TypeId::of::<T>();
let root = if self.roots.contains_key(&type_id) {
let root = if self.root_indexes.contains_key(&type_id) {
self.retrieve_root::<T>(type_id)
} else {
self.create_root::<T>(type_id)
};
RefMut::map(root, Self::downcast_root)
root.downcast_mut::<T>()
.expect("internal error: misconfigured root node")
}

fn create_root<T>(&mut self, type_id: TypeId) -> RefMut<'_, dyn Any>
fn create_root<T>(&mut self, type_id: TypeId) -> &mut dyn Any
where
T: RootNode,
{
let mut ctx = Context { app: self };
debug!("create root node `{}`...", any::type_name::<T>());
let root = RootNodeData::new(T::on_create(&mut ctx));
let root = RootNodeData::new(T::on_create(&mut self.ctx()));
debug!("root node `{}` created", any::type_name::<T>());
self.roots.entry(type_id).or_insert(root).value.borrow_mut()
}

fn retrieve_root<T>(&mut self, type_id: TypeId) -> RefMut<'_, dyn Any> {
self.roots
.get_mut(&type_id)
.expect("internal error: missing root node")
let index = self.roots.len();
self.root_indexes.insert(type_id, index);
self.roots.push(root);
&mut **self.roots[index]
.value
.try_borrow_mut()
.unwrap_or_else(|_| panic!("root node `{}` already borrowed", any::type_name::<T>()))
.as_mut()
.expect("internal error: root node already borrowed")
}

fn downcast_root<T>(value: &mut dyn Any) -> &mut T
where
T: Any,
{
value
.downcast_mut::<T>()
.expect("internal error: misconfigured root node")
fn retrieve_root<T>(&mut self, type_id: TypeId) -> &mut dyn Any {
&mut **self.roots[self.root_indexes[&type_id]]
.value
.as_mut()
.unwrap_or_else(|| panic!("root node `{}` already borrowed", any::type_name::<T>()))
}
}

// If `App` was directly accessible during update, it would be possible to run `App::update`.
// As this is not wanted, `App` is wrapped in `Context` to limit the allowed operations.
/// The context accessible during node update.
#[derive(Debug)]
pub struct Context<'a> {
Expand All @@ -112,18 +120,22 @@ impl Context<'_> {
/// Returns a mutable reference to a root node.
///
/// The root node is created using [`RootNode::on_create`] if it doesn't exist.
pub fn root<T>(&mut self) -> RefMut<'_, T>
///
/// # Panics
///
/// This will panic if root node `T` is currently updated.
pub fn root<T>(&mut self) -> &mut T
where
T: RootNode,
{
self.app.root()
}
}

#[derive(Clone, Debug)]
#[derive(Debug)]
struct RootNodeData {
value: Rc<RefCell<dyn Any>>,
update_fn: fn(Rc<RefCell<dyn Any>>, &mut Context<'_>),
value: Option<Box<dyn Any>>,
update_fn: fn(&mut dyn Any, &mut Context<'_>),
}

impl RootNodeData {
Expand All @@ -132,18 +144,17 @@ impl RootNodeData {
T: RootNode,
{
Self {
value: Rc::new(RefCell::new(value)),
value: Some(Box::new(value)),
update_fn: Self::update_root::<T>,
}
}

fn update_root<T>(value: Rc<RefCell<dyn Any>>, ctx: &mut Context<'_>)
fn update_root<T>(value: &mut dyn Any, ctx: &mut Context<'_>)
where
T: RootNode,
{
Node::update(
value
.borrow_mut()
.downcast_mut::<T>()
.expect("internal error: misconfigured root node"),
ctx,
Expand Down
21 changes: 19 additions & 2 deletions crates/modor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ pub use modor_derive::test;
///
/// ```rust
/// # use modor::*;
///
/// #
/// #[derive(Default, RootNode, Node, Visit)]
/// struct Root {
/// #[modor(skip)]
Expand All @@ -154,7 +154,24 @@ pub use modor_derive::RootNode;
/// See [`RootNode`](macro@crate::RootNode).
pub use modor_derive::Node;

/// Implements [`Visit`].
/// Implements [`Visit`] in case there is no inner node.
///
/// This macro can be used instead of [`Visit`](macro@crate::Visit) when there is no inner node.
/// This avoids unnecessary usage of `#[modor(skip)]` in case all fields should be skipped.
///
/// # Examples
///
/// ```rust
/// # use modor::*;
/// #
/// #[derive(Default, RootNode, Node, NoVisit)]
/// struct Root {
/// value: u32,
/// }
/// ```
pub use modor_derive::NoVisit;

/// Implements [`Visit`] so that inner node are visited.
///
/// `#[modor(skip)]` can be added on a field to skip its automatic update, for example because:
/// - the field type doesn't implement [`Node`].
Expand Down
4 changes: 4 additions & 0 deletions crates/modor/src/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ use std::ops::DerefMut;
///
/// See [`modor`](crate).
pub trait RootNode: 'static + Node {
/// Creates the root node.
///
/// Note that this method shouldn't be called manually to create the node.
fn on_create(ctx: &mut Context<'_>) -> Self;
}

Expand All @@ -36,6 +38,8 @@ pub trait Node: Visit {

/// Runs node update.
///
/// This method should be automatically called by [`Visit::visit`].
///
/// By default, the following methods are executed in order:
/// - [`Node::on_enter`]
/// - [`Visit::visit`]
Expand Down
9 changes: 8 additions & 1 deletion crates/modor_derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,14 @@ pub fn node_derive(item: TokenStream) -> TokenStream {
#[proc_macro_derive(Visit, attributes(modor))]
pub fn visit_derive(item: TokenStream) -> TokenStream {
let input = parse_macro_input!(item as DeriveInput);
visit::impl_block(&input)
visit::impl_block_with_visit(&input)
.unwrap_or_else(syn::Error::into_compile_error)
.into()
}

#[allow(missing_docs)] // doc available in `modor` crate
#[proc_macro_derive(NoVisit)]
pub fn no_visit_derive(item: TokenStream) -> TokenStream {
let input = parse_macro_input!(item as DeriveInput);
visit::impl_block_without_visit(&input).into()
}
16 changes: 15 additions & 1 deletion crates/modor_derive/src/visit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,20 @@ use quote::{quote, quote_spanned, ToTokens};
use syn::spanned::Spanned;
use syn::{DeriveInput, Index, Type};

pub(crate) fn impl_block(input: &DeriveInput) -> syn::Result<TokenStream> {
pub(crate) fn impl_block_without_visit(input: &DeriveInput) -> TokenStream {
let crate_ident = utils::crate_ident();
let ident = &input.ident;
let (impl_generics, type_generics, where_clause) = input.generics.split_for_impl();
quote! {
#[automatically_derived]
impl #impl_generics ::#crate_ident::Visit for #ident #type_generics #where_clause {
#[inline]
fn visit(&mut self, ctx: &mut ::#crate_ident::Context<'_>) {}
}
}
}

pub(crate) fn impl_block_with_visit(input: &DeriveInput) -> syn::Result<TokenStream> {
let crate_ident = utils::crate_ident();
let ident = &input.ident;
let (impl_generics, type_generics, where_clause) = input.generics.split_for_impl();
Expand All @@ -17,6 +30,7 @@ pub(crate) fn impl_block(input: &DeriveInput) -> syn::Result<TokenStream> {
Ok(quote! {
#[automatically_derived]
impl #impl_generics ::#crate_ident::Visit for #ident #type_generics #where_clause {
#[inline]
fn visit(&mut self, ctx: &mut ::#crate_ident::Context<'_>) {
#visit_body
}
Expand Down
3 changes: 2 additions & 1 deletion crates/modor_internal/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "modor_internal"
description = "Internal module of Modor game engine for common utils"
description = "Internal utilities for Modor game engine"
readme = "./README.md"
keywords = ["modor", "internal"]
categories = ["game-engines"]
Expand All @@ -14,6 +14,7 @@ rust-version.workspace = true

[dependencies]
approx.workspace = true
log.workspace = true

[lints]
workspace = true
Loading
Loading