Compare commits

..

No commits in common. "main" and "feat/shadow-maps" have entirely different histories.

315 changed files with 7363 additions and 14933 deletions

View File

@ -1,10 +1,5 @@
name: CI
env:
# Runners don't expose the TSC but we want to make sure these tests work, so we
# can ignore it.
TRACY_NO_INVARIANT_CHECK: 1
on:
push:
branches:

5
.gitmodules vendored
View File

@ -1,6 +1,3 @@
[submodule "lyra-scripting/elua"]
path = crates/lyra-scripting/elua
path = lyra-scripting/elua
url = ../elua.git # git@git.seanomik.net:SeanOMik/elua.git
[submodule "wgsl-preprocessor"]
path = crates/wgsl-preprocessor
url = git@git.seanomik.net:SeanOMik/wgsl-preprocessor.git

18
.vscode/launch.json vendored
View File

@ -4,24 +4,6 @@
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "lldb",
"request": "launch",
"name": "Debug lyra lua-scripting",
"cargo": {
"args": [
"build",
"--manifest-path", "${workspaceFolder}/examples/lua-scripting/Cargo.toml"
//"--bin=testbed",
],
"filter": {
"name": "lua-scripting",
"kind": "bin"
}
},
"args": [],
"cwd": "${workspaceFolder}/examples/lua-scripting"
},
{
"type": "lldb",
"request": "launch",

3276
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -5,15 +5,20 @@ edition = "2021"
[workspace]
members = [
"crates/*",
"examples/2d",
"lyra-resource",
"lyra-ecs",
"lyra-reflect",
"lyra-scripting",
"lyra-game",
"lyra-math",
"lyra-scene",
"examples/testbed",
"examples/many-lights",
"examples/fixed-timestep-rotating-model",
"examples/lua-scripting",
"examples/many-lights",
"examples/shadows",
"examples/simple_scene",
"examples/testbed",
"examples/shadows",
]
[features]
@ -22,11 +27,11 @@ lua_scripting = ["scripting", "lyra-scripting/lua"]
tracy = ["lyra-game/tracy"]
[dependencies]
lyra-game = { path = "crates/lyra-game" }
lyra-scripting = { path = "crates/lyra-scripting", optional = true }
lyra-game = { path = "lyra-game" }
lyra-scripting = { path = "lyra-scripting", optional = true }
#[profile.dev]
#opt-level = 1
[profile.release]
debug = true
debug = true

View File

@ -1,168 +0,0 @@
use std::ops::{Deref, DerefMut};
use crate::{query::Fetch, Entity, World};
use super::{DynamicType, FetchDynamicTypeUnsafe, QueryDynamicType};
/// A view of dynamic types (types that are not known to Rust).
///
/// This view gives you the ability to iterate over types that are unknown to Rust, which we call
/// dynamic types. This is great for embedding with a scripting language (*cough* *cough* WASM)
/// since Rust doesn't actually need to know the types of what its iterating over.
pub struct DynamicViewOne<'a> {
world: &'a World,
inner: DynamicViewOneOwned,
}
impl<'a> Deref for DynamicViewOne<'a> {
type Target = DynamicViewOneOwned;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl<'a> DerefMut for DynamicViewOne<'a> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}
}
impl<'a> DynamicViewOne<'a> {
pub fn new(world: &'a World, entity: Entity) -> Self {
Self {
world,
inner: DynamicViewOneOwned::new(entity)
}
}
/// Create a new [`DynamicViewOne`] with queries.
pub fn new_with(world: &'a World, entity: Entity, queries: Vec<QueryDynamicType>) -> Self {
Self {
world,
inner: DynamicViewOneOwned::new_with(entity, queries)
}
}
pub fn get(self) -> Option<Vec<DynamicType>> {
self.inner.get(&self.world)
}
}
/// A variant of [`DynamicViewOne`] that doesn't store a borrow of the world.
#[derive(Clone)]
pub struct DynamicViewOneOwned {
pub entity: Entity,
pub queries: Vec<QueryDynamicType>
}
impl DynamicViewOneOwned {
pub fn new(entity: Entity) -> Self {
Self {
entity,
queries: vec![],
}
}
/// Create a new [`DynamicViewOne`] with queries.
pub fn new_with(entity: Entity, queries: Vec<QueryDynamicType>) -> Self {
Self {
entity,
queries
}
}
pub fn get(self, world: &World) -> Option<Vec<DynamicType>> {
dynamic_view_one_get_impl(world, &self.queries, self.entity)
}
}
fn dynamic_view_one_get_impl(world: &World, queries: &Vec<QueryDynamicType>, entity: Entity) -> Option<Vec<DynamicType>> {
let arch = world.entity_archetype(entity)?;
let aid = arch.entity_indexes().get(&entity)?;
// get all fetchers for the queries
let mut fetchers: Vec<FetchDynamicTypeUnsafe> = queries.iter()
.map(|q| unsafe { q.fetch(world, arch.id(), arch) } )
.collect();
let mut fetch_res = vec![];
for fetcher in fetchers.iter_mut() {
if !fetcher.can_visit_item(*aid) {
return None;
} else {
let i = unsafe { fetcher.get_item(*aid) };
fetch_res.push(i);
}
}
if fetch_res.is_empty() {
None
} else {
Some(fetch_res)
}
}
#[cfg(test)]
mod tests {
use std::{alloc::Layout, ptr::NonNull};
use crate::{World, ComponentInfo, DynTypeId, DynamicBundle, query::dynamic::QueryDynamicType};
use super::DynamicViewOne;
#[test]
fn single_dynamic_view_one_state() {
let comp_layout = Layout::new::<u32>();
let comp_info = ComponentInfo::new_unknown(Some("u32".to_string()), DynTypeId::Unknown(100), comp_layout);
let mut dynamic_bundle = DynamicBundle::default();
let comp = 50u32;
let ptr = NonNull::from(&comp).cast::<u8>();
dynamic_bundle.push_unknown(ptr, comp_info.clone());
let mut world = World::new();
let e = world.spawn(dynamic_bundle);
let query = QueryDynamicType::from_info(comp_info);
let view = DynamicViewOne::new_with(&world, e, vec![query]);
let view_row = view.get()
.expect("failed to get entity row");
assert_eq!(view_row.len(), 1);
let mut row_iter = view_row.iter();
let dynamic_type = row_iter.next().unwrap();
let component_data = unsafe { dynamic_type.ptr.cast::<u32>().as_ref() };
assert_eq!(*component_data, 50);
}
#[test]
fn single_dynamic_view_one() {
let comp_layout = Layout::new::<u32>();
let comp_info = ComponentInfo::new_unknown(Some("u32".to_string()), DynTypeId::Unknown(100), comp_layout);
let mut dynamic_bundle = DynamicBundle::default();
let comp = 50u32;
let ptr = NonNull::from(&comp).cast::<u8>();
dynamic_bundle.push_unknown(ptr, comp_info.clone());
let mut world = World::new();
let e = world.spawn(dynamic_bundle);
let query = QueryDynamicType::from_info(comp_info);
let view = DynamicViewOne::new_with(&world, e, vec![query]);
let view_row = view.get()
.expect("failed to get entity row");
assert_eq!(view_row.len(), 1);
let mut row_iter = view_row.iter();
let dynamic_type = row_iter.next().unwrap();
let component_data = unsafe { dynamic_type.ptr.cast::<u32>().as_ref() };
assert_eq!(*component_data, 50);
}
}

View File

@ -1,97 +0,0 @@
use std::marker::PhantomData;
use crate::{query::{AsFilter, AsQuery, Fetch, Filter, Query}, Component, ComponentColumn, DynTypeId, Tick, World};
pub struct ChangedFetcher<'a, T> {
col: &'a ComponentColumn,
tick: Tick,
_phantom: PhantomData<&'a T>,
}
impl<'a, T> Fetch<'a> for ChangedFetcher<'a, T>
where
T: 'a,
{
type Item = bool;
fn dangling() -> Self {
unreachable!()
}
unsafe fn get_item(&mut self, entity: crate::world::ArchetypeEntityId) -> Self::Item {
let tick = self.col.entity_ticks[entity.0 as usize];
*tick >= (*self.tick) - 1
}
}
/// A filter that fetches components that have changed.
///
/// Since [`AsQuery`] is implemented for `&T`, you can use this query like this:
/// ```nobuild
/// for ts in world.view::<&T>() {
/// println!("Got a &T!");
/// }
/// ```
pub struct Changed<T> {
type_id: DynTypeId,
_phantom: PhantomData<T>
}
impl<T: Component> Default for Changed<T> {
fn default() -> Self {
Self {
type_id: DynTypeId::of::<T>(),
_phantom: PhantomData,
}
}
}
// manually implemented to avoid a Copy bound on T
impl<T> Copy for Changed<T> {}
// manually implemented to avoid a Clone bound on T
impl<T> Clone for Changed<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T: Component> Changed<T> {
pub fn new() -> Self {
Self::default()
}
}
impl<T: Component> Query for Changed<T>
where
T: 'static
{
type Item<'a> = bool;
type Fetch<'a> = ChangedFetcher<'a, T>;
fn new() -> Self {
Changed::<T>::new()
}
fn can_visit_archetype(&self, archetype: &crate::archetype::Archetype) -> bool {
archetype.has_column(self.type_id)
}
unsafe fn fetch<'a>(&self, w: &'a World, a: &'a crate::archetype::Archetype, _: crate::Tick) -> Self::Fetch<'a> {
ChangedFetcher {
col: a.get_column(self.type_id).unwrap(),
tick: w.current_tick(),
_phantom: PhantomData::<&T>,
}
}
}
impl<T: Component> AsQuery for Changed<T> {
type Query = Self;
}
impl<T: Component> Filter for Changed<T> { }
impl<T: Component> AsFilter for Changed<T> {
type Filter = Self;
}

View File

@ -1,44 +0,0 @@
mod has;
use std::marker::PhantomData;
pub use has::*;
mod or;
pub use or::*;
mod not;
pub use not::*;
mod changed;
pub use changed::*;
use super::Fetch;
/// A fetcher that just returns a provided value
pub struct StaticFetcher<T: Clone> {
value: T,
}
impl<'a, T: Clone> StaticFetcher<T> {
pub fn new(value: T) -> Self {
Self {
value
}
}
}
impl<'a, T> Fetch<'a> for StaticFetcher<T>
where
T: Clone + 'a,
{
type Item = T;
fn dangling() -> Self {
unreachable!()
}
unsafe fn get_item(&mut self, _: crate::world::ArchetypeEntityId) -> Self::Item {
self.value.clone()
}
}

View File

@ -1,102 +0,0 @@
use std::ops::Deref;
use crate::{system::FnArgFetcher, Tick, World};
use super::{Fetch, Query, AsQuery};
/// Fetcher used to fetch the current tick of the world.
pub struct FetchWorldTick {
tick: Tick
}
impl<'a> Fetch<'a> for FetchWorldTick {
type Item = WorldTick;
fn dangling() -> Self {
unreachable!()
}
fn can_visit_item(&mut self, _entity: crate::ArchetypeEntityId) -> bool {
true
}
unsafe fn get_item(&mut self, _entity: crate::world::ArchetypeEntityId) -> Self::Item {
WorldTick(self.tick)
}
}
/// Query used to query the current tick of the world.
#[derive(Clone, Copy)]
pub struct QueryWorldTick;
impl Default for QueryWorldTick {
fn default() -> Self {
Self
}
}
impl Query for QueryWorldTick {
type Item<'a> = WorldTick;
type Fetch<'a> = FetchWorldTick;
const ALWAYS_FETCHES: bool = true;
fn new() -> Self {
QueryWorldTick
}
fn can_visit_archetype(&self, _archetype: &crate::archetype::Archetype) -> bool {
true
}
unsafe fn fetch<'a>(&self, world: &'a World, _archetype: &'a crate::archetype::Archetype, _tick: crate::Tick) -> Self::Fetch<'a> {
FetchWorldTick {
tick: world.current_tick()
}
}
unsafe fn fetch_world<'a>(&self, world: &'a World) -> Option<Self::Fetch<'a>> {
Some(FetchWorldTick {
tick: world.current_tick()
})
}
}
impl AsQuery for QueryWorldTick {
type Query = Self;
}
/// Type that can be used in an fn system for fetching the current world tick.
#[derive(Debug, Clone, Copy)]
pub struct WorldTick(Tick);
impl Deref for WorldTick {
type Target = Tick;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl AsQuery for WorldTick {
type Query = QueryWorldTick;
}
impl FnArgFetcher for WorldTick {
type State = ();
type Arg<'a, 'state> = WorldTick;
fn create_state(_: std::ptr::NonNull<World>) -> Self::State {
()
}
unsafe fn get<'a, 'state>(_: &'state mut Self::State, world: std::ptr::NonNull<World>) -> Self::Arg<'a, 'state> {
let world = world.as_ref();
WorldTick(world.current_tick())
}
fn apply_deferred(_: Self::State, _: std::ptr::NonNull<World>) {
}
}

View File

@ -1,216 +0,0 @@
use std::sync::Arc;
use atomic_refcell::AtomicRefCell;
use lyra_ecs::{query::{ResMut, WorldTick}, system::FnArgFetcher, Tick};
pub trait Event: Clone + Send + Sync + 'static {}
impl<T: Clone + Send + Sync + 'static> Event for T {}
/// A Vec with other Vecs in it to track relative age of items.
///
/// The vec has 3 levels, a `newest`, `medium` and `old`. Items are pushed to the `newest`
/// internal vec. When [`WaterfallVec::waterfall`] is called the items in `newest` are
/// put into `medium`, and items in `medium` goes to `old`.
///
/// By checking the items in each internal vec, you can see a relative age between the items.
/// The event system uses this to clear the `old` vec to ensure keep events for only two
/// frames at a time.
struct WaterfallVec<T> {
newest: Vec<T>,
medium: Vec<T>,
old: Vec<T>,
}
impl<T> Default for WaterfallVec<T> {
fn default() -> Self {
Self {
newest: Default::default(),
medium: Default::default(),
old: Default::default(),
}
}
}
impl<T> WaterfallVec<T> {
fn total_len(&self) -> usize {
self.newest.len() + self.medium.len() + self.old.len()
}
fn get(&self, mut i: usize) -> Option<&T> {
if i >= self.old.len() {
i -= self.old.len();
if i >= self.medium.len() {
i -= self.medium.len();
self.newest.get(i)
} else {
self.medium.get(i)
}
} else {
self.old.get(i)
}
}
/// Age elements.
///
/// This moves elements in `newest` to `medium` and elements in `medium` to `old`.
/// This is what drives the relative age of the [`WaterfallVec`].
fn waterfall(&mut self) {
self.old.append(&mut self.medium);
self.medium.append(&mut self.newest);
}
/// Push a new element to the newest queue.
fn push(&mut self, event: T) {
self.newest.push(event);
}
/// Clear oldest items.
fn clear_oldest(&mut self) {
self.old.clear();
}
}
pub struct Events<T: Event> {
events: Arc<AtomicRefCell<WaterfallVec<T>>>,
/// Used to track when the old events were last cleared.
last_cleared_at: Tick,
/// Used to indicate when the cursor in readers should be reset to zero.
/// This becomes true after the old events are cleared.
reset_cursor: bool,
}
impl<T: Event> Default for Events<T> {
fn default() -> Self {
Self { events: Default::default(), last_cleared_at: Default::default(), reset_cursor: false }
}
}
impl<T: Event> Events<T> {
pub fn new() -> Self {
Self::default()
}
pub fn push_event(&mut self, event: T) {
let mut events = self.events.borrow_mut();
events.push(event);
}
pub fn reader(&self) -> EventReader<T> {
EventReader {
events: self.events.clone(),
cursor: Arc::new(AtomicRefCell::new(0)),
}
}
pub fn writer(&self) -> EventWriter<T> {
EventWriter {
events: self.events.clone(),
}
}
}
pub struct EventReader<T: Event> {
events: Arc<AtomicRefCell<WaterfallVec<T>>>,
cursor: Arc<AtomicRefCell<usize>>,
}
impl<T: Event> EventReader<T> {
pub fn read(&self) -> Option<atomic_refcell::AtomicRef<T>> {
let events = self.events.borrow();
let mut cursor = self.cursor.borrow_mut();
if *cursor >= events.total_len() {
None
} else {
let e = atomic_refcell::AtomicRef::map(events,
|e| e.get(*cursor).unwrap());
*cursor += 1;
Some(e)
}
}
}
pub struct EventWriter<T: Event> {
events: Arc<AtomicRefCell<WaterfallVec<T>>>,
}
impl<T: Event> EventWriter<T> {
pub fn write(&self, event: T) {
let mut events = self.events.borrow_mut();
events.push(event);
}
}
/// Clean events of event type `T` every 2 ticks.
pub fn event_cleaner_system<T>(tick: WorldTick, mut events: ResMut<Events<T>>) -> anyhow::Result<()>
where
T: Event
{
let last_tick = *events.last_cleared_at;
let world_tick = **tick;
if last_tick + 2 < world_tick {
events.last_cleared_at = *tick;
events.reset_cursor = true;
let mut events = events.events.borrow_mut();
events.clear_oldest();
} else {
events.reset_cursor = false;
}
let mut events = events.events.borrow_mut();
events.waterfall();
Ok(())
}
impl<T: Event> FnArgFetcher for EventReader<T> {
type State = Arc<AtomicRefCell<usize>>;
type Arg<'a, 'state> = EventReader<T>;
fn create_state(_: std::ptr::NonNull<lyra_ecs::World>) -> Self::State {
Arc::new(AtomicRefCell::new(0))
}
unsafe fn get<'a, 'state>(state: &'state mut Self::State, world: std::ptr::NonNull<lyra_ecs::World>) -> Self::Arg<'a, 'state> {
let world = world.as_ref();
let events = world.get_resource::<Events<T>>()
.unwrap_or_else(|| panic!("world missing Events<{}> resource", std::any::type_name::<T>()));
if events.reset_cursor {
let mut state_num = state.borrow_mut();
*state_num = 0;
}
let reader = EventReader {
events: events.events.clone(),
cursor: state.clone(),
};
reader
}
fn apply_deferred(_: Self::State, _: std::ptr::NonNull<lyra_ecs::World>) { }
}
impl<T: Event> FnArgFetcher for EventWriter<T> {
type State = ();
type Arg<'a, 'state> = EventWriter<T>;
fn create_state(_: std::ptr::NonNull<lyra_ecs::World>) -> Self::State {
()
}
unsafe fn get<'a, 'state>(_: &'state mut Self::State, world: std::ptr::NonNull<lyra_ecs::World>) -> Self::Arg<'a, 'state> {
let world = world.as_ref();
let events = world.get_resource::<Events<T>>()
.unwrap_or_else(|| panic!("world missing Events<{}> resource", std::any::type_name::<T>()));
events.writer()
}
fn apply_deferred(_: Self::State, _: std::ptr::NonNull<lyra_ecs::World>) { }
}

View File

@ -1,235 +0,0 @@
use std::{cell::OnceCell, collections::VecDeque, ptr::NonNull};
use lyra_ecs::{system::{IntoSystem, System}, ResourceObject, World};
use lyra_math::IVec2;
use tracing::{error, info, Level};
use tracing_appender::non_blocking;
use tracing_subscriber::{
layer::SubscriberExt,
filter,
util::SubscriberInitExt, fmt,
};
use crate::{event_cleaner_system, plugin::Plugin, render::renderer::Renderer, Event, Events, Stage, StagedExecutor};
#[derive(Clone, Copy, Hash, Debug)]
pub enum GameStages {
/// This stage runs before all other stages.
First,
/// This stage runs before `Update`.
PreUpdate,
/// This stage is where most game logic would be.
Update,
/// This stage is ran after `Update`.
PostUpdate,
/// This stage runs after all other stages.
Last,
}
impl Stage for GameStages {}
pub struct Controls<'a> {
pub world: &'a mut World,
}
#[derive(Clone, Default)]
pub struct WindowState {
/// Indicates if the window is currently focused.
pub focused: bool,
/// Indicates if the window is currently occluded.
pub occluded: bool,
/// Indicates if the cursor is inside of the window.
pub cursor_inside_window: bool,
pub position: IVec2,
}
impl WindowState {
pub fn new() -> Self {
Self::default()
}
}
pub struct App {
pub(crate) renderer: OnceCell<Box<dyn Renderer>>,
pub world: World,
plugins: VecDeque<Box<dyn Plugin>>,
startup_systems: VecDeque<Box<dyn System>>,
staged_exec: StagedExecutor,
run_fn: OnceCell<Box<dyn FnOnce(App)>>,
}
impl App {
pub fn new() -> Self {
// init logging
let (stdout_layer, stdout_nb) = non_blocking(std::io::stdout());
{
let t = tracing_subscriber::registry()
.with(fmt::layer().with_writer(stdout_layer));
#[cfg(feature = "tracy")]
let t = t.with(tracing_tracy::TracyLayer::default());
t.with(filter::Targets::new()
// done by prefix, so it includes all lyra subpackages
.with_target("lyra", Level::DEBUG)
.with_target("wgsl_preprocessor", Level::INFO)
.with_target("wgpu", Level::WARN)
.with_target("winit", Level::DEBUG)
.with_default(Level::INFO))
.init();
}
// store the logger worker guard to ensure logging still happens
let mut world = World::new();
world.add_resource(stdout_nb);
// initialize ecs system stages
let mut staged = StagedExecutor::new();
staged.add_stage(GameStages::First);
staged.add_stage_after(GameStages::First, GameStages::PreUpdate);
staged.add_stage_after(GameStages::PreUpdate, GameStages::Update);
staged.add_stage_after(GameStages::Update, GameStages::PostUpdate);
staged.add_stage_after(GameStages::PostUpdate, GameStages::Last);
Self {
renderer: OnceCell::new(),
world,
plugins: Default::default(),
startup_systems: Default::default(),
staged_exec: staged,
run_fn: OnceCell::new(),
}
}
pub fn update(&mut self) {
self.world.tick();
let wptr = NonNull::from(&self.world);
if let Err(e) = self.staged_exec.execute(wptr, true) {
error!("Error when executing staged systems: '{}'", e);
}
}
pub(crate) fn on_resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>) {
self.renderer.get_mut()
.expect("renderer was not initialized")
.on_resize(&mut self.world, new_size);
}
pub(crate) fn on_exit(&mut self) {
info!("On exit!");
}
pub fn add_resource<T: ResourceObject>(&mut self, data: T) {
self.world.add_resource(data);
}
/// Add a system to the ecs world
pub fn with_system<S, A>(&mut self, name: &str, system: S, depends: &[&str]) -> &mut Self
where
S: IntoSystem<A>,
<S as IntoSystem<A>>::System: 'static
{
self.staged_exec.add_system_to_stage(GameStages::Update, name, system.into_system(), depends);
self
}
/// Add a stage.
///
/// This stage could run at any moment if nothing is dependent on it.
pub fn add_stage<T: Stage>(&mut self, stage: T) -> &mut Self {
self.staged_exec.add_stage(stage);
self
}
/// Add a stage that executes after another one.
///
/// Parameters:
/// * `before` - The stage that will run before `after`.
/// * `after` - The stage that will run after `before`.
pub fn add_stage_after<T: Stage, U: Stage>(&mut self, before: T, after: U) -> &mut Self {
self.staged_exec.add_stage_after(before, after);
self
}
/// Add a system to an already existing stage.
///
/// # Panics
/// Panics if the stage was not already added to the executor
pub fn add_system_to_stage<T, S, A>(&mut self, stage: T,
name: &str, system: S, depends: &[&str]) -> &mut Self
where
T: Stage,
S: IntoSystem<A>,
<S as IntoSystem<A>>::System: 'static
{
self.staged_exec.add_system_to_stage(stage, name, system.into_system(), depends);
self
}
/// Add a startup system that will be ran right after plugins are setup.
/// They will only be ran once
pub fn with_startup_system<S>(&mut self, system: S) -> &mut Self
where
S: System + 'static
{
self.startup_systems.push_back(Box::new(system));
self
}
/// Add a plugin to the game. These are executed as they are added.
pub fn with_plugin<P>(&mut self, mut plugin: P) -> &mut Self
where
P: Plugin + 'static
{
plugin.setup(self);
let plugin = Box::new(plugin);
self.plugins.push_back(plugin);
self
}
/// Override the default (empty) world
///
/// This isn't recommended, you should create a startup system and add it to `with_startup_system`
pub fn with_world(&mut self, world: World) -> &mut Self {
self.world = world;
self
}
pub fn set_run_fn<F>(&self, f: F)
where
F: FnOnce(App) + 'static
{
// ignore if a runner function was already set
let _ = self.run_fn.set(Box::new(f));
}
pub fn run(mut self) {
let f = self.run_fn.take()
.expect("No run function set");
f(self);
}
pub fn register_event<T: Event>(&mut self) {
let world = &mut self.world;
// only register the event if it isn't already registered.
if !world.has_resource::<Events<T>>() {
world.add_resource_default::<Events<T>>();
let sys_name = format!("{}_event_cleaner_system", std::any::type_name::<T>().to_lowercase());
self.add_system_to_stage(GameStages::First, &sys_name, event_cleaner_system::<T>, &[]);
}
}
pub fn push_event<T: Event>(&mut self, event: T) {
let world = &mut self.world;
let mut events = world.get_resource_mut::<Events<T>>()
.expect("missing events for event type! Must use `App::register_event` first");
events.push_event(event);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,138 +0,0 @@
use std::ops::Deref;
use glam::Vec2;
use lyra_ecs::query::ResMut;
use winit::{event::{MouseScrollDelta, WindowEvent}, keyboard::PhysicalKey};
use crate::{game::GameStages, plugin::Plugin, winit::DeviceEventPair, EventReader, EventWriter};
use super::{events::*, InputButtons, KeyCode};
fn write_scroll_delta(mouse_scroll_ev: &mut EventWriter<MouseScroll>, delta: &MouseScrollDelta) {
let event = match delta {
MouseScrollDelta::LineDelta(x, y) => MouseScroll {
unit: MouseScrollUnit::Line(Vec2::new(*x, *y)),
},
MouseScrollDelta::PixelDelta(delta) => MouseScroll {
unit: MouseScrollUnit::Pixel(Vec2::new(delta.x as f32, delta.y as f32)),
},
};
mouse_scroll_ev.write(event);
}
fn write_key_event(key_buttons: &mut ResMut<InputButtons<KeyCode>>, physical_key: PhysicalKey, state: winit::event::ElementState) {
if let PhysicalKey::Code(code) = physical_key {
key_buttons.add_input_from_winit(KeyCode::from(code), state);
}
}
pub fn input_system(
mut key_code_res: ResMut<InputButtons<KeyCode>>,
mut mouse_btn_res: ResMut<InputButtons<MouseButton>>,
mut touches_res: ResMut<Touches>,
window_ev: EventReader<WindowEvent>,
device_ev: EventReader<DeviceEventPair>,
mut mouse_scroll_ev: EventWriter<MouseScroll>,
mouse_btn_ev: EventWriter<MouseButton>,
mouse_exact_ev: EventWriter<MouseExact>,
mouse_entered_ev: EventWriter<CursorEnteredWindow>,
mouse_left_ev: EventWriter<CursorLeftWindow>,
mouse_motion_ev: EventWriter<MouseMotion>,
) -> anyhow::Result<()> {
while let Some(event) = window_ev.read() {
match event.deref() {
WindowEvent::KeyboardInput { event, .. } => {
write_key_event(&mut key_code_res, event.physical_key, event.state);
},
WindowEvent::CursorMoved { position, .. } => {
let exact = MouseExact {
pos: Vec2::new(position.x as f32, position.y as f32)
};
mouse_exact_ev.write(exact);
},
WindowEvent::CursorEntered { .. } => {
mouse_entered_ev.write(CursorEnteredWindow);
},
WindowEvent::CursorLeft { .. } => {
mouse_left_ev.write(CursorLeftWindow);
},
WindowEvent::MouseWheel { delta, .. } => {
write_scroll_delta(&mut mouse_scroll_ev, delta);
},
WindowEvent::MouseInput { button, state, .. } => {
let button_event = match button {
winit::event::MouseButton::Left => MouseButton::Left,
winit::event::MouseButton::Right => MouseButton::Right,
winit::event::MouseButton::Middle => MouseButton::Middle,
winit::event::MouseButton::Back => MouseButton::Back,
winit::event::MouseButton::Forward => MouseButton::Forward,
winit::event::MouseButton::Other(v) => MouseButton::Other(*v),
};
mouse_btn_ev.write(button_event);
mouse_btn_res.add_input_from_winit(button_event, *state);
},
WindowEvent::Touch(t) => {
let touch = Touch {
phase: TouchPhase::from(t.phase),
location: Vec2::new(t.location.x as f32, t.location.y as f32),
force: t.force.map(Force::from),
finger_id: t.id,
};
touches_res.touches.push(touch);
},
_ => {},
}
}
while let Some(device) = device_ev.read() {
match &device.event {
winit::event::DeviceEvent::Motion { .. } => {
// TODO: handle device motion events
// A todo! isn't used since these are triggered alongside MouseMotion events
}
winit::event::DeviceEvent::MouseMotion { delta } => {
let delta = MouseMotion {
delta: Vec2::new(delta.0 as f32, delta.1 as f32)
};
mouse_motion_ev.write(delta);
},
winit::event::DeviceEvent::MouseWheel { delta } => {
write_scroll_delta(&mut mouse_scroll_ev, delta);
},
winit::event::DeviceEvent::Key(key) => {
write_key_event(&mut key_code_res, key.physical_key, key.state);
},
_ => {
todo!("unhandled device event: {:?}", device.event);
}
}
}
Ok(())
}
/// Plugin that runs InputSystem
#[derive(Default)]
pub struct InputPlugin;
impl Plugin for InputPlugin {
fn setup(&mut self, app: &mut crate::game::App) {
app.add_resource(InputButtons::<KeyCode>::default());
app.add_resource(InputButtons::<MouseButton>::default());
app.add_resource(Touches::default());
app.register_event::<MouseScroll>();
app.register_event::<MouseButton>();
app.register_event::<MouseMotion>();
app.register_event::<MouseExact>();
app.register_event::<CursorEnteredWindow>();
app.register_event::<CursorLeftWindow>();
app.add_system_to_stage(GameStages::PreUpdate, "input", input_system, &[]);
}
}

View File

@ -1,212 +0,0 @@
use lyra_ecs::{
query::{
filter::Or,
Entities,
},
Component, Entity,
};
use lyra_game_derive::RenderGraphLabel;
use lyra_math::Transform;
use lyra_resource::ResHandle;
use lyra_scene::{SceneGraph, WorldTransform};
use tracing::debug;
use crate::{
render::{
graph::{Node, NodeDesc, NodeType},
transform_buffer_storage::{TransformBuffers, TransformIndex},
},
DeltaTime,
};
/// An interpolated transform.
///
/// This transform is interpolated between frames to make movement appear smoother when the
/// transform is updated less often than rendering.
#[derive(Clone, Debug, Component)]
pub struct InterpTransform {
last_transform: Transform,
alpha: f32,
}
#[derive(Default, Debug, Clone, Copy, Hash, RenderGraphLabel)]
pub struct TransformsNodeLabel;
#[derive(Debug)]
pub struct TransformsNode {}
impl TransformsNode {
pub fn new() -> Self {
Self {}
}
}
fn process_component_queue(world: &mut lyra_ecs::World, component_queue: Vec<(Entity, Option<InterpTransform>, Option<TransformIndex>)>) {
for (en, interp, index) in component_queue {
println!("writing index {:?} for entity {}", index, en.id().0);
match (interp, index) {
(None, None) => unreachable!(),
(None, Some(index)) => world.insert(en, index),
(Some(interp), None) => world.insert(en, interp),
(Some(interp), Some(index)) => world.insert(en, (interp, index)),
}
}
}
fn update_transforms(
device: &wgpu::Device,
queue: &wgpu::Queue,
limits: &wgpu::Limits,
world: &mut lyra_ecs::World,
delta_time: DeltaTime,
buffers: &mut TransformBuffers,
parent_transform: Transform,
) {
let mut component_queue = vec![];
let view = world.view_iter::<(
Entities,
Or<&WorldTransform, &Transform>,
Option<&mut InterpTransform>,
Option<&TransformIndex>,
Option<&ResHandle<SceneGraph>>,
)>();
for (entity, transform, interp_tran, transform_index, scene_graph) in view {
// expand the transform buffers if they need to be.
if buffers.needs_expand() {
debug!("Expanding transform buffers");
buffers.expand_buffers(device);
}
// Get the world transform of the entity, else fall back to the transform
let transform = match transform {
(None, None) => unreachable!(),
(None, Some(t)) => *t,
(Some(wt), None) => **wt,
// Assume world transform since it *should* be updated by world systems
(Some(wt), Some(_)) => **wt,
};
// offset this transform by its parent
let transform = transform + parent_transform;
// Interpolate the transform for this entity using a component.
// If the entity does not have the component then it will be queued to be added
// to it after all the entities are prepared for rendering.
let transform = match interp_tran {
Some(mut interp_transform) => {
// found in https://youtu.be/YJB1QnEmlTs?t=472
interp_transform.alpha = 1.0 - interp_transform.alpha.powf(*delta_time);
interp_transform.last_transform = interp_transform
.last_transform
.lerp(transform, interp_transform.alpha);
interp_transform.last_transform
}
None => {
let interp = InterpTransform {
last_transform: transform,
alpha: 0.5,
};
component_queue.push((entity, Some(interp), None));
transform
}
};
// Get the TransformIndex from the entity, or reserve a new one if the entity doesn't have
// the component.
let index = match transform_index {
Some(i) => *i,
None => {
let i = buffers.reserve_transform(&device);
debug!(
"Reserved transform index {:?} for entity {}",
i,
entity.id().0
);
component_queue.push((entity, None, Some(i)));
i
}
};
// TODO: only update if the transform changed.
buffers.update(
&queue,
index,
transform.calculate_mat4(),
glam::Mat3::from_quat(transform.rotation),
);
if let Some(scene) = scene_graph {
if let Some(mut scene) = scene.data_mut() {
update_transforms(
device,
queue,
limits,
scene.world_mut(),
delta_time,
buffers,
transform,
);
}
}
}
process_component_queue(world, component_queue);
}
impl Node for TransformsNode {
fn desc(
&mut self,
_: &mut crate::render::graph::RenderGraph,
) -> crate::render::graph::NodeDesc {
NodeDesc::new(NodeType::Node, None, vec![])
}
fn prepare(
&mut self,
_: &mut crate::render::graph::RenderGraph,
world: &mut lyra_ecs::World,
context: &mut crate::render::graph::RenderGraphContext,
) {
let device = &context.device;
let queue = &context.queue;
let render_limits = device.limits();
// prepare the world with resources
if !world.has_resource::<TransformBuffers>() {
let buffers = TransformBuffers::new(device);
world.add_resource(buffers);
}
// I have to do this weird garbage to borrow the `TransformBuffers`
// without running into a borrow checker error from passing `world` as mutable.
// This is safe since I know that the recursive function isn't accessing this
// TransformBuffers, or any other ones in other worlds.
let buffers = world.get_resource_data::<TransformBuffers>()
.map(|r| r.clone()).unwrap();
let mut buffers = buffers.get_mut();
let dt = world.get_resource::<DeltaTime>().unwrap().clone();
update_transforms(
&device,
&queue,
&render_limits,
world,
dt,
&mut buffers,
Transform::default(),
);
}
fn execute(
&mut self,
_: &mut crate::render::graph::RenderGraph,
_: &crate::render::graph::NodeDesc,
_: &mut crate::render::graph::RenderGraphContext,
) {
}
}

View File

@ -1,33 +0,0 @@
mod shader;
use std::collections::HashMap;
pub use shader::*;
mod pipeline;
pub use pipeline::*;
mod compute_pipeline;
pub use compute_pipeline::*;
mod render_pipeline;
pub use render_pipeline::*;
mod pass;
pub use pass::*;
#[derive(Default, Clone)]
pub struct PipelineCompilationOptions {
pub constants: HashMap<String, f64>,
pub zero_initialize_workgroup_memory: bool,
pub vertex_pulling_transform: bool,
}
impl PipelineCompilationOptions {
pub fn as_wgpu(&self) -> wgpu::PipelineCompilationOptions {
wgpu::PipelineCompilationOptions {
constants: &self.constants,
zero_initialize_workgroup_memory: self.zero_initialize_workgroup_memory,
vertex_pulling_transform: self.vertex_pulling_transform,
}
}
}

View File

@ -1,285 +0,0 @@
#define_module lyra::main_3d
#import lyra::shadows::bindings::{u_light_shadow}
#import lyra::shadows::calc::{calc_shadow_dir_light, calc_shadow_point_light, calc_shadow_spot_light}
// Vertex shader
const LIGHT_TY_DIRECTIONAL = 0u;
const LIGHT_TY_POINT = 1u;
const LIGHT_TY_SPOT = 2u;
const ALPHA_CUTOFF = 0.1;
struct VertexInput {
@location(0) position: vec3<f32>,
@location(1) tex_coords: vec2<f32>,
@location(2) normal: vec3<f32>,
}
struct VertexOutput {
@builtin(position) clip_position: vec4<f32>,
@location(0) tex_coords: vec2<f32>,
@location(1) world_position: vec3<f32>,
@location(2) world_normal: vec3<f32>,
@location(3) frag_pos_light_space: vec4<f32>,
}
struct TransformData {
transform: mat4x4<f32>,
normal_matrix: mat4x4<f32>,
}
struct CameraUniform {
view: mat4x4<f32>,
inverse_projection: mat4x4<f32>,
view_projection: mat4x4<f32>,
projection: mat4x4<f32>,
position: vec3<f32>,
tile_debug: u32,
}
struct Light {
position: vec3<f32>,
light_ty: u32,
direction: vec3<f32>,
enabled: u32,
color: vec3<f32>,
range: f32,
intensity: f32,
smoothness: f32,
spot_cutoff: f32,
spot_outer_cutoff: f32,
light_shadow_uniform_index: array<i32, 6>,
}
struct Lights {
light_count: u32,
data: array<Light>,
}
@group(1) @binding(0)
var<uniform> u_model_transform_data: TransformData;
@group(2) @binding(0)
var<uniform> u_camera: CameraUniform;
@group(3) @binding(0)
var<storage> u_lights: Lights;
@vertex
fn vs_main(
model: VertexInput,
) -> VertexOutput {
var out: VertexOutput;
var world_position: vec4<f32> = u_model_transform_data.transform * vec4<f32>(model.position, 1.0);
out.world_position = world_position.xyz;
out.tex_coords = model.tex_coords;
out.clip_position = u_camera.view_projection * world_position;
// the normal mat is actually only a mat3x3, but there's a bug in wgpu: https://github.com/gfx-rs/wgpu-rs/issues/36
let normal_mat4 = u_model_transform_data.normal_matrix;
let normal_mat = mat3x3(normal_mat4[0].xyz, normal_mat4[1].xyz, normal_mat4[2].xyz);
out.world_normal = normalize(normal_mat * model.normal);
return out;
}
// Fragment shader
struct Material {
ambient: vec3<f32>,
diffuse: vec3<f32>,
shininess: f32,
specular_factor: f32,
specular_color: vec3<f32>,
}
@group(0) @binding(0)
var<uniform> u_material: Material;
@group(0) @binding(1)
var t_diffuse: texture_2d<f32>;
@group(0) @binding(2)
var s_diffuse: sampler;
@group(4) @binding(0)
var<storage, read_write> u_light_indices: array<u32>;
@group(4) @binding(1)
var t_light_grid: texture_storage_2d<rg32uint, read_write>; // rg32uint = vec2<u32>
@fragment
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
if (u_camera.tile_debug == 1u) {
return debug_grid(in);
}
let object_color: vec4<f32> = textureSample(t_diffuse, s_diffuse, in.tex_coords);
let specular_color: vec3<f32> = vec3<f32>(0.0); //textureSample(t_specular, s_specular, in.tex_coords).xyz;
var light_res = vec3<f32>(0.0);
if (object_color.a < ALPHA_CUTOFF) {
discard;
}
let tile_index = vec2<u32>(floor(in.clip_position.xy / 16.0));
let tile: vec2<u32> = textureLoad(t_light_grid, tile_index).xy;
let light_offset = tile.x;
let light_count = tile.y;
for (var i = 0u; i < light_count; i++) {
let light_index = u_light_indices[light_offset + i];
let light: Light = u_lights.data[light_index];
let light_dir = normalize(-light.direction);
if (light.light_ty == LIGHT_TY_DIRECTIONAL) {
let shadow_u: LightShadowMapUniform = u_light_shadow[light.light_shadow_uniform_index[0]];
let frag_pos_light_space = shadow_u.light_space_matrix * vec4<f32>(in.world_position, 1.0);
let shadow = calc_shadow_dir_light(in.world_position, in.world_normal, light_dir, light);
light_res += blinn_phong_dir_light(in.world_position, in.world_normal, light, u_material, specular_color, shadow);
} else if (light.light_ty == LIGHT_TY_POINT) {
let shadow = calc_shadow_point_light(in.world_position, in.world_normal, light_dir, light);
light_res += blinn_phong_point_light(in.world_position, in.world_normal, light, u_material, specular_color, shadow);
} else if (light.light_ty == LIGHT_TY_SPOT) {
let shadow = calc_shadow_spot_light(in.world_position, in.world_normal, light_dir, light);
light_res += blinn_phong_spot_light(in.world_position, in.world_normal, light, u_material, specular_color, shadow);
}
}
let light_object_res = light_res * (object_color.xyz);
return vec4<f32>(light_object_res, object_color.a);
}
fn debug_grid(in: VertexOutput) -> vec4<f32> {
let tile_index_float: vec2<f32> = in.clip_position.xy / 16.0;
let tile_index = vec2<u32>(floor(tile_index_float));
let tile: vec2<u32> = textureLoad(t_light_grid, tile_index).xy;
// detect where the line grids would be at
let x = tile_index_float.x - trunc(tile_index_float.x);
let y = tile_index_float.y - trunc(tile_index_float.y);
let ta: bool = x < 0.05 || y < 0.05;
let tb: bool = x > 0.95 || y > 0.95;
let ratio = f32(tile.y) / f32(u_lights.light_count);
return vec4<f32>(ratio, ratio, ratio, 1.0);
}
fn blinn_phong_dir_light(world_pos: vec3<f32>, world_norm: vec3<f32>, dir_light: Light, material: Material, specular_factor: vec3<f32>, shadow: f32) -> vec3<f32> {
let light_color = dir_light.color.xyz;
let camera_view_pos = u_camera.position;
//// Ambient light ////
var ambient_color = light_color * material.ambient.xyz * material.diffuse.xyz;
//// diffuse ////
let light_dir = normalize(-dir_light.direction);
let diffuse_strength = max(dot(world_norm, light_dir), 0.0);
var diffuse_color = light_color * (diffuse_strength * material.diffuse.xyz);
//// end of diffuse ////
//// specular ////
let view_dir = normalize(camera_view_pos - world_pos);
let half_dir = normalize(view_dir + light_dir);
let specular_strength = pow(max(dot(world_norm, half_dir), 0.0), material.shininess);
var specular_color = specular_strength * (light_color * specular_factor);
//// end of specular ////
/*ambient_color *= dir_light.ambient;
diffuse_color *= dir_light.diffuse;
specular_color *= dir_light.specular;*/
return (ambient_color + (shadow) * (diffuse_color + specular_color)) * dir_light.intensity;
}
fn blinn_phong_point_light(world_pos: vec3<f32>, world_norm: vec3<f32>, point_light: Light, material: Material, specular_factor: vec3<f32>, shadow: f32) -> vec3<f32> {
let light_color = point_light.color.xyz;
let light_pos = point_light.position.xyz;
let camera_view_pos = u_camera.position;
//// Ambient light ////
var ambient_color = light_color * material.ambient.xyz * material.diffuse.xyz;
//// diffuse ////
let light_dir = normalize(light_pos - world_pos);
let diffuse_strength = max(dot(world_norm, light_dir), 0.0);
var diffuse_color = light_color * (diffuse_strength * material.diffuse.xyz);
//// end of diffuse ////
//// specular ////
let view_dir = normalize(camera_view_pos - world_pos);
let half_dir = normalize(view_dir + light_dir);
let specular_strength = pow(max(dot(world_norm, half_dir), 0.0), material.shininess);
var specular_color = specular_strength * (light_color * specular_factor);
//// end of specular ////
let distance = length(light_pos - world_pos);
let attenuation = 1.0 - smoothstep(point_light.range * point_light.smoothness, point_light.range, distance);
ambient_color *= attenuation;
diffuse_color *= attenuation;
specular_color *= attenuation;
//return (ambient_color + shadow * (diffuse_color + specular_color)) * point_light.intensity;
return (shadow * (ambient_color + diffuse_color + specular_color)) * point_light.intensity;
}
fn blinn_phong_spot_light(world_pos: vec3<f32>, world_norm: vec3<f32>, spot_light: Light, material: Material, specular_factor: vec3<f32>, shadow: f32) -> vec3<f32> {
let light_color = spot_light.color;
let light_pos = spot_light.position;
let camera_view_pos = u_camera.position;
let light_dir = normalize(spot_light.position - world_pos);
var ambient_color = light_color * material.ambient.xyz * material.diffuse.xyz;
//// diffuse ////
//let light_dir = normalize(light_pos - world_pos);
let diffuse_strength = max(dot(world_norm, light_dir), 0.0);
var diffuse_color = light_color * (diffuse_strength * material.diffuse.xyz);
//// end of diffuse ////
//// specular ////
let view_dir = normalize(camera_view_pos - world_pos);
let half_dir = normalize(view_dir + light_dir);
let specular_strength = pow(max(dot(world_norm, half_dir), 0.0), material.shininess);
var specular_color = specular_strength * (light_color * specular_factor);
//// end of specular ////
//// spot light soft edges ////
let min_cos = cos(spot_light.spot_cutoff);
let max_cos = lerp(min_cos, 1.0, 0.5);
let cos_angle = dot(spot_light.direction, -light_dir);
let cone = smoothstep(min_cos, max_cos, cos_angle);
//// end of spot light soft edges ////
//// spot light attenuation ////
let distance = length(light_pos - world_pos);
let attenuation = calc_attenuation(spot_light, distance);
ambient_color *= attenuation * cone;
diffuse_color *= attenuation * cone;
specular_color *= attenuation * cone;
//// end of spot light attenuation ////
//return /*ambient_color +*/ diffuse_color + specular_color;
return (shadow * (diffuse_color + specular_color)) * spot_light.intensity;
}
fn calc_attenuation(light: Light, distance: f32) -> f32 {
return 1.0 - smoothstep(light.range * light.smoothness, light.range, distance);
}
fn lerp(start: f32, end: f32, alpha: f32) -> f32 {
return (start + (end - start) * alpha);
}

