summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ecs-macros/src/lib.rs72
-rw-r--r--ecs/src/actions.rs4
-rw-r--r--ecs/src/component.rs170
-rw-r--r--ecs/src/component/storage/archetype.rs1
-rw-r--r--ecs/src/entity.rs35
-rw-r--r--ecs/src/event/component.rs6
-rw-r--r--ecs/src/lib.rs140
-rw-r--r--ecs/src/pair.rs2
-rw-r--r--ecs/src/query.rs49
-rw-r--r--ecs/src/query/flexible.rs4
-rw-r--r--ecs/src/query/term.rs23
-rw-r--r--ecs/src/util/array_vec.rs14
-rw-r--r--engine/Cargo.toml3
-rw-r--r--engine/src/asset.rs777
-rw-r--r--engine/src/camera/fly.rs19
-rw-r--r--engine/src/data_types/dimens.rs40
-rw-r--r--engine/src/draw_flags.rs2
-rw-r--r--engine/src/file_format/wavefront/mtl.rs200
-rw-r--r--engine/src/image.rs184
-rw-r--r--engine/src/lib.rs14
-rw-r--r--engine/src/lighting.rs7
-rw-r--r--engine/src/material.rs117
-rw-r--r--engine/src/mesh.rs6
-rw-r--r--engine/src/mesh/cube.rs15
-rw-r--r--engine/src/model.rs176
-rw-r--r--engine/src/opengl/shader.rs101
-rw-r--r--engine/src/opengl/texture.rs97
-rw-r--r--engine/src/projection.rs2
-rw-r--r--engine/src/renderer/opengl.rs277
-rw-r--r--engine/src/texture.rs220
-rw-r--r--engine/src/transform.rs6
-rw-r--r--engine/src/util.rs38
-rw-r--r--engine/src/work_queue.rs44
-rw-r--r--src/main.rs97
34 files changed, 2040 insertions, 922 deletions
diff --git a/ecs-macros/src/lib.rs b/ecs-macros/src/lib.rs
index aab8dd2..7d00736 100644
--- a/ecs-macros/src/lib.rs
+++ b/ecs-macros/src/lib.rs
@@ -14,7 +14,6 @@ use syn::{
ItemStruct,
ItemUnion,
Path,
- Type,
};
use toml::value::{Table as TomlTable, Value as TomlValue};
@@ -49,15 +48,11 @@ macro_rules! syn_path_segment {
/// - Not attributed to a type item
/// - The attributed-to type item is generic
/// - If parsing the user crate's `Cargo.toml` file fails.
-#[proc_macro_derive(Component, attributes(component))]
+#[proc_macro_derive(Component)]
pub fn component_derive(input: TokenStream) -> TokenStream
{
let item: TypeItem = parse::<Item>(input).unwrap().try_into().unwrap();
- let ComponentAttribute { handle_type, handle_mut_type } = item
- .attribute::<ComponentAttribute>("component")
- .unwrap_or_default();
-
let item_ident = item.ident();
let (impl_generics, type_generics, where_clause) = item.generics().split_for_impl();
@@ -88,10 +83,6 @@ pub fn component_derive(input: TokenStream) -> TokenStream
use ::std::sync::{LazyLock, Mutex};
use #ecs_path::component::Component;
- use #ecs_path::component::{
- Handle as ComponentHandle,
- HandleMut as ComponentHandleMut
- };
use #ecs_path::uid::{Uid, Kind as UidKind};
use #ecs_path::system::Input as SystemInput;
@@ -102,9 +93,6 @@ pub fn component_derive(input: TokenStream) -> TokenStream
impl #impl_generics Component for #item_ident #type_generics
#where_clause
{
- type HandleMut<'component> = #handle_mut_type;
- type Handle<'component> = #handle_type;
-
fn id() -> Uid
{
*#id_var_ident
@@ -326,61 +314,3 @@ fn find_engine_ecs_crate_path() -> Option<Path>
None
})
}
-
-#[derive(Debug)]
-struct ComponentAttribute
-{
- handle_type: proc_macro2::TokenStream,
- handle_mut_type: proc_macro2::TokenStream,
-}
-
-impl FromAttribute for ComponentAttribute
-{
- fn from_attribute(attribute: &Attribute) -> Result<Self, syn::Error>
- {
- let mut handle_type: Option<Type> = None;
- let mut handle_mut_type: Option<Type> = None;
-
- attribute.parse_nested_meta(|meta| {
- let Some(flag) = meta.path.get_ident() else {
- return Err(meta.error("Not a single identifier"));
- };
-
- if flag == "handle_type" {
- let value = meta.value()?;
-
- handle_type = Some(value.parse::<Type>()?);
-
- return Ok(());
- } else if flag == "handle_mut_type" {
- let value = meta.value()?;
-
- handle_mut_type = Some(value.parse::<Type>()?);
-
- return Ok(());
- }
-
- Err(meta.error("Unrecognized token"))
- })?;
-
- Ok(Self {
- handle_type: handle_type
- .map_or_else(|| Self::default().handle_type, ToTokens::into_token_stream),
- handle_mut_type: handle_mut_type.map_or_else(
- || Self::default().handle_mut_type,
- ToTokens::into_token_stream,
- ),
- })
- }
-}
-
-impl Default for ComponentAttribute
-{
- fn default() -> Self
- {
- Self {
- handle_type: quote! { ComponentHandle<'component, Self> },
- handle_mut_type: quote! { ComponentHandleMut<'component, Self> },
- }
- }
-}
diff --git a/ecs/src/actions.rs b/ecs/src/actions.rs
index e0efeda..3dd8755 100644
--- a/ecs/src/actions.rs
+++ b/ecs/src/actions.rs
@@ -23,7 +23,7 @@ impl<'world> Actions<'world>
.push(Action::Spawn(components.into_parts_array().into()));
}
- /// Queues up despawning a entity at the end of the current tick.
+ /// Queues up despawning a entity at the end of the **next** tick.
pub fn despawn(&mut self, entity_uid: Uid)
{
debug_assert_eq!(entity_uid.kind(), UidKind::Entity);
@@ -48,7 +48,7 @@ impl<'world> Actions<'world>
));
}
- /// Queues up removing component(s) from a entity at the end of the current tick.
+ /// Queues up removing component(s) from a entity at the end of the **next** tick.
pub fn remove_components(
&mut self,
entity_uid: Uid,
diff --git a/ecs/src/component.rs b/ecs/src/component.rs
index a0ed752..5a8cd0b 100644
--- a/ecs/src/component.rs
+++ b/ecs/src/component.rs
@@ -1,8 +1,9 @@
use std::any::{type_name, Any};
-use std::error::Error;
use std::fmt::Debug;
use std::ops::{Deref, DerefMut};
+use ecs_macros::Component;
+use hashbrown::HashSet;
use seq_macro::seq;
use crate::lock::{
@@ -15,7 +16,7 @@ use crate::lock::{
use crate::system::Input as SystemInput;
use crate::uid::Uid;
use crate::util::Array;
-use crate::{EntityComponentRef, World};
+use crate::EntityComponentRef;
pub mod local;
@@ -23,14 +24,6 @@ pub(crate) mod storage;
pub trait Component: SystemInput + Any
{
- type HandleMut<'component>: HandleFromEntityComponentRef<'component>
- where
- Self: Sized;
-
- type Handle<'component>: HandleFromEntityComponentRef<'component>
- where
- Self: Sized;
-
/// Returns the ID of this component.
fn id() -> Uid
where
@@ -77,46 +70,31 @@ pub trait Sequence
fn into_parts_array(self) -> Self::PartsArray;
}
-/// [`Component`] metadata.
-#[derive(Debug, Clone)]
-#[non_exhaustive]
-pub struct Metadata
-{
- pub id: Uid,
-}
-
-impl Metadata
+#[derive(Debug)]
+pub struct Handle<'a, ComponentData: 'static>
{
- #[must_use]
- pub fn of<ComponentT: Component>() -> Self
- {
- Self { id: ComponentT::id() }
- }
+ inner: MappedReadGuard<'a, ComponentData>,
}
-pub trait HandleFromEntityComponentRef<'comp>: Sized
+impl<'comp, ComponentData: 'static> Handle<'comp, ComponentData>
{
- type Error: Error;
-
/// Creates a new handle instance from a [`EntityComponentRef`].
///
/// # Errors
- /// See the implementation's [`Self::Error`] type.
- fn from_entity_component_ref(
- entity_component_ref: Option<EntityComponentRef<'comp>>,
- world: &'comp World,
- ) -> Result<Self, Self::Error>;
-}
-
-#[derive(Debug)]
-pub struct Handle<'a, ComponentData: 'static>
-{
- inner: MappedReadGuard<'a, ComponentData>,
-}
+ /// Will return `Err` if acquiring the component's lock fails.
+ pub fn from_entity_component_ref(
+ entity_component_ref: EntityComponentRef<'comp>,
+ ) -> Result<Self, HandleError>
+ {
+ Ok(Self::new(
+ entity_component_ref
+ .component()
+ .read_nonblock()
+ .map_err(AcquireLockError)?,
+ ))
+ }
-impl<'a, ComponentData: 'static> Handle<'a, ComponentData>
-{
- pub(crate) fn new(inner: ReadGuard<'a, Box<dyn Any>>) -> Self
+ pub(crate) fn new(inner: ReadGuard<'comp, Box<dyn Any>>) -> Self
{
Self {
inner: inner.map(|component| {
@@ -133,28 +111,6 @@ impl<'a, ComponentData: 'static> Handle<'a, ComponentData>
}
}
-impl<'comp, ComponentData: 'static> HandleFromEntityComponentRef<'comp>
- for Handle<'comp, ComponentData>
-{
- type Error = HandleError;
-
- fn from_entity_component_ref(
- entity_component_ref: Option<EntityComponentRef<'comp>>,
- _world: &'comp World,
- ) -> Result<Self, Self::Error>
- {
- let entity_comp =
- entity_component_ref.ok_or(HandleError::ComponentDoesNotExist)?;
-
- Ok(Self::new(
- entity_comp
- .component()
- .read_nonblock()
- .map_err(AcquireComponentLockFailed)?,
- ))
- }
-}
-
impl<ComponentData: 'static> Deref for Handle<'_, ComponentData>
{
type Target = ComponentData;
@@ -171,9 +127,25 @@ pub struct HandleMut<'a, ComponentData: 'static>
inner: MappedWriteGuard<'a, ComponentData>,
}
-impl<'a, ComponentData: 'static> HandleMut<'a, ComponentData>
+impl<'comp, ComponentData: 'static> HandleMut<'comp, ComponentData>
{
- pub(crate) fn new(inner: WriteGuard<'a, Box<dyn Any>>) -> Self
+ /// Creates a new handle instance from a [`EntityComponentRef`].
+ ///
+ /// # Errors
+ /// Will return `Err` if acquiring the component's lock fails.
+ pub fn from_entity_component_ref(
+ entity_component_ref: EntityComponentRef<'comp>,
+ ) -> Result<Self, HandleError>
+ {
+ Ok(Self::new(
+ entity_component_ref
+ .component()
+ .write_nonblock()
+ .map_err(AcquireLockError)?,
+ ))
+ }
+
+ pub(crate) fn new(inner: WriteGuard<'comp, Box<dyn Any>>) -> Self
{
Self {
inner: inner.map(|component| {
@@ -190,28 +162,6 @@ impl<'a, ComponentData: 'static> HandleMut<'a, ComponentData>
}
}
-impl<'comp, ComponentData: 'static> HandleFromEntityComponentRef<'comp>
- for HandleMut<'comp, ComponentData>
-{
- type Error = HandleError;
-
- fn from_entity_component_ref(
- entity_component_ref: Option<EntityComponentRef<'comp>>,
- _world: &'comp World,
- ) -> Result<Self, Self::Error>
- {
- let entity_comp =
- entity_component_ref.ok_or(HandleError::ComponentDoesNotExist)?;
-
- Ok(Self::new(
- entity_comp
- .component()
- .write_nonblock()
- .map_err(AcquireComponentLockFailed)?,
- ))
- }
-}
-
impl<ComponentData: 'static> Deref for HandleMut<'_, ComponentData>
{
type Target = ComponentData;
@@ -234,15 +184,12 @@ impl<ComponentData: 'static> DerefMut for HandleMut<'_, ComponentData>
pub enum HandleError
{
#[error(transparent)]
- AcquireComponentLockFailed(#[from] AcquireComponentLockFailed),
-
- #[error("Component does not exist")]
- ComponentDoesNotExist,
+ AcquireLockFailed(#[from] AcquireLockError),
}
#[derive(Debug, thiserror::Error)]
-#[error(transparent)]
-pub struct AcquireComponentLockFailed(LockError);
+#[error("Failed to acquire component lock")]
+pub struct AcquireLockError(#[source] LockError);
macro_rules! inner {
($c: tt) => {
@@ -366,3 +313,38 @@ impl Default for PartsBuilder
Self { name: "(unspecified)" }
}
}
+
+/// Pending component removals for a entity.
+#[derive(Debug, Clone, Component)]
+pub struct Removals
+{
+ component_ids: HashSet<Uid>,
+}
+
+impl Removals
+{
+ pub fn contains<ComponentT: Component>(&self) -> bool
+ {
+ self.contains_id(ComponentT::id())
+ }
+
+ pub fn contains_id(&self, component_id: Uid) -> bool
+ {
+ self.component_ids.contains(&component_id)
+ }
+
+ pub(crate) fn add_ids(&mut self, ids: impl IntoIterator<Item = Uid>)
+ {
+ self.component_ids.extend(ids)
+ }
+}
+
+impl FromIterator<Uid> for Removals
+{
+ fn from_iter<T: IntoIterator<Item = Uid>>(iter: T) -> Self
+ {
+ Self {
+ component_ids: iter.into_iter().collect(),
+ }
+ }
+}
diff --git a/ecs/src/component/storage/archetype.rs b/ecs/src/component/storage/archetype.rs
index a88e0e8..bb29701 100644
--- a/ecs/src/component/storage/archetype.rs
+++ b/ecs/src/component/storage/archetype.rs
@@ -332,6 +332,7 @@ impl EntityComponent
}
}
+ #[allow(dead_code)]
pub fn id(&self) -> Uid
{
self.id
diff --git a/ecs/src/entity.rs b/ecs/src/entity.rs
index 562f7ea..bab3d61 100644
--- a/ecs/src/entity.rs
+++ b/ecs/src/entity.rs
@@ -10,7 +10,6 @@ use crate::component::storage::archetype::{
use crate::component::{
Component,
Handle as ComponentHandle,
- HandleFromEntityComponentRef,
HandleMut as ComponentHandleMut,
};
use crate::uid::{Kind as UidKind, Uid};
@@ -20,7 +19,6 @@ use crate::{EntityComponentRef, World};
#[derive(Debug)]
pub struct Handle<'a>
{
- world: &'a World,
archetype: &'a Archetype,
entity: &'a ArchetypeEntity,
}
@@ -50,13 +48,12 @@ impl<'a> Handle<'a>
let component = self.get_matching_components(ComponentT::id()).next()?;
Some(
- ComponentHandle::from_entity_component_ref(Some(component), self.world)
- .unwrap_or_else(|err| {
- panic!(
- "Taking component {} lock failed: {err}",
- type_name::<ComponentT>()
- );
- }),
+ ComponentHandle::from_entity_component_ref(component).unwrap_or_else(|err| {
+ panic!(
+ "Taking component {} lock failed: {err}",
+ type_name::<ComponentT>()
+ );
+ }),
)
}
@@ -77,13 +74,14 @@ impl<'a> Handle<'a>
let component = self.get_matching_components(ComponentT::id()).next()?;
Some(
- ComponentHandleMut::from_entity_component_ref(Some(component), self.world)
- .unwrap_or_else(|err| {
+ ComponentHandleMut::from_entity_component_ref(component).unwrap_or_else(
+ |err| {
panic!(
"Taking component {} lock failed: {err}",
type_name::<ComponentT>()
);
- }),
+ },
+ ),
)
}
@@ -98,13 +96,14 @@ impl<'a> Handle<'a>
}
}
- pub(crate) fn new(
- world: &'a World,
- archetype: &'a Archetype,
- entity: &'a ArchetypeEntity,
- ) -> Self
+ pub fn component_ids(&self) -> impl Iterator<Item = Uid> + '_
+ {
+ self.archetype.component_ids_sorted()
+ }
+
+ pub(crate) fn new(archetype: &'a Archetype, entity: &'a ArchetypeEntity) -> Self
{
- Self { world, archetype, entity }
+ Self { archetype, entity }
}
}
diff --git a/ecs/src/event/component.rs b/ecs/src/event/component.rs
index ef09480..72a78a3 100644
--- a/ecs/src/event/component.rs
+++ b/ecs/src/event/component.rs
@@ -10,9 +10,3 @@ use crate::Component;
/// b) The target component is added to a entity.
#[derive(Debug, Component)]
pub struct Added(Infallible);
-
-/// Pair relation for events emitted when:
-/// a) The target component is removed from a entity.
-/// b) A entity with the target component is despawned.
-#[derive(Debug, Component)]
-pub struct Removed(Infallible);
diff --git a/ecs/src/lib.rs b/ecs/src/lib.rs
index 53abc6b..07b1cba 100644
--- a/ecs/src/lib.rs
+++ b/ecs/src/lib.rs
@@ -15,14 +15,13 @@ use crate::component::storage::archetype::EntityComponent as ArchetypeEntityComp
use crate::component::storage::Storage as ComponentStorage;
use crate::component::{
Component,
+ IntoParts,
Parts as ComponentParts,
+ Removals as ComponentRemovals,
Sequence as ComponentSequence,
};
-use crate::entity::CREATE_STATIC_ENTITIES;
-use crate::event::component::{
- Added as ComponentAddedEvent,
- Removed as ComponentRemovedEvent,
-};
+use crate::entity::{Handle as EntityHandle, CREATE_STATIC_ENTITIES};
+use crate::event::component::Added as ComponentAddedEvent;
use crate::extension::{Collector as ExtensionCollector, Extension};
use crate::lock::Lock;
use crate::pair::{ChildOf, DependsOn, Pair};
@@ -209,8 +208,12 @@ impl World
self.data.component_storage.create_imaginary_archetypes();
+ let prev_pending_removals = std::mem::take(&mut self.data.pending_removals);
+
self.perform_queued_actions();
+ self.perform_removals(prev_pending_removals);
+
if self.stop.load(Ordering::Relaxed) {
return StepResult::Stop;
}
@@ -355,6 +358,8 @@ impl World
let mut has_swapped_active_queue = false;
+ // TODO: Figure out a good way to handle situations where there are multiple
+ // AddComponents/RemoveComponents actions that affect the same entity.
for action in active_action_queue.drain(..) {
match action {
Action::Spawn(components) => {
@@ -382,24 +387,12 @@ impl World
}
}
Action::Despawn(entity_uid) => {
- let removed_entity =
- match self.data.component_storage.remove_entity(entity_uid) {
- Ok(components) => components,
- Err(err) => {
- tracing::error!("Failed to despawn entity: {err}");
- return;
- }
- };
-
- if !has_swapped_active_queue {
- self.swap_event_queue(&mut has_swapped_active_queue);
- }
-
- for removed_ent_comp in removed_entity.components() {
- self.emit_event_by_id::<ComponentRemovedEvent>(
- removed_ent_comp.id(),
- );
- }
+ Self::schedule_removal(
+ &mut self.data.component_storage,
+ &mut self.data.pending_removals,
+ entity_uid,
+ PendingRemoval::Entity,
+ );
}
Action::AddComponents(entity_uid, components) => {
let added_component_ids = Self::add_entity_components(
@@ -417,19 +410,12 @@ impl World
}
}
Action::RemoveComponents(entity_uid, component_ids) => {
- let removed_component_ids = Self::remove_entity_components(
- entity_uid,
- component_ids,
+ Self::schedule_removal(
&mut self.data.component_storage,
+ &mut self.data.pending_removals,
+ entity_uid,
+ PendingRemoval::Components(component_ids),
);
-
- if !has_swapped_active_queue {
- self.swap_event_queue(&mut has_swapped_active_queue);
- }
-
- for comp_id in removed_component_ids {
- self.emit_event_by_id::<ComponentRemovedEvent>(comp_id);
- }
}
Action::Stop => {
self.stop.store(true, Ordering::Relaxed);
@@ -438,6 +424,66 @@ impl World
}
}
+ fn perform_removals(&mut self, removals: Vec<(Uid, PendingRemoval)>)
+ {
+ for (entity_id, removal) in removals {
+ match removal {
+ PendingRemoval::Components(component_ids) => {
+ Self::remove_entity_components(
+ entity_id,
+ component_ids.into_iter().chain([ComponentRemovals::id()]),
+ &mut self.data.component_storage,
+ );
+ }
+ PendingRemoval::Entity => {
+ if let Err(err) = self.data.component_storage.remove_entity(entity_id)
+ {
+ tracing::error!("Failed to remove entity {entity_id}: {err}");
+ }
+ }
+ }
+ }
+ }
+
+ #[tracing::instrument(skip(component_storage, pending_removals))]
+ fn schedule_removal(
+ component_storage: &mut ComponentStorage,
+ pending_removals: &mut Vec<(Uid, PendingRemoval)>,
+ entity_uid: Uid,
+ removal: PendingRemoval,
+ )
+ {
+ let Some(ent_handle) = Self::get_entity(component_storage, entity_uid) else {
+ tracing::warn!("Cannot schedule removal. Entity does not exist");
+ return;
+ };
+
+ let component_ids = match removal {
+ PendingRemoval::Components(ref component_ids) => component_ids,
+ PendingRemoval::Entity => &ent_handle.component_ids().collect::<Vec<_>>(),
+ };
+
+ let Some(mut component_removals) = ent_handle.get_mut::<ComponentRemovals>()
+ else {
+ Self::add_entity_components(
+ entity_uid,
+ [ComponentRemovals::from_iter(component_ids.iter().copied())
+ .into_parts()],
+ component_storage,
+ );
+
+ pending_removals.push((entity_uid, removal));
+
+ return;
+ };
+
+ component_removals.add_ids(component_ids.iter().copied());
+
+ drop(component_removals);
+
+ pending_removals.push((entity_uid, removal));
+ }
+
fn add_entity_components(
entity_uid: Uid,
components: impl IntoIterator<Item = ComponentParts>,
@@ -521,6 +567,21 @@ impl World
*has_swapped_active_queue = true;
}
+
+ fn get_entity(
+ component_storage: &mut ComponentStorage,
+ entity_uid: Uid,
+ ) -> Option<EntityHandle<'_>>
+ {
+ let archetype = component_storage.get_entity_archetype(entity_uid)?;
+
+ Some(EntityHandle::new(
+ archetype,
+ archetype
+ .get_entity_by_id(entity_uid)
+ .expect("Not possible"),
+ ))
+ }
}
impl Default for World
@@ -542,11 +603,12 @@ pub enum StepResult
}
#[derive(Debug)]
-pub struct WorldData
+struct WorldData
{
component_storage: ComponentStorage,
sole_storage: SoleStorage,
action_queue: Rc<ActionQueue>,
+ pending_removals: Vec<(Uid, PendingRemoval)>,
}
impl Default for WorldData
@@ -557,11 +619,19 @@ impl Default for WorldData
component_storage: ComponentStorage::default(),
sole_storage: SoleStorage::default(),
action_queue: Rc::new(ActionQueue::default()),
+ pending_removals: Vec::new(),
}
}
}
#[derive(Debug)]
+enum PendingRemoval
+{
+ Components(Vec<Uid>),
+ Entity,
+}
+
+#[derive(Debug)]
pub struct EntityComponentRef<'a>
{
component_id: Uid,
diff --git a/ecs/src/pair.rs b/ecs/src/pair.rs
index 2055d5e..4ff4995 100644
--- a/ecs/src/pair.rs
+++ b/ecs/src/pair.rs
@@ -140,7 +140,7 @@ impl Handle<'_>
unreachable!();
};
- Some(EntityHandle::new(self.world, archetype, archetype_entity))
+ Some(EntityHandle::new(archetype, archetype_entity))
}
}
diff --git a/ecs/src/query.rs b/ecs/src/query.rs
index 7e10c5b..ccb7add 100644
--- a/ecs/src/query.rs
+++ b/ecs/src/query.rs
@@ -6,7 +6,6 @@ use seq_macro::seq;
use crate::component::{
Component,
Handle as ComponentHandle,
- HandleFromEntityComponentRef,
HandleMut as ComponentHandleMut,
};
use crate::entity::Handle as EntityHandle;
@@ -342,18 +341,26 @@ impl<ComponentT: Component> TermWithField for &ComponentT
fn get_field<'world>(
entity_handle: &EntityHandle<'world>,
- world: &'world World,
+ _world: &'world World,
) -> Self::Field<'world>
{
assert_eq!(ComponentT::id().kind(), UidKind::Component);
- Self::Field::from_entity_component_ref(
- entity_handle
- .get_matching_components(ComponentT::id())
- .next(),
- world,
- )
- .unwrap_or_else(|err| {
+ let Some(component) = entity_handle
+ .get_matching_components(ComponentT::id())
+ .next()
+ else {
+ panic!(
+ concat!(
+ "Component {} was not found in entity {}. There ",
+ "is most likely a bug in the entity querying"
+ ),
+ type_name::<ComponentT>(),
+ entity_handle.uid()
+ );
+ };
+
+ Self::Field::from_entity_component_ref(component).unwrap_or_else(|err| {
panic!(
"Creating handle to component {} failed: {err}",
type_name::<ComponentT>()
@@ -375,18 +382,26 @@ impl<ComponentT: Component> TermWithField for &mut ComponentT
fn get_field<'world>(
entity_handle: &EntityHandle<'world>,
- world: &'world World,
+ _world: &'world World,
) -> Self::Field<'world>
{
assert_eq!(ComponentT::id().kind(), UidKind::Component);
- Self::Field::from_entity_component_ref(
- entity_handle
- .get_matching_components(ComponentT::id())
- .next(),
- world,
- )
- .unwrap_or_else(|err| {
+ let Some(component) = entity_handle
+ .get_matching_components(ComponentT::id())
+ .next()
+ else {
+ panic!(
+ concat!(
+ "Component {} was not found in entity {}. There ",
+ "is most likely a bug in the entity querying"
+ ),
+ type_name::<ComponentT>(),
+ entity_handle.uid()
+ );
+ };
+
+ Self::Field::from_entity_component_ref(component).unwrap_or_else(|err| {
panic!(
"Creating handle to component {} failed: {err}",
type_name::<ComponentT>()
diff --git a/ecs/src/query/flexible.rs b/ecs/src/query/flexible.rs
index 6d65ee0..add30b0 100644
--- a/ecs/src/query/flexible.rs
+++ b/ecs/src/query/flexible.rs
@@ -22,7 +22,6 @@ impl<'world, const MAX_TERM_CNT: usize> Query<'world, MAX_TERM_CNT>
pub fn iter(&self) -> Iter<'_>
{
Iter {
- world: self.world,
iter: self
.world
.data
@@ -59,7 +58,6 @@ impl<'query, const MAX_TERM_CNT: usize> IntoIterator for &'query Query<'_, MAX_T
pub struct Iter<'query>
{
- world: &'query World,
iter: QueryEntityIter<'query>,
}
@@ -71,7 +69,7 @@ impl<'query> Iterator for Iter<'query>
{
let (archetype, entity) = self.iter.next()?;
- Some(EntityHandle::new(self.world, archetype, entity))
+ Some(EntityHandle::new(archetype, entity))
}
}
diff --git a/ecs/src/query/term.rs b/ecs/src/query/term.rs
index 2e1ecca..9c772da 100644
--- a/ecs/src/query/term.rs
+++ b/ecs/src/query/term.rs
@@ -4,7 +4,6 @@ use std::marker::PhantomData;
use crate::component::{
Component,
Handle as ComponentHandle,
- HandleFromEntityComponentRef,
HandleMut as ComponentHandleMut,
};
use crate::query::{
@@ -65,17 +64,14 @@ impl<ComponentT: Component> TermWithField for Option<&ComponentT>
fn get_field<'world>(
entity_handle: &crate::entity::Handle<'world>,
- world: &'world crate::World,
+ _world: &'world crate::World,
) -> Self::Field<'world>
{
Some(
ComponentHandle::<'world, ComponentT>::from_entity_component_ref(
- Some(
- entity_handle
- .get_matching_components(ComponentT::id())
- .next()?,
- ),
- world,
+ entity_handle
+ .get_matching_components(ComponentT::id())
+ .next()?,
)
.unwrap_or_else(|err| {
panic!(
@@ -99,17 +95,14 @@ impl<ComponentT: Component> TermWithField for Option<&mut ComponentT>
fn get_field<'world>(
entity_handle: &crate::entity::Handle<'world>,
- world: &'world crate::World,
+ _world: &'world crate::World,
) -> Self::Field<'world>
{
Some(
ComponentHandleMut::<'world, ComponentT>::from_entity_component_ref(
- Some(
- entity_handle
- .get_matching_components(ComponentT::id())
- .next()?,
- ),
- world,
+ entity_handle
+ .get_matching_components(ComponentT::id())
+ .next()?,
)
.unwrap_or_else(|err| {
panic!(
diff --git a/ecs/src/util/array_vec.rs b/ecs/src/util/array_vec.rs
index 13a0349..a37b1f9 100644
--- a/ecs/src/util/array_vec.rs
+++ b/ecs/src/util/array_vec.rs
@@ -115,3 +115,17 @@ impl<Item, const CAPACITY: usize> Default for ArrayVec<Item, CAPACITY>
}
}
}
+
+impl<Item, const CAPACITY: usize> Drop for ArrayVec<Item, CAPACITY>
+{
+ fn drop(&mut self)
+ {
+ for item in &mut self.items[..self.len] {
+ // SAFETY: The items from index 0 to the length index will always be
+ // initialized and satisfy all the invariants of the Item type.
+ unsafe {
+ item.assume_init_drop();
+ }
+ }
+ }
+}
diff --git a/engine/Cargo.toml b/engine/Cargo.toml
index f6cd5cf..a62f458 100644
--- a/engine/Cargo.toml
+++ b/engine/Cargo.toml
@@ -14,7 +14,8 @@ paste = "1.0.14"
ecs = { path = "../ecs" }
util-macros = { path = "../util-macros" }
-[dependencies.image]
+[dependencies.image_rs]
version = "0.24.7"
default-features = false
features = ["png", "jpeg"]
+package = "image"
diff --git a/engine/src/asset.rs b/engine/src/asset.rs
new file mode 100644
index 0000000..db4d23c
--- /dev/null
+++ b/engine/src/asset.rs
@@ -0,0 +1,777 @@
+use std::any::{type_name, Any};
+use std::borrow::Cow;
+use std::cell::RefCell;
+use std::collections::HashMap;
+use std::convert::Infallible;
+use std::ffi::{OsStr, OsString};
+use std::fmt::{Debug, Display};
+use std::hash::{DefaultHasher, Hash, Hasher};
+use std::marker::PhantomData;
+use std::path::{Path, PathBuf};
+use std::sync::mpsc::{
+ channel as mpsc_channel,
+ Receiver as MpscReceiver,
+ Sender as MpscSender,
+};
+use std::sync::Arc;
+
+use ecs::phase::PRE_UPDATE as PRE_UPDATE_PHASE;
+use ecs::sole::Single;
+use ecs::Sole;
+
+use crate::work_queue::{Work, WorkQueue};
+
+/// Asset label.
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct Label<'a>
+{
+ pub path: Cow<'a, Path>,
+ pub name: Option<Cow<'a, str>>,
+}
+
+impl Label<'_>
+{
+ pub fn to_owned(&self) -> LabelOwned
+ {
+ LabelOwned {
+ path: self.path.to_path_buf(),
+ name: self.name.as_ref().map(|name| name.to_string()),
+ }
+ }
+}
+
+impl<'a> From<&'a Path> for Label<'a>
+{
+ fn from(path: &'a Path) -> Self
+ {
+ Self { path: path.into(), name: None }
+ }
+}
+
+impl From<PathBuf> for Label<'_>
+{
+ fn from(path: PathBuf) -> Self
+ {
+ Self { path: path.into(), name: None }
+ }
+}
+
+impl<'a> From<&'a LabelOwned> for Label<'a>
+{
+ fn from(label: &'a LabelOwned) -> Self
+ {
+ Self {
+ path: (&label.path).into(),
+ name: label.name.as_ref().map(|name| Cow::Borrowed(name.as_str())),
+ }
+ }
+}
+
+impl Display for Label<'_>
+{
+ fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result
+ {
+ write!(formatter, "{}", self.path.display())?;
+
+ if let Some(name) = &self.name {
+ formatter.write_str("::")?;
+ formatter.write_str(&name)?;
+ }
+
+ Ok(())
+ }
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct LabelOwned
+{
+ pub path: PathBuf,
+ pub name: Option<String>,
+}
+
+impl LabelOwned
+{
+ pub fn to_label(&self) -> Label<'_>
+ {
+ Label {
+ path: (&self.path).into(),
+ name: self.name.as_ref().map(|name| Cow::Borrowed(name.as_str())),
+ }
+ }
+}
+
+impl Display for LabelOwned
+{
+ fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result
+ {
+ write!(formatter, "{}", self.path.display())?;
+
+ if let Some(name) = &self.name {
+ formatter.write_str("::")?;
+ formatter.write_str(&name)?;
+ }
+
+ Ok(())
+ }
+}
+
+#[derive(Debug, Sole)]
+pub struct Assets
+{
+ assets: Vec<StoredAsset>,
+ asset_lookup: RefCell<HashMap<LabelHash, LookupEntry>>,
+ importers: Vec<WrappedImporterFn>,
+ importer_lookup: HashMap<OsString, usize>,
+ import_work_queue: WorkQueue<ImportWorkUserData>,
+ import_work_msg_receiver: MpscReceiver<ImportWorkMessage>,
+ import_work_msg_sender: MpscSender<ImportWorkMessage>,
+}
+
+impl Assets
+{
+ pub fn with_capacity(capacity: usize) -> Self
+ {
+ let (import_work_msg_sender, import_work_msg_receiver) =
+ mpsc_channel::<ImportWorkMessage>();
+
+ Self {
+ assets: Vec::with_capacity(capacity),
+ asset_lookup: RefCell::new(HashMap::with_capacity(capacity)),
+ importers: Vec::new(),
+ importer_lookup: HashMap::new(),
+ import_work_queue: WorkQueue::new(),
+ import_work_msg_receiver,
+ import_work_msg_sender,
+ }
+ }
+
+ pub fn set_importer<'file_ext, AssetSettings, Err>(
+ &mut self,
+ file_extensions: impl IntoIterator<Item: Into<Cow<'file_ext, str>>>,
+ func: impl Fn(&mut Submitter<'_>, &Path, Option<&AssetSettings>) -> Result<(), Err>,
+ ) where
+ AssetSettings: 'static,
+ Err: std::error::Error + 'static,
+ {
+ self.importers.push(WrappedImporterFn::new(func));
+
+ let importer_index = self.importers.len() - 1;
+
+ self.importer_lookup
+ .extend(file_extensions.into_iter().map(|file_ext| {
+ let file_ext: Cow<str> = file_ext.into();
+
+ (file_ext.into_owned().into(), importer_index)
+ }));
+ }
+
+ #[tracing::instrument(skip_all, fields(asset_type=type_name::<Asset>()))]
+ pub fn get<'this, 'handle, Asset: 'static + Send + Sync>(
+ &'this self,
+ handle: &'handle Handle<Asset>,
+ ) -> Option<&'handle Asset>
+ where
+ 'this: 'handle,
+ {
+ let LookupEntry::Occupied(asset_index) =
+ *self.asset_lookup.borrow().get(&handle.id.label_hash)?
+ else {
+ return None;
+ };
+
+ let stored_asset = self.assets.get(asset_index).expect("Not possible");
+
+ let Some(asset) = stored_asset.strong.downcast_ref::<Asset>() else {
+ tracing::error!("Wrong asset type");
+ return None;
+ };
+
+ Some(asset)
+ }
+
+ #[tracing::instrument(skip(self))]
+ pub fn load<'i, Asset: 'static + Send + Sync>(
+ &self,
+ label: impl Into<Label<'i>> + Debug,
+ ) -> Handle<Asset>
+ {
+ let label = label.into();
+
+ let label_hash = LabelHash::new(&label);
+
+ let mut asset_lookup = self.asset_lookup.borrow_mut();
+
+ if Self::is_pending(&asset_lookup, &label) {
+ return Handle::new(label_hash);
+ }
+
+ let Some(lookup_entry) = asset_lookup.get(&label_hash) else {
+ self.add_import_work::<Infallible>(
+ &label,
+ label_hash,
+ None,
+ &mut asset_lookup,
+ );
+
+ return Handle::new(label_hash);
+ };
+
+ match *lookup_entry {
+ LookupEntry::Occupied(asset_index) => {
+ let stored_asset = self.assets.get(asset_index).expect("Not possible");
+
+ if stored_asset.strong.downcast_ref::<Asset>().is_none() {
+ tracing::error!("Wrong asset type {}", type_name::<Asset>());
+ }
+ }
+ LookupEntry::Pending => {}
+ }
+
+ Handle::new(label_hash)
+ }
+
+ #[tracing::instrument(skip(self))]
+ pub fn load_with_settings<'i, Asset, AssetSettings>(
+ &self,
+ label: impl Into<Label<'i>> + Debug,
+ asset_settings: AssetSettings,
+ ) -> Handle<Asset>
+ where
+ Asset: Send + Sync + 'static,
+ AssetSettings: Send + Sync + Debug + 'static,
+ {
+ let label = label.into();
+
+ let label_hash = LabelHash::new(&label);
+
+ let mut asset_lookup = self.asset_lookup.borrow_mut();
+
+ if Self::is_pending(&asset_lookup, &label) {
+ return Handle::new(label_hash);
+ }
+
+ let Some(lookup_entry) = asset_lookup.get(&label_hash) else {
+ self.add_import_work::<AssetSettings>(
+ &label,
+ label_hash,
+ Some(asset_settings),
+ &mut asset_lookup,
+ );
+
+ return Handle::new(label_hash);
+ };
+
+ match *lookup_entry {
+ LookupEntry::Occupied(asset_index) => {
+ let stored_asset = self.assets.get(asset_index).expect("Not possible");
+
+ if stored_asset.strong.downcast_ref::<Asset>().is_none() {
+ tracing::error!(
+ "Wrong asset type {} for asset",
+ type_name::<Asset>()
+ );
+ }
+ }
+ LookupEntry::Pending => {}
+ }
+
+ Handle::new(label_hash)
+ }
+
+ pub fn store_with_name<'name, Asset: 'static + Send + Sync>(
+ &mut self,
+ name: impl Into<Cow<'name, str>>,
+ asset: Asset,
+ ) -> Handle<Asset>
+ {
+ self.store_with_label(
+ Label {
+ path: Path::new("").into(),
+ name: Some(name.into()),
+ },
+ asset,
+ )
+ }
+
+ #[tracing::instrument(skip(self, asset), fields(asset_type=type_name::<Asset>()))]
+ pub fn store_with_label<'i, Asset: 'static + Send + Sync>(
+ &mut self,
+ label: impl Into<Label<'i>> + Debug,
+ asset: Asset,
+ ) -> Handle<Asset>
+ {
+ let label = label.into();
+
+ let label_hash = LabelHash::new(&label);
+
+ if matches!(
+ self.asset_lookup.get_mut().get(&label_hash),
+ Some(LookupEntry::Occupied(_))
+ ) {
+ tracing::error!("Asset already exists");
+
+ return Handle::new(label_hash);
+ }
+
+ tracing::debug!("Storing asset");
+
+ self.assets.push(StoredAsset::new(asset));
+
+ let index = self.assets.len() - 1;
+
+ self.asset_lookup
+ .get_mut()
+ .insert(label_hash, LookupEntry::Occupied(index));
+
+ if label.name.is_some() {
+ let parent_asset_label_hash =
+ LabelHash::new(&Label { path: label.path, name: None });
+
+ if matches!(
+ self.asset_lookup.get_mut().get(&parent_asset_label_hash),
+ Some(LookupEntry::Pending)
+ ) {
+ self.asset_lookup.get_mut().remove(&parent_asset_label_hash);
+ } else if self
+ .asset_lookup
+ .get_mut()
+ .get(&parent_asset_label_hash)
+ .is_none()
+ {
+ self.assets
+ .push(StoredAsset::new::<Option<Infallible>>(None));
+
+ self.asset_lookup.get_mut().insert(
+ parent_asset_label_hash,
+ LookupEntry::Occupied(self.assets.len() - 1),
+ );
+ }
+ }
+
+ Handle::new(label_hash)
+ }
+
+ fn is_pending(asset_lookup: &HashMap<LabelHash, LookupEntry>, label: &Label) -> bool
+ {
+ if label.name.is_some() {
+ if let Some(LookupEntry::Pending) =
+ asset_lookup.get(&LabelHash::new(&Label {
+ path: label.path.as_ref().into(),
+ name: None,
+ }))
+ {
+ return true;
+ }
+ }
+
+ if let Some(LookupEntry::Pending) = asset_lookup.get(&LabelHash::new(label)) {
+ return true;
+ };
+
+ false
+ }
+
+ fn add_import_work<AssetSettings>(
+ &self,
+ label: &Label<'_>,
+ label_hash: LabelHash,
+ asset_settings: Option<AssetSettings>,
+ asset_lookup: &mut HashMap<LabelHash, LookupEntry>,
+ ) where
+ AssetSettings: Any + Send + Sync,
+ {
+ let Some(file_ext) = label.path.extension() else {
+ tracing::error!("Asset file is missing a file extension");
+ return;
+ };
+
+ let Some(importer) = self.get_importer(file_ext) else {
+ tracing::error!(
+ "No importer exists for asset file extension {}",
+ file_ext.to_string_lossy()
+ );
+ return;
+ };
+
+ self.import_work_queue.add_work(Work {
+ func: |ImportWorkUserData {
+ import_work_msg_sender,
+ asset_path,
+ asset_settings,
+ importer,
+ }| {
+ if let Err(err) = importer.call(
+ import_work_msg_sender,
+ asset_path.as_path(),
+ asset_settings.as_deref(),
+ ) {
+ tracing::error!(
+ "Failed to load asset {}: {err}",
+ asset_path.display()
+ );
+ }
+ },
+ user_data: ImportWorkUserData {
+ import_work_msg_sender: self.import_work_msg_sender.clone(),
+ asset_path: label.path.to_path_buf(),
+ asset_settings: asset_settings.map(|asset_settings| {
+ Box::new(asset_settings) as Box<dyn Any + Send + Sync>
+ }),
+ importer: importer.clone(),
+ },
+ });
+
+ asset_lookup.insert(label_hash, LookupEntry::Pending);
+
+ if label.name.is_some() {
+ asset_lookup.insert(
+ LabelHash::new(&Label {
+ path: label.path.as_ref().into(),
+ name: None,
+ }),
+ LookupEntry::Pending,
+ );
+ }
+ }
+
+ fn get_importer(&self, file_ext: &OsStr) -> Option<&WrappedImporterFn>
+ {
+ let index = *self.importer_lookup.get(file_ext)?;
+
+ Some(self.importers.get(index).expect("Not possible"))
+ }
+}
+
+impl Default for Assets
+{
+ fn default() -> Self
+ {
+ Self::with_capacity(0)
+ }
+}
+
+pub struct Submitter<'path>
+{
+ import_work_msg_sender: MpscSender<ImportWorkMessage>,
+ asset_path: &'path Path,
+}
+
+impl Submitter<'_>
+{
+ pub fn submit_load_other<'label, Asset: Send + Sync + 'static>(
+ &self,
+ label: impl Into<Label<'label>>,
+ ) -> Handle<Asset>
+ {
+ let label = label.into();
+
+ let _ = self.import_work_msg_sender.send(ImportWorkMessage::Load {
+ do_load: |assets, label, _asset_settings| {
+ let _ = assets.load::<Asset>(label);
+ },
+ label: label.to_owned(),
+ asset_settings: None,
+ });
+
+ Handle::new(LabelHash::new(&label))
+ }
+
+ pub fn submit_load_other_with_settings<'label, Asset, AssetSettings>(
+ &self,
+ label: impl Into<Label<'label>>,
+ asset_settings: AssetSettings,
+ ) -> Handle<Asset>
+ where
+ Asset: Send + Sync + 'static,
+ AssetSettings: Send + Sync + Debug + 'static,
+ {
+ let label = label.into();
+
+ let _ = self.import_work_msg_sender.send(ImportWorkMessage::Load {
+ do_load: |assets, label, asset_settings| {
+ let asset_settings = *asset_settings
+ .expect("Not possible")
+ .downcast::<AssetSettings>()
+ .expect("Not possible");
+
+ let _ = assets
+ .load_with_settings::<Asset, AssetSettings>(label, asset_settings);
+ },
+ label: label.to_owned(),
+ asset_settings: Some(Box::new(asset_settings)),
+ });
+
+ Handle::new(LabelHash::new(&label))
+ }
+
+ pub fn submit_store<Asset: Send + Sync + 'static>(
+ &self,
+ asset: Asset,
+ ) -> Handle<Asset>
+ {
+ let label = LabelOwned {
+ path: self.asset_path.into(),
+ name: None,
+ };
+
+ let label_hash = LabelHash::new(&label.to_label());
+
+ let _ = self.import_work_msg_sender.send(ImportWorkMessage::Store {
+ do_store: |assets, label, boxed_asset| {
+ let Ok(asset) = boxed_asset.downcast::<Asset>() else {
+ unreachable!();
+ };
+
+ assets.store_with_label::<Asset>(&label, *asset);
+ },
+ label,
+ asset: Box::new(asset),
+ });
+
+ Handle::new(label_hash)
+ }
+
+ pub fn submit_store_named<Asset: Send + Sync + 'static>(
+ &self,
+ name: impl AsRef<str>,
+ asset: Asset,
+ ) -> Handle<Asset>
+ {
+ let label = LabelOwned {
+ path: self.asset_path.into(),
+ name: Some(name.as_ref().into()),
+ };
+
+ let label_hash = LabelHash::new(&label.to_label());
+
+ let _ = self.import_work_msg_sender.send(ImportWorkMessage::Store {
+ do_store: |assets, label, boxed_asset| {
+ let Ok(asset) = boxed_asset.downcast::<Asset>() else {
+ unreachable!();
+ };
+
+ assets.store_with_label::<Asset>(&label, *asset);
+ },
+ label,
+ asset: Box::new(asset),
+ });
+
+ Handle::new(label_hash)
+ }
+}
+
+/// Asset handle.
+#[derive(Debug)]
+pub struct Handle<Asset: 'static>
+{
+ id: Id,
+ _pd: PhantomData<Asset>,
+}
+
+impl<Asset: 'static> Handle<Asset>
+{
+ pub fn id(&self) -> Id
+ {
+ self.id
+ }
+
+ fn new(label_hash: LabelHash) -> Self
+ {
+ Self {
+ id: Id { label_hash },
+ _pd: PhantomData,
+ }
+ }
+}
+
+impl<Asset: 'static> Clone for Handle<Asset>
+{
+ fn clone(&self) -> Self
+ {
+ Self { id: self.id, _pd: PhantomData }
+ }
+}
+
+/// Asset ID.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct Id
+{
+ label_hash: LabelHash,
+}
+
+#[derive(Debug, thiserror::Error)]
+enum ImporterError
+{
+ #[error("Settings has a incorrect type")]
+ IncorrectAssetSettingsType(PathBuf),
+
+ #[error(transparent)]
+ Other(Box<dyn std::error::Error>),
+}
+
+#[derive(Debug, Clone)]
+struct WrappedImporterFn
+{
+ wrapper_func: fn(
+ MpscSender<ImportWorkMessage>,
+ &Path,
+ Option<&(dyn Any + Send + Sync)>,
+ ) -> Result<(), ImporterError>,
+}
+
+impl WrappedImporterFn
+{
+ fn new<InnerFunc, AssetSettings, Err>(inner_func_param: InnerFunc) -> Self
+ where
+ InnerFunc:
+ Fn(&mut Submitter<'_>, &Path, Option<&AssetSettings>) -> Result<(), Err>,
+ AssetSettings: 'static,
+ Err: std::error::Error + 'static,
+ {
+ assert_eq!(size_of::<InnerFunc>(), 0);
+
+ let wrapper_func =
+ |import_work_msg_sender: MpscSender<ImportWorkMessage>,
+ asset_path: &Path,
+ asset_settings: Option<&(dyn Any + Send + Sync)>| {
+ let inner_func = unsafe { std::mem::zeroed::<InnerFunc>() };
+
+ let asset_settings = asset_settings
+ .map(|asset_settings| {
+ asset_settings
+ .downcast_ref::<AssetSettings>()
+ .ok_or_else(|| {
+ ImporterError::IncorrectAssetSettingsType(
+ asset_path.to_path_buf(),
+ )
+ })
+ })
+ .transpose()?;
+
+ inner_func(
+ &mut Submitter { import_work_msg_sender, asset_path },
+ asset_path,
+ asset_settings,
+ )
+ .map_err(|err| ImporterError::Other(Box::new(err)))?;
+
+ Ok(())
+ };
+
+ std::mem::forget(inner_func_param);
+
+ Self { wrapper_func }
+ }
+
+ fn call(
+ &self,
+ import_work_msg_sender: MpscSender<ImportWorkMessage>,
+ asset_path: &Path,
+ asset_settings: Option<&(dyn Any + Send + Sync)>,
+ ) -> Result<(), ImporterError>
+ {
+ (self.wrapper_func)(import_work_msg_sender, asset_path, asset_settings)
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
+struct LabelHash(u64);
+
+impl LabelHash
+{
+ fn new(label: &Label<'_>) -> Self
+ {
+ let mut hasher = DefaultHasher::new();
+
+ label.hash(&mut hasher);
+
+ Self(hasher.finish())
+ }
+}
+
+#[derive(Debug, Default)]
+pub(crate) struct Extension
+{
+ pub assets: Assets,
+}
+
+impl ecs::extension::Extension for Extension
+{
+ fn collect(self, mut collector: ecs::extension::Collector<'_>)
+ {
+ let _ = collector.add_sole(self.assets);
+
+ collector.add_system(*PRE_UPDATE_PHASE, add_received_assets);
+ }
+}
+
+fn add_received_assets(mut assets: Single<Assets>)
+{
+ while let Some(import_work_msg) = assets.import_work_msg_receiver.try_recv().ok() {
+ match import_work_msg {
+ ImportWorkMessage::Store { do_store, label, asset } => {
+ do_store(&mut assets, label, asset);
+ }
+ ImportWorkMessage::Load { do_load, label, asset_settings } => {
+ do_load(
+ &assets,
+ Label {
+ path: label.path.as_path().into(),
+ name: label.name.as_deref().map(|name| name.into()),
+ },
+ asset_settings,
+ );
+ }
+ }
+ }
+}
+
+#[derive(Debug)]
+struct ImportWorkUserData
+{
+ import_work_msg_sender: MpscSender<ImportWorkMessage>,
+ asset_path: PathBuf,
+ asset_settings: Option<Box<dyn Any + Send + Sync>>,
+ importer: WrappedImporterFn,
+}
+
+#[derive(Debug)]
+enum ImportWorkMessage
+{
+ Store
+ {
+ do_store: fn(&mut Assets, LabelOwned, Box<dyn Any + Send + Sync>),
+ label: LabelOwned,
+ asset: Box<dyn Any + Send + Sync>,
+ },
+
+ Load
+ {
+ do_load: fn(&Assets, Label<'_>, Option<Box<dyn Any + Send + Sync>>),
+ label: LabelOwned,
+ asset_settings: Option<Box<dyn Any + Send + Sync>>,
+ },
+}
+
+#[derive(Debug, Clone, Copy)]
+enum LookupEntry
+{
+ Occupied(usize),
+ Pending,
+}
+
+#[derive(Debug)]
+struct StoredAsset
+{
+ strong: Arc<dyn Any + Send + Sync>,
+}
+
+impl StoredAsset
+{
+ fn new<Asset: Any + Send + Sync>(asset: Asset) -> Self
+ {
+ let strong = Arc::new(asset);
+
+ Self { strong }
+ }
+}
diff --git a/engine/src/camera/fly.rs b/engine/src/camera/fly.rs
index 087f727..d6eac62 100644
--- a/engine/src/camera/fly.rs
+++ b/engine/src/camera/fly.rs
@@ -7,8 +7,8 @@ use ecs::{Component, Query};
use crate::camera::{Active as ActiveCamera, Camera};
use crate::delta_time::DeltaTime;
use crate::input::{Cursor, CursorFlags, Key, KeyState, Keys};
-use crate::transform::Position;
-use crate::util::builder;
+use crate::transform::WorldPosition;
+use crate::builder;
use crate::vector::{Vec2, Vec3};
builder! {
@@ -75,7 +75,7 @@ pub struct Options
}
fn update(
- camera_query: Query<(&mut Camera, &mut Position, &mut Fly, &ActiveCamera)>,
+ camera_query: Query<(&mut Camera, &mut WorldPosition, &mut Fly, &ActiveCamera)>,
keys: Single<Keys>,
cursor: Single<Cursor>,
cursor_flags: Single<CursorFlags>,
@@ -84,7 +84,7 @@ fn update(
options: Local<Options>,
)
{
- for (mut camera, mut camera_pos, mut fly_camera, _) in &camera_query {
+ for (mut camera, mut camera_world_pos, mut fly_camera, _) in &camera_query {
if cursor.has_moved && cursor_flags.is_first_move.flag {
tracing::debug!("First cursor move");
@@ -122,29 +122,30 @@ fn update(
camera.global_up = cam_right.cross(&direction).normalize();
if keys.get_key_state(Key::W) == KeyState::Pressed {
- camera_pos.position +=
+ camera_world_pos.position +=
direction * fly_camera.speed * delta_time.as_secs_f32();
}
if keys.get_key_state(Key::S) == KeyState::Pressed {
- camera_pos.position -=
+ camera_world_pos.position -=
direction * fly_camera.speed * delta_time.as_secs_f32();
}
if keys.get_key_state(Key::A) == KeyState::Pressed {
let cam_left = -direction.cross(&Vec3::UP).normalize();
- camera_pos.position += cam_left * fly_camera.speed * delta_time.as_secs_f32();
+ camera_world_pos.position +=
+ cam_left * fly_camera.speed * delta_time.as_secs_f32();
}
if keys.get_key_state(Key::D) == KeyState::Pressed {
let cam_right = direction.cross(&Vec3::UP).normalize();
- camera_pos.position +=
+ camera_world_pos.position +=
cam_right * fly_camera.speed * delta_time.as_secs_f32();
}
- camera.target = camera_pos.position + direction;
+ camera.target = camera_world_pos.position + direction;
}
}
diff --git a/engine/src/data_types/dimens.rs b/engine/src/data_types/dimens.rs
index 5002436..d8d0247 100644
--- a/engine/src/data_types/dimens.rs
+++ b/engine/src/data_types/dimens.rs
@@ -6,6 +6,22 @@ pub struct Dimens<Value>
pub height: Value,
}
+impl<Value: Clone> From<Value> for Dimens<Value>
+{
+ fn from(value: Value) -> Self
+ {
+ Self { width: value.clone(), height: value }
+ }
+}
+
+impl<Value> From<(Value, Value)> for Dimens<Value>
+{
+ fn from(value: (Value, Value)) -> Self
+ {
+ Self { width: value.0, height: value.1 }
+ }
+}
+
/// 3D dimensions.
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
pub struct Dimens3<Value>
@@ -14,3 +30,27 @@ pub struct Dimens3<Value>
pub height: Value,
pub depth: Value,
}
+
+impl<Value: Clone> From<Value> for Dimens3<Value>
+{
+ fn from(value: Value) -> Self
+ {
+ Self {
+ width: value.clone(),
+ height: value.clone(),
+ depth: value,
+ }
+ }
+}
+
+impl<Value: Clone> From<(Value, Value, Value)> for Dimens3<Value>
+{
+ fn from(value: (Value, Value, Value)) -> Self
+ {
+ Self {
+ width: value.0,
+ height: value.1,
+ depth: value.2,
+ }
+ }
+}
diff --git a/engine/src/draw_flags.rs b/engine/src/draw_flags.rs
index df5eed1..426f865 100644
--- a/engine/src/draw_flags.rs
+++ b/engine/src/draw_flags.rs
@@ -1,6 +1,6 @@
use ecs::Component;
-use crate::util::builder;
+use crate::builder;
builder! {
/// Flags for how a object should be drawn.
diff --git a/engine/src/file_format/wavefront/mtl.rs b/engine/src/file_format/wavefront/mtl.rs
index d90dbcf..f3c7a64 100644
--- a/engine/src/file_format/wavefront/mtl.rs
+++ b/engine/src/file_format/wavefront/mtl.rs
@@ -2,7 +2,7 @@
//!
//! File format documentation: <https://paulbourke.net/dataformats/mtl>
-use std::path::Path;
+use std::path::{Path, PathBuf};
use crate::color::Color;
use crate::file_format::wavefront::common::{
@@ -11,8 +11,6 @@ use crate::file_format::wavefront::common::{
ParsingError,
Statement,
};
-use crate::material::{Builder as MaterialBuilder, Material};
-use crate::texture::{Error as TextureError, Texture};
/// Parses the content of a Wavefront `.mtl`.
///
@@ -50,18 +48,41 @@ pub fn parse(obj_content: &str) -> Result<Vec<NamedMaterial>, Error>
}
#[derive(Debug, Clone)]
+#[non_exhaustive]
pub struct NamedMaterial
{
pub name: String,
- pub material: Material,
+ pub ambient: Color<f32>,
+ pub diffuse: Color<f32>,
+ pub specular: Color<f32>,
+ pub ambient_map: Option<TextureMap>,
+ pub diffuse_map: Option<TextureMap>,
+ pub specular_map: Option<TextureMap>,
+ pub shininess: f32,
+}
+
+impl Default for NamedMaterial
+{
+ fn default() -> Self
+ {
+ Self {
+ name: String::new(),
+ ambient: Color::WHITE_F32,
+ diffuse: Color::WHITE_F32,
+ specular: Color::WHITE_F32,
+ ambient_map: None,
+ diffuse_map: None,
+ specular_map: None,
+ shininess: 0.0,
+ }
+ }
}
#[derive(Debug, Clone)]
-pub struct UnfinishedNamedMaterial
+#[non_exhaustive]
+pub struct TextureMap
{
- name: String,
- material_builder: MaterialBuilder,
- ready: bool,
+ pub path: PathBuf,
}
#[derive(Debug, thiserror::Error)]
@@ -70,8 +91,14 @@ pub enum Error
#[error(transparent)]
ParsingError(#[from] ParsingError),
- #[error("Failed to open texture")]
- TextureError(#[from] TextureError),
+ #[error(
+ "A material start statement (newmtl) is expected before statement at line {}",
+ line_no
+ )]
+ ExpectedMaterialStartStmtBeforeStmt
+ {
+ line_no: usize
+ },
#[error(
"Unsupported number of arguments ({arg_count}) to {keyword} at line {line_no}"
@@ -100,59 +127,52 @@ fn statements_to_materials(
{
let mut materials = Vec::<NamedMaterial>::with_capacity(material_cnt);
- let mut curr_material = UnfinishedNamedMaterial {
- name: String::new(),
- material_builder: MaterialBuilder::new(),
- ready: false,
- };
-
for (line_no, statement) in statements {
if statement.keyword == Keyword::Newmtl {
- if curr_material.ready {
- tracing::debug!("Building material");
-
- let material = curr_material.material_builder.clone().build();
-
- materials.push(NamedMaterial { name: curr_material.name, material });
- }
-
let name = statement.get_text_arg(0, line_no)?;
- curr_material.name = name.to_string();
- curr_material.ready = true;
+ materials.push(NamedMaterial {
+ name: name.to_string(),
+ ..Default::default()
+ });
continue;
}
- if !curr_material.ready {
- // Discard statements not belonging to a material
- continue;
+ let Some(curr_material) = materials.last_mut() else {
+ return Err(Error::ExpectedMaterialStartStmtBeforeStmt { line_no });
};
match statement.keyword {
Keyword::Ka => {
let color = get_color_from_statement(&statement, line_no)?;
- tracing::debug!("Adding ambient color");
+ tracing::debug!(
+ "Adding ambient color {color:?} to material {}",
+ curr_material.name
+ );
- curr_material.material_builder =
- curr_material.material_builder.ambient(color);
+ curr_material.ambient = color;
}
Keyword::Kd => {
let color = get_color_from_statement(&statement, line_no)?;
- tracing::debug!("Adding diffuse color");
+ tracing::debug!(
+ "Adding diffuse color {color:?} to material {}",
+ curr_material.name
+ );
- curr_material.material_builder =
- curr_material.material_builder.diffuse(color);
+ curr_material.diffuse = color;
}
Keyword::Ks => {
let color = get_color_from_statement(&statement, line_no)?;
- tracing::debug!("Adding specular color");
+ tracing::debug!(
+ "Adding specular color {color:?} to material {}",
+ curr_material.name
+ );
- curr_material.material_builder =
- curr_material.material_builder.specular(color);
+ curr_material.specular = color;
}
Keyword::MapKa => {
if statement.arguments.len() > 1 {
@@ -165,51 +185,75 @@ fn statements_to_materials(
let texture_file_path = statement.get_text_arg(0, line_no)?;
- let texture = Texture::open(Path::new(texture_file_path))?;
-
- tracing::debug!("Adding ambient map");
+ tracing::debug!(
+ "Adding ambient map {texture_file_path} to material {}",
+ curr_material.name
+ );
- let texture_id = texture.id();
-
- curr_material.material_builder = curr_material
- .material_builder
- .texture(texture)
- .ambient_map(texture_id);
+ curr_material.ambient_map = Some(TextureMap {
+ path: Path::new(texture_file_path).to_path_buf(),
+ });
}
Keyword::MapKd => {
- let texture = get_map_from_texture(&statement, line_no)?;
+ if statement.arguments.len() > 1 {
+ return Err(Error::UnsupportedArgumentCount {
+ keyword: statement.keyword.to_string(),
+ arg_count: statement.arguments.len(),
+ line_no,
+ });
+ }
- tracing::debug!("Adding diffuse map");
+ let texture_file_path = statement.get_text_arg(0, line_no)?;
- let texture_id = texture.id();
+ tracing::debug!(
+ "Adding diffuse map {texture_file_path} to material {}",
+ curr_material.name
+ );
- curr_material.material_builder = curr_material
- .material_builder
- .texture(texture)
- .diffuse_map(texture_id);
+ curr_material.diffuse_map = Some(TextureMap {
+ path: Path::new(texture_file_path).to_path_buf(),
+ });
}
Keyword::MapKs => {
- let texture = get_map_from_texture(&statement, line_no)?;
+ if statement.arguments.len() > 1 {
+ return Err(Error::UnsupportedArgumentCount {
+ keyword: statement.keyword.to_string(),
+ arg_count: statement.arguments.len(),
+ line_no,
+ });
+ }
- tracing::debug!("Adding specular map");
+ let texture_file_path = statement.get_text_arg(0, line_no)?;
- let texture_id = texture.id();
+ tracing::debug!(
+ "Adding specular map {texture_file_path} to material {}",
+ curr_material.name
+ );
- curr_material.material_builder = curr_material
- .material_builder
- .texture(texture)
- .specular_map(texture_id);
+ curr_material.specular_map = Some(TextureMap {
+ path: Path::new(texture_file_path).to_path_buf(),
+ });
}
- Keyword::Newmtl => {}
- }
- }
+ Keyword::Ns => {
+ if statement.arguments.len() != 1 {
+ return Err(Error::UnsupportedArgumentCount {
+ keyword: statement.keyword.to_string(),
+ arg_count: statement.arguments.len(),
+ line_no,
+ });
+ }
- if curr_material.ready {
- tracing::debug!("Building last material");
+ let shininess = statement.get_float_arg(0, line_no)?;
- let material = curr_material.material_builder.build();
+ tracing::debug!(
+ "Adding shininess {shininess} to material {}",
+ curr_material.name
+ );
- materials.push(NamedMaterial { name: curr_material.name, material });
+ curr_material.shininess = shininess;
+ }
+ Keyword::Newmtl => {}
+ }
}
Ok(materials)
@@ -235,24 +279,6 @@ fn get_color_from_statement(
Ok(Color { red, green, blue })
}
-fn get_map_from_texture(
- statement: &Statement<Keyword>,
- line_no: usize,
-) -> Result<Texture, Error>
-{
- if statement.arguments.len() > 1 {
- return Err(Error::UnsupportedArgumentCount {
- keyword: statement.keyword.to_string(),
- arg_count: statement.arguments.len(),
- line_no,
- });
- }
-
- let texture_file_path = statement.get_text_arg(0, line_no)?;
-
- Ok(Texture::open(Path::new(texture_file_path))?)
-}
-
keyword! {
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
enum Keyword {
@@ -271,5 +297,7 @@ keyword! {
#[keyword(rename = "map_Ks")]
MapKs,
+
+ Ns,
}
}
diff --git a/engine/src/image.rs b/engine/src/image.rs
new file mode 100644
index 0000000..0e04412
--- /dev/null
+++ b/engine/src/image.rs
@@ -0,0 +1,184 @@
+use std::fs::File;
+use std::io::BufReader;
+use std::path::Path;
+
+use image_rs::GenericImageView as _;
+
+use crate::asset::{Assets, Submitter as AssetSubmitter};
+use crate::color::Color;
+use crate::data_types::dimens::Dimens;
+use crate::builder;
+
+#[derive(Debug)]
+pub struct Image
+{
+ inner: image_rs::DynamicImage,
+}
+
+impl Image
+{
+ pub fn open(path: impl AsRef<Path>) -> Result<Self, Error>
+ {
+ let buffered_reader =
+ BufReader::new(File::open(&path).map_err(Error::ReadFailed)?);
+
+ let image_reader = image_rs::io::Reader::with_format(
+ buffered_reader,
+ image_rs::ImageFormat::from_path(path)
+ .map_err(|_| Error::UnsupportedFormat)?,
+ );
+
+ Ok(Self {
+ inner: image_reader
+ .decode()
+ .map_err(|err| Error::DecodeFailed(DecodeError(err)))?,
+ })
+ }
+
+ pub fn from_color(dimens: impl Into<Dimens<u32>>, color: impl Into<Color<u8>>)
+ -> Self
+ {
+ let dimens: Dimens<u32> = dimens.into();
+
+ let color: Color<u8> = color.into();
+
+ Self {
+ inner: image_rs::RgbImage::from_pixel(
+ dimens.width,
+ dimens.height,
+ image_rs::Rgb([color.red, color.green, color.blue]),
+ )
+ .into(),
+ }
+ }
+
+ pub fn dimensions(&self) -> Dimens<u32>
+ {
+ self.inner.dimensions().into()
+ }
+
+ pub fn color_type(&self) -> ColorType
+ {
+ self.inner.color().into()
+ }
+
+ pub fn as_bytes(&self) -> &[u8]
+ {
+ self.inner.as_bytes()
+ }
+}
+
+builder! {
+#[builder(name = SettingsBuilder, derives=(Debug, Clone))]
+#[derive(Debug, Default, Clone)]
+#[non_exhaustive]
+pub struct Settings {
+}
+}
+
+impl Settings
+{
+ pub fn builder() -> SettingsBuilder
+ {
+ SettingsBuilder::default()
+ }
+}
+
+impl Default for SettingsBuilder
+{
+ fn default() -> Self
+ {
+ Settings::default().into()
+ }
+}
+
+/// An enumeration over supported color types and bit depths
+#[derive(Copy, PartialEq, Eq, Debug, Clone, Hash)]
+#[non_exhaustive]
+pub enum ColorType
+{
+ /// Pixel is 8-bit luminance
+ L8,
+
+ /// Pixel is 8-bit luminance with an alpha channel
+ La8,
+
+ /// Pixel contains 8-bit R, G and B channels
+ Rgb8,
+
+ /// Pixel is 8-bit RGB with an alpha channel
+ Rgba8,
+
+ /// Pixel is 16-bit luminance
+ L16,
+
+ /// Pixel is 16-bit luminance with an alpha channel
+ La16,
+
+ /// Pixel is 16-bit RGB
+ Rgb16,
+
+ /// Pixel is 16-bit RGBA
+ Rgba16,
+
+ /// Pixel is 32-bit float RGB
+ Rgb32F,
+
+ /// Pixel is 32-bit float RGBA
+ Rgba32F,
+}
+
+impl From<image_rs::ColorType> for ColorType
+{
+ fn from(color_type: image_rs::ColorType) -> Self
+ {
+ match color_type {
+ image_rs::ColorType::L8 => Self::L8,
+ image_rs::ColorType::La8 => Self::La8,
+ image_rs::ColorType::Rgb8 => Self::Rgb8,
+ image_rs::ColorType::Rgba8 => Self::Rgba8,
+ image_rs::ColorType::L16 => Self::L16,
+ image_rs::ColorType::La16 => Self::La16,
+ image_rs::ColorType::Rgb16 => Self::Rgb16,
+ image_rs::ColorType::Rgba16 => Self::Rgba16,
+ image_rs::ColorType::Rgb32F => Self::Rgb32F,
+ image_rs::ColorType::Rgba32F => Self::Rgba32F,
+ _ => {
+ panic!("Unrecognized image_rs::ColorType variant");
+ }
+ }
+ }
+}
+
+pub fn set_asset_importers(assets: &mut Assets)
+{
+ assets.set_importer::<_, _>(["png", "jpg"], import_asset);
+}
+
+#[derive(Debug, thiserror::Error)]
+pub enum Error
+{
+ #[error("Failed to read image file")]
+ ReadFailed(#[source] std::io::Error),
+
+ #[error("Failed to decode image")]
+ DecodeFailed(DecodeError),
+
+ #[error("Unsupported image format")]
+ UnsupportedFormat,
+}
+
+#[derive(Debug, thiserror::Error)]
+#[error(transparent)]
+pub struct DecodeError(image_rs::ImageError);
+
+fn import_asset(
+ asset_submitter: &mut AssetSubmitter<'_>,
+ path: &Path,
+ _settings: Option<&'_ Settings>,
+) -> Result<(), Error>
+{
+ asset_submitter.submit_store::<Image>(Image::open(path)?);
+
+ Ok(())
+}
diff --git a/engine/src/lib.rs b/engine/src/lib.rs
index c537e06..6ccba53 100644
--- a/engine/src/lib.rs
+++ b/engine/src/lib.rs
@@ -10,22 +10,27 @@ use ecs::system::{Into, System};
use ecs::uid::Uid;
use ecs::{SoleAlreadyExistsError, World};
+use crate::asset::{Assets, Extension as AssetExtension};
use crate::delta_time::{update as update_delta_time, DeltaTime, LastUpdate};
mod opengl;
mod util;
+mod work_queue;
+pub mod asset;
pub mod camera;
pub mod collision;
pub mod data_types;
pub mod delta_time;
pub mod draw_flags;
pub mod file_format;
+pub mod image;
pub mod input;
pub mod lighting;
pub mod material;
pub mod math;
pub mod mesh;
+pub mod model;
pub mod projection;
pub mod renderer;
pub mod texture;
@@ -37,6 +42,8 @@ pub extern crate ecs;
pub(crate) use crate::data_types::matrix;
pub use crate::data_types::{color, vector};
+const INITIAL_ASSET_CAPACITY: usize = 128;
+
#[derive(Debug)]
pub struct Engine
{
@@ -60,6 +67,13 @@ impl Engine
.initialize((LastUpdate::default(),)),
);
+ let mut assets = Assets::with_capacity(INITIAL_ASSET_CAPACITY);
+
+ crate::model::set_asset_importers(&mut assets);
+ crate::image::set_asset_importers(&mut assets);
+
+ world.add_extension(AssetExtension { assets });
+
Self { world }
}
diff --git a/engine/src/lighting.rs b/engine/src/lighting.rs
index 48adb0e..4406ed5 100644
--- a/engine/src/lighting.rs
+++ b/engine/src/lighting.rs
@@ -2,7 +2,7 @@ use ecs::{Component, Sole};
use crate::color::Color;
use crate::data_types::vector::Vec3;
-use crate::util::builder;
+use crate::builder;
builder! {
#[builder(name = PointLightBuilder, derives = (Debug, Clone))]
@@ -10,7 +10,8 @@ builder! {
#[non_exhaustive]
pub struct PointLight
{
- pub position: Vec3<f32>,
+ /// Position in local space.
+ pub local_position: Vec3<f32>,
pub diffuse: Color<f32>,
pub specular: Color<f32>,
pub attenuation_params: AttenuationParams,
@@ -31,7 +32,7 @@ impl Default for PointLight
fn default() -> Self
{
Self {
- position: Vec3::default(),
+ local_position: Vec3::default(),
diffuse: Color { red: 0.5, green: 0.5, blue: 0.5 },
specular: Color { red: 1.0, green: 1.0, blue: 1.0 },
attenuation_params: AttenuationParams::default(),
diff --git a/engine/src/material.rs b/engine/src/material.rs
index e368519..56ff15f 100644
--- a/engine/src/material.rs
+++ b/engine/src/material.rs
@@ -1,21 +1,19 @@
use ecs::Component;
use crate::color::Color;
-use crate::data_types::dimens::Dimens;
-use crate::texture::{Id as TextureId, Texture};
-use crate::util::builder;
+use crate::texture::Texture;
+use crate::builder;
-#[derive(Debug, Clone, Component)]
+#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct Material
{
pub ambient: Color<f32>,
pub diffuse: Color<f32>,
pub specular: Color<f32>,
- pub ambient_map: TextureId,
- pub diffuse_map: TextureId,
- pub specular_map: TextureId,
- pub textures: Vec<Texture>,
+ pub ambient_map: Option<Texture>,
+ pub diffuse_map: Option<Texture>,
+ pub specular_map: Option<Texture>,
pub shininess: f32,
}
@@ -27,17 +25,24 @@ impl Material
}
}
+impl Default for Material
+{
+ fn default() -> Self
+ {
+ Self::builder().build()
+ }
+}
+
/// [`Material`] builder.
#[derive(Debug, Clone)]
pub struct Builder
{
- ambient: Option<Color<f32>>,
- diffuse: Option<Color<f32>>,
- specular: Option<Color<f32>>,
- ambient_map: Option<TextureId>,
- diffuse_map: Option<TextureId>,
- specular_map: Option<TextureId>,
- textures: Vec<Texture>,
+ ambient: Color<f32>,
+ diffuse: Color<f32>,
+ specular: Color<f32>,
+ ambient_map: Option<Texture>,
+ diffuse_map: Option<Texture>,
+ specular_map: Option<Texture>,
shininess: f32,
}
@@ -47,13 +52,12 @@ impl Builder
pub fn new() -> Self
{
Self {
- ambient: None,
- diffuse: None,
- specular: None,
+ ambient: Color::WHITE_F32,
+ diffuse: Color::WHITE_F32,
+ specular: Color::WHITE_F32,
ambient_map: None,
diffuse_map: None,
specular_map: None,
- textures: Vec::new(),
shininess: 32.0,
}
}
@@ -61,7 +65,7 @@ impl Builder
#[must_use]
pub fn ambient(mut self, ambient: Color<f32>) -> Self
{
- self.ambient = Some(ambient);
+ self.ambient = ambient;
self
}
@@ -69,7 +73,7 @@ impl Builder
#[must_use]
pub fn diffuse(mut self, diffuse: Color<f32>) -> Self
{
- self.diffuse = Some(diffuse);
+ self.diffuse = diffuse;
self
}
@@ -77,13 +81,13 @@ impl Builder
#[must_use]
pub fn specular(mut self, specular: Color<f32>) -> Self
{
- self.specular = Some(specular);
+ self.specular = specular;
self
}
#[must_use]
- pub fn ambient_map(mut self, ambient_map: TextureId) -> Self
+ pub fn ambient_map(mut self, ambient_map: Texture) -> Self
{
self.ambient_map = Some(ambient_map);
@@ -91,7 +95,7 @@ impl Builder
}
#[must_use]
- pub fn diffuse_map(mut self, diffuse_map: TextureId) -> Self
+ pub fn diffuse_map(mut self, diffuse_map: Texture) -> Self
{
self.diffuse_map = Some(diffuse_map);
@@ -99,7 +103,7 @@ impl Builder
}
#[must_use]
- pub fn specular_map(mut self, specular_map: TextureId) -> Self
+ pub fn specular_map(mut self, specular_map: Texture) -> Self
{
self.specular_map = Some(specular_map);
@@ -107,22 +111,6 @@ impl Builder
}
#[must_use]
- pub fn textures(mut self, textures: impl IntoIterator<Item = Texture>) -> Self
- {
- self.textures = textures.into_iter().collect();
-
- self
- }
-
- #[must_use]
- pub fn texture(mut self, texture: Texture) -> Self
- {
- self.textures.push(texture);
-
- self
- }
-
- #[must_use]
pub fn shininess(mut self, shininess: f32) -> Self
{
self.shininess = shininess;
@@ -135,43 +123,15 @@ impl Builder
/// # Panics
/// Will panic if no ambient map, diffuse map or specular map is set.
#[must_use]
- pub fn build(mut self) -> Material
+ pub fn build(self) -> Material
{
- let ambient_map = self.ambient_map.unwrap_or_else(|| {
- let texture = create_1x1_white_texture();
- let texture_id = texture.id();
-
- self.textures.push(texture);
-
- texture_id
- });
-
- let diffuse_map = self.diffuse_map.unwrap_or_else(|| {
- let texture = create_1x1_white_texture();
- let texture_id = texture.id();
-
- self.textures.push(texture);
-
- texture_id
- });
-
- let specular_map = self.specular_map.unwrap_or_else(|| {
- let texture = create_1x1_white_texture();
- let texture_id = texture.id();
-
- self.textures.push(texture);
-
- texture_id
- });
-
Material {
- ambient: self.ambient.unwrap_or(Color::WHITE_F32),
- diffuse: self.diffuse.unwrap_or(Color::WHITE_F32),
- specular: self.specular.unwrap_or(Color::WHITE_F32),
- ambient_map,
- diffuse_map,
- specular_map,
- textures: self.textures,
+ ambient: self.ambient,
+ diffuse: self.diffuse,
+ specular: self.specular,
+ ambient_map: self.ambient_map,
+ diffuse_map: self.diffuse_map,
+ specular_map: self.specular_map,
shininess: self.shininess,
}
}
@@ -206,8 +166,3 @@ impl Flags
FlagsBuilder::default()
}
}
-
-fn create_1x1_white_texture() -> Texture
-{
- Texture::new_from_color(&Dimens { width: 1, height: 1 }, &Color::WHITE_U8)
-}
diff --git a/engine/src/mesh.rs b/engine/src/mesh.rs
index 91d199e..fb977af 100644
--- a/engine/src/mesh.rs
+++ b/engine/src/mesh.rs
@@ -1,11 +1,9 @@
-use ecs::Component;
-
-use crate::util::builder;
+use crate::builder;
use crate::vector::{Vec2, Vec3};
pub mod cube;
-#[derive(Debug, Clone, Component)]
+#[derive(Debug, Clone, Default)]
pub struct Mesh
{
vertices: Vec<Vertex>,
diff --git a/engine/src/mesh/cube.rs b/engine/src/mesh/cube.rs
index 6c9d381..e91cf0e 100644
--- a/engine/src/mesh/cube.rs
+++ b/engine/src/mesh/cube.rs
@@ -1,6 +1,7 @@
+use crate::data_types::dimens::Dimens3;
use crate::math::calc_triangle_surface_normal;
use crate::mesh::{Mesh, Vertex};
-use crate::util::builder;
+use crate::builder;
use crate::vector::{Vec2, Vec3};
builder! {
@@ -26,6 +27,18 @@ impl CreationSpec
}
}
+impl CreationSpecBuilder
+{
+ pub fn dimens(mut self, dimens: Dimens3<f32>) -> Self
+ {
+ self.width = dimens.width;
+ self.height = dimens.height;
+ self.depth = dimens.depth;
+
+ self
+ }
+}
+
/// Describes a single side of a cube (obviously).
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Side
diff --git a/engine/src/model.rs b/engine/src/model.rs
new file mode 100644
index 0000000..9f5840c
--- /dev/null
+++ b/engine/src/model.rs
@@ -0,0 +1,176 @@
+use std::borrow::Cow;
+use std::collections::HashMap;
+use std::fs::read_to_string;
+use std::path::Path;
+
+use ecs::Component;
+
+use crate::asset::{Assets, Handle as AssetHandle, Submitter as AssetSubmitter};
+use crate::material::Material;
+use crate::mesh::Mesh;
+use crate::texture::Texture;
+
+#[derive(Debug, Clone, Component)]
+#[non_exhaustive]
+pub struct Model
+{
+ pub asset_handle: AssetHandle<Data>,
+}
+
+impl Model
+{
+ pub fn new(asset_handle: AssetHandle<Data>) -> Self
+ {
+ Self { asset_handle }
+ }
+}
+
+#[derive(Debug, Default, Clone)]
+#[non_exhaustive]
+pub struct Data
+{
+ pub mesh: Mesh,
+ pub materials: HashMap<String, Material>,
+}
+
+impl Data
+{
+ pub fn builder() -> DataBuilder
+ {
+ DataBuilder::default()
+ }
+}
+
+#[derive(Debug, Default, Clone)]
+pub struct DataBuilder
+{
+ mesh: Mesh,
+ materials: HashMap<String, Material>,
+}
+
+impl DataBuilder
+{
+ pub fn mesh(mut self, mesh: Mesh) -> Self
+ {
+ self.mesh = mesh;
+
+ self
+ }
+
+ pub fn material<'name>(
+ mut self,
+ name: impl Into<Cow<'name, str>>,
+ material: Material,
+ ) -> Self
+ {
+ self.materials.insert(name.into().into_owned(), material);
+
+ self
+ }
+
+ pub fn build(self) -> Data
+ {
+ Data {
+ mesh: self.mesh,
+ materials: self.materials,
+ }
+ }
+}
+
+#[derive(Debug, Clone)]
+#[non_exhaustive]
+pub struct Settings {}
+
+#[derive(Debug, thiserror::Error)]
+enum Error
+{
+ #[error("Failed to read model file")]
+ ReadModelFileFailed(#[source] std::io::Error),
+
+ #[error("Failed to read material file")]
+ ReadMaterialFileFailed(#[source] std::io::Error),
+
+ #[error("Failed to parse model file")]
+ ParsingFailed(#[from] ParsingError),
+}
+
+pub fn set_asset_importers(assets: &mut Assets)
+{
+ assets.set_importer(["obj"], import_wavefront_obj_asset);
+}
+
+#[derive(Debug, thiserror::Error)]
+enum ParsingError
+{
+ #[error(transparent)]
+ Obj(#[from] crate::file_format::wavefront::obj::Error),
+
+ #[error(transparent)]
+ Mtl(#[from] crate::file_format::wavefront::mtl::Error),
+}
+
+fn import_wavefront_obj_asset(
+ asset_submitter: &mut AssetSubmitter<'_>,
+ path: &Path,
+ _settings: Option<&'_ Settings>,
+) -> Result<(), Error>
+{
+ let obj = crate::file_format::wavefront::obj::parse(
+ &read_to_string(path).map_err(Error::ReadModelFileFailed)?,
+ )
+ .map_err(|err| Error::ParsingFailed(ParsingError::Obj(err)))?;
+
+ let mesh = obj
+ .to_mesh()
+ .map_err(|err| Error::ParsingFailed(ParsingError::Obj(err)))?;
+
+ let mut materials =
+ HashMap::<String, Material>::with_capacity(obj.mtl_libs.iter().flatten().count());
+
+ for mtl_lib_path in obj.mtl_libs.iter().flatten() {
+ materials.extend(import_mtl(asset_submitter, &mtl_lib_path)?);
+ }
+
+ asset_submitter.submit_store(Data { mesh, materials });
+
+ Ok(())
+}
+
+fn import_mtl<'a>(
+ asset_submitter: &'a AssetSubmitter<'_>,
+ path: &Path,
+) -> Result<impl Iterator<Item = (String, Material)> + 'a, Error>
+{
+ let named_materials = crate::file_format::wavefront::mtl::parse(
+ &read_to_string(path).map_err(Error::ReadMaterialFileFailed)?,
+ )
+ .map_err(|err| Error::ParsingFailed(ParsingError::Mtl(err)))?;
+
+ Ok(named_materials.into_iter().map(|named_material| {
+ let mut material_builder = Material::builder()
+ .ambient(named_material.ambient)
+ .diffuse(named_material.diffuse)
+ .specular(named_material.specular)
+ .shininess(named_material.shininess);
+
+ if let Some(ambient_map) = named_material.ambient_map {
+ material_builder = material_builder.ambient_map(Texture::new(
+ asset_submitter.submit_load_other(ambient_map.path.as_path()),
+ ));
+ }
+
+ if let Some(diffuse_map) = named_material.diffuse_map {
+ material_builder = material_builder.diffuse_map(Texture::new(
+ asset_submitter.submit_load_other(diffuse_map.path.as_path()),
+ ));
+ }
+
+ if let Some(specular_map) = named_material.specular_map {
+ material_builder = material_builder.specular_map(Texture::new(
+ asset_submitter.submit_load_other(specular_map.path.as_path()),
+ ));
+ }
+
+ (named_material.name, material_builder.build())
+ }))
+}
diff --git a/engine/src/opengl/shader.rs b/engine/src/opengl/shader.rs
index 36dc1a4..a626fc7 100644
--- a/engine/src/opengl/shader.rs
+++ b/engine/src/opengl/shader.rs
@@ -159,82 +159,105 @@ impl Program
}
}
- pub fn set_uniform_matrix_4fv(&mut self, name: &CStr, matrix: &Matrix<f32, 4, 4>)
+ pub fn set_uniform(&mut self, name: &CStr, var: &impl UniformVariable)
{
- let uniform_location =
- unsafe { gl::GetUniformLocation(self.program, name.as_ptr().cast()) };
+ let location = UniformLocation(unsafe {
+ gl::GetUniformLocation(self.program, name.as_ptr().cast())
+ });
+
+ var.set(self, location);
+ }
+
+ fn get_info_log(&self) -> String
+ {
+ let mut buf = vec![gl::types::GLchar::default(); 512];
unsafe {
- gl::ProgramUniformMatrix4fv(
+ #[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)]
+ gl::GetProgramInfoLog(
self.program,
- uniform_location,
- 1,
- gl::FALSE,
- matrix.as_ptr(),
+ buf.len() as gl::types::GLsizei,
+ null_mut(),
+ buf.as_mut_ptr(),
);
}
+
+ let info_log = unsafe { CStr::from_ptr(buf.as_ptr()) };
+
+ unsafe { String::from_utf8_unchecked(info_log.to_bytes().to_vec()) }
}
+}
- pub fn set_uniform_vec_3fv(&mut self, name: &CStr, vec: &Vec3<f32>)
+impl Drop for Program
+{
+ fn drop(&mut self)
{
- let uniform_location =
- unsafe { gl::GetUniformLocation(self.program, name.as_ptr().cast()) };
-
unsafe {
- gl::ProgramUniform3fv(self.program, uniform_location, 1, vec.as_ptr());
+ gl::DeleteProgram(self.program);
}
}
+}
- pub fn set_uniform_1fv(&mut self, name: &CStr, num: f32)
- {
- let uniform_location =
- unsafe { gl::GetUniformLocation(self.program, name.as_ptr().cast()) };
+pub trait UniformVariable
+{
+ fn set(&self, program: &mut Program, uniform_location: UniformLocation);
+}
+impl UniformVariable for f32
+{
+ fn set(&self, program: &mut Program, uniform_location: UniformLocation)
+ {
unsafe {
- gl::ProgramUniform1fv(self.program, uniform_location, 1, &num);
+ gl::ProgramUniform1f(program.program, uniform_location.0, *self);
}
}
+}
- pub fn set_uniform_1i(&mut self, name: &CStr, num: i32)
+impl UniformVariable for i32
+{
+ fn set(&self, program: &mut Program, uniform_location: UniformLocation)
{
- let uniform_location =
- unsafe { gl::GetUniformLocation(self.program, name.as_ptr().cast()) };
-
unsafe {
- gl::ProgramUniform1i(self.program, uniform_location, num);
+ gl::ProgramUniform1i(program.program, uniform_location.0, *self);
}
}
+}
- fn get_info_log(&self) -> String
+impl UniformVariable for Vec3<f32>
+{
+ fn set(&self, program: &mut Program, uniform_location: UniformLocation)
{
- let mut buf = vec![gl::types::GLchar::default(); 512];
-
unsafe {
- #[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)]
- gl::GetProgramInfoLog(
- self.program,
- buf.len() as gl::types::GLsizei,
- null_mut(),
- buf.as_mut_ptr(),
+ gl::ProgramUniform3f(
+ program.program,
+ uniform_location.0,
+ self.x,
+ self.y,
+ self.z,
);
}
-
- let info_log = unsafe { CStr::from_ptr(buf.as_ptr()) };
-
- unsafe { String::from_utf8_unchecked(info_log.to_bytes().to_vec()) }
}
}
-impl Drop for Program
+impl UniformVariable for Matrix<f32, 4, 4>
{
- fn drop(&mut self)
+ fn set(&self, program: &mut Program, uniform_location: UniformLocation)
{
unsafe {
- gl::DeleteProgram(self.program);
+ gl::ProgramUniformMatrix4fv(
+ program.program,
+ uniform_location.0,
+ 1,
+ gl::FALSE,
+ self.as_ptr(),
+ );
}
}
}
+#[derive(Debug)]
+pub struct UniformLocation(gl::types::GLint);
+
/// Shader error.
#[derive(Debug, thiserror::Error)]
pub enum Error
diff --git a/engine/src/opengl/texture.rs b/engine/src/opengl/texture.rs
index 52c8554..80a5f37 100644
--- a/engine/src/opengl/texture.rs
+++ b/engine/src/opengl/texture.rs
@@ -1,5 +1,4 @@
use crate::data_types::dimens::Dimens;
-use crate::texture::Properties;
#[derive(Debug)]
pub struct Texture
@@ -20,10 +19,10 @@ impl Texture
Self { texture }
}
- pub fn bind(&self)
+ pub fn bind_to_texture_unit(&self, texture_unit: u32)
{
unsafe {
- gl::BindTexture(gl::TEXTURE_2D, self.texture);
+ gl::BindTextureUnit(texture_unit, self.texture);
}
}
@@ -41,16 +40,9 @@ impl Texture
}
}
- pub fn apply_properties(&mut self, properties: &Properties)
- {
- self.set_wrap(properties.wrap);
- self.set_magnifying_filter(properties.magnifying_filter);
- self.set_minifying_filter(properties.minifying_filter);
- }
-
pub fn set_wrap(&mut self, wrapping: Wrapping)
{
- let wrapping_gl = wrapping.to_gl();
+ let wrapping_gl = wrapping as gl::types::GLenum;
#[allow(clippy::cast_possible_wrap)]
unsafe {
@@ -61,7 +53,7 @@ impl Texture
pub fn set_magnifying_filter(&mut self, filtering: Filtering)
{
- let filtering_gl = filtering.to_gl();
+ let filtering_gl = filtering as gl::types::GLenum;
#[allow(clippy::cast_possible_wrap)]
unsafe {
@@ -75,7 +67,7 @@ impl Texture
pub fn set_minifying_filter(&mut self, filtering: Filtering)
{
- let filtering_gl = filtering.to_gl();
+ let filtering_gl = filtering as gl::types::GLenum;
#[allow(clippy::cast_possible_wrap)]
unsafe {
@@ -132,43 +124,21 @@ impl Drop for Texture
/// Texture wrapping.
#[derive(Debug, Clone, Copy)]
+#[repr(u32)]
pub enum Wrapping
{
- Repeat,
- MirroredRepeat,
- ClampToEdge,
- ClampToBorder,
-}
-
-impl Wrapping
-{
- fn to_gl(self) -> gl::types::GLenum
- {
- match self {
- Self::Repeat => gl::REPEAT,
- Self::MirroredRepeat => gl::MIRRORED_REPEAT,
- Self::ClampToEdge => gl::CLAMP_TO_EDGE,
- Self::ClampToBorder => gl::CLAMP_TO_BORDER,
- }
- }
+ Repeat = gl::REPEAT,
+ MirroredRepeat = gl::MIRRORED_REPEAT,
+ ClampToEdge = gl::CLAMP_TO_EDGE,
+ ClampToBorder = gl::CLAMP_TO_BORDER,
}
#[derive(Debug, Clone, Copy)]
+#[repr(u32)]
pub enum Filtering
{
- Nearest,
- Linear,
-}
-
-impl Filtering
-{
- fn to_gl(self) -> gl::types::GLenum
- {
- match self {
- Self::Linear => gl::LINEAR,
- Self::Nearest => gl::NEAREST,
- }
- }
+ Nearest = gl::NEAREST,
+ Linear = gl::LINEAR,
}
/// Texture pixel data format.
@@ -197,44 +167,3 @@ impl PixelDataFormat
}
}
}
-
-pub fn set_active_texture_unit(texture_unit: TextureUnit)
-{
- unsafe {
- gl::ActiveTexture(texture_unit.into_gl());
- }
-}
-
-macro_rules! texture_unit_enum {
- (cnt=$cnt: literal) => {
- seq_macro::seq!(N in 0..$cnt {
- #[derive(Debug, Clone, Copy)]
- pub enum TextureUnit {
- #(
- No~N,
- )*
- }
-
- impl TextureUnit {
- fn into_gl(self) -> gl::types::GLenum {
- match self {
- #(
- Self::No~N => gl::TEXTURE~N,
- )*
- }
- }
-
- pub fn from_num(num: usize) -> Option<Self> {
- match num {
- #(
- N => Some(Self::No~N),
- )*
- _ => None
- }
- }
- }
- });
- };
-}
-
-texture_unit_enum!(cnt = 31);
diff --git a/engine/src/projection.rs b/engine/src/projection.rs
index faa741f..115ca39 100644
--- a/engine/src/projection.rs
+++ b/engine/src/projection.rs
@@ -1,6 +1,6 @@
use crate::data_types::dimens::Dimens3;
use crate::matrix::Matrix;
-use crate::util::builder;
+use crate::builder;
use crate::vector::Vec3;
#[derive(Debug)]
diff --git a/engine/src/renderer/opengl.rs b/engine/src/renderer/opengl.rs
index 43ec16c..cfd046f 100644
--- a/engine/src/renderer/opengl.rs
+++ b/engine/src/renderer/opengl.rs
@@ -3,26 +3,29 @@
use std::collections::HashMap;
use std::ffi::{c_void, CString};
use std::io::{Error as IoError, ErrorKind as IoErrorKind};
-use std::ops::Deref;
use std::path::Path;
use std::process::abort;
use ecs::actions::Actions;
use ecs::component::local::Local;
+use ecs::component::Handle as ComponentHandle;
use ecs::phase::START as START_PHASE;
use ecs::query::term::Without;
use ecs::sole::Single;
use ecs::system::{Into as _, System};
use ecs::{Component, Query};
+use crate::asset::{Assets, Id as AssetId};
use crate::camera::{Active as ActiveCamera, Camera};
use crate::color::Color;
use crate::data_types::dimens::Dimens;
use crate::draw_flags::{DrawFlags, NoDraw, PolygonModeConfig};
+use crate::image::{ColorType as ImageColorType, Image};
use crate::lighting::{DirectionalLight, GlobalLight, PointLight};
use crate::material::{Flags as MaterialFlags, Material};
use crate::matrix::Matrix;
use crate::mesh::Mesh;
+use crate::model::Model;
use crate::opengl::buffer::{Buffer, Usage as BufferUsage};
use crate::opengl::debug::{
enable_debug_output,
@@ -44,9 +47,10 @@ use crate::opengl::shader::{
Shader as GlShader,
};
use crate::opengl::texture::{
- set_active_texture_unit,
+ Filtering as GlTextureFiltering,
+ PixelDataFormat as GlTexturePixelDataFormat,
Texture as GlTexture,
- TextureUnit,
+ Wrapping as GlTextureWrapping,
};
use crate::opengl::vertex_array::{
DataType as VertexArrayDataType,
@@ -64,19 +68,26 @@ use crate::opengl::{
use crate::projection::{ClipVolume, Projection};
use crate::renderer::opengl::vertex::{AttributeComponentType, Vertex};
use crate::renderer::RENDER_PHASE;
-use crate::texture::{Id as TextureId, Texture};
-use crate::transform::{Position, Scale};
+use crate::texture::{
+ Filtering as TextureFiltering,
+ Properties as TextureProperties,
+ Wrapping as TextureWrapping,
+};
+use crate::transform::{Scale, WorldPosition};
use crate::util::{defer, Defer, RefOrValue};
use crate::vector::{Vec2, Vec3};
use crate::window::Window;
mod vertex;
+const AMBIENT_MAP_TEXTURE_UNIT: u32 = 0;
+const DIFFUSE_MAP_TEXTURE_UNIT: u32 = 1;
+const SPECULAR_MAP_TEXTURE_UNIT: u32 = 2;
+
type RenderableEntity<'a> = (
- &'a Mesh,
- &'a Material,
+ &'a Model,
Option<&'a MaterialFlags>,
- Option<&'a Position>,
+ Option<&'a WorldPosition>,
Option<&'a Scale>,
Option<&'a DrawFlags>,
Option<&'a GlObjects>,
@@ -134,33 +145,31 @@ fn initialize(window: Single<Window>)
enable(Capability::MultiSample);
}
+#[tracing::instrument(skip_all)]
#[allow(clippy::too_many_arguments)]
fn render(
query: Query<RenderableEntity<'_>, (Without<NoDraw>,)>,
- point_light_query: Query<(&PointLight,)>,
+ point_light_query: Query<(&PointLight, &WorldPosition)>,
directional_lights: Query<(&DirectionalLight,)>,
- camera_query: Query<(&Camera, &Position, &ActiveCamera)>,
+ camera_query: Query<(&Camera, &WorldPosition, &ActiveCamera)>,
window: Single<Window>,
global_light: Single<GlobalLight>,
+ assets: Single<Assets>,
mut gl_objects: Local<GlobalGlObjects>,
mut actions: Actions,
)
{
- let Some((camera, camera_pos, _)) = camera_query.iter().next() else {
+ let Some((camera, camera_world_pos, _)) = camera_query.iter().next() else {
tracing::warn!("No current camera. Nothing will be rendered");
return;
};
- let point_lights = point_light_query
- .iter()
- .map(|(point_light,)| point_light)
- .collect::<Vec<_>>();
-
let directional_lights = directional_lights.iter().collect::<Vec<_>>();
let GlobalGlObjects {
shader_program,
textures: gl_textures,
+ default_1x1_texture: default_1x1_gl_texture,
} = &mut *gl_objects;
let shader_program =
@@ -168,18 +177,23 @@ fn render(
clear_buffers(BufferClearMask::COLOR | BufferClearMask::DEPTH);
- for (
+ 'subject_loop: for (
euid,
- (mesh, material, material_flags, position, scale, draw_flags, gl_objects),
+ (model, material_flags, position, scale, draw_flags, gl_objects),
) in query.iter_with_euids()
{
+ let Some(model_data) = assets.get(&model.asset_handle) else {
+ tracing::trace!("Missing model asset");
+ continue;
+ };
+
let material_flags = material_flags
.map(|material_flags| material_flags.clone())
.unwrap_or_default();
let gl_objs = match gl_objects.as_deref() {
Some(gl_objs) => RefOrValue::Ref(gl_objs),
- None => RefOrValue::Value(Some(GlObjects::new(&mesh))),
+ None => RefOrValue::Value(Some(GlObjects::new(&model_data.mesh))),
};
defer!(|gl_objs| {
@@ -195,34 +209,82 @@ fn render(
},
shader_program,
&camera,
- &camera_pos,
+ &camera_world_pos,
window.size().expect("Failed to get window size"),
);
+ if model_data.materials.len() > 1 {
+ tracing::warn!(concat!(
+ "Multiple model materials are not supported ",
+ "so only the first material will be used"
+ ));
+ }
+
+ let material = match model_data.materials.values().next() {
+ Some(material) => material,
+ None => {
+ tracing::warn!("Model has no materials. Using default material");
+
+ &Material::default()
+ }
+ };
+
apply_light(
&material,
&material_flags,
&global_light,
shader_program,
- point_lights.as_slice(),
+ (point_light_query.iter(), point_light_query.iter().count()),
directional_lights
.iter()
.map(|(dir_light,)| &**dir_light)
.collect::<Vec<_>>()
.as_slice(),
- &camera_pos,
+ &camera_world_pos,
);
- for (index, texture) in material.textures.iter().enumerate() {
- let gl_texture = gl_textures
- .entry(texture.id())
- .or_insert_with(|| create_gl_texture(texture));
+ let material_texture_maps = [
+ (&material.ambient_map, AMBIENT_MAP_TEXTURE_UNIT),
+ (&material.diffuse_map, DIFFUSE_MAP_TEXTURE_UNIT),
+ (&material.specular_map, SPECULAR_MAP_TEXTURE_UNIT),
+ ];
- let texture_unit = TextureUnit::from_num(index).expect("Too many textures");
+ for (texture, texture_unit) in material_texture_maps {
+ let Some(texture) = texture else {
+ let gl_texture = default_1x1_gl_texture.get_or_insert_with(|| {
+ create_gl_texture(
+ &Image::from_color(1, Color::WHITE_U8),
+ &TextureProperties::default(),
+ )
+ });
- set_active_texture_unit(texture_unit);
+ gl_texture.bind_to_texture_unit(texture_unit);
- gl_texture.bind();
+ continue;
+ };
+
+ let texture_image_asset_id = texture.asset_handle.id();
+
+ let gl_texture = match gl_textures.get(&texture_image_asset_id) {
+ Some(gl_texture) => gl_texture,
+ None => {
+ let Some(image) = assets.get::<Image>(&texture.asset_handle) else {
+ tracing::trace!("Missing texture asset");
+ continue 'subject_loop;
+ };
+
+ gl_textures.insert(
+ texture_image_asset_id,
+ create_gl_texture(image, &texture.properties),
+ );
+
+ gl_textures
+ .get(&texture.asset_handle.id())
+ .expect("Not possible")
+ }
+ };
+
+ gl_texture.bind_to_texture_unit(texture_unit);
}
shader_program.activate();
@@ -251,7 +313,8 @@ fn render(
struct GlobalGlObjects
{
shader_program: Option<GlShaderProgram>,
- textures: HashMap<TextureId, GlTexture>,
+ textures: HashMap<AssetId, GlTexture>,
+ default_1x1_texture: Option<GlTexture>,
}
fn set_viewport(position: Vec2<u32>, size: Dimens<u32>)
@@ -278,17 +341,31 @@ fn draw_mesh(gl_objects: &GlObjects)
}
}
-fn create_gl_texture(texture: &Texture) -> GlTexture
+fn create_gl_texture(image: &Image, texture_properties: &TextureProperties) -> GlTexture
{
let mut gl_texture = GlTexture::new();
gl_texture.generate(
- *texture.dimensions(),
- texture.image().as_bytes(),
- texture.pixel_data_format(),
+ image.dimensions(),
+ image.as_bytes(),
+ match image.color_type() {
+ ImageColorType::Rgb8 => GlTexturePixelDataFormat::Rgb8,
+ ImageColorType::Rgba8 => GlTexturePixelDataFormat::Rgba8,
+ _ => {
+ unimplemented!();
+ }
+ },
);
- gl_texture.apply_properties(texture.properties());
+ gl_texture.set_wrap(texture_wrapping_to_gl(texture_properties.wrap));
+
+ gl_texture.set_magnifying_filter(texture_filtering_to_gl(
+ texture_properties.magnifying_filter,
+ ));
+
+ gl_texture.set_minifying_filter(texture_filtering_to_gl(
+ texture_properties.minifying_filter,
+ ));
gl_texture
}
@@ -447,16 +524,16 @@ fn apply_transformation_matrices(
transformation: Transformation,
gl_shader_program: &mut GlShaderProgram,
camera: &Camera,
- camera_pos: &Position,
+ camera_world_pos: &WorldPosition,
window_size: Dimens<u32>,
)
{
gl_shader_program
- .set_uniform_matrix_4fv(c"model", &create_transformation_matrix(transformation));
+ .set_uniform(c"model", &create_transformation_matrix(transformation));
- let view_matrix = create_view_matrix(camera, &camera_pos.position);
+ let view_matrix = create_view_matrix(camera, &camera_world_pos.position);
- gl_shader_program.set_uniform_matrix_4fv(c"view", &view_matrix);
+ gl_shader_program.set_uniform(c"view", &view_matrix);
#[allow(clippy::cast_precision_loss)]
let proj_matrix = match &camera.projection {
@@ -464,27 +541,33 @@ fn apply_transformation_matrices(
window_size.width as f32 / window_size.height as f32,
ClipVolume::NegOneToOne,
),
- Projection::Orthographic(orthographic_proj) => {
- orthographic_proj.to_matrix_rh(&camera_pos.position, ClipVolume::NegOneToOne)
- }
+ Projection::Orthographic(orthographic_proj) => orthographic_proj
+ .to_matrix_rh(&camera_world_pos.position, ClipVolume::NegOneToOne),
};
- gl_shader_program.set_uniform_matrix_4fv(c"projection", &proj_matrix);
+ gl_shader_program.set_uniform(c"projection", &proj_matrix);
}
-fn apply_light<PointLightHolder>(
+fn apply_light<'point_light>(
material: &Material,
material_flags: &MaterialFlags,
global_light: &GlobalLight,
gl_shader_program: &mut GlShaderProgram,
- point_lights: &[PointLightHolder],
+ (point_light_iter, point_light_cnt): (
+ impl Iterator<
+ Item = (
+ ComponentHandle<'point_light, PointLight>,
+ ComponentHandle<'point_light, WorldPosition>,
+ ),
+ >,
+ usize,
+ ),
directional_lights: &[&DirectionalLight],
- camera_pos: &Position,
-) where
- PointLightHolder: Deref<Target = PointLight>,
+ camera_world_pos: &WorldPosition,
+)
{
debug_assert!(
- point_lights.len() < 64,
+ point_light_cnt < 64,
"Shader cannot handle more than 64 point lights"
);
@@ -494,7 +577,7 @@ fn apply_light<PointLightHolder>(
);
for (dir_light_index, dir_light) in directional_lights.iter().enumerate() {
- gl_shader_program.set_uniform_vec_3fv(
+ gl_shader_program.set_uniform(
&create_light_uniform_name(
"directional_lights",
dir_light_index,
@@ -514,78 +597,68 @@ fn apply_light<PointLightHolder>(
// There probably won't be more than 2147483648 directional lights
#[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)]
gl_shader_program
- .set_uniform_1i(c"directional_light_cnt", directional_lights.len() as i32);
+ .set_uniform(c"directional_light_cnt", &(directional_lights.len() as i32));
- for (point_light_index, point_light) in point_lights.iter().enumerate() {
- gl_shader_program.set_uniform_vec_3fv(
+ for (point_light_index, (point_light, point_light_world_pos)) in
+ point_light_iter.enumerate()
+ {
+ gl_shader_program.set_uniform(
&create_light_uniform_name("point_lights", point_light_index, "position"),
- &point_light.position,
+ &(point_light_world_pos.position + point_light.local_position),
);
set_light_phong_uniforms(
gl_shader_program,
"point_lights",
point_light_index,
- &**point_light,
+ &*point_light,
);
set_light_attenuation_uniforms(
gl_shader_program,
"point_lights",
point_light_index,
- point_light,
+ &*point_light,
);
}
// There probably won't be more than 2147483648 point lights
#[allow(clippy::cast_possible_wrap, clippy::cast_possible_truncation)]
- gl_shader_program.set_uniform_1i(c"point_light_cnt", point_lights.len() as i32);
+ gl_shader_program.set_uniform(c"point_light_cnt", &(point_light_cnt as i32));
- gl_shader_program.set_uniform_vec_3fv(
+ gl_shader_program.set_uniform(
c"material.ambient",
- &if material_flags.use_ambient_color {
+ &Vec3::from(if material_flags.use_ambient_color {
material.ambient.clone()
} else {
global_light.ambient.clone()
- }
- .into(),
+ }),
);
gl_shader_program
- .set_uniform_vec_3fv(c"material.diffuse", &material.diffuse.clone().into());
+ .set_uniform(c"material.diffuse", &Vec3::from(material.diffuse.clone()));
#[allow(clippy::cast_possible_wrap)]
gl_shader_program
- .set_uniform_vec_3fv(c"material.specular", &material.specular.clone().into());
-
- let texture_map = material
- .textures
- .iter()
- .enumerate()
- .map(|(index, texture)| (texture.id(), index))
- .collect::<HashMap<_, _>>();
+ .set_uniform(c"material.specular", &Vec3::from(material.specular.clone()));
#[allow(clippy::cast_possible_wrap)]
- gl_shader_program.set_uniform_1i(
- c"material.ambient_map",
- *texture_map.get(&material.ambient_map).unwrap() as i32,
- );
+ gl_shader_program
+ .set_uniform(c"material.ambient_map", &(AMBIENT_MAP_TEXTURE_UNIT as i32));
#[allow(clippy::cast_possible_wrap)]
- gl_shader_program.set_uniform_1i(
- c"material.diffuse_map",
- *texture_map.get(&material.diffuse_map).unwrap() as i32,
- );
+ gl_shader_program
+ .set_uniform(c"material.diffuse_map", &(DIFFUSE_MAP_TEXTURE_UNIT as i32));
#[allow(clippy::cast_possible_wrap)]
- gl_shader_program.set_uniform_1i(
+ gl_shader_program.set_uniform(
c"material.specular_map",
- *texture_map.get(&material.specular_map).unwrap() as i32,
+ &(SPECULAR_MAP_TEXTURE_UNIT as i32),
);
- gl_shader_program.set_uniform_1fv(c"material.shininess", material.shininess);
+ gl_shader_program.set_uniform(c"material.shininess", &material.shininess);
- gl_shader_program.set_uniform_vec_3fv(c"view_pos", &camera_pos.position);
+ gl_shader_program.set_uniform(c"view_pos", &camera_world_pos.position);
}
fn set_light_attenuation_uniforms(
@@ -595,27 +668,27 @@ fn set_light_attenuation_uniforms(
light: &PointLight,
)
{
- gl_shader_program.set_uniform_1fv(
+ gl_shader_program.set_uniform(
&create_light_uniform_name(
light_array,
light_index,
"attenuation_props.constant",
),
- light.attenuation_params.constant,
+ &light.attenuation_params.constant,
);
- gl_shader_program.set_uniform_1fv(
+ gl_shader_program.set_uniform(
&create_light_uniform_name(light_array, light_index, "attenuation_props.linear"),
- light.attenuation_params.linear,
+ &light.attenuation_params.linear,
);
- gl_shader_program.set_uniform_1fv(
+ gl_shader_program.set_uniform(
&create_light_uniform_name(
light_array,
light_index,
"attenuation_props.quadratic",
),
- light.attenuation_params.quadratic,
+ &light.attenuation_params.quadratic,
);
}
@@ -626,14 +699,14 @@ fn set_light_phong_uniforms(
light: &impl Light,
)
{
- gl_shader_program.set_uniform_vec_3fv(
+ gl_shader_program.set_uniform(
&create_light_uniform_name(light_array, light_index, "phong.diffuse"),
- &light.diffuse().clone().into(),
+ &Vec3::from(light.diffuse().clone()),
);
- gl_shader_program.set_uniform_vec_3fv(
+ gl_shader_program.set_uniform(
&create_light_uniform_name(light_array, light_index, "phong.specular"),
- &light.specular().clone().into(),
+ &Vec3::from(light.specular().clone()),
);
}
@@ -749,3 +822,23 @@ fn create_transformation_matrix(transformation: Transformation) -> Matrix<f32, 4
matrix
}
+
+#[inline]
+fn texture_wrapping_to_gl(texture_wrapping: TextureWrapping) -> GlTextureWrapping
+{
+ match texture_wrapping {
+ TextureWrapping::Repeat => GlTextureWrapping::Repeat,
+ TextureWrapping::MirroredRepeat => GlTextureWrapping::MirroredRepeat,
+ TextureWrapping::ClampToEdge => GlTextureWrapping::ClampToEdge,
+ TextureWrapping::ClampToBorder => GlTextureWrapping::ClampToBorder,
+ }
+}
+
+#[inline]
+fn texture_filtering_to_gl(texture_filtering: TextureFiltering) -> GlTextureFiltering
+{
+ match texture_filtering {
+ TextureFiltering::Linear => GlTextureFiltering::Linear,
+ TextureFiltering::Nearest => GlTextureFiltering::Nearest,
+ }
+}
diff --git a/engine/src/texture.rs b/engine/src/texture.rs
index 4a4fe86..d02b9ff 100644
--- a/engine/src/texture.rs
+++ b/engine/src/texture.rs
@@ -1,194 +1,34 @@
-use std::fmt::Display;
-use std::path::Path;
-use std::sync::atomic::{AtomicU32, Ordering};
-
-use image::io::Reader as ImageReader;
-use image::{DynamicImage, ImageError, Rgb, RgbImage};
-
-use crate::color::Color;
-use crate::data_types::dimens::Dimens;
-use crate::opengl::texture::PixelDataFormat;
-use crate::util::builder;
-
-static NEXT_ID: AtomicU32 = AtomicU32::new(0);
-
-mod reexports
-{
- pub use crate::opengl::texture::{Filtering, Wrapping};
-}
-
-pub use reexports::*;
+use crate::asset::Handle as AssetHandle;
+use crate::image::Image;
+use crate::builder;
#[derive(Debug, Clone)]
+#[non_exhaustive]
pub struct Texture
{
- id: Id,
- image: DynamicImage,
- pixel_data_format: PixelDataFormat,
- dimensions: Dimens<u32>,
- properties: Properties,
+ pub asset_handle: AssetHandle<Image>,
+ pub properties: Properties,
}
impl Texture
{
- pub fn builder() -> Builder
- {
- Builder::default()
- }
-
- /// Opens a texture image.
- ///
- /// # Errors
- /// Will return `Err` if:
- /// - Opening the image fails
- /// - The image data is not 8-bit/color RGB
- pub fn open(path: &Path) -> Result<Self, Error>
- {
- Self::builder().open(path)
- }
-
- #[must_use]
- pub fn new_from_color(dimensions: &Dimens<u32>, color: &Color<u8>) -> Self
- {
- Self::builder().build_with_single_color(dimensions, color)
- }
-
- #[must_use]
- pub fn id(&self) -> Id
- {
- self.id
- }
-
- #[must_use]
- pub fn properties(&self) -> &Properties
- {
- &self.properties
- }
-
- pub fn properties_mut(&mut self) -> &mut Properties
- {
- &mut self.properties
- }
-
- #[must_use]
- pub fn dimensions(&self) -> &Dimens<u32>
- {
- &self.dimensions
- }
-
- #[must_use]
- pub fn pixel_data_format(&self) -> PixelDataFormat
- {
- self.pixel_data_format
- }
-
- #[must_use]
- pub fn image(&self) -> &DynamicImage
- {
- &self.image
- }
-}
-
-impl Drop for Texture
-{
- fn drop(&mut self)
- {
- NEXT_ID.fetch_sub(1, Ordering::Relaxed);
- }
-}
-
-/// Texture builder.
-#[derive(Debug, Default, Clone)]
-pub struct Builder
-{
- properties: Properties,
-}
-
-impl Builder
-{
- pub fn properties(mut self, properties: Properties) -> Self
+ pub fn new(asset_handle: AssetHandle<Image>) -> Self
{
- self.properties = properties;
- self
- }
-
- /// Opens a image as a texture.
- ///
- /// # Errors
- /// Will return `Err` if:
- /// - Opening the image fails
- /// - Decoding the image fails
- /// - The image data is in a unsupported format
- pub fn open(&self, path: &(impl AsRef<Path> + ?Sized)) -> Result<Texture, Error>
- {
- let image = ImageReader::open(path)
- .map_err(Error::OpenImageFailed)?
- .decode()
- .map_err(Error::DecodeImageFailed)?;
-
- let pixel_data_format = match &image {
- DynamicImage::ImageRgb8(_) => PixelDataFormat::Rgb8,
- DynamicImage::ImageRgba8(_) => PixelDataFormat::Rgba8,
- _ => {
- return Err(Error::UnsupportedImageDataFormat);
- }
- };
-
- let dimensions = Dimens {
- width: image.width(),
- height: image.height(),
- };
-
- let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
-
- Ok(Texture {
- id: Id::new(id),
- image,
- pixel_data_format,
- dimensions,
- properties: self.properties.clone(),
- })
+ Self {
+ asset_handle,
+ properties: Properties::default(),
+ }
}
- #[must_use]
- pub fn build_with_single_color(
- &self,
- dimensions: &Dimens<u32>,
- color: &Color<u8>,
- ) -> Texture
+ pub fn with_properties(
+ asset_handle: AssetHandle<Image>,
+ properties: Properties,
+ ) -> Self
{
- let image = RgbImage::from_pixel(
- dimensions.width,
- dimensions.height,
- Rgb([color.red, color.green, color.blue]),
- );
-
- let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
-
- Texture {
- id: Id::new(id),
- image: image.into(),
- pixel_data_format: PixelDataFormat::Rgb8,
- dimensions: *dimensions,
- properties: self.properties.clone(),
- }
+ Self { asset_handle, properties }
}
}
-/// Texture error.
-#[derive(Debug, thiserror::Error)]
-pub enum Error
-{
- #[error("Failed to open texture image")]
- OpenImageFailed(#[source] std::io::Error),
-
- #[error("Failed to decode texture image")]
- DecodeImageFailed(#[source] ImageError),
-
- #[error("Unsupported image data format")]
- UnsupportedImageDataFormat,
-}
-
builder! {
/// Texture properties
#[builder(name = PropertiesBuilder, derives=(Debug, Clone))]
@@ -230,25 +70,21 @@ impl Default for PropertiesBuilder
}
}
-/// Texture ID.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
-pub struct Id
-{
- id: u32,
-}
-
-impl Id
+#[non_exhaustive]
+pub enum Filtering
{
- fn new(id: u32) -> Self
- {
- Self { id }
- }
+ Nearest,
+ Linear,
}
-impl Display for Id
+/// Texture wrapping.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
+#[non_exhaustive]
+pub enum Wrapping
{
- fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result
- {
- self.id.fmt(formatter)
- }
+ Repeat,
+ MirroredRepeat,
+ ClampToEdge,
+ ClampToBorder,
}
diff --git a/engine/src/transform.rs b/engine/src/transform.rs
index 5e5e296..7c0c941 100644
--- a/engine/src/transform.rs
+++ b/engine/src/transform.rs
@@ -2,14 +2,14 @@ use ecs::Component;
use crate::vector::Vec3;
-/// A position in 3D space.
+/// A position in world space.
#[derive(Debug, Default, Clone, Copy, Component)]
-pub struct Position
+pub struct WorldPosition
{
pub position: Vec3<f32>,
}
-impl From<Vec3<f32>> for Position
+impl From<Vec3<f32>> for WorldPosition
{
fn from(position: Vec3<f32>) -> Self
{
diff --git a/engine/src/util.rs b/engine/src/util.rs
index 0f6c78c..cc4677d 100644
--- a/engine/src/util.rs
+++ b/engine/src/util.rs
@@ -25,6 +25,18 @@ macro_rules! or {
pub(crate) use or;
+#[macro_export]
+macro_rules! expand_map_opt {
+ ($in: tt, no_occurance=($($no_occurance: tt)*), occurance=($($occurance: tt)*)) => {
+ $($occurance)*
+ };
+
+ (, no_occurance=($($no_occurance: tt)*), occurance=($($occurance: tt)*)) => {
+ $($no_occurance)*
+ };
+}
+
+#[macro_export]
macro_rules! builder {
(
$(#[doc = $doc: literal])*
@@ -36,7 +48,8 @@ macro_rules! builder {
$visibility: vis struct $name: ident
{
$(
- $(#[$field_attr: meta])*
+ $(#[doc = $field_doc: literal])*
+ $(#[builder(skip_generate_fn$($field_skip_generate_fn: tt)?)])?
$field_visibility: vis $field: ident: $field_type: ty,
)*
}
@@ -46,7 +59,7 @@ macro_rules! builder {
$visibility struct $name
{
$(
- $(#[$field_attr])*
+ $(#[doc = $field_doc])*
$field_visibility $field: $field_type,
)*
}
@@ -62,12 +75,18 @@ macro_rules! builder {
impl $builder_name
{
$(
- #[must_use]
- $visibility fn $field(mut self, $field: $field_type) -> Self
- {
- self.$field = $field;
- self
- }
+ $crate::expand_map_opt!(
+ $(true $($field_skip_generate_fn)?)?,
+ no_occurance=(
+ #[must_use]
+ $visibility fn $field(mut self, $field: $field_type) -> Self
+ {
+ self.$field = $field;
+ self
+ }
+ ),
+ occurance=()
+ );
)*
#[must_use]
@@ -82,6 +101,7 @@ macro_rules! builder {
impl From<$name> for $builder_name
{
+ #[allow(unused_variables)]
fn from(built: $name) -> Self
{
Self {
@@ -94,8 +114,6 @@ macro_rules! builder {
};
}
-pub(crate) use builder;
-
pub enum RefOrValue<'a, T>
{
Ref(&'a T),
diff --git a/engine/src/work_queue.rs b/engine/src/work_queue.rs
new file mode 100644
index 0000000..7226c7d
--- /dev/null
+++ b/engine/src/work_queue.rs
@@ -0,0 +1,44 @@
+use std::marker::PhantomData;
+use std::sync::mpsc::{channel as mpsc_channel, Sender as MpscSender};
+use std::thread::JoinHandle as ThreadHandle;
+
+pub struct Work<UserData: Send + Sync + 'static>
+{
+ pub func: fn(UserData),
+ pub user_data: UserData,
+}
+
+#[derive(Debug)]
+pub struct WorkQueue<UserData: Send + Sync + 'static>
+{
+ work_sender: MpscSender<Work<UserData>>,
+ _thread: ThreadHandle<()>,
+ _pd: PhantomData<UserData>,
+}
+
+impl<UserData: Send + Sync + 'static> WorkQueue<UserData>
+{
+ pub fn new() -> Self
+ {
+ let (work_sender, work_receiver) = mpsc_channel::<Work<UserData>>();
+
+ Self {
+ work_sender,
+ _thread: std::thread::spawn(move || {
+ let work_receiver = work_receiver;
+
+ while let Ok(work) = work_receiver.recv() {
+ (work.func)(work.user_data);
+ }
+ }),
+ _pd: PhantomData,
+ }
+ }
+
+ pub fn add_work(&self, work: Work<UserData>)
+ {
+ if self.work_sender.send(work).is_err() {
+ tracing::error!("Cannot add work to work queue. Work queue thread is dead");
+ }
+ }
+}
diff --git a/src/main.rs b/src/main.rs
index 1d0d33a..d5154ae 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,7 +1,7 @@
use std::error::Error;
-use std::fs::read_to_string;
use std::path::Path;
+use engine::asset::Assets;
use engine::camera::fly::{
Extension as FlyCameraExtension,
Fly as FlyCamera,
@@ -9,20 +9,20 @@ use engine::camera::fly::{
};
use engine::camera::{Active as ActiveCamera, Camera};
use engine::color::Color;
-use engine::data_types::dimens::Dimens;
+use engine::data_types::dimens::{Dimens, Dimens3};
+use engine::ecs::actions::Actions;
use engine::ecs::phase::START as START_PHASE;
use engine::ecs::sole::Single;
-use engine::file_format::wavefront::mtl::parse as parse_mtl;
-use engine::file_format::wavefront::obj::parse as parse_obj;
use engine::input::Extension as InputExtension;
use engine::lighting::{AttenuationParams, GlobalLight, PointLight};
-use engine::material::{Builder as MaterialBuilder, Flags as MaterialFlags};
+use engine::material::{Flags as MaterialFlags, Material};
use engine::mesh::cube::{
create as cube_mesh_create,
CreationSpec as CubeMeshCreationSpec,
};
+use engine::model::{Data as ModelData, Model};
use engine::renderer::opengl::Extension as OpenglRendererExtension;
-use engine::transform::Position;
+use engine::transform::WorldPosition;
use engine::vector::Vec3;
use engine::window::{
Builder as WindowBuilder,
@@ -59,53 +59,9 @@ fn main() -> Result<(), Box<dyn Error>>
let mut engine = Engine::new();
- let teapot_obj =
- parse_obj(&read_to_string(Path::new(RESOURCE_DIR).join("teapot.obj"))?)?;
-
- let teapot_mat_name = teapot_obj
- .faces
- .first()
- .and_then(|face| face.material_name.as_ref());
-
- let teapot_mats = teapot_obj.read_and_parse_material_libs(parse_mtl)?;
-
- let teapot_mat = teapot_mats
- .into_iter()
- .find(|mat| Some(&mat.name) == teapot_mat_name)
- .ok_or("Teapot material was not found")?;
-
- engine.spawn((
- teapot_obj.to_mesh()?,
- teapot_mat.material,
- Position::from(Vec3 { x: 1.6, y: 0.0, z: 0.0 }),
- ));
-
- engine.spawn((
- PointLight::builder()
- .position(Vec3 { x: -6.0, y: 3.0, z: 3.0 })
- .diffuse(YELLOW)
- .attenuation_params(AttenuationParams {
- linear: 0.045,
- quadratic: 0.0075,
- ..Default::default()
- })
- .build(),
- Position::from(Vec3 { x: -6.0, y: 3.0, z: 3.0 }),
- cube_mesh_create(
- CubeMeshCreationSpec::builder()
- .width(2.0)
- .height(2.0)
- .depth(2.0)
- .build(),
- |face_verts, _, _| face_verts,
- ),
- MaterialBuilder::new().ambient(YELLOW * 5.0).build(),
- MaterialFlags::builder().use_ambient_color(true).build(),
- ));
-
engine.spawn((
Camera::default(),
- Position {
+ WorldPosition {
position: Vec3 { x: 0.0, y: 0.0, z: 3.0 },
},
ActiveCamera,
@@ -114,7 +70,7 @@ fn main() -> Result<(), Box<dyn Error>>
engine.add_sole(GlobalLight::default())?;
- engine.register_system(*START_PHASE, prepare_window);
+ engine.register_system(*START_PHASE, init);
engine.add_extension(OpenglRendererExtension::default());
@@ -135,7 +91,42 @@ fn main() -> Result<(), Box<dyn Error>>
Ok(())
}
-fn prepare_window(window: Single<Window>)
+fn init(window: Single<Window>, mut assets: Single<Assets>, mut actions: Actions)
{
window.set_cursor_mode(CursorMode::Disabled).unwrap();
+
+ actions.spawn((
+ PointLight::builder()
+ .diffuse(YELLOW)
+ .attenuation_params(AttenuationParams {
+ linear: 0.045,
+ quadratic: 0.0075,
+ ..Default::default()
+ })
+ .build(),
+ WorldPosition::from(Vec3 { x: -6.0, y: 3.0, z: 3.0 }),
+ Model::new(
+ assets.store_with_name(
+ "light_cube",
+ ModelData::builder()
+ .mesh(cube_mesh_create(
+ CubeMeshCreationSpec::builder()
+ .dimens(Dimens3::from(2.0))
+ .build(),
+ |face_verts, _, _| face_verts,
+ ))
+ .material(
+ "surface",
+ Material::builder().ambient(YELLOW * 5.0).build(),
+ )
+ .build(),
+ ),
+ ),
+ MaterialFlags::builder().use_ambient_color(true).build(),
+ ));
+
+ actions.spawn((
+ Model::new(assets.load::<ModelData>(Path::new(RESOURCE_DIR).join("teapot.obj"))),
+ WorldPosition::from(Vec3 { x: 1.6, y: 0.0, z: 0.0 }),
+ ));
}