diff --git a/backends/sdl/src/input.rs b/backends/sdl/src/input.rs index 049e4ca1..e7771fb2 100644 --- a/backends/sdl/src/input.rs +++ b/backends/sdl/src/input.rs @@ -83,6 +83,7 @@ fn convert_scancode(scan_code: SDL_Keycode) -> Option { SDL_KeyCode::SDLK_DOWN => Some(ArrowDown), SDL_KeyCode::SDLK_LEFT => Some(ArrowLeft), SDL_KeyCode::SDLK_RIGHT => Some(ArrowRight), + SDL_KeyCode::SDLK_SPACE => Some(Space), _ => None, } } diff --git a/core/common/src/abstractions/callbacks.rs b/core/common/src/abstractions/callbacks.rs index 57661923..9d91f9cb 100644 --- a/core/common/src/abstractions/callbacks.rs +++ b/core/common/src/abstractions/callbacks.rs @@ -34,6 +34,12 @@ impl Display for CallbackError { #[derive(Clone)] pub struct Callable(Arc Result>); +/// Represents a value that can be converted into a [`Callable`] function. +pub trait IntoCallable { + /// Converts the value into a [`Callable`] function. + fn into_callable(self) -> Callable; +} + impl Callable { /// Creates a new boxed callable function from the given function. pub fn from_function(function: impl Fn(&[Variant]) -> Result + 'static) -> Self { diff --git a/core/common/src/abstractions/variant.rs b/core/common/src/abstractions/variant.rs index 1251d312..828f9db3 100644 --- a/core/common/src/abstractions/variant.rs +++ b/core/common/src/abstractions/variant.rs @@ -439,6 +439,19 @@ impl_variant!(Quat, Quat); impl_variant!(Color, Color); impl_variant!(Color32, Color32); +/// Allow [`Variant`] to be converted to/from itself. +impl ToVariant for Variant { + fn to_variant(&self) -> Variant { + self.clone() + } +} + +impl FromVariant for Variant { + fn from_variant(variant: Variant) -> Result { + Ok(variant) + } +} + /// Allow [`Arc`] of [`Any`] to be converted to/from Variant. impl ToVariant for Arc { fn to_variant(&self) -> Variant { diff --git a/core/common/src/concurrency/tasks.rs b/core/common/src/concurrency/tasks.rs index 6528a186..e246551a 100644 --- a/core/common/src/concurrency/tasks.rs +++ b/core/common/src/concurrency/tasks.rs @@ -7,6 +7,8 @@ use std::{ task::{Context, Poll}, }; +use crate::Singleton; + /// A continuation that can be executed. type Continuation = dyn FnOnce() -> (); @@ -55,20 +57,12 @@ impl Task { } /// A scheduler for [`Task`]s. -#[derive(Default)] +#[derive(Default, Singleton)] struct TaskScheduler { continuations: Mutex>>, } -/// The global task scheduler. -static TASK_SCHEDULER: crate::UnsafeSingleton = crate::UnsafeSingleton::default(); - impl TaskScheduler { - /// Returns the current task scheduler. - pub fn current() -> &'static Self { - &TASK_SCHEDULER - } - /// Schedules a [`Continuation`] to be executed. pub fn schedule(&self, continuation: Box) { let mut continuations = self.continuations.lock().unwrap(); @@ -129,11 +123,10 @@ impl Future for Task { #[cfg(test)] mod tests { use super::*; - use crate::BlockableFuture; #[test] fn test_basic_task_continuations() { - let scheduler = TaskScheduler::current(); + let scheduler = TaskScheduler::instance(); scheduler.schedule(Box::new(|| { println!("Hello, world!"); diff --git a/core/common/src/io/virtualfs.rs b/core/common/src/io/virtualfs.rs index 07351103..1bafe663 100644 --- a/core/common/src/io/virtualfs.rs +++ b/core/common/src/io/virtualfs.rs @@ -4,7 +4,7 @@ pub use local::*; pub use memory::*; use super::{InputStream, OutputStream}; -use crate::{StringName, ToStringName, UnsafeSingleton}; +use crate::{Singleton, StringName, ToStringName}; mod local; mod memory; @@ -35,12 +35,11 @@ pub trait FileSystem: Send + Sync { /// This is a singleton that is used to manage [`FileSystem`] implementations. /// File systems can be registered here, and will be used subsequently for file /// operations on [`VirtualPath`] instances. +#[derive(Singleton)] pub struct FileSystemManager { file_systems: Vec>, } -static mut FILE_SYSTEM_MANAGER: UnsafeSingleton = UnsafeSingleton::default(); - impl Default for FileSystemManager { fn default() -> Self { Self { @@ -56,22 +55,18 @@ impl Default for FileSystemManager { impl FileSystemManager { /// Registers a new [`FileSystem`] with the manager. pub fn register(file_system: impl FileSystem + 'static) { - unsafe { - FILE_SYSTEM_MANAGER.file_systems.push(Box::new(file_system)); - } + Self::instance().file_systems.push(Box::new(file_system)); } /// Finds the appropriate [`FileSystem`] for the given [`VirtualPath`]. pub fn with_filesystem(path: &VirtualPath, body: impl FnOnce(&dyn FileSystem) -> R) -> R { - unsafe { - for file_system in &FILE_SYSTEM_MANAGER.file_systems { - if file_system.can_handle(path) { - return body(file_system.as_ref()); - } + for file_system in &Self::instance().file_systems { + if file_system.can_handle(path) { + return body(file_system.as_ref()); } - - panic!("No file system found for scheme {}", path.scheme()); } + +˚ panic!("No file system found for scheme {}", path.scheme()); } } diff --git a/core/common/src/lib.rs b/core/common/src/lib.rs index 96bb27b9..9e17ce13 100644 --- a/core/common/src/lib.rs +++ b/core/common/src/lib.rs @@ -10,6 +10,8 @@ #![feature(noop_waker)] #![feature(async_closure)] +extern crate self as common; + pub use abstractions::*; pub use collections::*; pub use concurrency::*; @@ -33,4 +35,4 @@ mod network; mod strings; mod utilities; -pub use macros::{profiling, Asset, Deserialize, Reflect, Serialize, Trace}; +pub use macros::{profiling, Asset, Deserialize, Reflect, Serialize, Singleton, Trace}; diff --git a/core/common/src/lua.rs b/core/common/src/lua.rs index 32740753..2e9a6adc 100644 --- a/core/common/src/lua.rs +++ b/core/common/src/lua.rs @@ -555,8 +555,6 @@ struct LuaArc(Arc); impl LuaUserData for LuaArc {} -// TODO: think about using Reflect-able types for Lua? - #[cfg(test)] mod tests { use super::*; diff --git a/core/common/src/strings/names.rs b/core/common/src/strings/names.rs index baf70778..6cd4df7c 100644 --- a/core/common/src/strings/names.rs +++ b/core/common/src/strings/names.rs @@ -1,141 +1,173 @@ use std::{ fmt::{Debug, Display}, + ptr::NonNull, sync::RwLock, }; -use crate::{Arena, UnsafeSingleton}; +use crate::{Arena, Singleton}; crate::impl_arena_index!(StringId, "Identifies a string in a string pool."); -/// Represents an interned string that can be used as a name. -#[repr(transparent)] -#[derive(Copy, Clone, Eq, PartialEq, Hash)] -pub struct StringName(StringId); - -/// A trait for objects that have a [`StringName`]. +/// A trait for objects that can be converted to a [`StringName`]. pub trait ToStringName { - /// Returns the name of this object as a string. + /// Converts the value to a [`StringName`]. fn to_string_name(&self) -> StringName; } -/// Allows a string reference to be converted to a string name. -impl> ToStringName for R { - fn to_string_name(&self) -> StringName { - StringName::from(self.as_ref()) +/// Represents an interned string that can be cheaply passed around the engine. +#[repr(transparent)] +#[derive(Copy, Clone, Eq, Hash)] +pub struct StringName { + id: StringId, +} + +/// An internal global pool of strings. +#[derive(Default, Singleton)] +struct StringNamePool { + strings_by_id: RwLock>>, +} + +impl StringName { + /// Creates a new string name from a string reference. + pub fn new(value: &str) -> Self { + Self { + id: unsafe { intern_string(value) }, + } } } -/// Allows a string reference to be converted to a string name. -impl> From for StringName { - fn from(value: R) -> Self { - unsafe { StringName(STRING_NAME_POOL.intern(value.as_ref())) } +/// Converts a string reference to a string name. +impl From<&str> for StringName { + #[inline] + fn from(value: &str) -> Self { + Self::new(value) } } -/// Allows a string name to be converted to a string. +/// Converts a string to a string name. +impl From for StringName { + #[inline] + fn from(value: String) -> Self { + Self::new(&value) + } +} + +/// Converts a string name to an owned string. impl From for String { + #[inline] fn from(value: StringName) -> Self { value.to_string() } } -/// Allows a string name to be compared to a string reference. -impl> PartialEq for StringName { - fn eq(&self, other: &R) -> bool { - unsafe { - if let Some(value) = STRING_NAME_POOL.lookup(self.0) { - value == *other.as_ref() - } else { - false - } - } +/// Allows a string named to be interpreted as a string reference. +impl AsRef for StringName { + fn as_ref(&self) -> &str { + lookup_string(self.id).expect("String name not found in pool") } } -impl PartialEq for &str { - #[inline] +/// Allows a string reference to be converted to a string name. +impl> ToStringName for R { + fn to_string_name(&self) -> StringName { + StringName::new(self.as_ref()) + } +} + +/// Compares two string names. +impl PartialEq for StringName { + #[inline(always)] fn eq(&self, other: &StringName) -> bool { - other == self + self.id == other.id } } -/// Pretty-prints a string name. -impl Debug for StringName { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - unsafe { - if let Some(value) = STRING_NAME_POOL.lookup(self.0) { - write!(f, "{:?}", value) - } else { - write!(f, "StringName({:?})", self.0) - } +/// Compares a string reference with a string name. +impl PartialEq for &str { + fn eq(&self, other: &StringName) -> bool { + if let Some(value) = lookup_string(other.id) { + value == *self + } else { + false } } } -/// Pretty-prints a string name. -impl Display for StringName { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - unsafe { - if let Some(value) = STRING_NAME_POOL.lookup(self.0) { - write!(f, "{}", value) - } else { - write!(f, "") - } +/// Compares a string reference with a string name. +impl PartialEq<&str> for StringName { + fn eq(&self, other: &&str) -> bool { + if let Some(value) = lookup_string(self.id) { + value == *other + } else { + false } } } -/// An internal global pool of strings. -#[derive(Default)] -struct StringNamePool { - strings_by_id: RwLock>, +/// Compares a string with a string name. +impl PartialEq for StringName { + fn eq(&self, other: &String) -> bool { + if let Some(value) = lookup_string(self.id) { + value == other + } else { + false + } + } } -static mut STRING_NAME_POOL: UnsafeSingleton = UnsafeSingleton::default(); +impl Debug for StringName { + fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if let Some(value) = lookup_string(self.id) { + write!(formatter, "{:?}", value) + } else { + write!(formatter, "StringName({:?})", self.id) + } + } +} -/// An entry in the string pool. -struct StringPoolEntry { - string: String, - reference_count: usize, +/// Pretty-prints a string name. +impl Display for StringName { + fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if let Some(value) = lookup_string(self.id) { + write!(formatter, "{}", value) + } else { + write!(formatter, "") + } + } } -impl StringNamePool { - /// Looks up the string with the given ID. - /// - /// If the string is not interned, returns `None`. - pub fn lookup(&self, id: StringId) -> Option { - let entries = self.strings_by_id.read().unwrap(); +/// Interns the given string and returns its ID. +/// +/// If the string is already interned, its reference count is incremented. +/// Otherwise, it is inserted into the pool. +unsafe fn intern_string(value: &str) -> StringId { + let entries = StringNamePool::instance().strings_by_id.read().unwrap(); - entries.get(id).map(|entry| entry.string.clone()) + for (id, entry) in entries.enumerate() { + if entry.as_ref() == value { + return id; + } } - /// Interns the given string and returns its ID. - /// - /// If the string is already interned, its reference count is incremented. - /// Otherwise, it is inserted into the pool. - pub fn intern(&self, value: &str) -> StringId { - // we need to manually scan the strings here because we optimize - // for the case where the string is already interned - let mut entries = self.strings_by_id.write().unwrap(); + // we need to drop the read lock before we can write to the map + drop(entries); - for (id, entry) in entries.enumerate_mut() { - // if we find the string already in the pool, increment the reference count - if entry.string == value { - entry.reference_count += 1; - return id; - } - } + let mut entries = StringNamePool::instance().strings_by_id.write().unwrap(); + let raw = value.to_owned().leak(); // leak the string to make it static - // we need to drop the read lock before we can write to the map - drop(entries); + entries.insert(NonNull::new(raw).unwrap()) +} - // insert the string into the map - let mut entries = self.strings_by_id.write().unwrap(); +/// Looks up the string with the given ID. +/// +/// If the string is not interned, returns `None`. +fn lookup_string(id: StringId) -> Option<&'static str> { + let entries = unsafe { StringNamePool::instance().strings_by_id.read().unwrap() }; - entries.insert(StringPoolEntry { - string: value.to_owned(), - reference_count: 1, - }) + if let Some(entry) = entries.get(id) { + Some(unsafe { entry.as_ref() }) + } else { + None } } @@ -146,9 +178,9 @@ mod tests { #[test] fn test_string_name_should_intern_similar_strings() { unsafe { - let id1 = STRING_NAME_POOL.intern("test"); - let id2 = STRING_NAME_POOL.intern("test"); - let id3 = STRING_NAME_POOL.intern("test2"); + let id1 = intern_string("test"); + let id2 = intern_string("test"); + let id3 = intern_string("test2"); assert_eq!(id1, id2); assert_ne!(id1, id3); @@ -157,8 +189,8 @@ mod tests { #[test] fn test_string_name_should_convert_from_reference() { - let name1 = StringName::from("test"); - let name2 = StringName::from("test"); + let name1 = StringName::new("test"); + let name2 = StringName::new("test"); assert_eq!(name1, name2); } diff --git a/core/macros/src/lib.rs b/core/macros/src/lib.rs index 6a82a9d7..57769887 100644 --- a/core/macros/src/lib.rs +++ b/core/macros/src/lib.rs @@ -7,6 +7,7 @@ mod formats; mod objects; mod profiling; mod reflect; +mod singleton; mod vertex; /// Instruments a function with profiling code. @@ -45,6 +46,12 @@ pub fn derive_trace(input: TokenStream) -> TokenStream { objects::impl_trace(input) } +/// Builds a singleton for a type. +#[proc_macro_derive(Singleton)] +pub fn derive_singleton(input: TokenStream) -> TokenStream { + singleton::impl_singleton(input) +} + /// Derives the `Vertex` trait for a type. #[proc_macro_derive(Vertex, attributes(vertex))] pub fn derive_vertex(input: TokenStream) -> TokenStream { diff --git a/core/macros/src/singleton.rs b/core/macros/src/singleton.rs new file mode 100644 index 00000000..45b9eb3e --- /dev/null +++ b/core/macros/src/singleton.rs @@ -0,0 +1,20 @@ +use proc_macro::TokenStream; +use quote::quote; +use syn::{parse_macro_input, DeriveInput}; + +pub fn impl_singleton(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + let ident = &input.ident; + + let expanded = quote! { + impl #ident { + pub fn instance() -> &'static mut #ident { + static mut INSTANCE: common::UnsafeSingleton<#ident> = common::UnsafeSingleton::default(); + + unsafe { &mut INSTANCE } + } + } + }; + + expanded.into() +}