View File

@ -1,19 +0,0 @@
#define_module lyra::shadows::bindings
#import lyra::shadows::structs::{ShadowSettingsUniform, LightShadowMapUniform}
@group(5) @binding(0)
var t_shadow_maps_atlas: texture_depth_2d;
@group(5) @binding(1)
var s_shadow_maps_atlas: sampler;
@group(5) @binding(2)
var s_shadow_maps_atlas_compare: sampler_comparison;
@group(5) @binding(3)
var<uniform> u_shadow_settings: ShadowSettingsUniform;
@group(5) @binding(4)
var<storage, read> u_light_shadow: array<LightShadowMapUniform>;
@group(5) @binding(5)
var<storage, read> u_pcf_poisson_disc: array<vec2<f32>>;
@group(5) @binding(6)
var<storage, read> u_pcf_poisson_disc_3d: array<vec3<f32>>;
@group(5) @binding(7)
var<storage, read> u_pcss_poisson_disc: array<vec2<f32>>;

View File

@ -1,29 +0,0 @@
#define_module lyra::shadows::structs
struct TextureAtlasFrame {
/*offset: vec2<u32>,
size: vec2<u32>,*/
x: u32,
y: u32,
width: u32,
height: u32,
}
struct LightShadowMapUniform {
light_space_matrix: mat4x4<f32>,
atlas_frame: TextureAtlasFrame,
near_plane: f32,
far_plane: f32,
light_size_uv: f32,
light_pos: vec3<f32>,
/// boolean casted as u32
has_shadow_settings: u32,
pcf_samples_num: u32,
pcss_blocker_search_samples: u32,
constant_depth_bias: f32,
}
struct ShadowSettingsUniform {
pcf_samples_num: u32,
pcss_blocker_search_samples: u32,
}

View File

@ -1,51 +0,0 @@
use lyra_ecs::Component;
use lyra_reflect::Reflect;
use lyra_resource::ResHandle;
use lyra_math::{Vec3, Vec2};
/// How the sprite is positioned and rotated relative to its [`Transform`].
///
/// Default pivot is `Pivot::Center`, this makes it easier to rotate the sprites.
#[derive(Debug, Copy, Clone, PartialEq, Default, Component, Reflect)]
pub enum Pivot {
#[default]
Center,
CenterLeft,
CenterRight,
TopLeft,
TopRight,
TopCenter,
BottomLeft,
BottomRight,
BottomCenter,
/// A custom anchor point relative to top left.
/// Top left is `(0.0, 0.0)`.
Custom(Vec2)
}
impl Pivot {
/// Get the pivot point as a Vec2.
///
/// The point is offset from the top left `(0.0, 0.0)`.
pub fn as_vec(&self) -> Vec2 {
match self {
Pivot::Center => Vec2::new(0.5, 0.5),
Pivot::CenterLeft => Vec2::new(0.0, 0.5),
Pivot::CenterRight => Vec2::new(1.0, 0.5),
Pivot::TopLeft => Vec2::ZERO,
Pivot::TopRight => Vec2::new(1.0, 0.0),
Pivot::TopCenter => Vec2::new(0.0, 0.5),
Pivot::BottomLeft => Vec2::new(0.0, 1.0),
Pivot::BottomRight => Vec2::new(1.0, 1.0),
Pivot::BottomCenter => Vec2::new(0.5, 1.0),
Pivot::Custom(v) => *v,
}
}
}
#[derive(Clone, Component, Reflect)]
pub struct Sprite {
pub texture: ResHandle<lyra_resource::Texture>,
pub color: Vec3,
pub pivot: Pivot,
}

View File

@ -1,8 +0,0 @@
mod plugin;
pub use plugin::*;
mod window;
pub use window::*;
pub use winit::dpi as dpi;

View File

@ -1,336 +0,0 @@
use std::{collections::VecDeque, sync::Arc};
use async_std::task::block_on;
use glam::{DVec2, IVec2, UVec2};
use lyra_ecs::Entity;
use lyra_reflect::Reflect;
use rustc_hash::FxHashMap;
use tracing::{debug, error, warn};
use winit::{
application::ApplicationHandler,
event::WindowEvent,
event_loop::{ActiveEventLoop, EventLoop},
window::{Window, WindowAttributes, WindowId},
};
pub use winit::event::{DeviceId, DeviceEvent, MouseScrollDelta, ElementState, RawKeyEvent};
pub use winit::keyboard::PhysicalKey;
use crate::{
game::{App, WindowState},
plugin::Plugin,
render::renderer::BasicRenderer, winit::{FullscreenMode, LastWindow, PrimaryWindow},
};
use super::WindowOptions;
/// A struct that contains a [`DeviceEvent`](winit::event::DeviceEvent) with its source
/// [`DeviceId`](winit::event::DeviceId).
#[derive(Debug, Clone, Reflect)]
pub struct DeviceEventPair {
#[reflect(skip)]
pub device_src: DeviceId,
#[reflect(skip)]
pub event: DeviceEvent,
}
pub struct WinitPlugin {
/// The primary window that will be created.
///
/// This will become `None` after the window is created. If you want to get the
/// primary world later, query for an entity with the [`PrimaryWindow`] and
/// [`WindowOptions`] components.
pub primary_window: Option<WindowOptions>,
}
impl Default for WinitPlugin {
fn default() -> Self {
Self {
primary_window: Some(WindowOptions::default()),
}
}
}
impl Plugin for WinitPlugin {
fn setup(&mut self, app: &mut crate::game::App) {
app.set_run_fn(winit_app_runner);
app.register_event::<WindowEvent>();
app.register_event::<DeviceEventPair>();
if let Some(prim) = self.primary_window.take() {
app.add_resource(WinitWindows::with_window(prim));
} else {
app.add_resource(WinitWindows::default());
}
}
fn is_ready(&self, _app: &mut crate::game::App) -> bool {
true
}
fn complete(&self, _app: &mut crate::game::App) {}
fn cleanup(&self, _app: &mut crate::game::App) {}
}
#[derive(Default)]
pub struct WinitWindows {
pub windows: FxHashMap<WindowId, Arc<Window>>,
pub entity_to_window: FxHashMap<Entity, WindowId>,
pub window_to_entity: FxHashMap<WindowId, Entity>,
/// windows that will be created when the Winit runner first starts.
window_queue: VecDeque<WindowOptions>,
}
impl WinitWindows {
pub fn with_window(window: WindowOptions) -> Self {
Self {
window_queue: vec![window].into(),
..Default::default()
}
}
pub fn create_window(
&mut self,
event_loop: &ActiveEventLoop,
entity: Entity,
attr: WindowAttributes,
) -> Result<WindowId, winit::error::OsError> {
let win = event_loop.create_window(attr)?;
let id = win.id();
self.windows.insert(id, Arc::new(win));
self.entity_to_window.insert(entity, id);
self.window_to_entity.insert(id, entity);
Ok(id)
}
pub fn get_entity_window(&self, entity: Entity) -> Option<&Arc<Window>> {
self.entity_to_window
.get(&entity)
.and_then(|id| self.windows.get(id))
}
}
pub fn winit_app_runner(app: App) {
let evloop = EventLoop::new().expect("failed to create winit EventLoop");
let mut winit_runner = WinitRunner { app };
evloop.run_app(&mut winit_runner).expect("loop error");
}
struct WinitRunner {
app: App,
}
impl ApplicationHandler for WinitRunner {
fn about_to_wait(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
self.app.update();
let renderer = self
.app
.renderer
.get_mut()
.expect("renderer was not initialized");
renderer.prepare(&mut self.app.world);
match renderer.render() {
Ok(_) => {}
// Reconfigure the surface if lost
//Err(wgpu::SurfaceError::Lost) => self.on_resize(.surface_size()),
// The system is out of memory, we should probably quit
Err(wgpu::SurfaceError::OutOfMemory) => {
error!("OOM");
event_loop.exit();
}
// All other errors (Outdated, Timeout) should be resolved by the next frame
Err(e) => eprintln!("{:?}", e),
}
let windows = self.app.world.get_resource::<WinitWindows>()
.expect("world missing WinitWindows resource");
for window in windows.windows.values() {
window.request_redraw();
}
}
fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
let world = &mut self.app.world;
let en = world.reserve_entity();
let mut windows = world.get_resource_mut::<WinitWindows>()
.expect("world missing WinitWindows resource");
let mut to_create_window = windows.window_queue.pop_front().unwrap_or_default();
let window_attr = to_create_window.as_attributes();
//drop(windows);
//let en = world.spawn((to_create_window, last, PrimaryWindow));
//let mut windows = world.get_resource_mut::<WinitWindows>()
//.expect("world missing WinitWindows resource");
let wid = windows.create_window(event_loop, en, window_attr).unwrap();
let window = windows.windows.get(&wid).unwrap().clone();
drop(windows);
// update fields that default to `None`
to_create_window.position = window.outer_position()
.or_else(|_| window.inner_position())
.ok()
.map(|p| IVec2::new(p.x, p.y));
// See [`WindowOptions::as_attributes`], it defaults to Windowed fullscreen mode, so we
// must trigger an update in the sync system;
let mut last = LastWindow { last: to_create_window.clone() };
last.last.fullscreen_mode = FullscreenMode::Windowed;
world.insert(en, (to_create_window, last, PrimaryWindow));
debug!("Created window after resume");
let renderer = block_on(BasicRenderer::create_with_window(world, window));
if self.app.renderer.set(Box::new(renderer)).is_err() {
warn!("renderer was re-initialized");
}
}
fn device_event(
&mut self,
_: &ActiveEventLoop,
device_src: winit::event::DeviceId,
event: winit::event::DeviceEvent,
) {
self.app.push_event(DeviceEventPair { device_src, event });
}
fn window_event(
&mut self,
event_loop: &winit::event_loop::ActiveEventLoop,
window_id: winit::window::WindowId,
event: WindowEvent,
) {
/* let windows = self.app.world.get_resource::<WinitWindows>();
let window = match windows.windows.get(&window_id) {
Some(w) => w.clone(),
None => return,
};
drop(windows); */
self.app.push_event(event.clone());
match event {
WindowEvent::CursorMoved { position, .. } => {
let windows = self.app.world.get_resource::<WinitWindows>()
.expect("world missing WinitWindows resource");
let en = windows.window_to_entity.get(&window_id)
.expect("missing window entity");
// update the window and its cache so the sync system doesn't try to update the window
let (mut en_window, mut en_last_win) = self.app.world.view_one::<(&mut WindowOptions, &mut LastWindow)>(*en).get().unwrap();
let pos = Some(DVec2::new(position.x, position.y));
en_window.set_physical_cursor_position(pos);
en_last_win.set_physical_cursor_position(pos);
},
WindowEvent::ActivationTokenDone { .. } => todo!(),
WindowEvent::Resized(physical_size) => {
self.app.on_resize(physical_size);
let (mut window, mut last_window) = self
.app
.world
.get_resource::<WinitWindows>()
.expect("world missing WinitWindows resource")
.window_to_entity
.get(&window_id)
.and_then(|e| self.app.world.view_one::<(&mut WindowOptions, &mut LastWindow)>(*e).get())
.unwrap();
// update the window and its cache so the sync system doesn't try to update the window
let size = UVec2::new(physical_size.width, physical_size.height);
window.set_physical_size(size);
last_window.set_physical_size(size);
},
// Mark the cursor as outside the window when it leaves
WindowEvent::CursorLeft { .. } => {
let (mut window, mut last_window) = self
.app
.world
.get_resource::<WinitWindows>()
.expect("world missing WinitWindows resource")
.window_to_entity
.get(&window_id)
.and_then(|e| self.app.world.view_one::<(&mut WindowOptions, &mut LastWindow)>(*e).get())
.unwrap();
window.set_physical_cursor_position(None);
last_window.set_physical_cursor_position(None);
},
WindowEvent::Moved(physical_position) => {
let mut state = self.app.world.get_resource_or_else(WindowState::new);
state.position = IVec2::new(physical_position.x, physical_position.y);
},
WindowEvent::CloseRequested => {
self.app.on_exit();
event_loop.exit();
},
WindowEvent::Destroyed => todo!(),
WindowEvent::DroppedFile(_path_buf) => todo!(),
WindowEvent::HoveredFile(_path_buf) => todo!(),
WindowEvent::HoveredFileCancelled => todo!(),
WindowEvent::Focused(focused) => {
let mut window_opts = self
.app
.world
.get_resource::<WinitWindows>()
.expect("world missing WinitWindows resource")
.window_to_entity
.get(&window_id)
.and_then(|e| self.app.world.view_one::<&mut WindowOptions>(*e).get())
.unwrap();
window_opts.focused = focused;
},
WindowEvent::ModifiersChanged(modifiers) => {
debug!("modifiers changed: {:?}", modifiers)
},
WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
let mut window_opts = self
.app
.world
.get_resource::<WinitWindows>()
.expect("world missing WinitWindows resource")
.window_to_entity
.get(&window_id)
.and_then(|e| self.app.world.view_one::<&mut WindowOptions>(*e).get())
.unwrap();
window_opts.scale_factor = scale_factor;
},
WindowEvent::ThemeChanged(theme) => {
let mut window_opts = self
.app
.world
.get_resource::<WinitWindows>()
.expect("world missing WinitWindows resource")
.window_to_entity
.get(&window_id)
.and_then(|e| self.app.world.view_one::<&mut WindowOptions>(*e).get())
.unwrap();
window_opts.theme = Some(theme);
},
WindowEvent::Occluded(occ) => {
let mut window_opts = self
.app
.world
.get_resource::<WinitWindows>()
.expect("world missing WinitWindows resource")
.window_to_entity
.get(&window_id)
.and_then(|e| self.app.world.view_one::<&mut WindowOptions>(*e).get())
.unwrap();
window_opts.occluded = occ;
},
WindowEvent::RedrawRequested => {
//debug!("should redraw");
},
_ => {}
}
}
}

View File

@ -1,723 +0,0 @@
use std::ops::{Deref, DerefMut};
use glam::{DVec2, IVec2, UVec2, Vec2};
use lyra_ecs::{query::{filter::Changed, Entities, Res, View}, Component};
use lyra_math::Area;
use lyra_reflect::Reflect;
use lyra_resource::Image;
use tracing::{error, warn};
use winit::{dpi::{LogicalSize, PhysicalPosition, PhysicalSize, Position, Size}, monitor::{MonitorHandle, VideoModeHandle}, window::{CustomCursor, Window}};
pub use winit::window::{CursorGrabMode, CursorIcon, Icon, Theme, WindowButtons, WindowLevel};
use crate::{plugin::Plugin, winit::WinitWindows, lyra_engine};
/// Flag component that
#[derive(Clone, Component)]
pub struct PrimaryWindow;
#[derive(Clone, PartialEq, Eq)]
pub enum CursorAppearance {
Icon(CursorIcon),
Custom(CustomCursor)
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Reflect)]
pub enum FullscreenMode{
#[default]
Windowed,
BorderlessFullscreen,
SizedFullscreen,
Fullscreen,
}
impl FullscreenMode {
pub fn as_winit_fullscreen(&self, monitor: MonitorHandle, physical_size: UVec2) -> Option<winit::window::Fullscreen> {
match &self {
FullscreenMode::Windowed => None,
FullscreenMode::BorderlessFullscreen => Some(winit::window::Fullscreen::Borderless(None)),
// find closest video mode for full screen sizes
_ => {
let closest = find_closest_video_mode(monitor, physical_size);
if let Some(closest) = closest {
Some(winit::window::Fullscreen::Exclusive(closest))
} else {
warn!("Could not find closest video mode, falling back to windowed.");
None
}
}
}
}
}
#[derive(Clone)]
pub struct Cursor {
/// Modifies the cursor icon of the window.
///
/// Platform-specific
/// * **iOS / Android / Orbital:** Unsupported.
/// * **Web:** Custom cursors have to be loaded and decoded first, until then the previous cursor is shown.
pub appearance: CursorAppearance,
/// Gets/sets the window's cursor grab mode
///
/// # Tip:
/// First try confining the cursor, and if it fails, try locking it instead.
pub grab: CursorGrabMode,
/// Gets/sets whether the window catches cursor events.
///
/// If `false`, events are passed through the window such that any other window behind it
/// receives them. By default hittest is enabled.
///
/// Platform-specific
/// * **iOS / Android / Web / Orbital:** Unsupported.
pub hittest: bool,
/// Gets/sets the cursor's visibility
///
/// Platform-specific
/// * **Windows / X11 / Wayland:** The cursor is only hidden within the confines of the window.
/// * **macOS:** The cursor is hidden as long as the window has input focus, even if the
/// cursor is outside of the window.
/// * **iOS / Android:** Unsupported.
pub visible: bool,
//cursor_position: Option<PhysicalPosition<i32>>,
}
/// Options that the window will be created with.
#[derive(Clone, Component, Reflect)]
pub struct WindowOptions {
/// The enabled window buttons.
///
/// Platform-specific
/// * **Wayland / X11 / Orbital:** Not implemented. Always set to [`WindowButtons::all`].
/// * **Web / iOS / Android:** Unsupported. Always set to [`WindowButtons::all`].
#[reflect(skip)]
pub enabled_buttons: WindowButtons,
/// Gets or sets if the window is in focus.
///
/// Platform-specific
/// * **iOS / Android / Wayland / Orbital:** Unsupported.
pub focused: bool,
/// Gets or sets the fullscreen setting.
pub fullscreen_mode: FullscreenMode,
/// Gets/sets the position of the top-left hand corner of the window relative to
/// the top-left hand corner of the desktop.
///
/// Note that the top-left hand corner of the desktop is not necessarily the same
/// as the screen. If the user uses a desktop with multiple monitors, the top-left
/// hand corner of the desktop is the top-left hand corner of the monitor at the
/// top-left of the desktop.
///
/// If this is none, the position will be chosen by the windowing manager at creation, then set
/// when the window is created.
///
/// Platform-specific
/// * **iOS:** Value is the top left coordinates of the windows safe area in the screen
/// space coordinate system.
/// * **Web:** Value is the top-left coordinates relative to the viewport. Note: this will be
/// the same value as [`WindowOptions::outer_position`].
/// * **Android / Wayland:** Unsupported.
#[reflect(skip)]
pub position: Option<IVec2>,
/// Gets/sets the size of the view in the window.
///
/// The size does not include the window title bars and borders.
///
/// Platform-specific
/// * **Web:** The size of the canvas element. Doesnt account for CSS `transform`.
#[reflect(skip)]
physical_size: UVec2,
/// Gets/sets if the window has decorations.
///
/// Platform-specific
/// * **iOS / Android / Web:** Always set to `true`.
pub decorated: bool,
/// Gets/sets the window's current maximized state
///
/// Platform-specific
/// * **iOS / Android / Web:** Unsupported.
pub maximized: bool,
/// Gets/sets the window's current minimized state.
///
/// Is `None` if the minimized state could not be determined.
///
/// Platform-specific
/// * **Wayland:** always `None`, un-minimize is unsupported.
/// * **iOS / Android / Web / Orbital:** Unsupported.
pub minimized: Option<bool>,
/// Gets/sets the window's current resizable state
///
/// If this is false, the window can still be resized by changing [`WindowOptions::size`].
///
/// Platform-specific
/// Setting this only has an affect on desktop platforms.
///
/// * **X11:** Due to a bug in XFCE, setting this has no effect..
/// * **iOS / Android / Web:** Unsupported.
pub resizable: bool,
/// Gets/sets the window's current visibility state.
///
/// `None` means it couldn't be determined.
///
/// Platform-specific
/// * **X11:** Not implemented.
/// * **Wayland / Android / Web:** Unsupported.
/// * **iOS:** Setting is not implemented, getting is unsupported.
pub visible: Option<bool>,
/// Gets/sets the window resize increments.
///
/// This is a niche constraint hint usually employed by terminal emulators and other apps
/// that need “blocky” resizes.
///
/// Platform-specific
/// * **macOS:** Increments are converted to logical size and then macOS rounds them to whole numbers.
/// * **Wayland:** Not implemented, always `None`.
/// * **iOS / Android / Web / Orbital:** Unsupported.
#[reflect(skip)]
pub resize_increments: Option<Size>,
/// Gets the scale factor.
///
/// The scale factor is the ratio of physical pixels to logical pixels.
/// See [winit docs](https://docs.rs/winit/latest/winit/window/struct.Window.html#method.scale_factor)
/// for more information.
pub scale_factor: f64,
/// Gets/sets the window's blur state.
///
/// Platform-specific
/// * **Android / iOS / X11 / Web / Windows:** Unsupported.
/// * **Wayland:** Only works with org_kde_kwin_blur_manager protocol.
pub blur: bool,
#[reflect(skip)]
pub cursor: Cursor,
/// Sets whether the window should get IME events
///
/// When IME is allowed, the window will receive [`Ime`](winit::event::WindowEvent::Ime)
/// events, and during the preedit phase the window will NOT get KeyboardInput events.
/// The window should allow IME while it is expecting text input.
///
/// When IME is not allowed, the window wont receive [`Ime`](winit::event::WindowEvent::Ime)
/// events, and will receive [`KeyboardInput`](winit::event::WindowEvent::KeyboardInput) events
/// for every keypress instead. Not allowing IME is useful for games for example.
/// IME is not allowed by default.
///
/// Platform-specific
/// * **macOS:** IME must be enabled to receive text-input where dead-key sequences are combined.
/// * **iOS / Android / Web / Orbital:** Unsupported.
/// * **X11:** Enabling IME will disable dead keys reporting during compose.
pub ime_allowed: bool,
/// Sets area of IME box in physical coordinates relative to the top left.
///
/// Platform-specific
/// * **X11:** - area is not supported, only position.
/// * **iOS / Android / Web / Orbital:** Unsupported.
#[reflect(skip)]
physical_ime_cursor_area: Option<Area<Vec2, Vec2>>,
/// Gets/sets the minimum size of the window.
///
/// Units are in logical pixels.
///
/// Platform-specific
/// * **iOS / Android / Orbital:** Unsupported.
#[reflect(skip)]
pub min_size: Option<Vec2>,
/// Gets/sets the maximum size of the window.
///
/// Units are in logical pixels.
///
/// Platform-specific
/// * **iOS / Android / Orbital:** Unsupported.
#[reflect(skip)]
pub max_size: Option<Vec2>,
/// Gets/sets the current window theme.
///
/// Specify `None` to reset the theme to the system default. May also be `None` on unsupported
/// platforms.
///
/// Platform-specific
/// * **Wayland:** Sets the theme for the client side decorations. Using `None` will use dbus
/// to get the system preference.
/// * **X11:** Sets `_GTK_THEME_VARIANT` hint to `dark` or `light` and if `None` is used,
/// it will default to [`Theme::Dark`](winit::window::Theme::Dark).
/// * **iOS / Android / Web / Orbital:** Unsupported.
#[reflect(skip)]
pub theme: Option<Theme>,
/// Gets/sets the title of the window.
///
/// Platform-specific
/// * **iOS / Android:** Unsupported.
/// * **X11 / Wayland / Web:** Cannot get, will always be an empty string.
pub title: String,
/// Gets/sets the window's transparency state.
///
/// This is just a hint that may not change anything about the window transparency, however
/// doing a mismatch between the content of your window and this hint may result in visual
/// artifacts.
///
/// Platform-specific
/// * **macOS:** This will reset the windows background color.
/// * **Web / iOS / Android:** Unsupported.
/// * **X11:** Can only be set while building the window.
pub transparent: bool,
/// Sets the window's icon.
///
/// On Windows and X11, this is typically the small icon in the top-left corner of
/// the titlebar.
///
/// Platform-specific
/// * **iOS / Android / Web / Wayland / macOS / Orbital:** Unsupported.
/// * **Windows:** Sets `ICON_SMALL`. The base size for a window icon is 16x16, but its
/// recommended to account for screen scaling and pick a multiple of that, i.e. 32x32.
/// * **X11:** Has no universal guidelines for icon sizes, so youre at the whims of
/// the WM. That said, its usually in the same ballpark as on Windows.
pub window_icon: Option<lyra_resource::ResHandle<Image>>,
/// Change the window level.
///
/// This is just a hint to the OS, and the system could ignore it.
///
/// See [`WindowLevel`] for details.
#[reflect(skip)]
pub window_level: WindowLevel,
/// Show [window menu](https://en.wikipedia.org/wiki/Common_menus_in_Microsoft_Windows#System_menu)
/// at a specified position in physical coordinates.
///
/// This is the context menu that is normally shown when interacting with the title bar. This is useful when implementing custom decorations.
/// Platform-specific
/// * **Android / iOS / macOS / Orbital / Wayland / Web / X11:** Unsupported.
//pub physical_window_menu_pos: Option<Vec2>,
/// Gets the window's occluded state (completely hidden from view).
///
/// This is different to window visibility as it depends on whether the window is
/// closed, minimised, set invisible, or fully occluded by another window.
///
/// Platform-specific
/// * **iOS:** this is set to `false` in response to an applicationWillEnterForeground
/// callback which means the application should start preparing its data.
/// Its `true` in response to an applicationDidEnterBackground callback which means
/// the application should free resources (according to the iOS application lifecycle).
/// * **Web:** Doesn't take into account CSS border, padding, or transform.
/// * **Android / Wayland / Windows / Orbital:** Unsupported.
// TODO: update
pub(crate) occluded: bool,
/// Gets/sets the position of the cursor in physical coordinates.
///
/// Platform-specific
/// * **Wayland:** Cursor must be in [`CursorGrabMode::Locked`].
/// * **iOS / Android / Web / Orbital:** Unsupported.
#[reflect(skip)]
physical_cursor_position: Option<DVec2>,
}
/* fn physical_to_vec2<P: winit::dpi::Pixel>(size: PhysicalSize<P>) -> Vec2 {
let size = size.cast::<f32>();
Vec2::new(size.width, size.height)
} */
fn logical_to_vec2(size: LogicalSize<f32>) -> Vec2 {
Vec2::new(size.width, size.height)
}
impl From<winit::window::WindowAttributes> for WindowOptions {
fn from(value: winit::window::WindowAttributes) -> Self {
Self {
enabled_buttons: value.enabled_buttons,
focused: false,
fullscreen_mode: value.fullscreen.map(|m| match m {
winit::window::Fullscreen::Exclusive(video_mode_handle) => {
if video_mode_handle.size() == video_mode_handle.monitor().size() {
FullscreenMode::Fullscreen
} else {
FullscreenMode::SizedFullscreen
}
},
winit::window::Fullscreen::Borderless(_) => FullscreenMode::BorderlessFullscreen,
}).unwrap_or(FullscreenMode::Windowed),
position: value.position.map(|p| {
let s = p.to_physical::<i32>(1.0);
IVec2::new(s.x, s.y)
}),
physical_size: value.inner_size.map(|s| {
let s = s.to_physical::<u32>(1.0);
UVec2::new(s.width, s.height)
}).unwrap_or(UVec2::new(1280, 720)),
decorated: value.decorations,
maximized: value.maximized,
minimized: None,
resizable: value.resizable,
visible: Some(value.visible),
resize_increments: value.resize_increments.map(|r| r.into()),
scale_factor: 1.0,
blur: value.blur,
cursor: Cursor {
appearance: match value.cursor {
winit::window::Cursor::Icon(icon) => CursorAppearance::Icon(icon),
winit::window::Cursor::Custom(custom) => CursorAppearance::Custom(custom),
},
grab: CursorGrabMode::None,
hittest: true,
visible: true,
},
ime_allowed: false,
physical_ime_cursor_area: None,
min_size: value.min_inner_size.map(|m| logical_to_vec2(m.to_logical(1.0))),
max_size: value.max_inner_size.map(|m| logical_to_vec2(m.to_logical(1.0))),
theme: value.preferred_theme,
title: value.title,
transparent: value.transparent,
window_icon: None,
window_level: value.window_level,
occluded: false,
physical_cursor_position: None,
}
}
}
impl Default for WindowOptions {
fn default() -> Self {
Self::from(Window::default_attributes())
}
}
fn find_closest_video_mode(monitor: MonitorHandle, physical_size: UVec2) -> Option<VideoModeHandle> {
let mut modes = monitor.video_modes();
let mut closest = modes.next()?;
let closest_size = closest.size();
let mut closest_size = UVec2::new(closest_size.width, closest_size.height);
for mode in modes {
let s = closest.size();
let s = UVec2::new(s.width, s.height);
if (physical_size - s).length_squared() < (physical_size - closest_size).length_squared() {
closest = mode;
closest_size = s;
}
}
Some(closest)
}
impl WindowOptions {
/// Create winit [`WindowAttributes`] from self.
///
/// This will ignore [`WindowOptions::fullscreen`] mode on self, defaulting to
/// [`FullscreenMode::Windowed`]. It will be updated on first run of the sync system.
pub(crate) fn as_attributes(&self) -> winit::window::WindowAttributes {
let mut att = winit::window::Window::default_attributes();
att.enabled_buttons = self.enabled_buttons.clone();
att.fullscreen = None;
att.inner_size = Some(Size::Physical(PhysicalSize::new(self.physical_size.x, self.physical_size.y)));
att.decorations = self.decorated;
att.maximized = self.maximized;
att.resizable = self.resizable;
att.visible = self.visible.unwrap_or(true);
att.position = self.position.map(|p| Position::Physical(PhysicalPosition::new(p.x, p.y)));
att.resize_increments = self.resize_increments.map(|i| i.into());
att.blur = self.blur;
att.cursor = match self.cursor.appearance.clone() {
CursorAppearance::Icon(icon) => winit::window::Cursor::Icon(icon),
CursorAppearance::Custom(custom) => winit::window::Cursor::Custom(custom),
};
att.min_inner_size = self.min_size.map(|s| Size::Logical(LogicalSize::new(s.x as _, s.y as _)));
att.max_inner_size = self.max_size.map(|s| Size::Logical(LogicalSize::new(s.x as _, s.y as _)));
att.preferred_theme = self.theme;
att.title = self.title.clone();
att.transparent = self.transparent;
if self.window_icon.is_some() {
todo!("cannot set window attribute icon yet");
}
att.window_level = self.window_level;
att
}
/// The size of the window in physical coordinates.
pub fn physical_size(&self) -> UVec2 {
self.physical_size
}
/// Set the size of the window in physical coordinates.
pub fn set_physical_size(&mut self, size: UVec2) {
self.physical_size = size;
}
/// The size of the window in logical coordinates.
pub fn size(&self) -> Vec2 {
self.physical_size.as_vec2() / self.scale_factor as f32
}
/// Set the size of the window in logical coordinates.
pub fn set_size(&mut self, size: Vec2) {
self.physical_size = (size * self.scale_factor as f32).as_uvec2();
}
/// Returns a boolean indicating if the mouse is inside the window.
pub fn is_mouse_inside(&self) -> bool {
if let Some(pos) = self.physical_cursor_position {
let s = self.physical_size;
return pos.x >= 0.0 && pos.x <= s.x as f64
&& pos.y >= 0.0 && pos.y <= s.y as f64;
}
false
}
/// The cursor position in the window in logical coordinates.
///
/// Returns `None` if the cursor is not in the window.
pub fn cursor_position(&self) -> Option<Vec2> {
if !self.is_mouse_inside() {
return None;
}
self.physical_cursor_position.map(|p| (p / self.scale_factor).as_vec2())
}
/// The cursor position in the window in physical coordinates.
///
/// Returns `None` if the cursor is not in the window.
pub fn physical_cursor_position(&self) -> Option<Vec2> {
if !self.is_mouse_inside() {
return None;
}
self.physical_cursor_position.map(|p| p.as_vec2())
}
/// Set the cursor position in logical coordinates.
///
/// Can be used to mark the cursor outside of the window as well.
pub fn set_cursor_position(&mut self, pos: Option<Vec2>) {
self.physical_cursor_position = pos.map(|p| p.as_dvec2() * self.scale_factor);
}
/// Set the cursor position in physical coordinates.
///
/// Can be used to mark the cursor outside of the window as well.
pub fn set_physical_cursor_position(&mut self, pos: Option<DVec2>) {
self.physical_cursor_position = pos;
}
/// The window's occluded state (completely hidden from view).
///
/// This is different to window visibility as it depends on whether the window is
/// closed, minimised, set invisible, or fully occluded by another window.
///
/// Platform-specific
/// * **iOS:** this is set to `false` in response to an applicationWillEnterForeground
/// callback which means the application should start preparing its data.
/// Its `true` in response to an applicationDidEnterBackground callback which means
/// the application should free resources (according to the iOS application lifecycle).
/// * **Web:** Doesn't take into account CSS border, padding, or transform.
/// * **Android / Wayland / Windows / Orbital:** Unsupported.
pub fn occluded(&self) -> bool {
self.occluded
}
}
/// The state of the window last time it was changed.
///
/// This is used in [`window_sync_system`] to see what fields of [`WindowOptions`] changed
/// when syncing the winit window with the component.
#[derive(Clone, Component)]
pub struct LastWindow {
pub last: WindowOptions,
}
impl Deref for LastWindow {
type Target = WindowOptions;
fn deref(&self) -> &Self::Target {
&self.last
}
}
impl DerefMut for LastWindow {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.last
}
}
#[derive(Default)]
pub struct WindowPlugin {
#[allow(dead_code)]
create_options: WindowOptions,
}
/// A system that syncs Winit Windows with [`WindowOptions`] components.
pub fn window_sync_system(windows: Res<WinitWindows>, view: View<(Entities, &WindowOptions, &mut LastWindow), Changed<WindowOptions>>) -> anyhow::Result<()> {
for (entity, opts, mut last) in view.iter() {
let window = windows.get_entity_window(entity)
.expect("entity's window is missing");
if opts.enabled_buttons != last.enabled_buttons {
window.set_enabled_buttons(opts.enabled_buttons);
}
if opts.focused != last.focused && opts.focused {
window.focus_window();
}
if opts.fullscreen_mode != last.fullscreen_mode {
let monitor = window.primary_monitor().unwrap_or_else(|| {
let mut m = window.available_monitors();
m.next().expect("failed to find any available monitor")
});
window.set_fullscreen(opts.fullscreen_mode.as_winit_fullscreen(monitor, opts.physical_size));
}
if opts.physical_size != last.physical_size {
let size = PhysicalSize::new(opts.physical_size.x, opts.physical_size.y);
if window.request_inner_size(size).is_some() {
error!("request to increase window size failed");
}
}
if opts.decorated != last.decorated {
window.set_decorations(opts.decorated);
}
if opts.maximized != last.maximized {
window.set_maximized(opts.maximized);
}
if opts.minimized != last.minimized && opts.minimized.is_some() {
window.set_minimized(opts.minimized.unwrap());
}
if opts.visible != last.visible && opts.visible.is_some() {
window.set_visible(opts.visible.unwrap());
}
if opts.position != last.position && opts.position.is_some() {
let pos = opts.position.unwrap();
let pos = PhysicalPosition::new(pos.x, pos.y);
window.set_outer_position(pos);
}
if opts.resize_increments != last.resize_increments {
window.set_resize_increments(opts.resize_increments);
}
if opts.blur != last.blur {
window.set_blur(opts.blur);
}
if opts.cursor.appearance != last.cursor.appearance {
match opts.cursor.appearance.clone() {
CursorAppearance::Icon(icon) => window.set_cursor(winit::window::Cursor::Icon(icon)),
CursorAppearance::Custom(custom) => window.set_cursor(winit::window::Cursor::Custom(custom)),
}
}
if opts.cursor.grab != last.cursor.grab {
if let Err(e) = window.set_cursor_grab(opts.cursor.grab) {
error!("could not set cursor grab mode: {}", e);
}
}
if opts.cursor.hittest != last.cursor.hittest {
if let Err(e) = window.set_cursor_hittest(opts.cursor.hittest) {
error!("could not set cursor hittest: {}", e);
}
}
if opts.cursor.visible != last.cursor.visible {
window.set_cursor_visible(opts.cursor.visible);
}
if opts.ime_allowed != last.ime_allowed {
window.set_ime_allowed(opts.ime_allowed);
}
if opts.physical_ime_cursor_area != last.physical_ime_cursor_area && opts.physical_ime_cursor_area.is_some() {
let area = opts.physical_ime_cursor_area.unwrap();
let pos = PhysicalPosition::new(area.position.x, area.position.y);
let size = PhysicalSize::new(area.size.x, area.size.y);
window.set_ime_cursor_area(pos, size);
}
if opts.min_size != last.min_size {
let s = opts.min_size.map(|s| LogicalSize::new(s.x, s.y));
window.set_min_inner_size(s);
}
if opts.max_size != last.max_size {
let s = opts.max_size.map(|s| LogicalSize::new(s.x, s.y));
window.set_max_inner_size(s);
}
if opts.theme != last.theme {
window.set_theme(opts.theme);
}
if opts.title != last.title {
window.set_title(&opts.title);
}
if opts.transparent != last.transparent {
window.set_transparent(opts.transparent);
}
// compare the resource version and uuid. These will get changed
// when the image is reloaded
let opts_icon = opts.window_icon.as_ref()
.map(|i| (i.version(), i.uuid()));
let last_icon = last.window_icon.as_ref()
.map(|i| (i.version(), i.uuid()));
if opts_icon != last_icon {
todo!("cannot set window icon yet");
}
if opts.window_level != last.window_level {
window.set_window_level(opts.window_level);
}
if opts.physical_cursor_position != last.physical_cursor_position && opts.physical_cursor_position.is_some() {
let pos = opts.physical_cursor_position.unwrap();
let pos = PhysicalPosition::new(pos.x, pos.y);
if let Err(e) = window.set_cursor_position(pos) {
error!("failed to set cursor position: {}", e);
}
}
last.last = opts.clone();
}
Ok(())
}
impl Plugin for WindowPlugin {
fn setup(&mut self, app: &mut crate::game::App) {
app.with_system("window_sync", window_sync_system, &[]);
}
}

View File

@ -1,34 +0,0 @@
use lyra_math::Transform;
use lyra_resource::{optionally_add_to_dep, ResourceData, UntypedResHandle};
use super::Mesh;
use crate::ResHandle;
/// A Node in the Gltf file
#[derive(Clone, Default)]
pub struct GltfNode {
pub name: Option<String>,
pub mesh: Option<ResHandle<Mesh>>,
pub transform: Transform,
pub children: Vec<GltfNode>,
}
impl ResourceData for GltfNode {
fn dependencies(&self) -> Vec<crate::UntypedResHandle> {
let mut deps: Vec<UntypedResHandle> = self.children.iter()
.flat_map(|c| c.mesh.as_ref().map(|h| h.untyped_clone()))
.collect();
optionally_add_to_dep(&mut deps, &self.mesh);
deps
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
}

View File

@ -1,22 +0,0 @@
#[derive(Clone, Copy, PartialEq)]
pub struct Area<P, S>
where
P: Clone + Copy + PartialEq,
S: Clone + Copy + PartialEq,
{
pub position: P,
pub size: S
}
impl<P, S> Area<P, S>
where
P: Clone + Copy + PartialEq,
S: Clone + Copy + PartialEq,
{
pub fn new(pos: P, size: S) -> Self {
Self {
position: pos,
size,
}
}
}

View File

@ -1,148 +0,0 @@
use lyra_math::Angle;
use lyra_reflect_derive::{impl_reflect_simple_struct, impl_reflect_trait_value};
use crate::{lyra_engine, Enum, Method, Reflect, ReflectMut, ReflectRef};
impl_reflect_simple_struct!(lyra_math::Vec2, fields(x = f32, y = f32));
impl_reflect_simple_struct!(lyra_math::Vec3, fields(x = f32, y = f32, z = f32));
impl_reflect_simple_struct!(lyra_math::Vec4, fields(x = f32, y = f32, z = f32, w = f32));
impl_reflect_simple_struct!(lyra_math::Quat, fields(x = f32, y = f32, z = f32, w = f32));
impl_reflect_simple_struct!(
lyra_math::Transform,
fields(
translation = lyra_math::Vec3,
rotation = lyra_math::Quat,
scale = lyra_math::Vec3
)
);
impl_reflect_trait_value!(lyra_math::Mat4);
impl Reflect for Angle {
fn name(&self) -> String {
"Angle".into()
}
fn type_id(&self) -> std::any::TypeId {
std::any::TypeId::of::<Self>()
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
fn as_boxed_any(self: Box<Self>) -> Box<dyn std::any::Any> {
self
}
fn apply(&mut self, val: &dyn Reflect) {
if let ReflectRef::Enum(e) = val.reflect_ref() {
let s = e.as_any().downcast_ref::<Self>()
.expect("cannot apply mismatched reflected enum");
*self = *s;
} else {
panic!("Provided value was not an enum!");
}
}
fn clone_inner(&self) -> Box<dyn Reflect> {
Box::new(self.clone())
}
fn reflect_ref(&self) -> crate::ReflectRef {
ReflectRef::Enum(self)
}
fn reflect_mut(&mut self) -> crate::ReflectMut {
ReflectMut::Enum(self)
}
fn reflect_val(&self) -> &dyn Reflect {
self
}
fn reflect_val_mut(&mut self) -> &mut dyn Reflect {
self
}
}
impl Enum for Angle {
fn field(&self, _: &str) -> Option<&dyn Reflect> {
// no struct variants
None
}
fn field_mut(&mut self, _: &str) -> Option<&mut dyn Reflect> {
// no struct variants
None
}
fn field_at(&self, idx: usize) -> Option<&dyn Reflect> {
// all variants only have one tuple field
if idx != 0 {
return None;
}
match self {
Angle::Degrees(v) => Some(v),
Angle::Radians(v) => Some(v),
}
}
fn field_at_mut(&mut self, idx: usize) -> Option<&mut dyn Reflect> {
// all variants only have one tuple field
if idx != 0 {
return None;
}
match self {
Angle::Degrees(v) => Some(v),
Angle::Radians(v) => Some(v),
}
}
fn field_name_at(&self, _: usize) -> Option<String> {
// no struct variants
None
}
fn has_field(&self, _: &str) -> bool {
// no struct variants
false
}
fn fields_len(&self) -> usize {
1
}
fn variants_len(&self) -> usize {
2
}
fn variant_name(&self) -> String {
match self {
Angle::Degrees(_) => "degrees".into(),
Angle::Radians(_) => "radians".into(),
}
}
fn variant_index(&self) -> usize {
match self {
Angle::Degrees(_) => 0,
Angle::Radians(_) => 1,
}
}
fn is_variant_name(&self, name: &str) -> bool {
self.variant_name() == name
}
fn variant_type(&self) -> crate::EnumType {
crate::EnumType::Tuple
}
}

View File

@ -1,31 +0,0 @@
[package]
name = "lyra-resource"
version = "0.0.1"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
lyra-ecs = { path = "../lyra-ecs", features = [ "math" ] }
lyra-reflect = { path = "../lyra-reflect", features = [ "math" ] }
lyra-math = { path = "../lyra-math" }
anyhow = "1.0.75"
base64 = "0.21.4"
crossbeam = { version = "0.8.4", features = [ "crossbeam-channel" ] }
glam = "0.29.0"
image = "0.25.2"
# not using custom matcher, or file type from file path
infer = { version = "0.15.0", default-features = false }
mime = "0.3.17"
notify = "6.1.1"
notify-debouncer-full = "0.3.1"
#notify = { version = "6.1.1", default-features = false, features = [ "fsevent-sys", "macos_fsevent" ]} # disables crossbeam-channel
percent-encoding = "2.3.0"
thiserror = "1.0.48"
tracing = "0.1.37"
uuid = { version = "1.4.1", features = ["v4"] }
instant = "0.1"
async-std = "1.12.0"
[dev-dependencies]
rand = "0.8.5"

View File

@ -1 +0,0 @@
use base64::Engine;

View File

@ -1,143 +0,0 @@
use syn::{parenthesized, token, Token};
pub(crate) enum FieldType {
Unknown,
Type(syn::Path),
Wrapped(syn::Path),
}
impl FieldType {
pub fn is_unknown(&self) -> bool {
matches!(self, FieldType::Unknown)
}
pub fn is_wrapped(&self) -> bool {
matches!(self, FieldType::Wrapped(_))
}
pub fn get_type_path(&self) -> Option<&syn::Path> {
match self {
FieldType::Unknown => None,
FieldType::Type(path) => Some(path),
FieldType::Wrapped(path) => Some(path),
}
}
}
pub(crate) struct Field {
pub field: syn::Ident,
pub field_ty: FieldType,
pub skip_setter: bool,
pub setter: Option<syn::Block>,
pub getter: Option<syn::Block>,
}
impl Field {
fn parse_extended(input: syn::parse::ParseStream) -> syn::Result<Self> {
let field_name = input.parse()?;
let fty = if input.peek(Token![:]) {
let _col: Token![:] = input.parse()?;
let s: syn::Path = input.parse()?;
let mut fty = FieldType::Type(s.clone());
if let Some(ident) = s.get_ident() {
if ident.to_string() == "wrap" {
let content;
let _parens: token::Paren = parenthesized!(content in input);
fty = FieldType::Wrapped(content.parse()?);
}
}
fty
} else {
FieldType::Unknown
};
let mut s = Self {
field: field_name,
field_ty: fty,
skip_setter: false,
setter: None,
getter: None,
};
while input.peek(Token![,]) {
let _: Token![,] = input.parse()?;
if input.peek(syn::Ident) {
let ident: syn::Ident = input.parse()?;
let ident_str = ident.to_string();
let ident_str = ident_str.as_str();
match ident_str {
"skip_set" => {
s.skip_setter = true;
}
"set" => {
let _eq: Token![=] = input.parse()?;
s.setter = Some(input.parse()?);
}
"get" => {
let _eq: Token![=] = input.parse()?;
s.getter = Some(input.parse()?);
}
_ => {
return Err(syn::Error::new_spanned(ident, "unknown wrapper command"));
}
}
}
}
if (s.getter.is_some() || s.setter.is_some()) && s.field_ty.is_wrapped() {
return Err(syn::Error::new(
input.span(),
"cannot specify custom getter or setter \
with wrapped type",
));
}
Ok(s)
}
}
impl syn::parse::Parse for Field {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
if input.peek(token::Paren) {
let content;
let _parens: token::Paren = parenthesized!(content in input);
Self::parse_extended(&content)
} else {
let field_name = input.parse()?;
let fty = if input.peek(Token![:]) {
let _col: Token![:] = input.parse()?;
let s: syn::Path = input.parse()?;
let mut fty = FieldType::Type(s.clone());
if let Some(ident) = s.get_ident() {
if ident.to_string() == "wrap" {
let content;
let _parens: token::Paren = parenthesized!(content in input);
fty = FieldType::Wrapped(content.parse()?);
}
}
fty
} else {
FieldType::Unknown
};
Ok(Self {
field: field_name,
field_ty: fty,
skip_setter: false,
setter: None,
getter: None,
})
}
}
}

View File

@ -1,362 +0,0 @@
use proc_macro2::Span;
use syn::{braced, parenthesized, parse_macro_input, punctuated::Punctuated, token, Token};
use quote::{quote, ToTokens};
use crate::{field::Field, FN_NAME_INTERNAL_REFLECT, FN_NAME_INTERNAL_REFLECT_TYPE};
fn field_table_setter(field: &Field) -> proc_macro2::TokenStream {
let ident = &field.field;
match &field.setter {
Some(set) => {
quote! {
table.set(stringify!(#ident), #set)?;
}
},
None => {
let ty = field.field_ty.get_type_path()
.expect("no field type specified");
let arg = if field.field_ty.is_wrapped() {
quote!(#ty(self.#ident))
} else { quote!(self.#ident) };
quote! {
table.set(stringify!(#ident), #arg)?;
}
},
}
}
fn field_table_getter(field: &Field) -> proc_macro2::TokenStream {
let ident = &field.field;
match &field.getter {
Some(get) => {
quote! {
let #ident = #get;
}
},
None => {
let ty = field.field_ty.get_type_path()
.expect("no field type specified");
quote! {
let #ident: #ty = table.get(stringify!(#ident))?;
}
},
}
}
fn wrapper_creation(wrapper: &syn::Ident, type_path: &syn::Path, struct_type: StructType, create: Option<&syn::Block>, fields: &Vec<Field>) -> proc_macro2::TokenStream {
match create {
Some(b) => quote!(#b),
None => {
/* let field_iter = fields.iter().map(|f| match &f.field_ty {
crate::field::FieldType::Type(path) => quote!(#path),
crate::field::FieldType::Wrapped(path) => quote!(*#path),
_ => todo!()
}); */
let field_iter = fields.iter().map(|f| {
let ident = &f.field;
if f.field_ty.is_wrapped() && struct_type == StructType::Fields {
quote!(#ident: (*#ident).clone())
} else {
quote!(#ident)
}
});
match struct_type {
StructType::Fields => {
quote! {
#wrapper(#type_path {
#(
#field_iter
),*
})
}
},
StructType::Tuple => {
quote! {
#wrapper(#type_path( #(#field_iter),* ))
}
},
}
}
}
}
fn get_reflect_lua_functions(ty: &ReflectType, type_path: &syn::Path, set_data: bool) -> proc_macro2::TokenStream {
let data = if set_data {
quote!(Some(this.into_wrapped()))
} else { quote!(None) };
match ty {
ReflectType::Component => {
quote! {
Ok(ScriptBorrow::from_component::<#type_path>(#data))
}
},
ReflectType::Resource => {
quote! {
Ok(ScriptBorrow::from_component::<#type_path>(#data))
}
},
}
}
#[derive(Debug, Clone, Copy)]
enum ReflectType {
//Unknown,
Component,
Resource,
}
/// The type of the wrapping struct
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
enum StructType {
#[default]
Fields,
Tuple,
}
struct IntoLuaUsage {
type_path: syn::Path,
struct_type: StructType,
override_name: Option<syn::Ident>,
table_name: String,
derives: Vec<syn::Ident>,
fields: Vec<Field>,
create: Option<syn::Block>,
reflection_type: Option<ReflectType>,
}
impl syn::parse::Parse for IntoLuaUsage {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let type_path: syn::Path = input.parse()?;
let type_ident = &type_path
.segments
.last()
.expect("Failure to find typename in macro usage!")
.ident;
let lua_name = type_ident.to_string();
let mut s = Self {
type_path,
struct_type: StructType::Fields,
override_name: None,
table_name: lua_name,
derives: vec![],
fields: vec![],
create: None,
reflection_type: None,
};
while input.peek(Token![,]) {
let _: Token![,] = input.parse()?;
if input.peek(syn::Ident) {
let ident: syn::Ident = input.parse()?;
let ident_str = ident.to_string();
let ident_str = ident_str.as_str();
match ident_str {
"name" => {
let _eq: Token![=] = input.parse()?;
let name: syn::Ident = input.parse()?;
s.override_name = Some(name);
},
"struct_type" => {
let _eq: Token![=] = input.parse()?;
let st_token = input.parse::<syn::LitStr>()?;
let st_str = st_token.value().to_lowercase();
let st_str = st_str.as_str();
let st = match st_str {
"fields" => StructType::Fields,
"tuple" => StructType::Tuple,
_ => return Err(syn::Error::new_spanned(
st_token,
format!("unknown struct type: '{}', expected 'fields', or `tuple`", st_str),
)),
};
s.struct_type = st;
},
"lua_name" => {
let _eq: Token![=] = input.parse()?;
s.table_name = input.parse::<syn::LitStr>()?.value();
},
"derives" => {
if input.peek(token::Paren) {
let content;
let _parens: token::Paren = parenthesized!(content in input);
let derives: Punctuated<syn::Ident, Token![,]> =
content.parse_terminated(syn::Ident::parse, Token![,])?;
s.derives = derives.into_iter().collect();
}
},
"fields" => {
let _eq: Token![=] = input.parse()?;
if input.peek(token::Brace) {
let content;
let _braced: token::Brace = braced!(content in input);
let terminated = content.parse_terminated(Field::parse, Token![,])?;
s.fields.extend(terminated.into_iter());
}
},
"create" => {
let _eq: Token![=] = input.parse()?;
s.create = Some(input.parse()?);
},
"reflect" => {
let _eq: Token![=] = input.parse()?;
let ty: syn::Ident = input.parse()?;
let ty_str = ty.to_string();
let ty_str = ty_str.as_str();
let ty = match ty_str {
"component" => ReflectType::Component,
"resource" => ReflectType::Resource,
_ => return Err(syn::Error::new_spanned(
ident,
format!("unknown wrapper type: '{}', expected 'component' or 'resource'", ty_str),
)),
};
s.reflection_type = Some(ty);
},
_ => {
return Err(syn::Error::new_spanned(
ident,
format!("unknown wrapper command: '{}'", ident_str),
));
}
}
}
}
if s.reflection_type.is_none() {
return Err(syn::Error::new(
input.span(),
format!("Wrapper type not specified! Expected 'type=component' or 'type=resource'"),
));
}
if s.table_name.is_empty() {
return Err(syn::Error::new(
input.span(),
format!("No lua table specified. Use 'lua_name=\"Camera\"'"),
))
}
Ok(s)
}
}
pub fn to_lua_struct_impl(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input = parse_macro_input!(input as IntoLuaUsage);
// unwrap is fine since `Some` is ensured in parse impl
let reflect_type = input.reflection_type.as_ref().unwrap();
let type_path = &input.type_path;
let type_name = &type_path
.segments
.last()
.expect("Failure to find typename in macro usage!")
.ident;
let wrapper = input.override_name
.unwrap_or_else(|| syn::Ident::new(&format!("Lua{}", type_name), Span::call_site()));
let derives_iter = input.derives.into_iter();
let lua_name = &input.table_name;
let field_getters_iter = input.fields.iter().map(|f| field_table_getter(f));
let field_setters_iter = input.fields.iter().map(|f| field_table_setter(f));
let struct_creator = wrapper_creation(&wrapper, type_path, input.struct_type, input.create.as_ref(), &input.fields);
let reflect_fn = get_reflect_lua_functions(reflect_type, &input.type_path, true);
let reflect_type_fn = get_reflect_lua_functions(reflect_type, &input.type_path, false);
quote! {
#[derive(Clone, #(#derives_iter),*)]
pub struct #wrapper(pub(crate) #type_path);
impl std::ops::Deref for #wrapper {
type Target = #type_path;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl std::ops::DerefMut for #wrapper {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl mlua::FromLua for #wrapper {
fn from_lua(val: mlua::Value, _: &mlua::Lua) -> mlua::Result<Self> {
let ty = val.type_name();
let table = val.as_table().ok_or(mlua::Error::FromLuaConversionError {
from: ty,
to: "Table".into(),
message: Some("expected Table".into()),
})?;
#(
#field_getters_iter
)*
Ok(#struct_creator)
}
}
impl mlua::IntoLua for #wrapper {
fn into_lua(self, lua: &mlua::Lua) -> mlua::Result<mlua::Value> {
let table = lua.create_table()?;
#(
#field_setters_iter
)*
table.set(
#FN_NAME_INTERNAL_REFLECT,
lua.create_function(|_, this: Self| {
#reflect_fn
})?,
)?;
table.set(
#FN_NAME_INTERNAL_REFLECT_TYPE,
lua.create_function(|_, ()| {
#reflect_type_fn
})?,
)?;
table.set(mlua::MetaMethod::Type.name(), #lua_name)?;
Ok(mlua::Value::Table(table))
}
}
impl LuaWrapper for #wrapper {
type Wrap = #type_path;
#[inline(always)]
fn wrapped_type_id() -> std::any::TypeId {
std::any::TypeId::of::<#type_path>()
}
#[inline(always)]
fn into_wrapped(self) -> Self::Wrap {
self.0
}
}
}.into_token_stream().into()
}

View File

@ -1,72 +0,0 @@
---Create a Resource query that will return the specific ECS world resource.
---
---@see ResQuery
---@param resource table|userdata
---@return ResQuery
function Res(resource)
return ResQuery.new(resource)
end
---@alias Query function|table|userdata
---Create a `ChangedQuery` query that will return only if the resource or component has changed
---since last tick.
---
---@see ChangedQuery
---@param val table|userdata
---@return ChangedQuery
function Changed(val)
return ChangedQuery.new(val)
end
---Create a `HasQuery` filter that will return only if the entity has a specific component.
---
---@see HasQuery
---@param val table|userdata
---@return HasQuery
function Has(val)
return HasQuery.new(val)
end
---Create a `NotQuery` filter that will allow results if the query returns nothing or
---filter denies.
---
---@see NotQuery
---@param val Query
---@return NotQuery
function Not(val)
return NotQuery.new(val)
end
---Create a `AnyQuery` filter that will allow results if any of the queries return something.
---
---The queries are evaluated in the order they were provided.
---
---@see AnyQuery
---@param ... Query
---@return AnyQuery
function Any(...)
return AnyQuery.new(...)
end
---Create a `TickOfQuery` for retrieving the tick of the resource or component on the entity.
---
---@see TickOfQuery
---@param ... table|userdata
---@return TickOfQuery
function TickOf(...)
return TickOfQuery.new(...)
end
---Create any `OptionalQuery` that allows for a query to return nothing.
---
---If the query is a filter, its result will essentially be ignored. If the query returns `None`
---or `AlwaysNone`, this query will return `Nil`. If the query results in a value, its value
---will be the result of this query.
---
---@see OptionalQuery
---@param q Query
---@return OptionalQuery
function Optional(q)
return OptionalQuery.new(q)
end

View File

@ -1,94 +0,0 @@
---@enum WindowMode
WindowMode = {
WNDOWED = "windowed",
BORDERLESS_FULLSCREEN = "borderless_fullscreen",
SIZED_FULLSCREEN = "sized_fullscreen",
FULLSCREEN = "fullscreen",
}
---@enum CursorGrabMode
CursorGrabMode = {
NONE = "none",
CONFINED = "confined",
LOCKED = "locked",
}
---@enum WindowTheme
WindowTheme = {
LIGHT = "light",
DARK = "dark",
}
---@enum WindowLevel
WindowLevel = {
ALWAYS_ON_BOTTOM = "always_on_bottom",
NORMAL = "normal",
ALWAYS_ON_TOP = "always_on_top",
}
---@enum HandleState
HandleState = {
LOADING = "loading",
READY = "ready",
ERROR = "error",
}
---@enum ActionKind
ActionKind = {
BUTTON = "button",
AXIS = "axis",
}
---@enum ActionState
ActionState = {
IDLE = "idle",
PRESSED = "pressed",
JUST_PRESSED = "just_pressed",
JUST_RELEASED = "just_released",
AXIS = "axis",
OTHER = "other",
}
---@enum FilterMode
FilterMode = {
NEAREST = "nearest",
LINEAR = "linear",
}
---@enum WrappingMode
WrappingMode = {
CLAMP_TO_EDGE = "clamp_to_edge",
MIRRORED_REPEAT = "mirrored_repeat",
REPEAT = "repeat",
}
---@enum CameraProjectionMode
CameraProjectionMode = {
PERSPECTIVE = "perspective",
ORTHOGRAPHIC = "orthographic",
}
---@enum DeviceEventKind
DeviceEventKind = {
ADDED = "added",
REMOVED = "removed",
MOUSE_MOTION = "mouse_motion",
MOUSE_WHEEL = "mouse_wheel",
MOTION = "motion",
BUTTON = "button",
KEY = "key",
}
---@enum NativeKeyCodeKind
NativeKeyCodeKind = {
ANDROID = "android",
MACOS = "macos",
WINDOWS = "windows",
XKB = "xkb",
}
---@enum ElementState
ElementState = {
PRESSED = "pressed",
RELEASED = "released",
}

View File

@ -1,23 +0,0 @@
---@meta
---@class GltfHandle: Handle
---
---A handle to a GLTF asset.
GltfHandle = {
}
---Get a list of scenes in the GLTF file.
---
---@return SceneHandle[]
function GltfHandle:scenes() end
---Get a list of materials in the GLTF file.
---
---@return MaterialHandle[]
function GltfHandle:materials() end
---Get a list of meshes in the GLTF file.
---
---@return MeshHandle[]
function GltfHandle:meshes() end

View File

@ -1,43 +0,0 @@
---@meta
---@class Handle: userdata
---
---A handle to an asset. Assets are loaded asynchronously, so you cannot immediately
---use them after you request them from the World.
Handle = {
---The path the asset was loaded from.
---
---@type string
path = nil,
---The version of the resource.
---
---Increments every time a resource is loaded.
---
---@type number
version = nil,
---The unique id of the resource.
---
---This is not changed for the entire lifetime of the handle, it does not change
---when an asset is reloaded.
---
---@type string
uuid = nil,
---Current state of the asset handle.
---@type HandleState
state = nil,
}
---Returns true if the asset is watched for auto-reloading.
---
---@return boolean
function Handle:is_watched() end
---Returns true if the asset is loaded.
---@return boolean
function Handle:is_loaded() end
---Blocks execution until the asset and its dependencies are loaded.
function Handle:wait_until_loaded() end

View File

@ -1,102 +0,0 @@
---@meta
---@class MaterialHandle: Handle
---
---A handle to a material asset in a GLTF file.
MaterialHandle = {
---The unique id of the GPU shader.
---@type number?
shader_uuid = nil,
---The name of the material from GLTF.
---@type string?
name = nil,
---@type boolean
double_sided = nil,
---The RGBA base color of the model.
---
---If a texture is supplied with `base_color_texture`, this value will tint the
---texture. If a texture is not provided, this value would be the color of
---the Material.
---
---@type Vec4
base_color = nil,
---The metalness of the material
---
---From 0.0 (non-metal) to 1.0 (metal).
---
---@type number
metallic = nil,
---The roughness of the material
---
---From 0.0 (smooth) to 1.0 (rough)
---
---@type number
roughness = nil,
---The base color texture of the model.
---@type TextureHandle?
base_color_texture = nil,
---The metallic-roughness texture.
---
---The metalness values are sampled from the B channel. The roughness values are sampled from
---the G channel. These values are linear. If other channels are present (R or A), they are
---ignored for metallic-roughness calculations.
---@type TextureHandle?
metallic_roughness_texture = nil,
---A set of parameter values that are used to define the specular-glossiness material model
---from Physically-Based Rendering (PBR) methodology.
---GLTF extension: [KHR_materials_pbrSpecularGlossiness](https://kcoley.github.io/glTF/extensions/2.0/Khronos/KHR_materials_pbrSpecularGlossiness)
---@type TextureHandle?
pbr_glossiness = nil,
---The optional alpha cutoff value of the material.
---
---This will be used instead of the renderer's default.
---
---@type number?
alpha_cutoff = nil,
---The alpha rendering mode of the material.
---
---The material's alpha rendering
---mode enumeration specifying the interpretation of the alpha value of the main
---factor and texture.
---
---* In `Opaque` mode (default) the alpha value is ignored
--- and the rendered output is fully opaque.
---* In `Mask` mode, the rendered
--- output is either fully opaque or fully transparent depending on the alpha
--- value and the specified alpha cutoff value.
---* In `Blend` mode, the alpha value is used to composite the source and
--- destination areas and the rendered output is combined with the background
--- using the normal painting operation (i.e. the Porter and Duff over
--- operator).
---
---@type AlphaMode
alpha_mode = nil,
---@type Specular?
specular = nil,
}
---@enum AlphaMode
AlphaMode = {
OPAQUE = "opaque",
MASK = "mask",
BLEND = "blend",
}
---@class PbrGlossiness
---TODO: implement
PbrGlossiness = {}
---@class Specular
---TODO: implement
Specular = {}

View File

@ -1,14 +0,0 @@
---@meta
---@class MeshHandle: Handle
---
---A handle to a mesh in a GLTF file.
MeshHandle = {
---The material of the mesh
---@type MaterialHandle
material = nil,
}
---Get the indices in the mesh.
---@return number[]
function MeshHandle:indices() end

View File

@ -1,9 +0,0 @@
---@meta
---@class SceneHandle: Handle
---
---A handle to a scene asset in a GLTF file.
SceneHandle = {
}

View File

@ -1,4 +0,0 @@
---@meta
---@class TextureHandle
TextureHandle = {}

View File

@ -1,125 +0,0 @@
---@meta
---@class ActionHandler: userdata
ActionHandler = {}
--- Create a new `ActionHandler`.
---
--- ```lua
--- local handler = ActionHandler.new {
--- -- A list of layout IDs
--- layouts = { 0 },
--- actions = {
--- -- A list of action names and the `ActionKind`s.
--- -- Actions can be buttons or axes.
--- MoveForwardBackward = ActionKind.AXIS,
--- MoveLeftRight = ActionKind.AXIS,
--- MoveUpDown = ActionKind.AXIS,
--- LookLeftRight = ActionKind.AXIS,
--- LookUpDown = ActionKind.AXIS,
--- LookRoll = ActionKind.AXIS,
--- ObjectsMoveUpDown = ActionKind.AXIS
--- },
--- mappings = {
--- -- Each entry here is a mapping of actions for a layout.
--- -- This can be used so that when the current layout is changed,
--- -- the mapping would also change.
--- {
--- -- Specify the layout id that this mapping is for.
--- layout = 0,
--- binds = {
--- -- This is an Action bind. A bind is used to bind an input to an action.
--- -- These actions are defined above in "actions".
--- MoveForwardBackward = {
--- -- This is how you bind a button. In this case the button is a key.
--- -- "key" is the device the bind comes from, then after the colon is the
--- -- input name, in this case a specific key. We specify a modifier to the bind
--- -- after the equal sign.
--- "key:w=1.0",
--- "key:s=-1.0"
--- },
--- MoveLeftRight = {
--- "key:a=-1.0", "key:d=1.0"
--- },
--- MoveUpDown = {
--- "key:c=1.0", "key:z=-1.0"
--- },
--- LookLeftRight = {
--- "key:left=-1.0", "key:right=1.0",
--- -- Here is a bind to an axis.
--- -- We use "mouse", for the device the bind is from, then "axis" to specify
--- -- that we want to bind a specific axis, then we use the name of the axis,
--- -- in this case "x".
--- -- So this binds to the x axis of the mouse.
--- "mouse:axis:x"
--- },
--- LookUpDown = {
--- "key:up=-1.0", "key:down=1.0",
--- -- Here we bind to the y axis of the mouse.
--- "mouse:axis:y",
--- },
--- LookRoll = {
--- "key:e=-1.0", "key:q=1.0",
--- },
--- ObjectsMoveUpDown = {
--- "key:u=1.0", "key:j=-1.0"
--- }
--- }
--- }
--- }
--- }
---
--- -- Add the handler to the world so the host will process it.
--- world:add_resource(handler)
--- ```
---
---@param table table See above example to see the format of this table.
function ActionHandler.new(table) end
---Returns the action's modifier if its an updated axis.
---
---Returns `nil` if the action's state is not `ActionState::Axis`, or if the
---action was not found.
---@param action string
---@return number?
function ActionHandler:get_axis(action) end
---Returns true if the action is pressed (or was just pressed).
---
---Returns `nil` if the action's was not found.
---@param action string
---@return boolean?
function ActionHandler:is_pressed(action) end
---Returns true if the action was **just** pressed.
---
---Returns `nil` if the action was not found
---
---@param action string
---@return boolean?
function ActionHandler:was_just_pressed(action) end
---Returns true if the action was just released.
---
---Returns `nil` if the action was not found
---
---@param action string
---@return boolean?
function ActionHandler:was_just_released(action) end
---Returns the action's modifier if it was just pressed.
---
---Returns `nil` if the action's state is not `ActionState.JUST_PRESSED`,
---or if the action was not found.
---
---@param action string
---@return number?
function ActionHandler:get_just_pressed(action) end
---Returns the current state of the action.
---
---The first element in the returned tuple is the state enum, and the second
---is the state modifier. The modifer will be `nil` if the state is "idle"
---
---@return [ActionState, number?]
function ActionHandler:get_action_state(action) end

View File

@ -1,18 +0,0 @@
---@meta
---@class Camera: userdata
Camera = {
---The position of the camera
---@type Transform
transform = nil,
---The field of view of the camera
---@type Angle
fov = nil,
---The projection mode the camera.
---Can be used to specify if the camera is 2D (orthographic), or 3D (perspective).
---@type CameraProjectionMode
mode = nil,
---Flag to enable some debug rendering stuff.
---@type boolean
debug = nil,
}

View File

@ -1,15 +0,0 @@
---@meta
---@class DeltaTime: userdata
---
---DeltaTime is an ECS world resource. When its requested from the world, a `number`
---is returned.
---
---Example:
---```lua
------@type number
---local dt = world:resource(DeltaTime)
---
---print(type(dt)) --> number
---```
DeltaTime = {}

View File

@ -1,29 +0,0 @@
---@meta
---An entity handle.
---@class Entity: userdata
Entity = {}
---Get the id of the Entity.
---@return number
function Entity:id() end
---Get the generation number of the Entity.
---
---Entity handles are reused by the ECS World, the generation is used to tell reused Entity
---id's apart from previous generations.
---
---@return number
function Entity:generation() end
---A reference to an entity in the world.
---
---Can be used to insert and update components on the entity.
---
---@class EntityRef: userdata
EntityRef = {}
---Update components that are **already** on an Entity.
---
---@param ... any The components to update on the entity.
function EntityRef:update(...) end

View File

@ -1,9 +0,0 @@
---@meta
---@class EventReader<T>: userdata
EventReader = {}
---Get an iterator for reading the event.
---@generic T
---@return fun(): T? iterator An iterator for reading the events.
function EventReader:read() end

View File

@ -1,17 +0,0 @@
---@meta
---@class FreeFlyCamera: userdata
FreeFlyCamera = {
---Movement speed of the camera.
---@type number
speed = nil,
---The speed of the camera rotation.
---@type number
look_speed = nil,
---The sensitivity of the mouse when looking.
---
---This is additional to `look_speed`, but onyl applied to mouse movement.
---
---@type number
mouse_sensitivity = nil,
}

View File

@ -1,9 +0,0 @@
require "action_handler"
require "camera"
require "delta_time"
require "entity"
require "event_reader"
require "free_fly_camera"
require "window"
require "world_transform"
require "world"

View File

@ -1,25 +0,0 @@
---@meta
---An ECS filter that will return if any of the provided queries return.
---
---The queries are evaluated in the order they were provided. When a query or filter returns a value,
---that value will be returned.
---
---Use the utility function `Any(...)` to create a new query since its faster to
---write than this.
---
---@see Any
---@class AnyQuery: userdata
AnyQuery = {}
---Create a new AnyQuery.
---
---Use the utility function `Any(...)` to create a new query since its faster to
---write than this.
---
---@see Any
---@param ... Query The query to invert.
function AnyQuery:new(...) end
---An internal function used by the engine to retrieve the query result.
function AnyQuery:__lyra_internal_ecs_query_result(world, entity) end

View File

@ -1,22 +0,0 @@
---@meta
---An ECS query used for obtaining **changed** resources or components from the world.
---
---Use the utility function `Changed(...)` to create a new query since its faster to
---write than this.
---
---This query will not return if the resource or component has not changed since the last tick.
---
---@class ChangedQuery: userdata
ChangedQuery = {}
---Create a new ChangedQuery.
---
---Use the utility function `Changed(...)` to create a new query since its faster to
---write than this.
---
---@param val table|userdata The component or resource to detect changed of.
function ChangedQuery:new(val) end
---An internal function used by the engine to retrieve the query result.
function ChangedQuery:__lyra_internal_ecs_query_result(world, entity) end

View File

@ -1,22 +0,0 @@
---@meta
---An ECS filter that allows the query if the entity has the Component.
---
---Use the utility function `Has(...)` to create a new query since its faster to
---write than this.
---
---@see Has
---@class HasQuery: userdata
HasQuery = {}
---Create a new HasQuery.
---
---Use the utility function `Has(...)` to create a new query since its faster to
---write than this.
---
---@see Has
---@param val table|userdata The component to look for on the entity.
function HasQuery:new(val) end
---An internal function used by the engine to retrieve the query result.
function HasQuery:__lyra_internal_ecs_query_result(world, entity) end

View File

@ -1,9 +0,0 @@
require "view"
require "view_one"
require "changed"
require "res"
require "has"
require "any"
require "not"
require "optional"
require "tick_of"

View File

@ -1,22 +0,0 @@
---@meta
---An ECS filter that inverts the provided filter/query result.
---
---Use the utility function `Not(...)` to create a new query since its faster to
---write than this.
---
---@see Not
---@class NotQuery: userdata
NotQuery = {}
---Create a new NotQuery.
---
---Use the utility function `Not(...)` to create a new query since its faster to
---write than this.
---
---@see Not
---@param val Query The query to invert.
function NotQuery:new(val) end
---An internal function used by the engine to retrieve the query result.
function NotQuery:__lyra_internal_ecs_query_result(world, entity) end

View File

@ -1,26 +0,0 @@
---@meta
---An ECS query that ignores filters and queries that dont return anything.
---
---If the provided query returns nothing, this query will provide a `nil` value.
---The results of filters are essentially ignored, since it doesn't matter the result, this query
---will return. If the provided query has a result, this query will also return it.
---
---Use the utility function `Optional(...)` to create a new query since its faster to
---write than this.
---
---@see Optional
---@class OptionalQuery: userdata
OptionalQuery = {}
---Create a new OptionalQuery.
---
---Use the utility function `Optional(...)` to create a new query since its faster to
---write than this.
---
---@see Optional
---@param val Query The query to invert.
function OptionalQuery:new(val) end
---An internal function used by the engine to retrieve the query result.
function OptionalQuery:__lyra_internal_ecs_query_result(world, entity) end

View File

@ -1,16 +0,0 @@
---@meta
---An ECS query used for obtaining Resources from the `World`.
---@class ResQuery: userdata
ResQuery = {}
---Create a new ResQuery for getting a Resource from the `World`.
---
---Use the utility function `Res(...)` to create a new query since its faster to
---write than this.
---
---@param val table|userdata The resource type to obtain.
function ResQuery:new(val) end
---An internal function used by the engine to retrieve the query result.
function ResQuery:__lyra_internal_ecs_query_result(world, entity) end

View File

@ -1,22 +0,0 @@
---@meta
---An ECS query that returns the tick of the resource or component provided.
---
---Use the utility function `TickOf(...)` to create a new query since its faster to
---write than this.
---
---@see TickOf
---@class TickOfQuery: userdata
TickOfQuery = {}
---Create a new TickOfQuery.
---
---Use the utility function `TickOf(...)` to create a new query since its faster to
---write than this.
---
---@see TickOf
---@param val table|userdata The component or resource to retrieve the tick of.
function TickOfQuery:new(val) end
---An internal function used by the engine to retrieve the query result.
function TickOfQuery:__lyra_internal_ecs_query_result(world, entity) end

View File

@ -1,23 +0,0 @@
---@meta
---@class View: userdata
View = {}
---Create a new view to query for components and world resources.
---
---Each parameter is a query. If you want to query entities with components, you would just use
---the component names.
---There are other queries, like `Changed` for querying for changed resources and components,
---and `Res` for querying for resources.
---
---@return View
function View.new(...) end
---@class ViewResult: userdata
ViewResult = {}
---Returns an interator over the results of the View.
---
---@generic T...
---@return fun(): EntityRef, T... iterator An iterator over the results. In the same order of the created View.
function ViewResult:iter() end

View File

@ -1,19 +0,0 @@
---@meta
---Results of a View over a single entity.
---@class ViewOneResult: userdata
ViewOneResult = {}
---Returns the results of the view over a single entity.
---
---@see ViewOneResult.__call
---@generic T...
---@return T...
function ViewOneResult:get() end
---Returns the results of the view over a single entity.
---
---@see ViewOneResult.get
---@generic T...
---@return T...
function ViewOneResult:__call() end

View File

@ -1,128 +0,0 @@
---@meta
---@class Window: userdata
Window = {
---Gets or sets the window's focus.
---@type boolean
focused = nil,
---Gets or sets the window mode.
---@type WindowMode
window_mode = nil,
---Gets or sets the position of the top-left corner of the window.
---
---The top-left hand corner of the desktop is not necessarily the same
---as the screen. If the user uses a desktop with multiple monitors, the top-left
---hand corner of the desktop is the top-left hand corner of the monitor at the
---top-left of the desktop.
---
---If this is `nil`, the position will be chosen by the windowing manager at creation,
---then set when the window is created.
---
---@type Vec2?
position = nil,
---@type Vec2
physical_size = nil,
---@type Vec2
size = nil,
---Gets/sets if the window has decorations.
---@type boolean
decorated = nil,
---Gets/sets the window's current maximized state.
---@type boolean
maximized = nil,
---Gets/sets the window's current minimized state.
---
---Is `nil` if the minimized state could not be determined.
---
---@type boolean?
minimized = nil,
---Gets/sets the window's current resizable state
---@type boolean
resizable = nil,
---Gets/sets the window's current visibility state.
---
---Is `nil` when it could not be determined.
---
---@type boolean?
visible = nil,
--TODO: resize_increments
---Gets the scale factor.
---
---You cannot set this field.
---
---@type number
scale_factor = nil,
---Gets/sets the window's blur state.
---@type boolean
blur = nil,
--TODO: cursor appearance
---Gets/sets the window's cursor grab mode.
---@type CursorGrabMode
cursor_grab = nil,
---Gets/sets whether the window catches cursor events.
---@type boolean
cursor_hittest = nil,
---Gets/sets the cursor's visibility.
---@type boolean
cursor_visible = nil,
---Sets whether the window should get IME events.
---
---When IME is allowed, the window will receive Ime events, and during the preedit phase
---the window will NOT get KeyboardInput events. The window should allow IME while
---it is expecting text input.
---
---When IME is not allowed, the window wont receive window ime events, and will receive
---KeyboardInput events for every keypress instead. Not allowing IME is useful for games
---for example. IME is not allowed by default.
---
---@type boolean
ime_allowed = nil,
---Gets/sets the minimum size of the window.
---@type Vec2?
min_size = nil,
---Gets/sets the maximum size of the window.
---@type Vec2?
max_size = nil,
---Gets/sets the current window theme.
---
---Specify `nil` to reset the theme to the system default. May also be `nil` on
---unsupported platforms.
---
---@type WindowTheme?
theme = nil,
---Gets/sets the title of the window.
---@type string
title = nil,
---Gets/sets the window's transparency state.
---@type boolean
transparent = nil,
--TODO: window_icon
---Change the window level.
---@type WindowLevel
window_level = nil,
---Gets the window's occluded state (completely hidden from view).
---@type boolean
occluded = nil,
---Gets/sets the cursor position in the window in logical coordinates.
---
---The value is `nil` when the cursor is not in the window.
---
---@type Vec2?
cursor_position = nil,
---Gets/sets the cursor position in the window in physical coordinates.
---
---The value is `nil` when the cursor is not in the window.
---
---@type Vec2?
physical_cursor_position = nil,
---Checks if the mouse is inside the window
---@param self Window
---@return boolean
is_mouse_inside = function (self) return false end,
}

View File

@ -1,95 +0,0 @@
---@meta
---@class World: userdata
World = {}
---Spawn an entity with components.
---
---@param ... userdata The components to spawn on the new entity, currently must be userdata.
---@return Entity
function World:spawn(...) end
---Get an ECS resource.
---
---Returns `nil` if the resource was not found in the world. Many resources will
---return userdata, however some may return Lua types like `DeltaTime`
---returning a `number`.
---
---Example:
---```lua
------@type number
---local dt = world:resource(DeltaTime)
---
---print(type(dt)) --> number
---```
---
---@param resource userdata This shouldn't be an instance of userdata.
---@return any?
function World:resource(resource) end
---Add a resource to the world.
---
---If the resource already exists, it will be overwritten.
---
---@param resource userdata
function World:add_resource(resource) end
---Request an asset.
---
---Assets are loaded asyncronously, so you must wait before trying to access fields on
---the asset. You can spawn an entity with it when its still loading.
---
---Returns an asset handle to the requested resource type
---
---@param path string
---@return Handle asset An asset handle to the requested resource type.
function World:request_asset(path) end
---Get the current tick of the world.
---
---The tick is used to drive changed detection of resources and components.
---The world tick is iterated every frame.
---
---@return number
function World:get_tick() end
---Get an event reader of a specific event.
---
---@generic T
---@param event T
---@return EventReader<T>
function World:read_event(event) end
---View the world using the queries contained in a View.
---
---Example:
---```lua
----- Get entities without WorldTransform
---local view = View.new(Transform, Not(Has(WorldTransform)), Res(DeltaTime))
---local res = world:view_query(view)
------@param transform Transform
------@param dt DeltaTime
---for entity, transform, dt in res:iter() do
--- transform:translate(0, 0.15 * dt, 0)
--- entity:update(transform)
---end
---```
---
---@see View
---@see ViewResult
---@param view View
---@return ViewResult
function World:view(view) end
---View a single entity in the world.
---
---The view can contain queries and filters, but they will only be evaluated for a single entity.
---
---@param en Entity
---@param view View
---@return ViewOneResult
function World:view_one(en, view) end
--World global
---@type World
world = nil

View File

@ -1,9 +0,0 @@
---@meta
--- A Transform represents the relative position of the entity to its parent entity, while
--- a world transform is the position relative to the World. When wanting to move an entity,
--- you should use its [`Transform`]. You cannot mutate [`WorldTransform`] as its managed completey
--- by the [`system_update_world_transforms`] system. For the WorldTransform to work properly, you
--- must have both a [`Transform`] and [`WorldTransform`] on the entities in the scene.
---@alias WorldTransform Transform The world transform of an entity.
WorldTransform = {}

View File

@ -1,140 +0,0 @@
---@meta
---@class DeviceEventAdded: table
DeviceEventAdded = {
---@type DeviceEventKind
kind = DeviceEventKind.ADDED,
}
---@class DeviceEventRemoved: table
DeviceEventRemoved = {
---@type DeviceEventKind
kind = DeviceEventKind.REMOVED,
}
---@class DeviceEventMouseMotion: table
DeviceEventMouseMotion = {
---@type DeviceEventKind
kind = DeviceEventKind.MOUSE_MOTION,
---The change in physical position of a pointing device.
---
---This represents raw, unfiltered physical motion.
---
---@type Vec2
delta = nil,
}
---A physical scroll event from a device.
---
---`line_delta` and `pixel_delta` are exclusive, only one is non-nil at a time.
---
---@class DeviceEventMouseWheel: table
DeviceEventMouseWheel = {
---@type DeviceEventKind
kind = DeviceEventKind.MOUSE_WHEEL,
---Amount in lines or rows to scroll.
---
---Positive values indicate that the content that is being scrolled should move right
---and down (revealing more content left and up).
---
---@type Vec2?
line_delta = nil,
---Amount in pixels to scroll in the horizontal and vertical direction.
---
---Positive values indicate that the content being scrolled should move right/down.
---
---@type Vec2?
pixel_delta = nil,
}
---Device event that was triggered by motion on an analog axis.
---@class DeviceEventMotion: table
DeviceEventMotion = {
---@type DeviceEventKind
kind = DeviceEventKind.MOTION,
---The analog axis that motion was triggered from.
---@type number
axis = nil,
---The amount of motion.
---@type number
value = nil,
}
---@class DeviceEventButton: table
DeviceEventButton = {
---@type DeviceEventKind
kind = DeviceEventKind.BUTTON,
---The button id that the event is from.
---@type number
button = nil,
---The state of the button.
---@type ElementState
state = nil,
}
---A device event triggered from a key press.
---
---The field `code` will be nil if the key code identifier is unknown.
---When the identifier is unknown, it can be retrieved with `native_code`. The field
---`native` specifies the kind of platform the code is from.
---
---@class DeviceEventKey: table
DeviceEventKey = {
---@type DeviceEventKind
kind = DeviceEventKind.KEY,
---@type ElementState
state = nil,
---The known key name.
---
---This is `nil` if `native` or `native_code` is specified.
---
---@type string
code = nil,
---The kind of native platform this code is for.
---
---This is `nil` if `code` is specified.
---
---@type NativeKeyCodeKind
native = nil,
---The platform-native physical key identifier.
---
---This is `nil` if `code` is specified.
---
---@type number
native_code = nil,
}
---@alias DeviceEventRaw
---| DeviceEventAdded
---| DeviceEventRemoved
---| DeviceEventMotion
---| DeviceEventMouseMotion
---| DeviceEventMouseWheel
---| DeviceEventButton
---| DeviceEventKey
DeviceEventRaw = { }
---@class DeviceId: userdata
DeviceId = {}
---@class DeviceEvent: table
DeviceEvent = {
---The device id that the event came from
---@type DeviceId
source = nil,
---The actual device event
---@type DeviceEventRaw
event = nil,
}

View File

@ -1,18 +0,0 @@
---@meta
---@class EventReader: userdata
EventReader = {}
---Returns an iterator over the events in the reader.
---@return fun(): any
function EventReader:read() end
---@class EventWriter: userdata
EventWriter = {}
---Writes an event.
---
---The event must be the same type that this writer wraps.
---
---@param event any
function EventWriter:write(event) end

View File

@ -1,5 +0,0 @@
---@meta
---The world global that is provided to every Lua script.
---@type World
world = nil

View File

@ -1,4 +0,0 @@
require "math.init"
require "ecs.init"
require "asset.handle"

View File

@ -1,25 +0,0 @@
---@meta
---@class Angle: userdata
Angle = {}
---Create a new angle in degrees.
---@param deg number
function Angle.new_degrees(deg) end
---Create a new angle in radians.
---@param rad number
function Angle.new_radians(rad) end
---Get the angle in radians.
---
---Will convert from degrees automatically.
---
---@return number radians
function Angle:radians() end
---Get the angle in degrees.
---
---Will convert from radians automatically.
---
---@return number degrees
function Angle:degrees() end

View File

@ -1,6 +0,0 @@
require "math.vec2"
require "math.vec3"
require "math.vec4"
require "math.quat"
require "math.transform"
require "math.angle"

View File

@ -1,188 +0,0 @@
---@meta
---@class Quat: userdata
---This is a Lua export of [`glam::Quat`](https://docs.rs/glam/latest/glam/f32/struct.Quat.html)
---
---@operator add(self): self
---@operator sub(self): self
---@operator div(number): self
---@operator mul(self|Vec3|number): self
---@diagnostic disable-next-line: unknown-operator
---@operator eq: self
Quat = {
---The x coordinate
---@type number
x = nil,
---The y coordinate
---@type number
y = nil,
---The z coordinate
---@type number
z = nil,
---The w coordinate
---@type number
w = nil,
}
---Create a new `Quat`
---@param x number
---@param y number
---@param z number
---@param w number
---@return self
function Quat.new(x, y, z, w) end
---Creates a quaternion from the angle (in radians) around the x axis.
---@param rad number
---@return self
function Quat.from_rotation_x(rad) end
---Creates a quaternion from the angle (in radians) around the y axis.
---@param rad number
---@return self
function Quat.from_rotation_y(rad) end
---Creates a quaternion from the angle (in radians) around the z axis.
---@param rad number
---@return self
function Quat.from_rotation_z(rad) end
---Creates a quaternion from a `Vec4`.
---@param vec4 Vec4
---@return self
function Quat.from_vec4(vec4) end
---Create a quaternion for a normalized rotation axis and angle (in radians).
---
---The axis must be a unit vector.
---
---@param axis Vec3
---@param rad number
---@return self
function Quat.from_axis_angle(axis, rad) end
---Computes the dot product of self and rhs.
---
---The dot product is equal to the cosine of the angle between two
---quaternion rotations.
---
---@param rhs Quat
---@return number
function Quat:dot(rhs) end
---Computes the length of self.
---
---@return number
function Quat:length() end
---Computes the squared length of self.
---
---This is generally faster than length() as it avoids a square root operation.
---
---@return number
function Quat:length_squared() end
---Computes 1.0 / length().
---
---For valid results, self must not be of length zero.
---@return number
function length_recip() end
---Returns `self` normalized to length `1.0`.
---
---For valid results, `self` must not be of length zero.
---
---@return self
function Quat:normalize() end
---Multipies `self` with a `Quat`
---@param rhs Quat
function Quat:mult_quat(rhs) end
---Multiplies `self` with a `Vec3`
---@param rhs Vec3
function Quat:mult_vec3(rhs) end
---Performs a linear interpolation between `self` and `rhs` based on `alpha`.
---
---Both `Quat`s must be normalized.
---
---When `alpha` is `0.0`, the result will be equal to `self`. When `alpha` is `1.0`,
---the result will be equal to `rhs`.
---
---@param rhs Quat
---@param alpha number
function Quat:lerp(rhs, alpha) end
---Performs a spherical linear interpolation between `self` and `rhs` based on `alpha`.
---
---Both `Quat`s must be normalized.
---
---When `alpha` is `0.0`, the result will be equal to `self`. When `alpha` is `1.0`,
---the result will be equal to `rhs`.
---
---@param rhs Quat
---@param alpha number
function Quat:slerp(rhs, alpha) end
---Returns the inverse of a normalized quaternion.
---
---Typically quaternion inverse returns the conjugate of a normalized quaternion.
---Because `self` is assumed to already be unit length this method does not
---normalize before returning the conjugate.
---@return self
function Quat:inverse() end
---Returns `true` if, and only if, all elements are finite. If any element is either
---`NaN`, positive or negative infinity, this will return `false`.
---
---@return boolean
function Quat:is_finite() end
---@return boolean
function Quat:is_nan() end
---Returns whether `self` is of length `1.0` or not.
---
---Uses a precision threshold of `1e-6`.
---@return boolean
function Quat:is_normalized() end
---@return boolean
function Quat:is_near_identity() end
---Returns the angle (in radians) for the minimal rotation for transforming
---this quaternion into another.
---
---Both quaternions must be normalized.
---@return number
function Quat:angle_between(rhs) end
---Rotates towards `rhs` up to `max_angle` (in radians).
---
---When `max_angle` is `0.0`, the result will be equal to `self`. When `max_angle`
---is equal to `self.angle_between(rhs)`, the result will be equal to `rhs`.
---If `max_angle` is negative, rotates towards the exact opposite of `rhs`.
---Will not go past the target.
---
---Both quaternions must be normalized.
---@return self
function Quat:rotate_towards(rhs, max_angle) end
---Returns true if the absolute difference of all elements between `self` and `rhs` is less
---than or equal to `max_abs_diff`.
---
---This can be used to compare if two quaternions contain similar elements. It works best when
---comparing with a known value. The `max_abs_diff` that should be used used depends on the
---values being compared against.
---
---For more see [comparing floating point numbers](https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/).
---
---@param rhs Quat
---@param max_abs_diff number
---@return boolean
function Quat:abs_diff_eq(rhs, max_abs_diff) end

View File

@ -1,134 +0,0 @@
---@meta
---@class Transform: userdata
---
---A Transform represents a transformation of an object. A transform includes the position
---(called translation here), rotation, and scale. Rotation is represented using a Quaternion
---(or Quat for short).
---
---Although Quats can be scary, they are much more robust than euler angles for games
---since they do not suffer from things like
---[gimbal-lock](https://en.wikipedia.org/wiki/Gimbal_lock).
---
---This is a Lua export of [`lyra_math::Transform`].
---
---@operator add(Quat): self
---@operator mul(Vec3): self
---@diagnostic disable-next-line: unknown-operator
---@operator eq: self
Transform = {
---The translation/position of the transform.
---@type Vec3
translation = nil,
---The rotation of the transform.
---@type Quat
rotation = nil,
---The scale of the transform.
---@type Vec3
scale = nil,
}
function Transform:__tostring() end
---@return self
function Transform.default() end
---Create a new transform with its components.
---
---@param translation Vec3
---@param rotation Quat
---@param scale Vec3
---@return self
function Transform.new(translation, rotation, scale) end
---Create a new transform with a `Vec3` translation.
---@param translation Vec3
---@return self
function Transform.from_translation(translation) end
---Create a new transform with a translation of `x`, `y`, and `z`.
---
---@param x number
---@param y number
---@param z number
---@return self
function Transform.from_translation(x, y, z) end
---Create a clone of `self`
---@return self
function Transform:clone() end
---Returns a normalized vector pointing in the direction the Transform is facing.
---
---This represents the front of the object can be used for movement, camera orientation, and
---other directional calculations.
---
---@return Vec3
function Transform:forward() end
---Returns a normalized vector pointing to the left side of the Transform.
---
---The vector is in local space. This represents the direction that is
---perpendicular to the object's forward direction.
---
---@return Vec3
function Transform:left() end
---Returns a normalized vector that indicates the upward direction of the Transform.
---
---This vector is commonly used to define an object's orientation and is essential for maintaining
---consistent vertical alignment in 3D environments, such as for camera positioning and object alignment.
---@return Vec3
function Transform:up() end
---Rotate `self` using a quaternion
---@param quat Quat
function Transform:rotate(quat) end
---Rotate `self` around the x axis by **degrees**.
---
---@param deg number The amount of **degrees** to rotate by.
function Transform:rotate_x(deg) end
---Rotate `self` around the y axis by **degrees**.
---
---@param deg number The amount of **degrees** to rotate by.
function Transform:rotate_y(deg) end
---Rotate `self` around the z axis by **degrees**.
---
---@param deg number The amount of **degrees** to rotate by.
function Transform:rotate_z(deg) end
---Rotate `self` around the x axis by **radians** .
---
---@param rad number The amount of **radians** to rotate by.
function Transform:rotate_x_rad(rad) end
---Rotate `self` around the y axis by **radians** .
---
---@param rad number The amount of **radians** to rotate by.
function Transform:rotate_y_rad(rad) end
---Rotate `self` around the z axis by **radians** .
---
---@param rad number The amount of **radians** to rotate by.
function Transform:rotate_z_rad(rad) end
---Move `self` by `x`, `y`, and `z`.
---
---@param x number
---@param y number
---@param z number
function Transform:translate(x, y, z) end
---Performs a linear interpolation between `self` and `rhs` based on `alpha`.
---
---This will normalize the rotation `Quat`.
---
---When `alpha` is `0.0`, the result will be equal to `self`. When `alpha` is `1.0`,
---the result will be equal to `rhs`.
---
---@param rhs Transform
---@param alpha number
function Transform:lerp(rhs, alpha) end

View File

@ -1,125 +0,0 @@
---@meta
---@class Vec2: userdata
---This is a Lua export of [`glam::Vec2`](https://docs.rs/glam/latest/glam/f32/struct.Vec2.html)
---
---@operator add(self|number): self
---@operator sub(self|number): self
---@operator div(self|number): self
---@operator mul(self|number): self
---@operator mod(self|number): self
---@operator unm: self
---@diagnostic disable-next-line: unknown-operator
---@operator eq: self
Vec2 = {
---The x coordinate
---@type number
x = nil,
---The y coordinate
---@type number
y = nil,
---A constant `Vec2` with coordinates as `f32::NAN`.
---@type Vec2
NAN = nil,
---A constant `Vec2` with `x` as `-1.0`.
---@type Vec2
NEG_X = nil,
---A constant `Vec2` with `y` as `-1.0`.
---@type Vec2
NEG_Y = nil,
---A constant `Vec2` with both components as `-1.0`.
---@type Vec2
NEG_ONE = nil,
---A constant `Vec2` with `x` as `1.0`.
---@type Vec2
POS_X = nil,
---A constant `Vec2` with `y` as `1.0`.
---@type Vec2
POS_Y = nil,
---A constant `Vec2` with both components as `1.0`.
---@type Vec2
ONE = nil,
---A constant `Vec2` with both components as `0.0`.
---@type Vec2
ZERO = nil,
}
function Vec2:__tostring() end
---Create a new `Vec2`
---@param x number
---@param y number
---@return self
function Vec2.new(x, y) end
---Returns a vector with a length no less than min and no more than max.
---@param min number the minimum value to clamp the length to
---@param max number the maximum value to clamp the length to
---@return self
function Vec2:clamp_length(min, max) end
---Returns true if the absolute difference of all elements between `self` and `rhs` is less
---than or equal to `max_abs_diff`.
---
---This can be used to compare if two vectors contain similar elements. It works best when
---comparing with a known value. The `max_abs_diff` that should be used used depends on the
---values being compared against.
---
---For more see [comparing floating point numbers](https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/).
---
---@param rhs Vec2 The other `Vec2` to compare to.
---@param max_abs_diff number Maximum absolute difference between `self` and `rhs`.
---@return boolean
function Vec2:abs_diff_eq(rhs, max_abs_diff) end
---Returns a vector containing the smallest integer greater than or equal to a number for each
---element of self.
---@return self
function Vec2:ceil() end
---Returns the angle of rotation (in radians) from `self` to `rhs` in the range [-π, +π].
---
---The inputs do not need to be unit vectors however they must be non-zero.
---
---@param rhs Vec2 The other `Vec2` to get the angle to.
---@return number
function Vec2:angle_to(rhs) end
---Returns a vector containing the absolute value of each element of `self`.
---
---@return self
function Vec2:abs() end
---Component-wise clamping of values.
---
---Each element in `min` must be less-or-equal to the corresponding element in `max`.
---
---@param min self The minimum `Vec2` components to clamp the components of `self` to.
---@param max self The maximum `Vec2` components to clamp the components of `self` to.
---@return self
function Vec2:clamp(min, max) end
---Converts `self` to an array `[x, y]`
---
---@return number[]
function Vec2:to_array() end
---Move `self` by `x` and `y` values.
---
---@param x number
---@param y number
function Vec2:move_by(x, y) end
---Move `self` by a `Vec2`.
---
---@param rhs Vec2
function Vec2:move_by(rhs) end

View File

@ -1,139 +0,0 @@
---@meta
---@class Vec3: userdata
---This is a Lua export of [`glam::Vec3`](https://docs.rs/glam/latest/glam/f32/struct.Vec3.html)
---
---@operator add(self|number): self
---@operator sub(self|number): self
---@operator div(self|number): self
---@operator mul(self|number): self
---@operator mod(self|number): self
---@operator unm: self
---@diagnostic disable-next-line: unknown-operator
---@operator eq: self
Vec3 = {
---The x coordinate
---@type number
x = nil,
---The y coordinate
---@type number
y = nil,
---The z coordinate
---@type number
z = nil,
---A constant `Vec3` with coordinates as `f32::NAN`.
---@type Vec3
NAN = nil,
---A constant `Vec3` with `x` as `-1.0`.
---@type Vec3
NEG_X = nil,
---A constant `Vec3` with `y` as `-1.0`.
---@type Vec3
NEG_Y = nil,
---A constant `Vec3` with `z` as `-1.0`.
---@type Vec3
NEG_Z = nil,
---A constant `Vec3` with all components as `-1.0`.
---@type Vec3
NEG_ONE = nil,
---A constant `Vec3` with `x` as `1.0`.
---@type Vec3
POS_X = nil,
---A constant `Vec3` with `y` as `1.0`.
---@type Vec3
POS_Y = nil,
---A constant `Vec3` with `z` as `1.0`.
---@type Vec3
POS_Z = nil,
---A constant `Vec3` with all components as `1.0`.
---@type Vec3
ONE = nil,
---A constant `Vec3` with all components as `0.0`.
---@type Vec3
ZERO = nil,
}
function Vec3:__tostring() end
---Create a new `Vec3`
---@param x number
---@param y number
---@param z number
---@return self
function Vec3.new(x, y, z) end
---Returns a vector with a length no less than min and no more than max.
---@param min number the minimum value to clamp the length to
---@param max number the maximum value to clamp the length to
---@return self
function Vec3:clamp_length(min, max) end
---Returns true if the absolute difference of all elements between `self` and `rhs` is less
---than or equal to `max_abs_diff`.
---
---This can be used to compare if two vectors contain similar elements. It works best when
---comparing with a known value. The `max_abs_diff` that should be used used depends on the
---values being compared against.
---
---For more see [comparing floating point numbers](https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/).
---
---@param rhs Vec3 The other `Vec3` to compare to.
---@param max_abs_diff number Maximum absolute difference between `self` and `rhs`.
---@return boolean
function Vec3:abs_diff_eq(rhs, max_abs_diff) end
---Returns a vector containing the smallest integer greater than or equal to a number for each
---element of self.
---@return self
function Vec3:ceil() end
---Returns the angle (in radians) between two vectors in the range [-π, +π].
---
---The inputs do not need to be unit vectors however they must be non-zero.
---
---@param rhs Vec3 The other `Vec3` to get the angle to.
---@return number
function Vec3:angle_between(rhs) end
---Returns a vector containing the absolute value of each element of `self`.
---
---@return self
function Vec3:abs() end
---Component-wise clamping of values.
---
---Each element in `min` must be less-or-equal to the corresponding element in `max`.
---
---@param min self The minimum `Vec3` components to clamp the components of `self` to.
---@param max self The maximum `Vec3` components to clamp the components of `self` to.
---@return self
function Vec3:clamp(min, max) end
---Converts `self` to an array `[x, y, z]`
---
---@return number[]
function Vec3:to_array() end
---Move `self` by `x`, `y`, and `z` values.
---
---@param x number
---@param y number
---@param z number
function Vec3:move_by(x, y, z) end
---Move `self` by a `Vec3`.
---
---@param rhs Vec3
function Vec3:move_by(rhs) end

View File

@ -1,132 +0,0 @@
---@meta
---@class Vec4: userdata
---This is a Lua export of [`glam::Vec4`](https://docs.rs/glam/latest/glam/f32/struct.Vec4.html)
---
---@operator add(self|number): self
---@operator sub(self|number): self
---@operator div(self|number): self
---@operator mul(self|number): self
---@operator mod(self|number): self
---@operator unm: self
---@diagnostic disable-next-line: unknown-operator
---@operator eq: self
Vec4 = {
---The x coordinate
---@type number
x = nil,
---The y coordinate
---@type number
y = nil,
---The z coordinate
---@type number
z = nil,
---The w coordinate
---@type number
w = nil,
---A constant `Vec4` with coordinates as `f32::NAN`.
---@type Vec4
NAN = nil,
---A constant `Vec4` with `x` as `-1.0`.
---@type Vec4
NEG_X = nil,
---A constant `Vec4` with `y` as `-1.0`.
---@type Vec4
NEG_Y = nil,
---A constant `Vec4` with `z` as `-1.0`.
---@type Vec4
NEG_Z = nil,
---A constant `Vec4` with `w` as `-1.0`.
---@type Vec4
NEG_W = nil,
---A constant `Vec4` with all components as `-1.0`.
---@type Vec4
NEG_ONE = nil,
---A constant `Vec4` with `x` as `1.0`.
---@type Vec4
POS_X = nil,
---A constant `Vec4` with `y` as `1.0`.
---@type Vec4
POS_Y = nil,
---A constant `Vec4` with `z` as `1.0`.
---@type Vec4
POS_Z = nil,
---A constant `Vec4` with `w` as `1.0`.
---@type Vec4
POS_W = nil,
---A constant `Vec4` with all components as `1.0`.
---@type Vec4
ONE = nil,
---A constant `Vec4` with all components as `0.0`.
---@type Vec4
ZERO = nil,
}
function Vec4:__tostring() end
---Create a new `Vec4`
---@param x number
---@param y number
---@param z number
---@param w number
---@return self
function Vec4.new(x, y, z, w) end
---Returns a vector with a length no less than min and no more than max.
---@param min number the minimum value to clamp the length to
---@param max number the maximum value to clamp the length to
---@return self
function Vec4:clamp_length(min, max) end
---Returns true if the absolute difference of all elements between `self` and `rhs` is less
---than or equal to `max_abs_diff`.
---
---This can be used to compare if two vectors contain similar elements. It works best when
---comparing with a known value. The `max_abs_diff` that should be used used depends on the
---values being compared against.
---
---For more see [comparing floating point numbers](https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/).
---
---@param rhs Vec4 The other `Vec4` to compare to.
---@param max_abs_diff number Maximum absolute difference between `self` and `rhs`.
---@return boolean
function Vec4:abs_diff_eq(rhs, max_abs_diff) end
---Returns a vector containing the smallest integer greater than or equal to a number for each
---element of self.
---@return self
function Vec4:ceil() end
---Returns a vector containing the absolute value of each element of `self`.
---
---@return self
function Vec4:abs() end
---Component-wise clamping of values.
---
---Each element in `min` must be less-or-equal to the corresponding element in `max`.
---
---@param min self The minimum `Vec4` components to clamp the components of `self` to.
---@param max self The maximum `Vec4` components to clamp the components of `self` to.
---@return self
function Vec4:clamp(min, max) end
---Converts `self` to an array `[x, y, z]`
---
---@return number[]
function Vec4:to_array() end

View File

@ -1,82 +0,0 @@
use std::ptr::NonNull;
use lyra_ecs::{query::dynamic::DynamicViewStateIter, Entity};
use lyra_reflect::TypeRegistry;
use crate::ScriptWorldPtr;
#[cfg(feature = "lua")]
use super::ReflectLuaProxy;
#[cfg(feature = "lua")]
pub struct ReflectedItem {
pub comp_ptr: NonNull<u8>,
pub comp_val: mlua::Value,
}
#[cfg(feature = "lua")]
pub struct ReflectedRow {
pub entity: Entity,
pub row: Vec<ReflectedItem>,
}
pub struct ReflectedIteratorOwned {
pub world_ptr: ScriptWorldPtr,
pub dyn_view: DynamicViewStateIter,
}
impl ReflectedIteratorOwned {
pub fn next_lua(&mut self, lua: &mlua::Lua) -> Option<ReflectedRow> {
let world = self.world_ptr.read();
next_lua(lua, &world, &mut self.dyn_view)
}
}
pub struct ReflectedIterator<'a> {
pub world: &'a lyra_ecs::World,
pub dyn_view: DynamicViewStateIter,
}
impl<'a> ReflectedIterator<'a> {
pub fn next_lua(&mut self, lua: &mlua::Lua) -> Option<ReflectedRow> {
next_lua(lua, &self.world, &mut self.dyn_view)
}
}
fn next_lua(lua: &mlua::Lua, world: &lyra_ecs::World, dyn_view: &mut DynamicViewStateIter) -> Option<ReflectedRow> {
use mlua::IntoLua;
//let world = world.read();
let n = dyn_view.next(&world);
if let Some((en, row)) = n {
let reflected_components = world.get_resource::<TypeRegistry>().unwrap();
let mut dynamic_row = vec![];
for d in row.iter() {
let id = d.info.type_id().as_rust();
let reg_type = reflected_components.get_type(id)
.expect("Requested type was not found in TypeRegistry");
let proxy = reg_type.get_data::<ReflectLuaProxy>()
// TODO: properly handle this error
.expect("Type does not have ReflectLuaProxy as a TypeData");
let value = proxy.as_lua(lua, d.ptr.cast()).unwrap()
.into_lua(lua).unwrap();
dynamic_row.push(ReflectedItem {
comp_ptr: d.ptr,
comp_val: value
});
}
let row = ReflectedRow {
entity: en,
row: dynamic_row
};
Some(row)
} else {
None
}
}

View File

@ -1,7 +0,0 @@
mod view;
pub use view::*;
mod view_one;
pub use view_one::*;
pub mod query;

View File

@ -1,107 +0,0 @@
use lyra_reflect::{ReflectWorldExt, RegisteredType, TypeRegistry};
use mlua::IntoLua;
use crate::{
lua::{LuaComponent, ReflectLuaProxy, FN_NAME_INTERNAL_ECS_QUERY_RESULT},
ReflectBranch, ScriptEntity, ScriptWorldPtr,
};
use super::LuaQueryResult;
#[derive(Clone)]
pub struct LuaChangedQuery(LuaComponent);
impl mlua::FromLua for LuaChangedQuery {
fn from_lua(value: mlua::Value, _: &mlua::Lua) -> mlua::Result<Self> {
let tyname = value.type_name();
value
.as_userdata()
.ok_or(mlua::Error::FromLuaConversionError {
from: tyname,
to: "ChangedQuery".into(),
message: None,
})
.and_then(|ud| ud.borrow::<Self>())
.map(|ud| ud.clone())
}
}
impl mlua::UserData for LuaChangedQuery {
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
methods.add_function("new", |_, comp: LuaComponent| Ok(Self(comp)));
methods.add_method(
FN_NAME_INTERNAL_ECS_QUERY_RESULT,
|lua, this, (world, en): (ScriptWorldPtr, ScriptEntity)| {
let mut world = world.write();
let reflect = this.0.reflect_type()?;
let tyid = reflect.reflect_branch.reflect_type_id();
match &reflect.reflect_branch {
ReflectBranch::Component(comp) => {
if !comp.reflect_is_changed(&world, *en).unwrap_or(false) {
return Ok(LuaQueryResult::FilterDeny);
}
// get the pointer of the component in the archetype column.
let arch = match world.entity_archetype(*en) {
Some(a) => a,
None => return Ok(LuaQueryResult::FilterDeny),
};
let arch_idx = *arch.entity_indexes().get(&en).unwrap();
let col = match arch.get_column(tyid) {
Some(col) => col,
None => {
// the entity doesn't have the component
return Ok(LuaQueryResult::FilterDeny);
}
};
let col_ptr = col.component_ptr_non_tick(*arch_idx as usize).cast();
// get the type registry to apply the new value
let reg = world.get_resource::<TypeRegistry>().unwrap();
let reg_type = reg.get_type(tyid).unwrap();
let proxy = reg_type
.get_data::<ReflectLuaProxy>()
// this should actually be safe since the ReflectedIterator
// attempts to get the type data before it is tried here
.expect("Type does not have ReflectLuaProxy as a TypeData");
Ok(LuaQueryResult::Some(proxy.as_lua(lua, col_ptr)?))
}
ReflectBranch::Resource(res) => {
// Check if the resource was changed. Per API spec, must return false.
match res.reflect_is_changed(&world) {
Some(false) => {
return Ok(LuaQueryResult::FilterDeny);
}
None => {
// the resource was not found
return Ok(LuaQueryResult::AlwaysNone);
}
_ => {}
}
// unwrap is safe here since the match above would verify that the
// resource exists.
let res_ptr = res.reflect_ptr(&mut world).unwrap();
let reg_type = world
.get_type::<RegisteredType>(tyid)
.expect("Resource is not type registered!");
let proxy = reg_type
.get_data::<ReflectLuaProxy>()
.expect("Type does not have ReflectLuaProxy as a TypeData");
Ok(LuaQueryResult::Some(
proxy
.as_lua(lua, res_ptr.cast())
.and_then(|ud| ud.into_lua(lua))?,
))
}
}
},
);
}
}

View File

@ -1,60 +0,0 @@
use crate::{
lua::{LuaComponent, FN_NAME_INTERNAL_ECS_QUERY_RESULT},
ScriptEntity, ScriptWorldPtr,
};
use super::LuaQueryResult;
#[derive(Clone)]
pub struct LuaHasQuery(LuaComponent);
impl mlua::FromLua for LuaHasQuery {
fn from_lua(value: mlua::Value, _: &mlua::Lua) -> mlua::Result<Self> {
let tyname = value.type_name();
value
.as_userdata()
.ok_or(mlua::Error::FromLuaConversionError {
from: tyname,
to: "HasQuery".into(),
message: None,
})
.and_then(|ud| ud.borrow::<Self>())
.map(|ud| ud.clone())
}
}
impl mlua::UserData for LuaHasQuery {
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
methods.add_function("new", |_, comp: LuaComponent| {
let reflect = comp.reflect_type()?;
if !reflect.reflect_branch.is_component() {
Err(mlua::Error::runtime("provided type is not a component!"))
} else {
Ok(Self(comp))
}
});
methods.add_method(
FN_NAME_INTERNAL_ECS_QUERY_RESULT,
|_, this, (world, en): (ScriptWorldPtr, ScriptEntity)| {
let world = world.write();
let reflect = this.0.reflect_type()?;
let tyid = reflect.reflect_branch.reflect_type_id();
// try to find the entity's archetype and the component column in the archetype
let arch = match world.entity_archetype(*en) {
Some(a) => a,
None => return Ok(LuaQueryResult::FilterDeny)
};
let component_col = arch.get_column(tyid);
if component_col.is_some() {
Ok(LuaQueryResult::FilterPass)
} else {
Ok(LuaQueryResult::FilterDeny)
}
},
);
}
}

View File

@ -1,183 +0,0 @@
mod res;
pub use res::*;
mod changed;
pub use changed::*;
mod has;
pub use has::*;
mod not;
pub use not::*;
mod or;
pub use or::*;
mod tick_of;
pub use tick_of::*;
mod optional;
pub use optional::*;
use lyra_ecs::Entity;
use crate::{
lua::{LuaComponent, FN_NAME_INTERNAL_ECS_QUERY_RESULT},
ScriptEntity, ScriptWorldPtr,
};
#[derive(Clone)]
enum QueryInner {
Component(LuaComponent),
Function(mlua::Function),
}
#[derive(Clone)]
pub struct LuaQuery(QueryInner);
impl LuaQuery {
pub fn new(query: LuaComponent) -> Self {
Self(QueryInner::Component(query))
}
pub fn from_function(f: mlua::Function) -> Self {
Self(QueryInner::Function(f))
}
/// Get the result of the query
///
/// > WARNING: ensure that the world pointer is not locked. If its locked when you call this,
/// you WILL cause a deadlock.
pub fn get_query_result(
&self,
world: ScriptWorldPtr,
entity: Entity,
) -> mlua::Result<LuaQueryResult> {
let lua_en = ScriptEntity(entity);
match &self.0 {
QueryInner::Component(comp) => {
comp.call_method(FN_NAME_INTERNAL_ECS_QUERY_RESULT, (world, lua_en))
}
QueryInner::Function(function) => function.call((world, lua_en)),
}
}
}
impl mlua::FromLua for LuaQuery {
fn from_lua(value: mlua::Value, lua: &mlua::Lua) -> mlua::Result<Self> {
let tyname = value.type_name();
if let Some(f) = value.as_function() {
Ok(Self(QueryInner::Function(f.clone())))
} else if let Ok(c) = LuaComponent::from_lua(value, lua) {
Ok(Self(QueryInner::Component(c)))
} else {
Err(mlua::Error::FromLuaConversionError {
from: tyname,
to: "Query".into(),
message: Some("expected query function, table, or user data".into()),
})
}
}
}
#[derive(Debug, Clone)]
pub enum LuaQueryResult {
None,
AlwaysNone,
FilterPass,
FilterDeny,
Some(mlua::Value),
}
impl mlua::IntoLua for LuaQueryResult {
fn into_lua(self, lua: &mlua::Lua) -> mlua::Result<mlua::Value> {
let t = lua.create_table()?;
t.set("enum_ty", "query_result")?;
match self {
LuaQueryResult::None => {
t.set("result", "none")?;
}
LuaQueryResult::AlwaysNone => {
t.set("result", "always_none")?;
}
LuaQueryResult::FilterPass => {
t.set("result", "filter_pass")?;
}
LuaQueryResult::FilterDeny => {
t.set("result", "filter_deny")?;
}
LuaQueryResult::Some(value) => {
t.set("result", "some")?;
t.set("val", value)?;
}
}
t.into_lua(lua)
}
}
#[inline(always)]
fn from_lua_error_query_result(ty: &'static str, msg: &str) -> mlua::Error {
mlua::Error::FromLuaConversionError {
from: ty,
to: "QueryResult".into(),
message: Some(msg.into()),
}
}
#[inline(always)]
fn malformed_table_error_query_result(ty: &'static str, missing_field: &str) -> mlua::Error {
mlua::Error::FromLuaConversionError {
from: ty,
to: "QueryResult".into(),
message: Some(format!(
"malformed table, cannot convert, failed to get field '{}'",
missing_field
)),
}
}
impl mlua::FromLua for LuaQueryResult {
fn from_lua(value: mlua::Value, _: &mlua::Lua) -> mlua::Result<Self> {
let ty = value.type_name();
let table = value
.as_table()
.ok_or(from_lua_error_query_result(ty, "expected Table"))?;
let var_name: String = table
.get("enum_ty")
.map_err(|_| malformed_table_error_query_result(ty, "enum_ty"))?;
if var_name != "query_result" {
return Err(mlua::Error::FromLuaConversionError {
from: ty,
to: "QueryResult".into(),
message: Some(format!("mismatched enum_ty: '{}'", var_name)),
});
}
let result: String = table
.get("result")
.map_err(|_| malformed_table_error_query_result(ty, "result"))?;
let result_str = result.as_str();
match result_str {
"none" => Ok(Self::None),
"always_none" => Ok(Self::AlwaysNone),
"filter_pass" => Ok(Self::FilterPass),
"filter_deny" => Ok(Self::FilterDeny),
"some" => {
let val: mlua::Value = table
.get("val")
.map_err(|_| malformed_table_error_query_result(ty, "val"))?;
Ok(Self::Some(val))
}
_ => Err(mlua::Error::FromLuaConversionError {
from: ty,
to: "QueryResult".into(),
message: Some(format!("unknown result type: '{}'", result_str)),
}),
}
}
}

View File

@ -1,47 +0,0 @@
use crate::{
lua::FN_NAME_INTERNAL_ECS_QUERY_RESULT,
ScriptEntity, ScriptWorldPtr,
};
use super::{LuaQuery, LuaQueryResult};
#[derive(Clone)]
pub struct LuaNotQuery(LuaQuery);
impl mlua::FromLua for LuaNotQuery {
fn from_lua(value: mlua::Value, _: &mlua::Lua) -> mlua::Result<Self> {
let tyname = value.type_name();
value
.as_userdata()
.ok_or(mlua::Error::FromLuaConversionError {
from: tyname,
to: "NotQuery".into(),
message: None,
})
.and_then(|ud| ud.borrow::<Self>())
.map(|ud| ud.clone())
}
}
impl mlua::UserData for LuaNotQuery {
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
methods.add_function("new", |_, q: LuaQuery| {
Ok(Self(q))
});
methods.add_method(
FN_NAME_INTERNAL_ECS_QUERY_RESULT,
|_, this, (world, en): (ScriptWorldPtr, ScriptEntity)| {
let res = this.0.get_query_result(world, en.0)?;
match res {
LuaQueryResult::None => Ok(LuaQueryResult::FilterPass),
LuaQueryResult::AlwaysNone => Ok(LuaQueryResult::FilterPass),
LuaQueryResult::FilterPass => Ok(LuaQueryResult::FilterDeny),
LuaQueryResult::FilterDeny => Ok(LuaQueryResult::FilterPass),
LuaQueryResult::Some(_) => Ok(LuaQueryResult::FilterDeny),
}
},
);
}
}

View File

@ -1,44 +0,0 @@
use crate::{
lua::FN_NAME_INTERNAL_ECS_QUERY_RESULT, ScriptEntity, ScriptWorldPtr,
};
use super::{LuaQuery, LuaQueryResult};
#[derive(Clone)]
pub struct LuaOptionalQuery(LuaQuery);
impl mlua::FromLua for LuaOptionalQuery {
fn from_lua(value: mlua::Value, _: &mlua::Lua) -> mlua::Result<Self> {
let tyname = value.type_name();
value
.as_userdata()
.ok_or(mlua::Error::FromLuaConversionError {
from: tyname,
to: "OptionalQuery".into(),
message: None,
})
.and_then(|ud| ud.borrow::<Self>())
.map(|ud| ud.clone())
}
}
impl mlua::UserData for LuaOptionalQuery {
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
methods.add_function("new", |_, q: LuaQuery| Ok(Self(q)));
methods.add_method(
FN_NAME_INTERNAL_ECS_QUERY_RESULT,
|_, this, (world, en): (ScriptWorldPtr, ScriptEntity)| {
let res = this.0.get_query_result(world, en.0)?;
match res {
LuaQueryResult::None => Ok(LuaQueryResult::Some(mlua::Value::Nil)),
LuaQueryResult::AlwaysNone => Ok(LuaQueryResult::Some(mlua::Value::Nil)),
LuaQueryResult::FilterPass => Ok(LuaQueryResult::FilterPass),
LuaQueryResult::FilterDeny => Ok(LuaQueryResult::FilterPass),
LuaQueryResult::Some(v) => Ok(LuaQueryResult::Some(v)),
}
},
);
}
}

View File

@ -1,48 +0,0 @@
use crate::{lua::FN_NAME_INTERNAL_ECS_QUERY_RESULT, ScriptEntity, ScriptWorldPtr};
use super::{LuaQuery, LuaQueryResult};
#[derive(Clone)]
pub struct LuaOrQuery(Vec<LuaQuery>);
impl mlua::FromLua for LuaOrQuery {
fn from_lua(value: mlua::Value, _: &mlua::Lua) -> mlua::Result<Self> {
let tyname = value.type_name();
value
.as_userdata()
.ok_or(mlua::Error::FromLuaConversionError {
from: tyname,
to: "OrQuery".into(),
message: None,
})
.and_then(|ud| ud.borrow::<Self>())
.map(|ud| ud.clone())
}
}
impl mlua::UserData for LuaOrQuery {
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
methods.add_function("new", |_, qs: mlua::Variadic<LuaQuery>| {
Ok(Self(qs.to_vec()))
});
methods.add_method(
FN_NAME_INTERNAL_ECS_QUERY_RESULT,
|_, this, (world, en): (ScriptWorldPtr, ScriptEntity)| {
for q in &this.0 {
let res = q.get_query_result(world.clone(), en.0)?;
match res {
LuaQueryResult::None
| LuaQueryResult::AlwaysNone
| LuaQueryResult::FilterDeny => {}
LuaQueryResult::FilterPass => return Ok(LuaQueryResult::FilterPass),
LuaQueryResult::Some(v) => return Ok(LuaQueryResult::Some(v)),
}
}
Ok(LuaQueryResult::FilterDeny)
},
);
}
}

View File

@ -1,63 +0,0 @@
use lyra_reflect::{ReflectWorldExt, RegisteredType};
use mlua::IntoLua;
use crate::{
lua::{LuaComponent, ReflectLuaProxy, FN_NAME_INTERNAL_ECS_QUERY_RESULT},
ScriptEntity, ScriptWorldPtr,
};
use super::LuaQueryResult;
#[derive(Clone)]
pub struct LuaResQuery {
ty: LuaComponent,
}
impl mlua::FromLua for LuaResQuery {
fn from_lua(value: mlua::Value, _: &mlua::Lua) -> mlua::Result<Self> {
let tyname = value.type_name();
value
.as_userdata()
.ok_or(mlua::Error::FromLuaConversionError {
from: tyname,
to: "ResQuery".into(),
message: None,
})
.and_then(|ud| ud.borrow::<Self>())
.map(|ud| ud.clone())
}
}
impl mlua::UserData for LuaResQuery {
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
methods.add_function("new", |_, comp: LuaComponent| Ok(Self { ty: comp }));
methods.add_method(
FN_NAME_INTERNAL_ECS_QUERY_RESULT,
|lua, this, (world, _): (ScriptWorldPtr, ScriptEntity)| {
let mut world = world.write();
let reflect = this.ty.reflect_type()?;
let res = reflect.reflect_branch.as_resource_unchecked();
if let Some(res_ptr) = res.reflect_ptr(&mut world) {
let reg_type = world
.get_type::<RegisteredType>(reflect.reflect_branch.reflect_type_id())
.expect("Resource is not type registered!");
let proxy = reg_type
.get_data::<ReflectLuaProxy>()
.expect("Type does not have ReflectLuaProxy as a TypeData");
Ok(LuaQueryResult::Some(
proxy
.as_lua(lua, res_ptr.cast())
.and_then(|ud| ud.into_lua(lua))?,
))
} else {
// if the resource is not found in the world, return nil
//Ok(mlua::Value::Nil)
Ok(LuaQueryResult::AlwaysNone)
}
},
);
}
}

View File

@ -1,54 +0,0 @@
use crate::{
lua::{LuaComponent, FN_NAME_INTERNAL_ECS_QUERY_RESULT},
ReflectBranch, ScriptEntity, ScriptWorldPtr,
};
use super::LuaQueryResult;
#[derive(Clone)]
pub struct LuaTickOfQuery(LuaComponent);
impl mlua::FromLua for LuaTickOfQuery {
fn from_lua(value: mlua::Value, _: &mlua::Lua) -> mlua::Result<Self> {
let tyname = value.type_name();
value
.as_userdata()
.ok_or(mlua::Error::FromLuaConversionError {
from: tyname,
to: "TickOfQuery".into(),
message: None,
})
.and_then(|ud| ud.borrow::<Self>())
.map(|ud| ud.clone())
}
}
impl mlua::UserData for LuaTickOfQuery {
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
methods.add_function("new", |_, comp: LuaComponent| Ok(Self(comp)));
methods.add_method(
FN_NAME_INTERNAL_ECS_QUERY_RESULT,
|_, this, (world, en): (ScriptWorldPtr, ScriptEntity)| {
let world = world.read();
let reflect = this.0.reflect_type()?;
match &reflect.reflect_branch {
ReflectBranch::Component(comp) => {
if let Some(tick) = comp.reflect_tick(&world, *en) {
Ok(LuaQueryResult::Some(mlua::Value::Number(*tick as _)))
} else {
Ok(LuaQueryResult::FilterDeny)
}
}
ReflectBranch::Resource(res) => {
if let Some(tick) = res.reflect_tick(&world) {
Ok(LuaQueryResult::Some(mlua::Value::Number(*tick as _)))
} else {
Ok(LuaQueryResult::FilterDeny)
}
}
}
},
);
}
}

View File

@ -1,345 +0,0 @@
use std::sync::Arc;
use atomic_refcell::AtomicRefCell;
use lyra_ecs::{
query::dynamic::{DynamicViewState, QueryDynamicType},
Entity,
};
use mlua::{IntoLua, IntoLuaMulti, ObjectLike};
use crate::{
lua::{
LuaComponent, LuaEntityRef, ReflectedIteratorOwned, TypeLookup, WorldError,
FN_NAME_INTERNAL_ECS_QUERY_RESULT, FN_NAME_INTERNAL_REFLECT_TYPE,
},
ScriptBorrow, ScriptWorldPtr,
};
use super::query::{LuaQuery, LuaQueryResult};
#[derive(Clone)]
pub(crate) enum ViewQueryItem {
UserData(mlua::AnyUserData),
Table(mlua::Table),
Function(mlua::Function),
}
impl mlua::FromLua for ViewQueryItem {
fn from_lua(value: mlua::Value, _: &mlua::Lua) -> mlua::Result<Self> {
let tyname = value.type_name();
match value {
mlua::Value::Table(table) => Ok(Self::Table(table)),
mlua::Value::Function(function) => Ok(Self::Function(function)),
mlua::Value::UserData(any_user_data) => Ok(Self::UserData(any_user_data)),
_ => Err(mlua::Error::FromLuaConversionError {
from: tyname,
to: "ViewQueryItem".into(),
message: Some("expected Table, Function, or UserData".into()),
}),
}
}
}
impl ViewQueryItem {
/// Returns `true` if the QueryItem has a function of `name`.
///
/// Returns `false` if self is a function.
pub fn has_function(&self, name: &str) -> mlua::Result<bool> {
match self {
Self::UserData(ud) => ud.get::<mlua::Value>(name).map(|v| !v.is_nil()),
Self::Table(t) => t.contains_key(name),
Self::Function(_) => Ok(false),
}
}
/// Returns `true` if self is a Query.
///
/// If self is a function, it will return true. Else, it checks for a function with the
/// name of [`FN_NAME_INTERNAL_ECS_QUERY_RESULT`] on the table or userdata. If the function
/// is found, it returns true.
pub fn is_query(&self) -> mlua::Result<bool> {
Ok(matches!(self, ViewQueryItem::Function(_))
|| self.has_function(FN_NAME_INTERNAL_ECS_QUERY_RESULT)?)
}
/// Get self as a [`LuaQuery`].
///
/// If self is a function, it assumes that it is a filter.
pub fn as_query(&self) -> LuaQuery {
match self.clone() {
ViewQueryItem::UserData(ud) => LuaQuery::new(LuaComponent::UserData(ud)),
ViewQueryItem::Table(t) => LuaQuery::new(LuaComponent::Table(t)),
ViewQueryItem::Function(function) => LuaQuery::from_function(function),
}
}
}
#[derive(Clone)]
pub struct View {
pub(crate) items: Vec<ViewQueryItem>,
}
impl mlua::FromLua for View {
fn from_lua(value: mlua::Value, _: &mlua::Lua) -> mlua::Result<Self> {
let tyname = value.type_name();
value
.as_userdata()
.ok_or(mlua::Error::FromLuaConversionError {
from: tyname,
to: "View".into(),
message: None,
})
.and_then(|ud| ud.borrow::<Self>())
.map(|ud| ud.clone())
}
}
impl mlua::UserData for View {
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
methods.add_function("new", |_, args: mlua::Variadic<ViewQueryItem>| {
Ok(Self {
items: args.iter().cloned().collect(),
})
});
}
}
/// Results of queries in a View.
///
/// Represents the results of multiple queries.
#[derive(Debug, Clone)]
pub(crate) enum ViewQueryResult {
None,
AlwaysNone,
FilterDeny,
/// The results of the queries and the index they should be inserted at in the resulting row.
Some(Vec<(mlua::Value, u32)>),
}
#[derive(Clone)]
pub struct ViewResult {
world: ScriptWorldPtr,
reflected_iter: Arc<atomic_refcell::AtomicRefCell<ReflectedIteratorOwned>>,
/// The queries and the index they would be inserted in the result.
queries: Vec<(LuaQuery, u32)>,
}
unsafe impl Send for ViewResult {}
impl ViewResult {
pub fn new(world: ScriptWorldPtr, view: &View) -> Result<Self, mlua::Error> {
let items = view.items.clone();
let w = world.read();
let mut view = DynamicViewState::new();
let mut queries = vec![];
for (idx, comp) in items.iter().enumerate() {
if comp.is_query()? {
queries.push((comp.as_query(), idx as u32));
continue;
}
match comp {
ViewQueryItem::Table(t) => {
let name: String = t.get(mlua::MetaMethod::Type.name())?;
let lookup = w.get_resource::<TypeLookup>().ok_or(mlua::Error::runtime(
"Unable to lookup table proxy, none were ever registered!",
))?;
let info = lookup.comp_info_from_name.get(&name).ok_or_else(|| {
mlua::Error::BadArgument {
to: Some("ViewResult.new".into()),
pos: 2 + idx,
name: Some("query...".into()),
cause: Arc::new(mlua::Error::external(WorldError::LuaInvalidUsage(
format!("the 'Table' with name {} is unknown to the engine!", name),
))),
}
})?;
let dyn_type = QueryDynamicType::from_info(info.clone());
view.push(dyn_type);
}
ViewQueryItem::UserData(ud) => {
let reflect = ud
.call_function::<ScriptBorrow>(FN_NAME_INTERNAL_REFLECT_TYPE, ())
.expect("Type does not implement 'reflect_type' properly");
let refl_comp = reflect.reflect_branch.as_component()
.expect("`self` is not an instance of `ReflectBranch::Component`");
let dyn_type = QueryDynamicType::from_info(refl_comp.info);
view.push(dyn_type);
}
// functions are queries, the if statement at the start would cause this to
// be unreachable.
ViewQueryItem::Function(_) => unreachable!()
}
}
drop(w);
let view_iter = view.into_iter();
let reflected_iter = ReflectedIteratorOwned {
world_ptr: world.clone(),
dyn_view: view_iter,
};
Ok(Self {
world,
reflected_iter: Arc::new(AtomicRefCell::new(reflected_iter)),
queries,
})
}
/// Get the next row of components
fn next_components(
&mut self,
lua: &mlua::Lua,
) -> Result<Option<(Entity, mlua::MultiValue)>, mlua::Error> {
let mut query_iter = self.reflected_iter.borrow_mut();
if let Some(row) = query_iter.next_lua(lua) {
let (values, _): (Vec<_>, Vec<_>) = row
.row
.into_iter()
.into_iter()
.map(|r| (r.comp_val, r.comp_ptr.cast::<()>()))
.unzip();
let mult_val = mlua::MultiValue::from_iter(values.into_iter());
Ok(Some((row.entity, mult_val)))
} else {
Ok(None)
}
}
/// Get the query results and the indexes that they were provided in.
///
/// The indexes are used to make sure that the results are in the same order that the script
/// requested them in.
fn get_query_results(&self, entity: Entity) -> mlua::Result<ViewQueryResult> {
let mut query_vals = vec![];
// A modifier is used that will be incremented every time a filter allowed the query.
// this is used to remove the value of a filter without leaving a gap in the results.
let mut index_mod = 0;
for (query, i) in &self.queries {
let qres = query.get_query_result(self.world.clone(), entity)?;
match qres {
LuaQueryResult::None => return Ok(ViewQueryResult::None),
LuaQueryResult::AlwaysNone => return Ok(ViewQueryResult::AlwaysNone),
LuaQueryResult::FilterPass => {
// do not push a boolean to values, its considered a filter
index_mod += 1;
},
LuaQueryResult::FilterDeny => return Ok(ViewQueryResult::FilterDeny),
LuaQueryResult::Some(value) => {
let idx = (*i - index_mod).max(0);
query_vals.push((value, idx));
},
}
}
Ok(ViewQueryResult::Some(query_vals))
}
}
impl mlua::FromLua for ViewResult {
fn from_lua(value: mlua::Value, _: &mlua::Lua) -> mlua::Result<Self> {
let tyname = value.type_name();
value
.as_userdata()
.ok_or(mlua::Error::FromLuaConversionError {
from: tyname,
to: "View".into(),
message: None,
})
.and_then(|ud| ud.borrow::<Self>())
.map(|ud| ud.clone())
}
}
impl mlua::UserData for ViewResult {
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
methods.add_method_mut("next", |lua, this, ()| {
match this.next_components(lua)? {
Some((en, mut vals)) => {
loop {
let query_vals = match this.get_query_results(en)? {
ViewQueryResult::Some(v) => v,
ViewQueryResult::AlwaysNone => {
return mlua::Value::Nil.into_lua_multi(lua);
},
ViewQueryResult::None | ViewQueryResult::FilterDeny => {
// try to get it next loop
continue;
},
};
// insert query values to the result row
for (qval, qi) in query_vals {
vals.insert(qi as _, qval);
}
vals.push_front(LuaEntityRef::new(this.world.clone(), en).into_lua(lua)?);
return Ok(vals);
}
}
None => mlua::Value::Nil.into_lua_multi(lua),
}
});
methods.add_method("iter", |lua, this, ()| {
let key_arc = Arc::new(atomic_refcell::AtomicRefCell::new(Some(
lua.create_registry_value(this.clone())?,
)));
lua.create_function(move |lua, ()| {
let mut key_mut = key_arc.borrow_mut();
if let Some(key) = key_mut.as_ref() {
let mut this = lua.registry_value::<mlua::UserDataRefMut<Self>>(&key)?;
loop {
match this.next_components(lua)? {
Some((en, mut vals)) => {
let lua_en =
LuaEntityRef::new(this.world.clone(), en).into_lua(lua)?;
let query_vals = match this.get_query_results(en)? {
ViewQueryResult::Some(v) => v,
ViewQueryResult::AlwaysNone => {
return mlua::Value::Nil.into_lua_multi(lua);
},
ViewQueryResult::None | ViewQueryResult::FilterDeny => {
// try to get it next loop
continue;
},
};
// insert query values to the result row
for (qval, qi) in query_vals {
vals.insert(qi as _, qval);
}
vals.push_front(lua_en);
return Ok(vals);
}
None => {
// If this is the last row, remove the registry value
// This doesn't protect against iterators that aren't fully consumed,
// that would cause a leak in the lua registry.
// TODO: fix leak
let key = key_mut.take().unwrap();
lua.remove_registry_value(key)?;
return mlua::Value::Nil.into_lua_multi(lua);
}
}
}
} else {
mlua::Value::Nil.into_lua_multi(lua)
}
})
});
}
}

View File

@ -1,175 +0,0 @@
use std::sync::Arc;
use lyra_ecs::{query::dynamic::{DynamicViewOneOwned, QueryDynamicType}, Entity};
use lyra_reflect::TypeRegistry;
use mlua::{IntoLua, IntoLuaMulti, ObjectLike};
use crate::{lua::{ReflectLuaProxy, TypeLookup, WorldError, FN_NAME_INTERNAL_REFLECT_TYPE}, ScriptBorrow, ScriptWorldPtr};
use super::{query::{LuaQuery, LuaQueryResult}, View, ViewQueryItem, ViewQueryResult};
/// The result of an ecs world View of a single entity.
#[derive(Clone)]
pub struct ViewOneResult {
world: ScriptWorldPtr,
dynamic_view: DynamicViewOneOwned,
/// The queries and the index they would be inserted in the result.
queries: Vec<(LuaQuery, u32)>,
}
impl mlua::FromLua for ViewOneResult {
fn from_lua(value: mlua::Value, _: &mlua::Lua) -> mlua::Result<Self> {
let tyname = value.type_name();
value
.as_userdata()
.ok_or(mlua::Error::FromLuaConversionError {
from: tyname,
to: "View".into(),
message: None,
})
.and_then(|ud| ud.borrow::<Self>())
.map(|ud| ud.clone())
}
}
impl ViewOneResult {
pub fn new(world: ScriptWorldPtr, entity: Entity, view: &View) -> Result<Self, mlua::Error> {
let items = view.items.clone();
let w = world.read();
let mut view = DynamicViewOneOwned::new(entity);
let mut queries = vec![];
for (idx, comp) in items.iter().enumerate() {
if comp.is_query()? {
queries.push((comp.as_query(), idx as u32));
continue;
}
match comp {
ViewQueryItem::Table(t) => {
let name: String = t.get(mlua::MetaMethod::Type.name())?;
let lookup = w.get_resource::<TypeLookup>().ok_or(mlua::Error::runtime(
"Unable to lookup table proxy, none were ever registered!",
))?;
let info = lookup.comp_info_from_name.get(&name).ok_or_else(|| {
mlua::Error::BadArgument {
to: Some("ViewOneResult.new".into()),
pos: 2 + idx,
name: Some("query...".into()),
cause: Arc::new(mlua::Error::external(WorldError::LuaInvalidUsage(
format!("the 'Table' with name {} is unknown to the engine!", name),
))),
}
})?;
let dyn_type = QueryDynamicType::from_info(info.clone());
view.queries.push(dyn_type);
}
ViewQueryItem::UserData(ud) => {
let reflect = ud
.call_function::<ScriptBorrow>(FN_NAME_INTERNAL_REFLECT_TYPE, ())
.expect("Type does not implement 'reflect_type' properly");
let refl_comp = reflect.reflect_branch.as_component()
.expect("`self` is not an instance of `ReflectBranch::Component`");
let dyn_type = QueryDynamicType::from_info(refl_comp.info);
view.queries.push(dyn_type);
}
// functions are queries, the if statement at the start would cause this to
// be unreachable.
ViewQueryItem::Function(_) => unreachable!()
}
}
drop(w);
Ok(Self {
world,
dynamic_view: view,
queries,
})
}
/// Get the query results and the indexes that they were provided in.
///
/// The indexes are used to make sure that the results are in the same order that the script
/// requested them in.
fn get_query_results(&self, entity: Entity) -> mlua::Result<ViewQueryResult> {
let mut query_vals = vec![];
// A modifier is used that will be incremented every time a filter allowed the query.
// this is used to remove the value of a filter without leaving a gap in the results.
let mut index_mod = 0;
for (query, i) in &self.queries {
let qres = query.get_query_result(self.world.clone(), entity)?;
match qres {
LuaQueryResult::None => return Ok(ViewQueryResult::None),
LuaQueryResult::AlwaysNone => return Ok(ViewQueryResult::AlwaysNone),
LuaQueryResult::FilterPass => {
// do not push a boolean to values, its considered a filter
index_mod += 1;
},
LuaQueryResult::FilterDeny => return Ok(ViewQueryResult::FilterDeny),
LuaQueryResult::Some(value) => {
let idx = (*i - index_mod).max(0);
query_vals.push((value, idx));
},
}
}
Ok(ViewQueryResult::Some(query_vals))
}
fn get_res_impl(&self, lua: &mlua::Lua) -> mlua::Result<mlua::MultiValue> {
let world = self.world.read();
let qresults = self.get_query_results(self.dynamic_view.entity)?;
let qvals = match qresults {
ViewQueryResult::None => return mlua::Value::Nil.into_lua_multi(lua),
ViewQueryResult::AlwaysNone => return mlua::Value::Nil.into_lua_multi(lua),
ViewQueryResult::FilterDeny => return mlua::Value::Nil.into_lua_multi(lua),
ViewQueryResult::Some(vec) => vec,
};
let dv = self.dynamic_view.clone();
if let Some(row) = dv.get(&world) {
let reg = world.get_resource::<TypeRegistry>().unwrap();
let mut vals = vec![];
for d in row.iter() {
let id = d.info.type_id().as_rust();
let reg_type = reg.get_type(id)
.expect("Requested type was not found in TypeRegistry");
let proxy = reg_type.get_data::<ReflectLuaProxy>()
// TODO: properly handle this error
.expect("Type does not have ReflectLuaProxy as a TypeData");
let value = proxy.as_lua(lua, d.ptr.cast()).unwrap()
.into_lua(lua).unwrap();
vals.push(value);
}
// insert query values to the result row
for (v, i) in qvals {
vals.insert(i as _, v);
}
vals.into_lua_multi(lua)
} else {
mlua::Value::Nil.into_lua_multi(lua)
}
}
}
impl mlua::UserData for ViewOneResult {
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
methods.add_method_mut("get", |lua, this, ()| {
this.get_res_impl(lua)
});
methods.add_meta_method(mlua::MetaMethod::Call, |lua, this, ()| {
this.get_res_impl(lua)
});
}
}

View File

@ -1,159 +0,0 @@
use std::{any::TypeId, sync::Arc};
use lyra_ecs::{Entity, World};
use lyra_reflect::TypeRegistry;
use mlua::{IntoLua, ObjectLike};
use crate::{ScriptBorrow, ScriptWorldPtr};
use super::{reflect_type_user_data, Error, ReflectLuaProxy, TypeLookup, FN_NAME_INTERNAL_REFLECT_TYPE};
#[derive(Clone)]
pub enum LuaComponent {
UserData(mlua::AnyUserData),
Table(mlua::Table),
}
impl mlua::FromLua for LuaComponent {
fn from_lua(value: mlua::Value, _: &mlua::Lua) -> mlua::Result<Self> {
let tyname = value.type_name();
match value {
mlua::Value::UserData(ud) => Ok(Self::UserData(ud)),
mlua::Value::Table(t) => Ok(Self::Table(t)),
_ => Err(mlua::Error::FromLuaConversionError {
from: tyname,
to: "LuaComponent".into(),
message: Some(
"expected Table or UserData that can be converted to a native struct".into(),
),
}),
}
}
}
impl mlua::IntoLua for LuaComponent {
fn into_lua(self, lua: &mlua::Lua) -> mlua::Result<mlua::Value> {
match self {
Self::Table(t) => t.into_lua(lua),
Self::UserData(ud) => ud.into_lua(lua),
}
}
}
impl LuaComponent {
pub fn get_typeid(&self, world: &World) -> Option<TypeId> {
match self {
Self::Table(t) => {
let name: String = t.get(mlua::MetaMethod::Type.name()).ok()?;
let lookup = world.get_resource::<TypeLookup>().unwrap();
lookup.typeid_from_name.get(&name).cloned()
}
Self::UserData(ud) => {
let lua_comp = reflect_type_user_data(ud);
let refl_comp = lua_comp.reflect_branch.as_component_unchecked();
Some(refl_comp.info.type_id().as_rust())
}
}
}
/// Call the internal reflect type function and return the result.
///
/// This calls the [`FN_NAME_INTERNAL_REFLECT_TYPE`] function on the Component.
pub fn reflect_type(&self) -> Result<ScriptBorrow, Error> {
self.call_function(FN_NAME_INTERNAL_REFLECT_TYPE, ())
.map_err(|_| Error::Reflect)
}
/// Call a Lua function on the Component.
///
/// This is a helper function so you don't have to match on the component.
pub fn call_function<R: mlua::FromLuaMulti>(&self, name: &str, args: impl mlua::IntoLuaMulti) -> mlua::Result<R> {
match self {
LuaComponent::UserData(ud) => ud.call_function(name, args),
LuaComponent::Table(t) => t.call_function(name, args),
}
}
/// Call a Lua method on the Component.
///
/// This is a helper function so you don't have to match on the component.
pub fn call_method<R: mlua::FromLuaMulti>(&self, name: &str, args: impl mlua::IntoLuaMulti) -> mlua::Result<R> {
match self {
LuaComponent::UserData(ud) => ud.call_method(name, args),
LuaComponent::Table(t) => t.call_method(name, args),
}
}
/// Returns `true` if the Component has a function of `name`.
pub fn has_function(&self, name: &str) -> mlua::Result<bool> {
match self {
LuaComponent::UserData(ud) => {
ud.get::<mlua::Value>(name).map(|v| !v.is_nil())
},
LuaComponent::Table(t) => {
t.contains_key(name)
},
}
}
}
/// Reference to an Entity for Lua
///
/// This can be used to make it easier to update things on the Entity.
pub struct LuaEntityRef {
en: Entity,
world: ScriptWorldPtr,
}
impl LuaEntityRef {
pub fn new(world: ScriptWorldPtr, en: Entity) -> Self {
Self {
en,
world,
}
}
}
impl mlua::UserData for LuaEntityRef {
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
methods.add_method(
"update",
|lua, this, comps: mlua::Variadic<LuaComponent>| {
let mut world = this.world.write();
let world_tick = world.current_tick();
for (i, comp) in comps.iter().enumerate() {
let tid = comp.get_typeid(&world).ok_or(mlua::Error::BadArgument {
to: Some("Entity:update".into()),
pos: 2 + i,
name: Some("comps...".into()),
cause: Arc::new(mlua::Error::runtime(
"failed to get native TypeId from component",
)),
})?;
// convert component to mlua::Value
let comp = comp.clone().into_lua(lua)?;
// get the pointer of the component in the archetype column.
let arch = world.entity_archetype_mut(this.en).unwrap();
let arch_idx = *arch.entity_indexes().get(&this.en).unwrap();
let col = arch.get_column_mut(tid).unwrap();
let col_ptr = col.component_ptr(*arch_idx as usize, &world_tick).cast();
// get the type registry to apply the new value
let reg = world.get_resource::<TypeRegistry>().unwrap();
let reg_type = reg.get_type(tid).unwrap();
let proxy = reg_type
.get_data::<ReflectLuaProxy>()
// this should actually be safe since the ReflectedIterator
// attempts to get the type data before it is tried here
.expect("Type does not have ReflectLuaProxy as a TypeData");
proxy.apply(lua, col_ptr, &comp)?;
}
Ok(())
},
);
}
}

View File

@ -1,308 +0,0 @@
pub mod dynamic_iter;
pub use dynamic_iter::*;
pub mod world;
use lyra_resource::ResourceData;
use mlua::ObjectLike;
pub use world::*;
pub mod script;
pub use script::*;
pub mod loader;
pub use loader::*;
pub mod providers;
pub mod wrappers;
pub mod proxy;
pub use proxy::*;
pub mod ecs;
mod entity_ref;
pub use entity_ref::*;
pub mod system;
pub use system::*;
use wrappers::{LuaHandleWrapper, LuaResHandleToComponent, LuaWrappedEventProxy};
use std::{any::TypeId, sync::Mutex};
use lyra_ecs::World;
use lyra_reflect::{FromType, Reflect, TypeRegistry};
use crate::ScriptBorrow;
pub type LuaContext = Mutex<mlua::Lua>;
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("mismatched type, expected `{expected}`, got: `{got}`")]
TypeMismatch {
expected: String,
got: String,
},
#[error("received nil value from Lua")]
Nil,
#[error("{0}")]
Mlua(#[from] mlua::Error),
#[error("unimplemented: {0}")]
Unimplemented(String),
#[error("Error calling internal reflection type")]
Reflect,
}
/* impl Into<mlua::Error> for Error {
fn into(self) -> mlua::Error {
match self {
Error::Mlua(error) => error,
_ => mlua::Error::external(self)
}
}
}
*/
impl From<Error> for mlua::Error {
fn from(value: Error) -> Self {
match value {
Error::Mlua(error) => error,
_ => mlua::Error::external(value)
}
}
}
impl Error {
pub fn type_mismatch(expected: &str, got: &str) -> Self {
Self::TypeMismatch { expected: expected.into(), got: got.into() }
}
pub fn unimplemented(msg: &str) -> Self {
Self::Unimplemented(msg.into())
}
}
/// Name of a Lua function that is used to Reflect the Userdata, but without a value.
///
/// This is used for reflecting the userdata as an ECS Component or Resource. This **function**
/// returns a [`ScriptBorrow`] with data as `None`.
pub const FN_NAME_INTERNAL_REFLECT_TYPE: &str = "__lyra_internal_reflect_type";
/// Name of a Lua function that is used to Reflect the Userdata.
///
/// This is used for reflecting the userdata as an ECS Component or Resource. This **method**
/// returns a [`ScriptBorrow`] with data as `Some`. **Anything that calls this expects the
/// method to return data**.
pub const FN_NAME_INTERNAL_REFLECT: &str = "__lyra_internal_reflect";
/// Name of a Lua function to retrieve the query result from a Userdata, or Table.
///
/// The function must match the following definition: `fn(ScriptWorldPtr, Entity) -> LuaValue`.
///
/// When `nil` is returned, its considered that the query will not result in anything for this
/// [`View`], **no matter the entity**. When the query is used in a [`View`] and returns `nil`,
/// it will NOT check for other entities. This is used in the [`ResQuery`] Lua query. If the
/// resource is missing, it will always be missing for the [`View`], no matter the entity.
///
/// If it returns a boolean, the query will act as a filter. The boolean value will not be in the
/// result. When the boolean is `false`, other entities will be checked by the [`View`].
///
/// Any other value will be included in the result.
pub const FN_NAME_INTERNAL_ECS_QUERY_RESULT: &str = "__lyra_internal_ecs_query_result";
/// Name of a Lua function implemented for Userdata types that can be made into components.
///
/// This is used for types that can be converted into components. When implementing this function,
/// you must return a [`ScriptBorrow`] that contains the component for this userdata.
/// You can return [`mlua::Value::Nil`] if for some reason the type could not be converted
/// into a component.
///
/// A good example of this is `LuaResHandle`. The resource handle is requested from the
/// world, and could be a 3d model. The 3d model could then be manually wrapped as
/// [`LuaModelComponent`] with its `new` function. But for quality of life, this internal
/// function was created so that the userdata can be converted into its component
/// type without having to wrap it.
///
/// Without implementing this function:
/// ```lua
/// local cube = world:request_res("assets/cube-texture-embedded.gltf")
/// local cube_comp = ModelComponent.new(cube) -- annoying to write
///
/// local pos = Transform.from_translation(Vec3.new(0, 0, -8.0))
/// world:spawn(pos, cube_comp)
/// ```
///
/// With this function:
/// ```lua
/// local cube = world:request_res("assets/cube-texture-embedded.gltf")
/// local pos = Transform.from_translation(Vec3.new(0, 0, -8.0))
/// world:spawn(pos, cube)
/// ```
pub const FN_NAME_INTERNAL_AS_COMPONENT: &str = "__lyra_internal_refl_as_component";
/// A trait used for registering a Lua type with the world.
pub trait RegisterLuaType {
/// Register a type to Lua that **is not wrapped**.
fn register_lua_type<'a, T>(&mut self)
where
T: Reflect + LuaProxy + Clone + mlua::FromLua + mlua::UserData;
/// Registers a type to Lua that is wrapped another type.
/// This would be used for something like `UserdataRef<T>`.
fn register_lua_wrapper<'a, W>(&mut self)
where
W: Reflect + LuaProxy + LuaWrapper + Clone + mlua::FromLua + mlua::UserData;
/// Registers a type to Lua that can be converted into and from Lua types.
fn register_lua_convert<T>(&mut self)
where
T: Clone + mlua::FromLua + mlua::IntoLua + LuaWrapper + 'static;
/// Registers a type that can be converted to and from lua and adds a lookup entry.
///
/// This is a shortcut for `register_lua_convert` and `add_component_lookup_entry`.
fn register_lua_convert_component<T>(&mut self, name: &str)
where
T: Clone + mlua::FromLua + mlua::IntoLua + LuaWrapper + 'static,
T::Wrap: lyra_ecs::Component + Reflect;
/// Register an asset handle wrapper.
fn register_asset_handle<T>(&mut self, name: &str)
where
T: LuaHandleWrapper + Reflect + LuaProxy,
T::ResourceType: ResourceData;
/// Add an entry for a non-component in the [`TypeLookup`] table.
fn add_lua_event<T>(&mut self, name: &str)
where
T: Reflect + LuaWrapper + mlua::FromLua + mlua::IntoLua + Send + Sync,
T::Wrap: Clone + lyra_game::Event;
/// Add an entry for a component in the [`TypeLookup`] table.
fn add_component_lookup_entry<T>(&mut self, name: &str)
where
T: lyra_ecs::Component;
/// Add an entry for a non-component in the [`TypeLookup`] table.
fn add_lookup_entry<T>(&mut self, name: &str)
where
T: 'static;
}
impl RegisterLuaType for World {
fn register_lua_type<'a, T>(&mut self)
where
T: Reflect + LuaProxy + Clone + mlua::FromLua + mlua::UserData
{
let mut registry = self.get_resource_mut::<TypeRegistry>().unwrap();
let type_id = TypeId::of::<T>();
let reg_type = registry.get_type_or_default(type_id);
reg_type.add_data(ReflectLuaProxy::from_lua_proxy::<T>());
}
fn register_lua_wrapper<'a, W>(&mut self)
where
W: Reflect + LuaProxy + LuaWrapper + Clone + mlua::FromLua + mlua::UserData
{
let mut registry = self.get_resource_mut::<TypeRegistry>().unwrap();
let reg_type = registry.get_type_or_default(W::wrapped_type_id());
reg_type.add_data(ReflectLuaProxy::from_lua_proxy::<W>());
}
fn register_lua_convert<T>(&mut self)
where
T: Clone + mlua::FromLua + mlua::IntoLua + LuaWrapper + 'static,
{
let mut registry = self.get_resource_mut::<TypeRegistry>().unwrap();
let reg_type = registry.get_type_or_default(T::wrapped_type_id());
reg_type.add_data(ReflectLuaProxy::from_as_and_from_lua::<T>());
}
fn register_lua_convert_component<T>(&mut self, name: &str)
where
T: Clone + mlua::FromLua + mlua::IntoLua + LuaWrapper + 'static,
T::Wrap: lyra_ecs::Component + Reflect
{
self.register_lua_convert::<T>();
self.add_component_lookup_entry::<T::Wrap>(name);
}
fn register_asset_handle<T>(&mut self, name: &str)
where
T: LuaHandleWrapper + Reflect + LuaProxy,
T::ResourceType: ResourceData
{
{
let mut registry = self.get_resource_mut::<TypeRegistry>().unwrap();
let reg_type = registry.get_type_or_default(TypeId::of::<T::ResourceType>());
reg_type.add_data(ReflectLuaProxy::from_lua_proxy::<T>());
let l: LuaResHandleToComponent = FromType::<T>::from_type();
reg_type.add_data(l);
}
self.add_lookup_entry::<T>(name);
}
fn add_lua_event<T>(&mut self, name: &str)
where
T: Reflect + LuaWrapper + mlua::FromLua + mlua::IntoLua + Send + Sync,
T::Wrap: Clone + lyra_game::Event
{
{
let mut registry = self.get_resource_mut::<TypeRegistry>().unwrap();
let reg_type = registry.get_type_or_default(TypeId::of::<T::Wrap>());
let proxy: LuaWrappedEventProxy = FromType::<T>::from_type();
reg_type.add_data(proxy);
}
self.add_lookup_entry::<T::Wrap>(name);
}
fn add_component_lookup_entry<T>(&mut self, name: &str)
where
T: lyra_ecs::Component
{
let mut lookup = self.get_resource_or_default::<TypeLookup>();
lookup.comp_info_from_name.insert(name.into(), lyra_ecs::ComponentInfo::new::<T>());
lookup.typeid_from_name.insert(name.into(), std::any::TypeId::of::<T>());
}
fn add_lookup_entry<T>(&mut self, name: &str)
where
T: 'static
{
let mut lookup = self.get_resource_or_default::<TypeLookup>();
lookup.typeid_from_name.insert(name.into(), std::any::TypeId::of::<T>());
}
}
impl mlua::FromLua for ScriptBorrow {
fn from_lua(value: mlua::Value, _: &mlua::Lua) -> mlua::Result<Self> {
match value {
mlua::Value::UserData(ud) => Ok(ud.borrow::<Self>()?.clone()),
_ => unreachable!(),
}
}
}
impl mlua::UserData for ScriptBorrow {
}
/// Helper function used for reflecting userdata as a ScriptBorrow
pub fn reflect_user_data(ud: &mlua::AnyUserData) -> ScriptBorrow {
let ud_name = ud.metatable().and_then(|mt| mt.get::<String>(mlua::MetaMethod::Type))
.unwrap_or("Unknown".to_string());
ud.call_method::<ScriptBorrow>(FN_NAME_INTERNAL_REFLECT, ())
.unwrap_or_else(|_| panic!("UserData of name '{}' does not implement internal reflect method properly", ud_name))
}
/// Helper function used for reflecting userdata type as a ScriptBorrow
pub fn reflect_type_user_data(ud: &mlua::AnyUserData) -> ScriptBorrow {
let ud_name = ud.metatable().and_then(|mt| mt.get::<String>(mlua::MetaMethod::Type))
.unwrap_or("Unknown".to_string());
ud.call_function::<ScriptBorrow>(FN_NAME_INTERNAL_REFLECT_TYPE, ())
.unwrap_or_else(|_| panic!("UserData of name '{}' does not implement internal reflect type function properly", ud_name))
}

View File

@ -1,130 +0,0 @@
use lyra_ecs::ResourceObject;
use lyra_reflect::Reflect;
use crate::{lua::{ecs::{query::{LuaChangedQuery, LuaHasQuery, LuaNotQuery, LuaOptionalQuery, LuaOrQuery, LuaResQuery, LuaTickOfQuery}, View}, wrappers::*, LuaContext, LuaWrapper, RegisterLuaType, FN_NAME_INTERNAL_REFLECT, FN_NAME_INTERNAL_REFLECT_TYPE}, ScriptApiProvider, ScriptBorrow, ScriptData, ScriptDynamicBundle, ScriptWorldPtr};
//fn register_lua_proxy::<T:
#[derive(Default)]
pub struct LyraEcsApiProvider;
impl ScriptApiProvider for LyraEcsApiProvider {
type ScriptContext = LuaContext;
fn prepare_world(&mut self, world: &mut lyra_ecs::World) {
world.register_lua_convert::<LuaDeltaTime>();
world.register_lua_wrapper::<LuaSceneHandle>();
world.register_lua_wrapper::<LuaActionHandler>();
world.register_lua_wrapper::<LuaWindow>();
world.register_lua_convert_component::<LuaCamera>("Camera");
world.register_lua_convert_component::<LuaFreeFlyCamera>("FreeFlyCamera");
world.register_lua_convert_component::<LuaWorldTransform>("WorldTransform");
world.register_lua_wrapper::<LuaDeviceId>();
world.register_lua_convert::<LuaDeviceEventRaw>();
world.register_lua_convert::<LuaDeviceEvent>();
world.add_lua_event::<LuaDeviceEvent>("DeviceEvent");
world.register_asset_handle::<LuaGltfHandle>("Gltf");
}
fn expose_api(&mut self, _: &ScriptData, ctx: &mut Self::ScriptContext) -> Result<(), crate::ScriptError> {
let ctx = ctx.lock().unwrap();
// load enums
let bytes = include_str!("../../../scripts/lua/enums.lua");
ctx.load(bytes).exec().unwrap();
let bytes = include_str!("../../../scripts/lua/ecs.lua");
ctx.load(bytes).exec().unwrap();
let globals = ctx.globals();
globals.set("World", ctx.create_proxy::<ScriptWorldPtr>()?)?;
globals.set("DynamicBundle", ctx.create_proxy::<ScriptDynamicBundle>()?)?;
globals.set("SceneHandler", ctx.create_proxy::<LuaSceneHandle>()?)?;
globals.set("ActionHandler", ctx.create_proxy::<LuaActionHandler>()?)?;
globals.set("Window", ctx.create_proxy::<LuaWindow>()?)?;
globals.set("View", ctx.create_proxy::<View>()?)?;
globals.set("ResQuery", ctx.create_proxy::<LuaResQuery>()?)?;
globals.set("ChangedQuery", ctx.create_proxy::<LuaChangedQuery>()?)?;
globals.set("HasQuery", ctx.create_proxy::<LuaHasQuery>()?)?;
globals.set("NotQuery", ctx.create_proxy::<LuaNotQuery>()?)?;
globals.set("AnyQuery", ctx.create_proxy::<LuaOrQuery>()?)?;
globals.set("TickOfQuery", ctx.create_proxy::<LuaTickOfQuery>()?)?;
globals.set("OptionalQuery", ctx.create_proxy::<LuaOptionalQuery>()?)?;
expose_comp_table_wrapper::<LuaCamera>(&ctx, &globals, "Camera")?;
expose_comp_table_wrapper::<LuaFreeFlyCamera>(&ctx, &globals, "FreeFlyCamera")?;
expose_comp_table_wrapper::<LuaWorldTransform>(&ctx, &globals, "WorldTransform")?;
expose_table_wrapper::<LuaDeviceEvent>(&ctx, &globals, "DeviceEvent")?;
expose_resource_table_wrapper::<lyra_game::DeltaTime>(&ctx, &globals, "DeltaTime")?;
Ok(())
}
fn setup_script(&mut self, _: &crate::ScriptData, _: &mut Self::ScriptContext) -> Result<(), crate::ScriptError> {
Ok(())
}
fn update_script_environment(&mut self, _: crate::ScriptWorldPtr, _: &crate::ScriptData, _: &mut Self::ScriptContext) -> Result<(), crate::ScriptError> {
Ok(())
}
}
fn expose_resource_table_wrapper<T: Reflect + ResourceObject + Default + 'static>(lua: &mlua::Lua, globals: &mlua::Table, name: &str) -> mlua::Result<()> {
let table = lua.create_table()?;
table.set(FN_NAME_INTERNAL_REFLECT_TYPE, lua.create_function(|_, ()| {
Ok(ScriptBorrow::from_resource::<T>(None))
})?)?;
globals.set(name, table)?;
Ok(())
}
fn create_reflect_comp_table<T>(lua: &mlua::Lua, name: &str) -> mlua::Result<mlua::Table>
where
T: LuaWrapper + mlua::FromLua,
T::Wrap: lyra_ecs::Component + Reflect
{
let table = lua.create_table()?;
table.set(FN_NAME_INTERNAL_REFLECT, lua.create_function(|_, this: T| {
Ok(ScriptBorrow::from_component::<T::Wrap>(Some(this.into_wrapped())))
})?)?;
table.set(FN_NAME_INTERNAL_REFLECT_TYPE, lua.create_function(|_, ()| {
Ok(ScriptBorrow::from_component::<T::Wrap>(None))
})?)?;
table.set(mlua::MetaMethod::Type.name(), name)?;
Ok(table)
}
/// Expose a wrapper of a component that converts to/from a lua type.
///
/// This type of wrapper could convert to/from a Lua table, or number, string, etc., any Lua type.
///
/// This creates the reflection functions on a table specified in globals.
/// The table name is set to `name`, which is also how the script will use the table.
fn expose_comp_table_wrapper<T>(lua: &mlua::Lua, globals: &mlua::Table, name: &str) -> mlua::Result<()>
where
T: LuaWrapper + mlua::FromLua,
T::Wrap: lyra_ecs::Component + Reflect
{
let table = create_reflect_comp_table::<T>(&lua, name)?;
globals.set(name, table)?;
Ok(())
}
fn expose_table_wrapper<T>(lua: &mlua::Lua, globals: &mlua::Table, name: &str) -> mlua::Result<()>
where
T: LuaWrapper,
{
let table = lua.create_table()?;
table.set(mlua::MetaMethod::Type.name(), name.to_string())?;
globals.set(name, table)?;
Ok(())
}

View File

@ -1,182 +0,0 @@
use std::{ops::Deref, sync::{Arc, Mutex}};
use mlua::{AnyUserData, ObjectLike};
use tracing::{debug, debug_span};
use crate::{ScriptApiProvider, ScriptData};
/// This Api provider provides some nice utility functions.
///
/// Functions:
/// ```lua
/// ---@param str (string) A format string.
/// ---@param ... (any varargs) The variables to format into the string. These values must be
/// primitives, or if UserData, have the '__tostring' meta method
/// function printf(str, ...)
/// ```
#[derive(Default)]
pub struct UtilityApiProvider;
impl ScriptApiProvider for UtilityApiProvider {
type ScriptContext = Mutex<mlua::Lua>;
fn expose_api(
&mut self,
data: &ScriptData,
ctx: &mut Self::ScriptContext,
) -> Result<(), crate::ScriptError> {
let ctx = ctx.lock().unwrap();
//fn printf(lua: &mlua::State, (mut text, formats): (String, mlua::Variadic<mlua::Value>)) -> mlua::Result<()> {
let printf =
|lua: &mlua::Lua, (mut text, formats): (String, mlua::Variadic<mlua::Value>)| {
let mut formatted = String::new();
let mut arg_num = 0;
while let Some(start) = text.find("{}") {
let val_str = match formats.get(arg_num) {
Some(v) => match v {
mlua::Value::Nil => "nil".to_string(),
mlua::Value::Boolean(b) => b.to_string(),
mlua::Value::Integer(n) => n.to_string(),
mlua::Value::Number(n) => n.to_string(),
mlua::Value::String(s) => s.to_string_lossy().to_string(),
mlua::Value::Table(_) => {
return Err(mlua::Error::runtime(
"unable to get string representation of Table",
));
},
mlua::Value::Function(_) => {
return Err(mlua::Error::runtime(
"unable to get string representation of Function",
));
},
mlua::Value::Thread(_) => {
return Err(mlua::Error::runtime(
"unable to get string representation of Thread",
));
},
mlua::Value::UserData(ud) => {
if let Ok(tos) =
ud.get::<mlua::Function>(mlua::MetaMethod::ToString.name())
{
tos.call::<String>(())?
} else {
return Err(mlua::Error::runtime(
"UserData does not implement MetaMethod '__tostring'",
));
}
},
mlua::Value::LightUserData(_) => {
return Err(mlua::Error::runtime(
"unable to get string representation of LightUserData",
));
},
mlua::Value::Error(error) => {
return Err(error.deref().clone());
},
},
None => {
let got_args = arg_num; // - 1;
// continue searching for {} to get the number of format spots for the error message.
while let Some(start) = text.find("{}") {
text = text[start + 2..].to_string();
arg_num += 1;
}
return Err(mlua::Error::BadArgument {
to: Some("printf".into()),
pos: 2,
name: Some("fmt...".into()),
cause: Arc::new(mlua::Error::runtime(format!(
"not enough args \
given for the amount of format areas in the string. Expected {}, \
got {}.",
arg_num, got_args
))),
});
}
};
formatted = format!("{}{}{}", formatted, &text[0..start], val_str);
text = text[start + 2..].to_string();
arg_num += 1;
}
if arg_num < formats.len() {
return Err(mlua::Error::BadArgument {
to: Some("printf".into()),
pos: 2,
name: Some("fmt...".into()),
cause: Arc::new(mlua::Error::runtime(format!(
"got more args \
than format areas in the string. Expected {}, got {}.",
formats.len(),
arg_num
))),
});
}
formatted = format!("{}{}", formatted, text);
lua.globals()
.get::<mlua::Function>("print")?
.call::<()>(formatted)?;
Ok(())
};
let script_name_reg = ctx.create_registry_value(data.name.clone())?;
let printf_func = ctx.create_function(printf)?;
let print_func = ctx.create_function(move |lua, text: String| {
let name = lua.registry_value::<String>(&script_name_reg)?;
let _span = debug_span!("lua", script = &name).entered();
debug!(target: "lyra_scripting::lua", "{}", text);
Ok(())
})?;
// a custom implementation of `getmetatable` is required since mlua protects __metatable,
// making it impossible to get the metatable of userdata.
let getmetatable_func = ctx.create_function(|lua, ud: AnyUserData| {
// the userdata is left on the stack from `lua_getmetatable`, so that needs to be
// included in the returns
let (_ud, table): (mlua::AnyUserData, mlua::Table) = unsafe {
lua.exec_raw(ud, |state| {
mlua::ffi::lua_getmetatable(state, -1);
})
}?;
Ok(table)
})?;
let globals = ctx.globals();
globals.set("printf", printf_func)?;
globals.set("print", print_func)?;
globals.set("getmetatable2", getmetatable_func)?;
Ok(())
}
fn setup_script(
&mut self,
_data: &ScriptData,
_ctx: &mut Self::ScriptContext,
) -> Result<(), crate::ScriptError> {
Ok(())
}
fn update_script_environment(
&mut self,
_world: crate::ScriptWorldPtr,
_data: &ScriptData,
_ctx: &mut Self::ScriptContext,
) -> Result<(), crate::ScriptError> {
Ok(())
}
}

View File

@ -1,163 +0,0 @@
use std::{any::TypeId, collections::HashMap, ptr::NonNull};
use mlua::ObjectLike;
use lyra_ecs::{ComponentInfo, DynamicBundle};
use lyra_reflect::Reflect;
use crate::{ScriptBorrow, ScriptDynamicBundle};
use super::{Error, FN_NAME_INTERNAL_REFLECT};
pub trait LuaWrapper: Sized {
type Wrap: 'static;
/// The type id of the wrapped type.
#[inline(always)]
fn wrapped_type_id() -> TypeId {
TypeId::of::<Self::Wrap>()
}
fn into_wrapped(self) -> Self::Wrap;
#[inline(always)]
fn from_wrapped(wrap: Self::Wrap) -> Option<Self> {
let _ = wrap;
None
}
}
/// A trait that used to convert something into lua, or to set something to a value from lua.
pub trait LuaProxy {
fn as_lua_value(
lua: &mlua::Lua,
this: &dyn Reflect,
) -> mlua::Result<mlua::Value>;
fn apply(
lua: &mlua::Lua,
this: &mut dyn Reflect,
value: &mlua::Value,
) -> mlua::Result<()>;
}
impl<'a, T> LuaProxy for T
where
T: Reflect + Clone + mlua::FromLua + mlua::IntoLua
{
fn as_lua_value(
lua: &mlua::Lua,
this: &dyn Reflect,
) -> mlua::Result<mlua::Value> {
let this = this.as_any().downcast_ref::<T>().unwrap();
this.clone().into_lua(lua)
}
fn apply(
lua: &mlua::Lua,
this: &mut dyn Reflect,
apply: &mlua::Value,
) -> mlua::Result<()> {
let this = this.as_any_mut().downcast_mut::<T>().unwrap();
let apply = T::from_lua(apply.clone(), lua)?;
*this = apply;
Ok(())
}
}
/// ECS resource that can be used to lookup types via name.
///
/// You can get the [`TypeId`] of the type via name, or the [`ComponentInfo`].
#[derive(Default)]
pub struct TypeLookup {
pub(crate) typeid_from_name: HashMap<String, TypeId>,
pub(crate) comp_info_from_name: HashMap<String, ComponentInfo>,
}
/// A struct used for Proxying types to and from Lua.
#[derive(Clone)]
pub struct ReflectLuaProxy {
fn_as_lua:
for<'a> fn(lua: &'a mlua::Lua, this_ptr: NonNull<()>) -> mlua::Result<mlua::Value>,
fn_apply: for<'a> fn(
lua: &'a mlua::Lua,
this_ptr: NonNull<()>,
value: &'a mlua::Value,
) -> mlua::Result<()>,
}
impl ReflectLuaProxy {
/// Create from a type that implements LuaProxy (among some other required traits)
pub fn from_lua_proxy<'a, T>() -> Self
where
T: Reflect + LuaProxy
{
Self {
fn_as_lua: |lua, this| -> mlua::Result<mlua::Value> {
let this = unsafe { this.cast::<T>().as_ref() };
<T as LuaProxy>::as_lua_value(lua, this)
},
fn_apply: |lua, ptr, apply| {
let this = unsafe { ptr.cast::<T>().as_mut() };
<T as LuaProxy>::apply(lua, this, apply)
},
}
}
/// Create from a type that implements FromLua and AsLua
pub fn from_as_and_from_lua<T>() -> Self
where
T: mlua::FromLua + mlua::IntoLua + Clone
{
Self {
fn_as_lua: |lua, this| -> mlua::Result<mlua::Value> {
let this = unsafe { this.cast::<T>().as_ref() };
this.clone().into_lua(lua)
},
fn_apply: |lua, ptr, value| {
let this = unsafe { ptr.cast::<T>().as_mut() };
let new_val = T::from_lua(value.clone(), lua)?;
*this = new_val;
Ok(())
},
}
}
/// Reflect the pointer to get a Lua value.
pub fn as_lua(&self, lua: &mlua::Lua, this_ptr: NonNull<()>) -> mlua::Result<mlua::Value> {
(self.fn_as_lua)(lua, this_ptr)
}
/// Set the contents in the pointer to a Lua value.
pub fn apply(&self, lua: &mlua::Lua, this_ptr: NonNull<()>, value: &mlua::Value) -> mlua::Result<()> {
(self.fn_apply)(lua, this_ptr, value)
}
}
impl mlua::FromLua for ScriptDynamicBundle {
fn from_lua(val: mlua::Value, _: &mlua::Lua) -> mlua::Result<Self> {
match val {
mlua::Value::UserData(ud) => Ok(ud.borrow::<Self>()?.clone()),
mlua::Value::Nil => Err(Error::Nil.into()),
_ => unreachable!(),
}
}
}
impl mlua::UserData for ScriptDynamicBundle {
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
methods.add_function("new", |_, ()| Ok(ScriptDynamicBundle(DynamicBundle::new())));
methods.add_method_mut("push", |_, this, comp: mlua::AnyUserData| {
let script_brw = comp.call_method::<ScriptBorrow>(FN_NAME_INTERNAL_REFLECT, ())?;
let reflect = script_brw.reflect_branch.as_component_unchecked();
let refl_data = script_brw.data.unwrap();
reflect.bundle_insert(&mut this.0, refl_data);
Ok(())
});
}
}

View File

@ -1,229 +0,0 @@
use std::{ops::DerefMut, sync::Arc};
use crate::{ScriptBorrow, ScriptEntity, ScriptWorldPtr};
use lyra_ecs::{CommandQueue, Commands, DynamicBundle, World};
use lyra_reflect::{ReflectWorldExt, RegisteredType, TypeRegistry};
use lyra_resource::ResourceManager;
use mlua::{IntoLua, ObjectLike};
use super::{
ecs::{View, ViewOneResult, ViewResult},
wrappers::{LuaResHandleToComponent, LuaTick, LuaWrappedEventProxy},
Error, ReflectLuaProxy, TypeLookup, FN_NAME_INTERNAL_AS_COMPONENT, FN_NAME_INTERNAL_REFLECT,
FN_NAME_INTERNAL_REFLECT_TYPE,
};
impl mlua::FromLua for ScriptEntity {
fn from_lua(value: mlua::Value, _: &mlua::Lua) -> mlua::Result<Self> {
match value {
mlua::Value::UserData(ud) => Ok(ud.borrow::<Self>()?.clone()),
mlua::Value::Nil => Err(mlua::Error::external(Error::type_mismatch(
"ScriptEntity",
"Nil",
))),
_ => panic!(),
}
}
}
impl mlua::UserData for ScriptEntity {
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
methods.add_meta_method(mlua::MetaMethod::ToString, |_, this, ()| {
Ok(format!("{:?}", this.0))
});
}
}
#[derive(thiserror::Error, Debug, Clone)]
pub enum WorldError {
#[error("{0}")]
LuaInvalidUsage(String),
}
impl mlua::FromLua for ScriptWorldPtr {
fn from_lua(val: mlua::Value, _: &mlua::Lua) -> mlua::Result<Self> {
match val {
mlua::Value::UserData(ud) => Ok(ud.borrow::<Self>()?.clone()),
mlua::Value::Nil => Err(mlua::Error::external(Error::type_mismatch(
"ScriptWorldPtr",
"Nil",
))),
_ => panic!(),
}
}
}
impl mlua::UserData for ScriptWorldPtr {
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
methods.add_method_mut("spawn", |_, this, vals: mlua::MultiValue| {
let mut world = this.write();
let mut bundle = DynamicBundle::new();
//while let Some(val) = vals.pop_front() {
for (i, val) in vals.into_iter().enumerate() {
let ud = val.as_userdata().ok_or(mlua::Error::BadArgument {
to: Some("World:spawn".into()),
pos: 2 + i, // i starts at 0
name: Some("components...".into()),
cause: Arc::new(mlua::Error::runtime("provided component is not userdata")),
})?;
let comp_borrow = {
if let Ok(as_comp) = ud.get::<mlua::Function>(FN_NAME_INTERNAL_AS_COMPONENT) {
let ud = match as_comp.call(ud.clone())? {
mlua::Value::UserData(ud) => ud,
mlua::Value::Nil => ud.clone(),
_ => todo!(),
};
ud.call_method::<ScriptBorrow>(FN_NAME_INTERNAL_REFLECT, ())?
} else {
ud.call_method::<ScriptBorrow>(FN_NAME_INTERNAL_REFLECT, ())?
}
};
let reflect = comp_borrow.reflect_branch.as_component_unchecked();
let refl_data = comp_borrow.data.unwrap();
reflect.bundle_insert(&mut bundle, refl_data);
}
// defer the entity spawn
// SAFETY: Commands borrows Entities from World, the resource borrows from the world resources,
// They are borrowing different parts of World.
let world_ptr: *mut World = world.deref_mut();
let mut commands_queue = world.get_resource_mut::<CommandQueue>().unwrap();
let mut commands = Commands::new(&mut commands_queue, unsafe { &mut *world_ptr });
let entity = commands.spawn(bundle);
Ok(ScriptEntity(entity))
});
methods.add_method_mut("resource", |lua, this, (ty,): (mlua::Value,)| {
let reflect = match ty {
mlua::Value::UserData(ud) => ud
.call_function::<ScriptBorrow>(FN_NAME_INTERNAL_REFLECT_TYPE, ())
.expect("Type does not implement 'reflect_type' properly"),
mlua::Value::Table(t) => {
let f: mlua::Function = t.get(FN_NAME_INTERNAL_REFLECT_TYPE)?;
f.call::<ScriptBorrow>(())
.expect("Type does not implement 'reflect_type' properly")
}
_ => {
panic!("how");
}
};
let mut world = this.write();
let res = reflect.reflect_branch.as_resource_unchecked();
if let Some(res_ptr) = res.reflect_ptr(&mut world) {
let reg_type = world
.get_type::<RegisteredType>(reflect.reflect_branch.reflect_type_id())
.expect("Resource is not type registered!");
let proxy = reg_type
.get_data::<ReflectLuaProxy>()
.expect("Type does not have ReflectLuaProxy as a TypeData");
proxy
.as_lua(lua, res_ptr.cast())
.and_then(|ud| ud.into_lua(lua))
} else {
// if the resource is not found in the world, return nil
Ok(mlua::Value::Nil)
}
});
methods.add_method_mut("add_resource", |_, this, res: mlua::Value| {
let reflect = match res {
mlua::Value::UserData(ud) => ud
.call_method::<ScriptBorrow>(FN_NAME_INTERNAL_REFLECT, ())
.expect("Type does not implement 'reflect_type' properly"),
mlua::Value::Table(t) => {
let f: mlua::Function = t.get(FN_NAME_INTERNAL_REFLECT)?;
f.call::<ScriptBorrow>(())
.expect("Type does not implement 'reflect_type' properly")
}
_ => {
panic!("how");
}
};
let data = reflect.data.expect(
"Its expected that 'FN_NAME_INTERNAL_REFLECT' returns data in World:add_resource",
);
let res = reflect
.reflect_branch
.as_resource()
.ok_or(mlua::Error::runtime("Provided type is not a resource!"))?;
let mut world = this.write();
res.insert(&mut world, data);
Ok(())
});
methods.add_method_mut("request_asset", |lua, this, path: String| {
let world = this.write();
let man = world.get_resource_mut::<ResourceManager>().unwrap();
let handle = man.request_raw(&path).unwrap();
// get the actual resource handle wrapper
let registry = world.get_resource::<TypeRegistry>().unwrap();
let ty = registry
.get_type(handle.resource_type_id())
.expect("Could not find asset type in registry");
let data = ty
.get_data::<LuaResHandleToComponent>()
.expect("Asset type does not have 'LuaResHandleToComponent' as TypeData");
Ok((data.fn_to_lua)(lua, handle).unwrap())
});
methods.add_method_mut(
"read_event",
|lua, this, either: mlua::Either<String, mlua::Table>| {
let mut world = this.write();
// get the type name
let name = match either {
mlua::Either::Left(name) => name,
mlua::Either::Right(table) => table.get(mlua::MetaMethod::Type.name())?,
};
// lookup the type id using the name, return an error if not found
let lookup = world.get_resource::<TypeLookup>().unwrap();
let tyid = lookup.typeid_from_name.get(&name).cloned();
if tyid.is_none() {
return Err(mlua::Error::runtime(format!(
"failed to lookup type id from type name: {}",
name
)));
}
let tyid = tyid.unwrap();
drop(lookup);
let registry = world.get_resource::<TypeRegistry>().unwrap();
let ty = registry
.get_type(tyid)
.expect("Could not find asset type in registry");
let data = ty
.get_data::<LuaWrappedEventProxy>()
.expect("Asset type does not have 'LuaWrappedEventProxy' as TypeData")
.clone();
drop(registry);
data.reader(&mut world).into_lua(lua)
},
);
methods.add_method("view", |_, this, view: mlua::UserDataRef<View>| {
ViewResult::new(this.clone(), &view)
});
methods.add_method("get_tick", |_, this, ()| {
let w = this.read();
Ok(LuaTick(w.current_tick()))
});
methods.add_method(
"view_one",
|_, this, (entity, view): (ScriptEntity, mlua::UserDataRef<View>)| {
ViewOneResult::new(this.clone(), *entity, &view)
},
);
}
}

View File

@ -1,237 +0,0 @@
use std::{any::TypeId, ops::Deref};
use lyra_resource::{FilterMode, ResHandle, Texture, WrappingMode};
use lyra_game::{scene::SceneGraph, gltf::{Gltf, Material, Mesh, PbrGlossiness, Specular, MeshIndices, AlphaMode}};
use lyra_reflect::Reflect;
use lyra_scripting_derive::{lua_wrap_handle, wrap_lua_struct};
use crate::{lua::{LuaWrapper, FN_NAME_INTERNAL_REFLECT, FN_NAME_INTERNAL_REFLECT_TYPE}, lyra_engine, ScriptBorrow};
use crate as lyra_scripting;
use super::LuaHandleWrapper;
use mlua::IntoLua;
fn filter_mode_to_str(fm: FilterMode) -> &'static str {
match fm {
FilterMode::Nearest => "nearest",
FilterMode::Linear => "linear",
}
}
fn wrapping_mode_to_str(wm: WrappingMode) -> &'static str {
match wm {
WrappingMode::ClampToEdge => "clamp_to_edge",
WrappingMode::MirroredRepeat => "mirrored_repeat",
WrappingMode::Repeat => "repeat",
}
}
wrap_lua_struct!(lyra_resource::TextureSampler,
// this can be safely skipped since it wont be a component or resource.
skip(lua_reflect),
fields={
// don't need to specify field types since setters are skipped
(mag_filter, skip_set, get={
this.mag_filter.map(|f| filter_mode_to_str(f))
.into_lua(lua)
}),
(min_filter, skip_set, get={
this.min_filter.map(|f| filter_mode_to_str(f))
.into_lua(lua)
}),
(mipmap_filter, skip_set, get={
this.mipmap_filter.map(|f| filter_mode_to_str(f))
.into_lua(lua)
}),
(wrap_u, skip_set, get={
wrapping_mode_to_str(this.wrap_u)
.into_lua(lua)
}),
(wrap_v, skip_set, get={
wrapping_mode_to_str(this.wrap_v)
.into_lua(lua)
}),
(wrap_w, skip_set, get={
wrapping_mode_to_str(this.wrap_w)
.into_lua(lua)
}),
}
);
wrap_lua_struct!(PbrGlossiness,
// this can be safely skipped since it wont be a component or resource.
skip(lua_reflect),
fields={
(diffuse_color: wrap(crate::lua::wrappers::LuaVec4), skip_set),
(specular: wrap(crate::lua::wrappers::LuaVec3), skip_set),
(glossiness, skip_set),
}
);
wrap_lua_struct!(Specular,
// this can be safely skipped since it wont be a component or resource.
skip(lua_reflect),
fields={
(factor, skip_set),
(color_factor: wrap(crate::lua::wrappers::LuaVec3), skip_set),
(texture, skip_set, get={
this.texture.clone()
.map(|t| LuaTextureHandle(t))
.into_lua(lua)
}),
(color_texture, skip_set, get={
this.color_texture.clone()
.map(|t| LuaTextureHandle(t))
.into_lua(lua)
})
}
);
// TODO: fields
lua_wrap_handle!(SceneGraph, name=Scene, {});
lua_wrap_handle!(Mesh,
field_getters={
(material, {
data.material.clone()
.map(|v| LuaMaterialHandle(v.clone()))
.into_lua(lua)
})
},
{
methods.add_method("indices", |lua, this, ()| {
if let Some(data) = this.0.data_ref() {
let table = lua.create_table()?;
match &data.indices {
Some(MeshIndices::U16(v)) => {
for (i, ind) in v.iter().enumerate() {
let i = i as i64 + 1; // lua indexes start at 1
table.raw_set(i, *ind)?;
}
},
Some(MeshIndices::U32(v)) => {
for (i, ind) in v.iter().enumerate() {
let i = i as i64 + 1; // lua indexes start at 1
table.raw_set(i, *ind)?;
}
},
None => {},
}
Ok(mlua::Value::Table(table))
} else {
Ok(mlua::Value::Nil)
}
});
// TODO: mesh attributes
}
);
lua_wrap_handle!(lyra_resource::Image,
field_getters={
(width, {
data.width().into_lua(lua)
}),
(height, {
data.height().into_lua(lua)
})
}
);
lua_wrap_handle!(Texture,
field_getters={
(image, wrapper=LuaImageHandle),
(sampler, {
data.sampler.clone()
.map(|s| LuaTextureSampler(s))
.into_lua(lua)
})
}
);
lua_wrap_handle!(Material,
field_getters={
shader_uuid,
name,
double_sided,
(base_color, wrapper=crate::lua::wrappers::LuaVec4),
metallic,
roughness,
(base_color_texture, {
data.base_color_texture.clone()
.map(|v| LuaTextureHandle(v.clone()))
.into_lua(lua)
}),
(metallic_roughness_texture, {
data.metallic_roughness_texture.clone()
.map(|v| LuaTextureHandle(v.clone()))
.into_lua(lua)
}),
(pbr_glossiness, {
data.pbr_glossiness.clone()
.map(|v| LuaPbrGlossiness(v.clone()))
.into_lua(lua)
}),
alpha_cutoff,
(alpha_mode, {
match data.alpha_mode {
AlphaMode::Opaque => "opaque",
AlphaMode::Mask => "mask",
AlphaMode::Blend => "blend",
}.into_lua(lua)
}),
(specular, {
data.specular.clone()
.map(|v| LuaSpecular(v.clone()))
.into_lua(lua)
}),
}
);
lua_wrap_handle!(Gltf, {
methods.add_method("scenes", |lua, this, ()| {
if let Some(data) = this.0.data_ref() {
let table = lua.create_table()?;
for (i, scene) in data.scenes.iter().enumerate() {
let i = i as i64 + 1; // lua indexes start at 1
table.raw_set(i, LuaSceneHandle(scene.clone()))?;
}
Ok(mlua::Value::Table(table))
} else {
Ok(mlua::Value::Nil)
}
});
methods.add_method("materials", |lua, this, ()| {
if let Some(data) = this.0.data_ref() {
let table = lua.create_table()?;
for (i, mat) in data.materials.iter().enumerate() {
let i = i as i64 + 1; // lua indexes start at 1
table.raw_set(i, LuaMaterialHandle(mat.clone()))?;
}
Ok(mlua::Value::Table(table))
} else {
Ok(mlua::Value::Nil)
}
});
methods.add_method("meshes", |lua, this, ()| {
if let Some(data) = this.0.data_ref() {
let table = lua.create_table()?;
for (i, mesh) in data.meshes.iter().enumerate() {
let i = i as i64 + 1; // lua indexes start at 1
table.raw_set(i, LuaMeshHandle(mesh.clone()))?;
}
Ok(mlua::Value::Table(table))
} else {
Ok(mlua::Value::Nil)
}
});
});

View File

@ -1,41 +0,0 @@
use lyra_resource::{ResHandle, ResourceData, UntypedResHandle};
use lyra_reflect::FromType;
use crate::lua::LuaWrapper;
mod gltf;
pub use gltf::*;
pub trait LuaHandleWrapper: LuaWrapper + mlua::UserData + Send + 'static {
type ResourceType: lyra_resource::ResourceData;
fn from_handle(handle: ResHandle<Self::ResourceType>) -> Self;
}
#[derive(Clone)]
pub struct LuaResHandleToComponent {
/// Create the userdata component that
pub fn_to_lua: fn(&mlua::Lua, UntypedResHandle) -> Option<mlua::AnyUserData>,
}
impl<T> FromType<T> for LuaResHandleToComponent
where
T: LuaHandleWrapper,
T::ResourceType: ResourceData,
{
fn from_type() -> Self {
Self {
fn_to_lua: |lua: &mlua::Lua, handle: UntypedResHandle| {
handle.as_typed::<T::ResourceType>()
.map(|res| lua.create_userdata(T::from_handle(res)).unwrap())
},
}
}
}
impl LuaResHandleToComponent {
pub fn new(f: fn(&mlua::Lua, UntypedResHandle) -> Option<mlua::AnyUserData>) -> Self {
Self {
fn_to_lua: f
}
}
}

View File

@ -1,46 +0,0 @@
use crate::{
lua::{
wrappers::{LuaAngle, LuaTransform},
LuaWrapper
},
ScriptBorrow,
};
use lyra_game::{render::camera::CameraProjectionMode, scene::CameraComponent};
use lyra_reflect::Enum;
use lyra_scripting_derive::to_lua_convert;
fn projection_mode_from_str(s: &str) -> Option<CameraProjectionMode> {
match s {
"perspective" => Some(CameraProjectionMode::Perspective),
"orthographic" => Some(CameraProjectionMode::Orthographic),
_ => None,
}
}
to_lua_convert!(
// Struct that is being wrapped
CameraComponent,
// Name of wrapping struct
name=LuaCamera,
// The name of the type in Lua
lua_name="Camera",
// Reflection type, can be 'component' or 'resource'
reflect=component,
fields={
transform: wrap(LuaTransform),
fov: wrap(LuaAngle),
(
mode,
// Get the table from the value, result must be `CameraProjectionMode`
get={
let mode: String = table.get("mode")?;
projection_mode_from_str(&mode).unwrap()
},
// Get the value from self, result must be the type in Lua, here its `String`.
set={
self.mode.variant_name().to_lowercase()
}
),
debug: bool
}
);

View File

@ -1,341 +0,0 @@
use crate::lua::LuaWrapper;
use lyra_game::{
input::{KeyCode, NativeKeyCode},
math::Vec2,
winit::{
dpi::PhysicalPosition, DeviceEvent, DeviceEventPair, DeviceId, MouseScrollDelta,
PhysicalKey, RawKeyEvent,
},
};
use lyra_reflect::Reflect;
use lyra_scripting_derive::wrap_lua_struct;
use crate as lyra_scripting;
use crate::lyra_engine;
use super::LuaVec2;
wrap_lua_struct!(DeviceId, skip(lua_reflect));
/// Wraps [`DeviceEvent`] and implements [`IntoLua`]
#[derive(Clone)]
pub struct LuaDeviceEventRaw(pub(crate) DeviceEvent);
impl std::ops::Deref for LuaDeviceEventRaw {
type Target = DeviceEvent;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl std::ops::DerefMut for LuaDeviceEventRaw {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl mlua::FromLua for LuaDeviceEventRaw {
fn from_lua(v: mlua::Value, _: &mlua::Lua) -> mlua::Result<Self> {
let ty = v.type_name();
let table = v.as_table().ok_or(mlua::Error::FromLuaConversionError {
from: ty,
to: "LuaDeviceEventRaw".into(),
message: Some("expected Lua Table".into()),
})?;
let type_str: String = table.get("kind")?;
let type_str = type_str.as_str();
let ret = match type_str {
"added" => Self(DeviceEvent::Added),
"removed" => Self(DeviceEvent::Removed),
"mouse_motion" => {
let delta: LuaVec2 = table.get("delta")?;
Self(DeviceEvent::MouseMotion {
delta: (delta.x as _, delta.y as _),
})
}
"mouse_wheel" => {
let delta = if table.contains_key("line_delta")? {
let ld: LuaVec2 = table.get("line_delta")?;
MouseScrollDelta::LineDelta(ld.x, ld.y)
} else if table.contains_key("pixel_delta")? {
let pd: LuaVec2 = table.get("pixel_delta")?;
let pos = PhysicalPosition::new(pd.x as f64, pd.y as f64);
MouseScrollDelta::PixelDelta(pos)
} else {
return Err(mlua::Error::FromLuaConversionError {
from: ty,
to: "LuaDeviceEventRaw".into(),
message: Some(
"could not find line_delta or pixel_delta in 'mouse_wheel' \
device event"
.into(),
),
});
};
Self(DeviceEvent::MouseWheel { delta })
}
"motion" => {
let axis: u32 = table.get("axis")?;
let value: f64 = table.get("value")?;
Self(DeviceEvent::Motion { axis, value })
}
"button" => {
let button: u32 = table.get("button")?;
let state_str: String = table.get("state")?;
let state_str = state_str.as_str();
let state = match state_str {
"pressed" => lyra_game::winit::ElementState::Pressed,
"released" => lyra_game::winit::ElementState::Released,
_ => {
return Err(mlua::Error::FromLuaConversionError {
from: ty,
to: "LuaDeviceEventRaw".into(),
message: Some(format!("unknown button state: '{}'", state_str)),
});
}
};
Self(DeviceEvent::Button { button, state })
}
"key" => {
let state_str: String = table.get("state")?;
let state_str = state_str.as_str();
let state = match state_str {
"pressed" => lyra_game::winit::ElementState::Pressed,
"released" => lyra_game::winit::ElementState::Released,
_ => {
return Err(mlua::Error::FromLuaConversionError {
from: ty,
to: "LuaDeviceEventRaw".into(),
message: Some(format!("unknown key state: '{}'", state_str)),
});
}
};
if table.contains_key("code")? {
let code_str: String = table.get("code")?;
let code = KeyCode::from_str(&code_str);
if code.is_none() {
return Err(mlua::Error::FromLuaConversionError {
from: ty,
to: "LuaDeviceEventRaw".into(),
message: Some(format!("unknown key code: '{}'", code_str)),
});
}
let code = code.unwrap();
let phy = PhysicalKey::Code(code.as_winit_keycode().unwrap());
Self(DeviceEvent::Key(RawKeyEvent {
physical_key: phy,
state,
}))
} else if table.contains_key("native")? {
let native_str: String = table.get("native")?;
let native_str = native_str.as_str();
let native_code: u32 = table.get("native_code")?;
let native_code = match native_str {
"unknown" => NativeKeyCode::Unidentified,
"android" => NativeKeyCode::Android(native_code),
"macos" => NativeKeyCode::MacOS(native_code as u16),
"windows" => NativeKeyCode::Windows(native_code as u16),
"xkb" => NativeKeyCode::Xkb(native_code),
_ => {
return Err(mlua::Error::FromLuaConversionError {
from: ty,
to: "LuaDeviceEventRaw".into(),
message: Some(format!("unknown native code type: {}", native_str)),
});
}
};
Self(DeviceEvent::Key(RawKeyEvent {
physical_key: PhysicalKey::Unidentified(native_code.into()),
state,
}))
} else {
return Err(mlua::Error::FromLuaConversionError {
from: ty,
to: "LuaDeviceEventRaw".into(),
message: Some(
"malformed Table for LuaDeviceEventRaw, missing key \
code"
.into(),
),
});
}
}
_ => {
return Err(mlua::Error::FromLuaConversionError {
from: ty,
to: "LuaDeviceEventRaw".into(),
message: Some(format!("unknown device event type '{}'", type_str)),
});
}
};
Ok(ret)
}
}
impl mlua::IntoLua for LuaDeviceEventRaw {
fn into_lua(self, lua: &mlua::Lua) -> mlua::Result<mlua::Value> {
let table = lua.create_table()?;
match self.0 {
DeviceEvent::Added => {
table.set("kind", "added")?;
}
DeviceEvent::Removed => {
table.set("kind", "removed")?;
}
DeviceEvent::MouseMotion { delta } => {
table.set("kind", "mouse_motion")?;
table.set("delta", LuaVec2(Vec2::new(delta.0 as _, delta.1 as _)))?;
}
DeviceEvent::MouseWheel { delta } => {
table.set("kind", "mouse_wheel")?;
match delta {
MouseScrollDelta::LineDelta(x, y) => {
let d = LuaVec2(Vec2::new(x, y));
table.set("line_delta", d)?;
}
MouseScrollDelta::PixelDelta(delta) => {
let d = delta.cast::<f32>();
let d = LuaVec2(Vec2::new(d.x, d.y));
table.set("pixel_delta", d)?;
}
}
}
DeviceEvent::Motion { axis, value } => {
table.set("kind", "motion")?;
table.set("axis", axis)?;
table.set("value", value)?;
}
DeviceEvent::Button { button, state } => {
table.set("kind", "button")?;
table.set("button", button)?;
let state = match state {
lyra_game::winit::ElementState::Pressed => "pressed",
lyra_game::winit::ElementState::Released => "released",
};
table.set("state", state)?;
}
DeviceEvent::Key(raw_key_event) => {
table.set("kind", "key")?;
let state = match raw_key_event.state {
lyra_game::winit::ElementState::Pressed => "pressed",
lyra_game::winit::ElementState::Released => "released",
};
table.set("state", state)?;
let e = KeyCode::from_raw_event(&raw_key_event);
let s = e.as_str();
if let Some(s) = s {
table.set("code", s)?;
} else if let Some(un) = e.get_unknown() {
match un {
lyra_game::input::NativeKeyCode::Unidentified => {
table.set("native", "unknown")?;
}
lyra_game::input::NativeKeyCode::Android(v) => {
table.set("native", "android")?;
table.set("native_code", *v)?;
}
lyra_game::input::NativeKeyCode::MacOS(v) => {
table.set("native", "macos")?;
table.set("native_code", *v)?;
}
lyra_game::input::NativeKeyCode::Windows(v) => {
table.set("native", "windows")?;
table.set("native_code", *v)?;
}
lyra_game::input::NativeKeyCode::Xkb(v) => {
table.set("native", "xkb")?;
table.set("native_code", *v)?;
}
}
}
}
}
table.into_lua(lua)
}
}
impl LuaWrapper for LuaDeviceEventRaw {
type Wrap = DeviceEvent;
#[inline(always)]
fn into_wrapped(self) -> Self::Wrap {
self.0
}
}
#[derive(Clone, Reflect)]
pub struct LuaDeviceEvent(pub(crate) DeviceEventPair);
impl std::ops::Deref for LuaDeviceEvent {
type Target = DeviceEventPair;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl std::ops::DerefMut for LuaDeviceEvent {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl mlua::FromLua for LuaDeviceEvent {
fn from_lua(v: mlua::Value, _: &mlua::Lua) -> mlua::Result<Self> {
let ty = v.type_name();
let table = v.as_table().ok_or(mlua::Error::FromLuaConversionError {
from: ty,
to: "LuaDeviceEvent".into(),
message: Some("expected Lua Table".into()),
})?;
let id: LuaDeviceId = table.get("device")?;
let ev: LuaDeviceEventRaw = table.get("event")?;
Ok(Self(DeviceEventPair {
device_src: id.0,
event: ev.0,
}))
}
}
impl mlua::IntoLua for LuaDeviceEvent {
fn into_lua(self, lua: &mlua::Lua) -> mlua::Result<mlua::Value> {
let table = lua.create_table()?;
table.set("device", LuaDeviceId(self.device_src))?;
table.set("event", LuaDeviceEventRaw(self.event.clone()))?;
table.into_lua(lua)
}
}
impl LuaWrapper for LuaDeviceEvent {
type Wrap = DeviceEventPair;
#[inline(always)]
fn into_wrapped(self) -> Self::Wrap {
self.0
}
#[inline(always)]
fn from_wrapped(wrap: Self::Wrap) -> Option<Self> {
Some(Self(wrap))
}
}

View File

@ -1,151 +0,0 @@
use std::{marker::PhantomData, sync::Arc};
use crate::lua::LuaWrapper;
use atomic_refcell::AtomicRefCell;
use lyra_ecs::World;
use lyra_game::{
Event, EventReader, EventWriter, Events,
};
use lyra_reflect::{FromType, Reflect};
use mlua::{FromLua, IntoLua};
use super::LuaVec2;
mod device_event;
pub use device_event::*;
/// Helper trait to type erase EventReader<T>
trait UntypedEventReader: Send + Sync {
fn next(&self, lua: &mlua::Lua) -> mlua::Result<mlua::Value>;
}
/// Helper trait to type erase EventWriter<T>
trait UntypedEventWriter: Send + Sync {
fn push(&self, lua: &mlua::Lua, event: mlua::Value) -> mlua::Result<()>;
}
/// Event reader exposed to Lua
#[derive(Clone)]
pub struct LuaEventReader(Arc<AtomicRefCell<dyn UntypedEventReader>>);
impl mlua::FromLua for LuaEventReader {
fn from_lua(value: mlua::Value, _: &mlua::Lua) -> mlua::Result<Self> {
let ty = value.type_name();
let ud = value.as_userdata()
.ok_or(mlua::Error::FromLuaConversionError { from: ty, to: "EventReader".into(), message: None })?;
let reader = ud.borrow::<Self>()?;
Ok(reader.clone())
}
}
impl mlua::UserData for LuaEventReader {
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
methods.add_method("read", |lua, this, ()| {
let reg = lua.create_registry_value(this.clone())?;
lua.create_function(move |lua: &mlua::Lua, ()| {
let reader = lua.registry_value::<Self>(&reg)?;
let reader = reader.0.borrow();
reader.next(lua)
})
});
}
}
/// Event writer exposed to Lua
#[derive(Clone)]
pub struct LuaEventWriter(Arc<AtomicRefCell<dyn UntypedEventWriter>>);
impl mlua::UserData for LuaEventWriter {
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
methods.add_method("write", |lua, this, event: mlua::Value| {
let writer = this.0.borrow();
writer.push(lua, event)?;
Ok(())
});
}
}
struct TypedReader<T>
where
T: Reflect + LuaWrapper + IntoLua + Send + Sync,
T::Wrap: Clone + Event,
{
reader: EventReader<T::Wrap>,
_marker: PhantomData<T>,
}
impl<T> UntypedEventReader for TypedReader<T>
where
T: Reflect + LuaWrapper + IntoLua + Send + Sync,
T::Wrap: Clone + Event,
{
fn next(&self, lua: &mlua::Lua) -> mlua::Result<mlua::Value> {
self.reader
.read()
.map(|e| T::from_wrapped(e.clone()).into_lua(lua))
.unwrap_or(Ok(mlua::Value::Nil))
}
}
struct TypedWriter<T>
where
T: Reflect + LuaWrapper + FromLua,
T::Wrap: Clone + Event,
{
writer: EventWriter<T::Wrap>,
_marker: PhantomData<T>,
}
impl<T> UntypedEventWriter for TypedWriter<T>
where
T: Reflect + LuaWrapper + FromLua,
T::Wrap: Clone + Event,
{
fn push(&self, lua: &mlua::Lua, event: mlua::Value) -> mlua::Result<()> {
let ev = T::from_lua(event, lua)?.into_wrapped();
self.writer.write(ev);
Ok(())
}
}
#[derive(Clone)]
pub struct LuaWrappedEventProxy {
fn_reader: for<'a> fn(world: &'a mut World) -> Arc<AtomicRefCell<dyn UntypedEventReader>>,
fn_writer: for<'a> fn(world: &'a mut World) -> Arc<AtomicRefCell<dyn UntypedEventWriter>>,
}
impl<T> FromType<T> for LuaWrappedEventProxy
where
T: Reflect + LuaWrapper + FromLua + IntoLua + Send + Sync,
T::Wrap: Clone + Event,
{
fn from_type() -> Self {
Self {
fn_reader: |world| {
let events = world.get_resource_or_default::<Events<T::Wrap>>();
Arc::new(AtomicRefCell::new(TypedReader {
reader: events.reader(),
_marker: PhantomData::<T>,
}))
},
fn_writer: |world| {
let events = world.get_resource_or_default::<Events<T::Wrap>>();
Arc::new(AtomicRefCell::new(TypedWriter {
writer: events.writer(),
_marker: PhantomData::<T>,
}))
},
}
}
}
impl LuaWrappedEventProxy {
pub fn reader(&self, world: &mut World) -> LuaEventReader {
LuaEventReader((self.fn_reader)(world))
}
pub fn writer(&self, world: &mut World) -> LuaEventWriter {
LuaEventWriter((self.fn_writer)(world))
}
}

View File

@ -1,15 +0,0 @@
use crate::{lua::LuaWrapper, ScriptBorrow};
use lyra_game::scene::FreeFlyCamera;
use lyra_scripting_derive::to_lua_convert;
to_lua_convert!(
// Struct that is being wrapped
FreeFlyCamera,
// Reflection type, can be 'component' or 'resource'
reflect=component,
fields={
speed: f32,
look_speed: f32,
mouse_sensitivity: f32,
}
);

View File

@ -1,386 +0,0 @@
use std::sync::Arc;
use crate::{lua::Error, lyra_engine};
use lyra_game::math;
use lyra_scripting_derive::{lua_vec_wrap_extension, wrap_lua_struct};
use mlua::FromLuaMulti;
use crate as lyra_scripting;
wrap_lua_struct!(
math::Vec2,
derives(PartialEq, Copy),
new,
fields={x: f32, y: f32},
metamethods(
Add(LuaVec2, f32),
Sub(LuaVec2, f32),
Div(LuaVec2, f32),
Mul(LuaVec2, f32),
Mod(LuaVec2, f32),
Eq, Unm, ToString
),
extra_methods = {
lua_vec_wrap_extension!(math::Vec2, LuaVec2);
methods.add_method_mut("move_by", |lua, this, vals: mlua::MultiValue| {
let vals_clone = vals.clone();
if let Ok((x, y)) = <(f32, f32) as FromLuaMulti>::from_lua_multi(vals, lua) {
this.x += x;
this.y += y;
} else if let Ok(v) = Self::from_lua_multi(vals_clone, lua) {
this.0 += v.0;
} else {
return Err(mlua::Error::BadArgument {
to: Some("Vec2:move_by".into()),
pos: 2,
name: None,
cause: Arc::new(mlua::Error::runtime(
"expected (number, number, number) or (Vec2), received neither"
)),
});
}
Ok(())
});
}
);
wrap_lua_struct!(
math::Vec3,
derives(PartialEq, Copy),
new,
fields={x: f32, y: f32, z: f32},
metamethods(
Add(LuaVec3, f32),
Sub(LuaVec3, f32),
Div(LuaVec3, f32),
Mul(LuaVec3, f32),
Mod(LuaVec3, f32),
Eq, Unm, ToString
),
extra_methods = {
lua_vec_wrap_extension!(math::Vec3, LuaVec3);
methods.add_method_mut("move_by", |lua, this, vals: mlua::MultiValue| {
let vals_clone = vals.clone();
if let Ok((x, y, z)) = <(f32, f32, f32) as FromLuaMulti>::from_lua_multi(vals, lua) {
this.x += x;
this.y += y;
this.z += z;
} else if let Ok(v) = Self::from_lua_multi(vals_clone, lua) {
this.0 += v.0;
} else {
return Err(mlua::Error::BadArgument {
to: Some("Vec3:move_by".into()),
pos: 2,
name: None,
cause: Arc::new(mlua::Error::runtime(
"expected (number, number, number) or (Vec3), received neither"
)),
});
}
Ok(())
});
}
);
wrap_lua_struct!(
math::Vec4,
derives(PartialEq, Copy),
new,
fields={x: f32, y: f32, z: f32, w: f32},
metamethods(
Add(LuaVec4, f32),
Sub(LuaVec4, f32),
Div(LuaVec4, f32),
Mul(LuaVec4, f32),
Mod(LuaVec4, f32),
Eq, Unm, ToString
),
extra_methods = {
lua_vec_wrap_extension!(math::Vec4, LuaVec4);
}
);
wrap_lua_struct!(
math::Quat,
derives(PartialEq, Copy),
//new,
fields={x: f32, y: f32, z: f32, w: f32},
metamethods(
Eq,
Add,
Sub,
Div(f32),
// __mul for LuaVec3 is manually implemented below since it doesn't return Self
//Mul(LuaQuat, f32),
),
extra_methods = {
// manually implemented since Quat doesn't have a `new` function
methods.add_function("new", |_, (x, y, z, w)| {
Ok(Self(math::Quat::from_xyzw(x, y, z, w)))
});
methods.add_function("from_rotation_x", |_, (rad,)| {
let q = math::Quat::from_rotation_x(rad);
Ok(Self(q))
});
methods.add_function("from_rotation_y", |_, (rad,)| {
let q = math::Quat::from_rotation_y(rad);
Ok(Self(q))
});
methods.add_function("from_rotation_z", |_, (rad,)| {
let q = math::Quat::from_rotation_z(rad);
Ok(Self(q))
});
methods.add_function("from_vec4", |_, v: LuaVec4| {
Ok(Self(math::Quat::from_vec4(*v)))
});
methods.add_function("from_axis_angle", |_, (axis, angle): (LuaVec3, f32)| {
let q = math::Quat::from_axis_angle(*axis, angle);
Ok(Self(q))
});
methods.add_method("dot", |_, this, (rhs,): (Self,)| {
Ok(this.dot(rhs.0))
});
methods.add_method("conjugate", |_, this, ()| {
Ok(Self(this.conjugate()))
});
methods.add_method("inverse", |_, this, ()| {
Ok(Self(this.inverse()))
});
methods.add_method("length", |_, this, ()| {
Ok(this.length())
});
methods.add_method("length_squared", |_, this, ()| {
Ok(this.length_squared())
});
methods.add_method("length_recip", |_, this, ()| {
Ok(this.length_recip())
});
methods.add_method("normalize", |_, this, ()| {
Ok(Self(this.normalize()))
});
methods.add_method("mult_quat", |_, this, (rhs,): (Self,)| {
Ok(Self(this.0 * rhs.0))
});
methods.add_method("mult_vec3", |_, this, (rhs,): (LuaVec3,)| {
Ok(LuaVec3(this.0 * rhs.0))
});
methods.add_method("is_finite", |_, this, ()| {
Ok(this.is_finite())
});
methods.add_method("is_nan", |_, this, ()| {
Ok(this.is_nan())
});
methods.add_method("is_normalized", |_, this, ()| {
Ok(this.is_normalized())
});
methods.add_method("is_near_identity", |_, this, ()| {
Ok(this.is_near_identity())
});
methods.add_method("angle_between", |_, this, rhs: LuaQuat| {
Ok(this.angle_between(*rhs))
});
methods.add_method("rotate_towards", |_, this, (rhs, max_angle): (LuaQuat, f32)| {
Ok(Self(this.rotate_towards(*rhs, max_angle)))
});
methods.add_method("abs_diff_eq", |_, this, (rhs, max_abs_diff): (LuaQuat, f32)| {
Ok(this.abs_diff_eq(*rhs, max_abs_diff))
});
// manually implemented here since multiplying may not return `Self`.
methods.add_meta_method(mlua::MetaMethod::Mul, |lua, this, (val,): (mlua::Value,)| {
use mlua::IntoLua;
match val {
mlua::Value::UserData(ud) => {
if ud.is::<LuaVec3>() {
let v3 = ud.borrow::<LuaVec3>()?;
LuaVec3(this.0 * v3.0)
.into_lua(lua)
} else {
let quat = ud.borrow::<LuaQuat>()?;
LuaQuat(this.0 * quat.0)
.into_lua(lua)
}
},
mlua::Value::Number(n) => {
LuaQuat(this.0 * (n as f32))
.into_lua(lua)
},
_ => {
let t = val.type_name();
Err(mlua::Error::BadArgument {
to: Some("Quat:__mul".into()),
pos: 2,
name: None,
cause: Arc::new(mlua::Error::external(
Error::type_mismatch("Vec3, Quat, or Number", t)
)),
})
}
}
});
methods.add_method("lerp", |_, this, (rhs, alpha): (Self, f32)| {
Ok(Self(this.lerp(*rhs, alpha)))
});
methods.add_method("slerp", |_, this, (rhs, alpha): (Self, f32)| {
Ok(Self(this.slerp(*rhs, alpha)))
});
}
);
wrap_lua_struct!(
math::Transform,
derives(PartialEq, Copy),
metamethods(ToString, Eq),
extra_methods = {
methods.add_function("default", |_, ()| {
Ok(Self(math::Transform::default()))
});
methods.add_function("new", |_, (pos, rot, scale): (LuaVec3, LuaQuat, LuaVec3)| {
Ok(Self(math::Transform::new(*pos, *rot, *scale)))
});
methods.add_function("from_translation", |lua, vals: mlua::MultiValue| {
let vals_clone = vals.clone();
if let Ok((x, y, z)) = <(f32, f32, f32) as FromLuaMulti>::from_lua_multi(vals, lua) {
Ok(Self(math::Transform::from_xyz(x, y, z)))
} else if let Ok(v) = LuaVec3::from_lua_multi(vals_clone, lua) {
Ok(Self(math::Transform::from_translation(*v)))
} else {
Err(mlua::Error::BadArgument {
to: Some("Transform:from_translation".into()),
pos: 2,
name: None,
cause: Arc::new(mlua::Error::runtime(
"expected (number, number, number) or (Vec3), received neither"
)),
})
}
});
methods.add_method("clone", |_, this, ()| {
Ok(this.clone())
});
methods.add_method("forward", |_, this, ()| {
Ok(LuaVec3(this.forward()))
});
methods.add_method("left", |_, this, ()| {
Ok(LuaVec3(this.left()))
});
methods.add_method("up", |_, this, ()| {
Ok(LuaVec3(this.up()))
});
methods.add_method_mut("rotate", |_, this, (quat,): (LuaQuat,)| {
this.rotate(*quat);
Ok(())
});
methods.add_method_mut("rotate_x", |_, this, (deg,): (f32,)| {
this.rotate_x(math::Angle::Degrees(deg));
Ok(())
});
methods.add_method_mut("rotate_y", |_, this, (deg,): (f32,)| {
this.rotate_y(math::Angle::Degrees(deg));
Ok(())
});
methods.add_method_mut("rotate_z", |_, this, (deg,): (f32,)| {
this.rotate_z(math::Angle::Degrees(deg));
Ok(())
});
methods.add_method_mut("rotate_x_rad", |_, this, (rad,): (f32,)| {
this.rotate_x(math::Angle::Radians(rad));
Ok(())
});
methods.add_method_mut("rotate_y_rad", |_, this, (rad,): (f32,)| {
this.rotate_y(math::Angle::Radians(rad));
Ok(())
});
methods.add_method_mut("rotate_z_rad", |_, this, (rad,): (f32,)| {
this.rotate_z(math::Angle::Radians(rad));
Ok(())
});
methods.add_method_mut("translate", |_, this, (x, y, z): (f32, f32, f32)| {
this.translate(x, y, z);
Ok(())
});
methods.add_method("lerp", |_, this, (rhs, alpha): (Self, f32)| {
Ok(Self(this.lerp(*rhs, alpha)))
});
// rotate a transform
methods.add_meta_method(mlua::MetaMethod::Mul, |_, this, (quat,): (LuaQuat,)| {
let mut t = *this;
t.rotation *= *quat;
Ok(t)
});
// move a transform
methods.add_meta_method(mlua::MetaMethod::Add, |_, this, (pos,): (LuaVec3,)| {
let mut t = *this;
t.translation += *pos;
Ok(t)
});
}
);
wrap_lua_struct!(
math::Angle,
derives(Copy),
skip(lua_reflect),
extra_methods = {
methods.add_function("new_degrees", |_, deg: f32| {
Ok(Self(math::Angle::Degrees(deg)))
});
methods.add_function("new_radians", |_, rad: f32| {
Ok(Self(math::Angle::Radians(rad)))
});
methods.add_method("radians", |_, this, ()| {
Ok(this.to_radians())
});
methods.add_method("degrees", |_, this, ()| {
Ok(this.to_degrees())
});
},
);

View File

@ -1,29 +0,0 @@
mod math;
pub use math::*;
mod input_actions;
pub use input_actions::*;
mod asset;
pub use asset::*;
mod delta_time;
pub use delta_time::*;
mod window;
pub use window::*;
mod camera;
pub use camera::*;
mod free_fly_camera;
pub use free_fly_camera::*;
mod events;
pub use events::*;
mod world_transform;
pub use world_transform::*;
mod tick;
pub use tick::*;

View File

@ -1,55 +0,0 @@
use std::any::TypeId;
use lyra_ecs::Tick;
use crate::lua::LuaWrapper;
#[derive(Clone, Default)]
pub struct LuaTick(pub(crate) Tick);
impl std::ops::Deref for LuaTick {
type Target = Tick;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl std::ops::DerefMut for LuaTick {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl mlua::FromLua for LuaTick {
fn from_lua(v: mlua::Value, _: &mlua::Lua) -> mlua::Result<Self> {
let tyname = v.type_name();
let num = v.as_number()
.ok_or(mlua::Error::FromLuaConversionError { from: tyname, to: "Tick".into(), message: None })?;
Ok(Self(Tick::from(num as u64)))
}
}
impl mlua::IntoLua for LuaTick {
fn into_lua(self, _: &mlua::Lua) -> mlua::Result<mlua::Value> {
Ok(mlua::Value::Number(*self.0 as f64))
}
}
impl LuaWrapper for LuaTick {
type Wrap = Tick;
#[inline(always)]
fn wrapped_type_id() -> std::any::TypeId {
TypeId::of::<Tick>()
}
#[inline(always)]
fn into_wrapped(self) -> Self::Wrap {
self.0
}
fn from_wrapped(wrap: Self::Wrap) -> Option<Self> {
Some(Self(wrap))
}
}

View File

@ -1,323 +0,0 @@
use lyra_scripting_derive::wrap_lua_struct;
use lyra_game::winit::{WindowOptions, FullscreenMode, Theme, CursorGrabMode, WindowLevel};
use crate::lyra_engine;
use crate as lyra_scripting;
use super::super::Error;
use super::LuaVec2;
use lyra_game::math::{Vec2, IVec2, UVec2};
wrap_lua_struct!(
WindowOptions,
//derives(Clone),
name=LuaWindow,
fields={
(focused, skip_set)
},
extra_fields={
fields.add_field_method_get("window_mode", |lua, this| {
let s = match &this.fullscreen_mode {
FullscreenMode::Windowed => "windowed",
FullscreenMode::BorderlessFullscreen => "borderless_fullscreen",
FullscreenMode::SizedFullscreen => "sized_fullscreen",
FullscreenMode::Fullscreen => "fullscreen"
};
s.into_lua(lua)
});
fields.add_field_method_set("window_mode", |_, this, mode: String| {
let mode = mode.as_str();
match mode {
"windowed" => {
this.fullscreen_mode = FullscreenMode::Windowed;
},
"borderless_fullscreen" => {
this.fullscreen_mode = FullscreenMode::BorderlessFullscreen;
},
"sized_fullscreen" => {
this.fullscreen_mode = FullscreenMode::SizedFullscreen;
},
"fullscreen" => {
this.fullscreen_mode = FullscreenMode::Fullscreen;
},
_ => {
return Err(mlua::Error::runtime(format!("invalid window mode {}", mode)));
}
}
Ok(())
});
// nil if its unsupported
fields.add_field_method_get("position", |lua, this| {
this.position.map(|p| {
LuaVec2(Vec2::new(p.x as f32, p.y as f32))
}).into_lua(lua)
});
fields.add_field_method_set("position", |_, this, pos: Option<LuaVec2>| {
this.position = pos.map(|p| IVec2::new(p.x as i32, p.y as i32));
Ok(())
});
fields.add_field_method_get("physical_size", |lua, this| {
let p = this.physical_size();
LuaVec2(Vec2::new(p.x as f32, p.y as f32))
.into_lua(lua)
});
fields.add_field_method_set("physical_size", |_, this, size: LuaVec2| {
this.set_physical_size(UVec2::new(size.x as _, size.y as _));
Ok(())
});
fields.add_field_method_get("size", |lua, this| {
LuaVec2(this.size())
.into_lua(lua)
});
fields.add_field_method_set("size", |_, this, size: LuaVec2| {
this.set_size(*size);
Ok(())
});
fields.add_field_method_get("decorated", |lua, this| {
this.decorated.into_lua(lua)
});
fields.add_field_method_set("decorated", |_, this, val: bool| {
this.decorated = val;
Ok(())
});
fields.add_field_method_get("maximized", |lua, this| {
this.maximized.into_lua(lua)
});
fields.add_field_method_set("maximized", |_, this, val: bool| {
this.maximized = val;
Ok(())
});
// returns `nil` if minimized state could not be determined
fields.add_field_method_get("minimized", |lua, this| {
this.minimized.into_lua(lua)
});
fields.add_field_method_set("minimized", |_, this, val: bool| {
this.minimized = Some(val);
Ok(())
});
fields.add_field_method_get("resizable", |lua, this| {
this.resizable.into_lua(lua)
});
fields.add_field_method_set("resizable", |_, this, val: bool| {
this.resizable = val;
Ok(())
});
// returns `nil` if minimized state could not be determined
fields.add_field_method_get("visible", |lua, this| {
this.visible.into_lua(lua)
});
fields.add_field_method_set("visible", |_, this, val: bool| {
this.visible = Some(val);
Ok(())
});
//todo!("resize_increments");
/* fields.add_field_method_get("resize_increments", |lua, this| {
this.visible.into_lua(lua)
});
fields.add_field_method_set("resize_increments", |_, this, val: bool| {
this.visible = Some(val);
Ok(())
}); */
fields.add_field_method_get("scale_factor", |lua, this| {
this.scale_factor.into_lua(lua)
});
fields.add_field_method_get("blur", |lua, this| {
this.blur.into_lua(lua)
});
fields.add_field_method_set("blur", |_, this, val: bool| {
this.blur = val;
Ok(())
});
// TODO: implement get/setting window icon
// must have ResHandle<Image> exposed to Lua.
fields.add_field_method_get::<_, bool>("cursor_appearance", |_, _| {
Err(mlua::Error::external(Error::unimplemented("field 'cursor_appearance' is unimplemented")))
});
fields.add_field_method_get("cursor_grab", |lua, this| {
let v = match &this.cursor.grab {
CursorGrabMode::None => "none",
CursorGrabMode::Confined => "confined",
CursorGrabMode::Locked => "locked",
};
v.into_lua(lua)
});
fields.add_field_method_set("cursor_grab", |_, this, val: String| {
let val = val.as_str();
let v = match val {
"none" => CursorGrabMode::None,
"confined" => CursorGrabMode::Confined,
"locked" => CursorGrabMode::Locked,
_ => {
return Err(mlua::Error::runtime(format!("invalid cursor grab mode {}", val)));
}
};
this.cursor.grab = v;
Ok(())
});
fields.add_field_method_get("cursor_hittest", |lua, this| {
this.cursor.hittest.into_lua(lua)
});
fields.add_field_method_set("cursor_hittest", |_, this, val: bool| {
this.cursor.hittest = val;
Ok(())
});
fields.add_field_method_get("cursor_visible", |lua, this| {
this.cursor.visible.into_lua(lua)
});
fields.add_field_method_set("cursor_visible", |_, this, val: bool| {
this.cursor.visible = val;
Ok(())
});
//todo!("window cursor");
fields.add_field_method_get("ime_allowed", |lua, this| {
this.ime_allowed.into_lua(lua)
});
fields.add_field_method_set("ime_allowed", |_, this, val: bool| {
this.ime_allowed = val;
Ok(())
});
fields.add_field_method_get("min_size", |lua, this| {
this.min_size.map(|p| LuaVec2(p))
.into_lua(lua)
});
fields.add_field_method_set("min_size", |_, this, size: Option<LuaVec2>| {
this.min_size = size.map(|p| *p);
Ok(())
});
fields.add_field_method_get("max_size", |lua, this| {
this.max_size.map(|p| LuaVec2(p))
.into_lua(lua)
});
fields.add_field_method_set("max_size", |_, this, size: Option<LuaVec2>| {
this.max_size = size.map(|p| *p);
Ok(())
});
fields.add_field_method_get("theme", |lua, this| {
this.theme.as_ref().map(|t| match t {
Theme::Light => "light",
Theme::Dark => "dark"
}).into_lua(lua)
});
fields.add_field_method_set("theme", |_, this, theme: Option<String>| {
let t = if let Some(theme) = theme.as_ref() {
let theme = theme.as_str();
match theme {
"light" => Some(Theme::Light),
"dark" => Some(Theme::Dark),
_ => {
return Err(mlua::Error::runtime(format!("invalid theme {}", theme)));
}
}
} else { None };
this.theme = t;
Ok(())
});
fields.add_field_method_get("title", |lua, this| {
this.title.clone().into_lua(lua)
});
fields.add_field_method_set("title", |_, this, title: String| {
this.title = title;
Ok(())
});
fields.add_field_method_get("transparent", |lua, this| {
this.transparent.into_lua(lua)
});
fields.add_field_method_set("transparent", |_, this, val: bool| {
this.transparent = val;
Ok(())
});
// TODO: implement get/setting window icon
// must have ResHandle<Image> exposed to Lua.
fields.add_field_method_get::<_, bool>("window_icon", |_, _| {
Err(mlua::Error::external(Error::unimplemented("field 'window_icon' is unimplemented")))
});
fields.add_field_method_get("window_level", |lua, this| {
let v = match &this.window_level {
WindowLevel::AlwaysOnBottom => "always_on_bottom",
WindowLevel::Normal => "normal",
WindowLevel::AlwaysOnTop => "always_on_top",
};
v.into_lua(lua)
});
fields.add_field_method_set("window_level", |_, this, val: String| {
let window_level = val.as_str();
let l = match window_level {
"always_on_bottom" => WindowLevel::AlwaysOnBottom,
"normal" => WindowLevel::Normal,
"always_on_top" => WindowLevel::AlwaysOnTop,
_ => {
return Err(mlua::Error::runtime(format!("invalid window level {}", window_level)));
}
};
this.window_level = l;
Ok(())
});
fields.add_field_method_get("occluded", |lua, this| {
this.occluded().into_lua(lua)
});
fields.add_field_method_get("cursor_position", |lua, this| {
this.cursor_position().map(|p| LuaVec2(p))
.into_lua(lua)
});
fields.add_field_method_set("cursor_position", |_, this, val: Option<LuaVec2>| {
this.set_cursor_position(val.map(|p| *p));
Ok(())
});
fields.add_field_method_get("physical_cursor_position", |lua, this| {
this.physical_cursor_position().map(|p| LuaVec2(p))
.into_lua(lua)
});
fields.add_field_method_set("physical_cursor_position", |_, this, val: Option<LuaVec2>| {
this.set_physical_cursor_position(val.map(|p| p.as_dvec2()));
Ok(())
});
},
extra_methods={
methods.add_method("is_mouse_inside", |lua, this, ()| {
this.is_mouse_inside().into_lua(lua)
});
}
);

View File

@ -1,49 +0,0 @@
use crate::lua::{wrappers::LuaTransform, LuaWrapper};
use crate::lyra_engine;
use lyra_game::scene::WorldTransform;
use lyra_reflect::Reflect;
#[derive(Clone, Default, Reflect)]
pub struct LuaWorldTransform(pub(crate) WorldTransform);
impl std::ops::Deref for LuaWorldTransform {
type Target = WorldTransform;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl std::ops::DerefMut for LuaWorldTransform {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl mlua::FromLua for LuaWorldTransform {
fn from_lua(v: mlua::Value, lua: &mlua::Lua) -> mlua::Result<Self> {
let t = LuaTransform::from_lua(v, lua)?;
Ok(Self(WorldTransform::from(*t)))
}
}
impl mlua::IntoLua for LuaWorldTransform {
fn into_lua(self, lua: &mlua::Lua) -> mlua::Result<mlua::Value> {
let t = LuaTransform(*self.0);
t.into_lua(lua)
}
}
impl LuaWrapper for LuaWorldTransform {
type Wrap = WorldTransform;
#[inline(always)]
fn into_wrapped(self) -> Self::Wrap {
self.0
}
#[inline(always)]
fn from_wrapped(wrap: Self::Wrap) -> Option<Self> {
Some(Self(wrap))
}
}

View File

@ -1,55 +0,0 @@
use std::{ops::Deref, ptr::NonNull, sync::Arc};
use lyra_ecs::{World, Entity};
use parking_lot::{MappedRwLockReadGuard, MappedRwLockWriteGuard, RwLock, RwLockReadGuard, RwLockWriteGuard};
#[derive(Clone)]
pub struct ScriptEntity(pub Entity);
impl std::ops::Deref for ScriptEntity {
type Target = Entity;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Clone)]
pub struct ScriptWorldPtr(Arc<RwLock<NonNull<World>>>);
impl ScriptWorldPtr {
pub fn read(&self) -> MappedRwLockReadGuard<World> {
RwLockReadGuard::map(self.0.read(), |p| unsafe { p.as_ref() })
}
pub fn write(&self) -> MappedRwLockWriteGuard<World> {
RwLockWriteGuard::map(self.0.write(), |p| unsafe { p.as_mut() })
}
}
// SAFETY: The inner NonNull pointer is wrapped in an Arc<RwLock<>>
unsafe impl Send for ScriptWorldPtr {}
unsafe impl Sync for ScriptWorldPtr {}
#[derive(Clone)]
pub struct ScriptWorldPtrGuard(ScriptWorldPtr);
impl Deref for ScriptWorldPtrGuard {
type Target = ScriptWorldPtr;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl ScriptWorldPtrGuard {
/// Creates a new world pointer.
///
/// # Safety
/// The original `&mut World` must not be used while this guard is created.
/// The [World] may only be accessed through this guard or one of its cloned [ScriptWorldPtr]s.
#[allow(clippy::arc_with_non_send_sync)]
pub unsafe fn new(world: &mut World) -> Self {
ScriptWorldPtrGuard(ScriptWorldPtr(Arc::new(RwLock::new(NonNull::from(world)))))
}
}

@ -1 +0,0 @@
Subproject commit 70daf320827f64b325a77718df07177d74d7ea58

View File

@ -1,10 +0,0 @@
[package]
name = "dim_2d"
version = "0.1.0"
edition = "2021"
[dependencies]
lyra-engine = { path = "../../", features = ["tracy"] }
anyhow = "1.0.75"
async-std = "1.12.0"
tracing = "0.1.37"

Some files were not shown because too many files have changed in this diff Show More