Add early scripting system with lua
ci/woodpecker/push/debug Pipeline failed
Details
ci/woodpecker/push/debug Pipeline failed
Details
This commit is contained in:
parent
0f062217ca
commit
6caf235a6f
|
@ -41,6 +41,27 @@
|
|||
"args": [],
|
||||
"cwd": "${workspaceFolder}"
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "Debug unit tests in executable 'lyra-scripting'",
|
||||
"cargo": {
|
||||
"args": [
|
||||
"test",
|
||||
"--no-run",
|
||||
"--lib",
|
||||
"--package=lyra-scripting",
|
||||
"--",
|
||||
"--nocapture"
|
||||
],
|
||||
"filter": {
|
||||
"name": "lyra-scripting",
|
||||
"kind": "lib"
|
||||
}
|
||||
},
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}/lyra-scripting"
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
|
|
|
@ -90,9 +90,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.75"
|
||||
version = "1.0.78"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
|
||||
checksum = "ca87830a3e3fb156dc96cfbd31cb620265dd053be734723f22b760d6cc3c3051"
|
||||
|
||||
[[package]]
|
||||
name = "arrayref"
|
||||
|
@ -369,6 +369,16 @@ dependencies = [
|
|||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6798148dccfbff0fae41c7574d2fa8f1ef3492fba0face179de5d8d447d67b05"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.13.0"
|
||||
|
@ -1300,6 +1310,7 @@ dependencies = [
|
|||
"lyra-ecs",
|
||||
"lyra-reflect",
|
||||
"lyra-resource",
|
||||
"lyra-scripting",
|
||||
"quote",
|
||||
"syn 2.0.42",
|
||||
"tracing",
|
||||
|
@ -1346,6 +1357,20 @@ dependencies = [
|
|||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lyra-scripting"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"lyra-ecs",
|
||||
"lyra-reflect",
|
||||
"lyra-resource",
|
||||
"mlua",
|
||||
"thiserror",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mach2"
|
||||
version = "0.4.1"
|
||||
|
@ -1445,6 +1470,30 @@ dependencies = [
|
|||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mlua"
|
||||
version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c81f8ac20188feb5461a73eabb22a34dd09d6d58513535eb587e46bff6ba250"
|
||||
dependencies = [
|
||||
"bstr",
|
||||
"mlua-sys",
|
||||
"num-traits",
|
||||
"once_cell",
|
||||
"rustc-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mlua-sys"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc29228347d6bdc9e613dc95c69df2817f755434ee0f7f3b27b57755fe238b7f"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "naga"
|
||||
version = "0.11.1"
|
||||
|
|
10
Cargo.toml
10
Cargo.toml
|
@ -5,15 +5,21 @@ edition = "2021"
|
|||
|
||||
[workspace]
|
||||
members = [
|
||||
"examples/testbed",
|
||||
"lyra-resource",
|
||||
"lyra-ecs",
|
||||
"examples/testbed"
|
||||
, "lyra-reflect"]
|
||||
"lyra-reflect",
|
||||
"lyra-scripting"
|
||||
]
|
||||
|
||||
[features]
|
||||
scripting = ["dep:lyra-scripting"]
|
||||
|
||||
[dependencies]
|
||||
lyra-resource = { path = "lyra-resource", version = "0.0.1" }
|
||||
lyra-ecs = { path = "lyra-ecs" }
|
||||
lyra-reflect = { path = "lyra-reflect" }
|
||||
lyra-scripting = { path = "lyra-scripting", optional = true }
|
||||
|
||||
winit = "0.28.1"
|
||||
tracing = "0.1.37"
|
||||
|
|
|
@ -6,7 +6,7 @@ edition = "2021"
|
|||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
lyra-engine = { path = "../../", version = "0.0.1" }
|
||||
lyra-engine = { path = "../../", version = "0.0.1", features = ["scripting"] }
|
||||
#lyra-ecs = { path = "../../lyra-ecs"}
|
||||
anyhow = "1.0.75"
|
||||
async-std = "1.12.0"
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
[package]
|
||||
name = "lyra-scripting"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[features]
|
||||
default = ["lua"]
|
||||
lua = ["dep:mlua"]
|
||||
|
||||
[dependencies]
|
||||
lyra-ecs = { path = "../lyra-ecs" }
|
||||
lyra-reflect = { path = "../lyra-reflect" }
|
||||
lyra-resource = { path = "../lyra-resource" }
|
||||
thiserror = "1.0.50"
|
||||
anyhow = "1.0.77"
|
||||
tracing = "0.1.37"
|
||||
|
||||
# enabled with lua feature
|
||||
mlua = { version = "0.9.2", features = ["lua54"], optional = true } # luajit maybe?
|
||||
|
||||
|
||||
[dev-dependencies]
|
||||
tracing-subscriber = { version = "0.3.16", features = [ "tracing-log" ] }
|
|
@ -0,0 +1,114 @@
|
|||
use std::{sync::{Arc, RwLock}, error::Error, collections::HashMap};
|
||||
|
||||
use anyhow::anyhow;
|
||||
use lyra_ecs::{query::Res, ResourceObject};
|
||||
|
||||
use crate::ScriptWorldPtr;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ScriptError {
|
||||
#[error("{0}")]
|
||||
#[cfg(feature = "lua")]
|
||||
MluaError(mlua::Error),
|
||||
|
||||
#[error("{0}")]
|
||||
Other(anyhow::Error),
|
||||
}
|
||||
|
||||
#[cfg(feature = "lua")]
|
||||
impl From<mlua::Error> for ScriptError {
|
||||
fn from(value: mlua::Error) -> Self {
|
||||
ScriptError::MluaError(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<anyhow::Error> for ScriptError {
|
||||
fn from(value: anyhow::Error) -> Self {
|
||||
ScriptError::Other(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Hash, PartialEq, Eq)]
|
||||
pub struct ScriptData {
|
||||
pub script_id: u64,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
/// Provides an API to a scripting context.
|
||||
pub trait ScriptApiProvider {
|
||||
/// The type used as the script's context.
|
||||
type ScriptContext;
|
||||
|
||||
/// Exposes an API in the provided script context.
|
||||
fn expose_api(&mut self, ctx: &mut Self::ScriptContext) -> Result<(), ScriptError>;
|
||||
|
||||
/// Create a script in the script host.
|
||||
///
|
||||
/// This only creates the script for the host, it does not setup the script for execution. See [`ScriptHostProvider::setup_script`].
|
||||
fn setup_script(&mut self, data: &ScriptData, ctx: &mut Self::ScriptContext) -> Result<(), ScriptError>;
|
||||
|
||||
/// A function that is used to update a script's environment.
|
||||
///
|
||||
/// This is used to update stuff like the world pointer in the script context.
|
||||
fn update_script_environment(&mut self, world: ScriptWorldPtr, data: &ScriptData, ctx: &mut Self::ScriptContext) -> Result<(), ScriptError>;
|
||||
}
|
||||
|
||||
/// A storage for a [`ScriptHost`]'s api providers
|
||||
#[derive(Default)]
|
||||
pub struct ScriptApiProviders<T: ScriptHost> {
|
||||
pub apis: Vec<Box<dyn ScriptApiProvider<ScriptContext = T::ScriptContext>>>,
|
||||
}
|
||||
|
||||
impl<T: ScriptHost> ScriptApiProviders<T> {
|
||||
pub fn add_provider<P>(&mut self, provider: P)
|
||||
where
|
||||
P: ScriptApiProvider<ScriptContext = T::ScriptContext> + 'static
|
||||
{
|
||||
self.apis.push(Box::new(provider));
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ScriptHost: Default + ResourceObject {
|
||||
/// The type used as the script's context.
|
||||
type ScriptContext;
|
||||
|
||||
/// Loads a script and creates a context for it.
|
||||
///
|
||||
/// Before the script is executed, the API providers are exposed to the script.
|
||||
fn load_script(&mut self, script: &[u8], script_data: &ScriptData, providers: &mut crate::ScriptApiProviders<Self>) -> Result<Self::ScriptContext, ScriptError>;
|
||||
|
||||
/// Setup a script for execution.
|
||||
fn setup_script(&mut self, script_data: &ScriptData, ctx: &mut Self::ScriptContext, providers: &mut ScriptApiProviders<Self>) -> Result<(), ScriptError>;
|
||||
|
||||
/// Executes the update step for the script.
|
||||
fn update_script(&mut self, world: ScriptWorldPtr, script_data: &crate::ScriptData, ctx: &mut Self::ScriptContext, providers: &mut crate::ScriptApiProviders<Self>) -> Result<(), ScriptError>;
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ScriptContexts<T> {
|
||||
contexts: HashMap<u64, T>,
|
||||
}
|
||||
|
||||
impl<T> ScriptContexts<T> {
|
||||
pub fn new(contexts: HashMap<u64, T>) -> Self {
|
||||
Self {
|
||||
contexts,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_context(&mut self, script_id: u64, context: T) {
|
||||
self.contexts.insert(script_id, context);
|
||||
}
|
||||
|
||||
pub fn get_context(&self, script_id: u64) -> Option<&T> {
|
||||
self.contexts.get(&script_id)
|
||||
}
|
||||
|
||||
pub fn get_context_mut(&mut self, script_id: u64) -> Option<&mut T> {
|
||||
self.contexts.get_mut(&script_id)
|
||||
}
|
||||
|
||||
pub fn has_context(&self, script_id: u64) -> bool {
|
||||
self.contexts.contains_key(&script_id)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
|
||||
#[cfg(feature = "lua")]
|
||||
pub mod lua;
|
||||
|
||||
pub mod world;
|
||||
pub use world::*;
|
||||
|
||||
pub mod wrap;
|
||||
pub use wrap::*;
|
||||
|
||||
pub mod host;
|
||||
pub use host::*;
|
||||
|
||||
pub mod script;
|
||||
pub use script::*;
|
||||
|
||||
#[allow(unused_imports)]
|
||||
pub(crate) mod lyra_engine {
|
||||
pub use lyra_ecs as ecs;
|
||||
}
|
||||
|
||||
use lyra_reflect::{ReflectedComponent, Reflect};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum ReflectBranch {
|
||||
Component(ReflectedComponent),
|
||||
}
|
||||
|
||||
impl ReflectBranch {
|
||||
/// Gets self as a [`ReflectedComponent`].
|
||||
///
|
||||
/// # Panics
|
||||
/// If `self` is not a variant of [`ReflectBranch::Component`].
|
||||
pub fn as_component_unchecked(&self) -> &ReflectedComponent {
|
||||
match self {
|
||||
ReflectBranch::Component(c) => c,
|
||||
_ => panic!("`self` is not an instance of `ReflectBranch::Component`")
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_component(&self) -> bool {
|
||||
matches!(self, ReflectBranch::Component(_))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ScriptBorrow {
|
||||
reflect_branch: ReflectBranch,
|
||||
data: Option<Box<dyn Reflect>>,
|
||||
}
|
||||
|
||||
impl Clone for ScriptBorrow {
|
||||
fn clone(&self) -> Self {
|
||||
let data = self.data.as_ref().map(|b| b.clone_inner());
|
||||
|
||||
Self {
|
||||
reflect_branch: self.reflect_branch.clone(),
|
||||
data,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,166 @@
|
|||
use std::{ptr::NonNull, ops::{Range, Deref}};
|
||||
|
||||
use lyra_ecs::{ComponentColumn, ComponentInfo, Archetype, ArchetypeId, ArchetypeEntityId, query::dynamic::{DynamicType, QueryDynamicType}, query::Fetch};
|
||||
use lyra_reflect::TypeRegistry;
|
||||
|
||||
#[cfg(feature = "lua")]
|
||||
use super::ReflectLuaProxy;
|
||||
|
||||
use crate::ScriptWorldPtr;
|
||||
|
||||
/// A reimplementation of lyra_ecs::FetchDynamicType that doesn't store references and
|
||||
/// uses a pointer instead.
|
||||
/// This makes it easier to expose to Lua
|
||||
#[derive(Clone)]
|
||||
pub struct FetchDynamicType {
|
||||
col: NonNull<ComponentColumn>,
|
||||
info: ComponentInfo,
|
||||
}
|
||||
|
||||
impl<'a> Fetch<'a> for FetchDynamicType {
|
||||
type Item = DynamicType;
|
||||
|
||||
fn dangling() -> Self {
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
unsafe fn get_item(&mut self, entity: ArchetypeEntityId) -> Self::Item {
|
||||
let ptr = unsafe { self.col.as_ref().borrow_ptr() };
|
||||
let ptr = NonNull::new_unchecked(ptr.as_ptr()
|
||||
.add(entity.0 as usize * self.info.layout.size));
|
||||
|
||||
DynamicType {
|
||||
info: self.info,
|
||||
ptr,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<lyra_ecs::query::dynamic::FetchDynamicType<'a>> for FetchDynamicType {
|
||||
fn from(value: lyra_ecs::query::dynamic::FetchDynamicType<'a>) -> Self {
|
||||
Self {
|
||||
col: NonNull::from(value.col),
|
||||
info: value.info,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DynamicViewIter {
|
||||
world_ptr: ScriptWorldPtr,
|
||||
queries: Vec<QueryDynamicType>,
|
||||
fetchers: Vec<FetchDynamicType>,
|
||||
archetypes: Vec<NonNull<Archetype>>,
|
||||
next_archetype: usize,
|
||||
component_indices: Range<u64>,
|
||||
}
|
||||
|
||||
impl<'a> From<lyra_ecs::query::dynamic::DynamicViewIter<'a>> for DynamicViewIter {
|
||||
fn from(value: lyra_ecs::query::dynamic::DynamicViewIter<'a>) -> Self {
|
||||
Self {
|
||||
world_ptr: ScriptWorldPtr::from_ref(value.world),
|
||||
queries: value.queries,
|
||||
fetchers: value.fetchers.into_iter().map(|f| FetchDynamicType::from(f)).collect(),
|
||||
archetypes: value.archetypes.into_iter().map(|a| NonNull::from(a)).collect(),
|
||||
next_archetype: value.next_archetype,
|
||||
component_indices: value.component_indices,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for DynamicViewIter {
|
||||
type Item = Vec<DynamicType>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
loop {
|
||||
if let Some(entity_index) = self.component_indices.next() {
|
||||
let mut fetch_res = vec![];
|
||||
|
||||
for fetcher in self.fetchers.iter_mut() {
|
||||
let entity_index = ArchetypeEntityId(entity_index);
|
||||
if !fetcher.can_visit_item(entity_index) {
|
||||
break;
|
||||
} else {
|
||||
let i = unsafe { fetcher.get_item(entity_index) };
|
||||
fetch_res.push(i);
|
||||
}
|
||||
}
|
||||
|
||||
if fetch_res.len() != self.fetchers.len() {
|
||||
continue;
|
||||
}
|
||||
|
||||
return Some(fetch_res);
|
||||
} else {
|
||||
if self.next_archetype >= self.archetypes.len() {
|
||||
return None; // ran out of archetypes to go through
|
||||
}
|
||||
|
||||
let arch_id = self.next_archetype;
|
||||
self.next_archetype += 1;
|
||||
let arch = unsafe { self.archetypes.get_unchecked(arch_id).as_ref() };
|
||||
|
||||
if arch.entities().len() == 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
if self.queries.iter().any(|q| !q.can_visit_archetype(arch)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
//let world = unsafe { self.world_ptr.as_ref() };
|
||||
let world = self.world_ptr.as_ref();
|
||||
|
||||
self.fetchers = self.queries.iter()
|
||||
.map(|q| unsafe { q.fetch(world, ArchetypeId(arch_id as u64), arch) } )
|
||||
.map(|f| FetchDynamicType::from(f))
|
||||
.collect();
|
||||
self.component_indices = 0..arch.entities().len() as u64;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ReflectedIterator {
|
||||
pub world: ScriptWorldPtr,
|
||||
pub dyn_view: DynamicViewIter,
|
||||
pub reflected_components: Option<NonNull<TypeRegistry>>
|
||||
}
|
||||
|
||||
impl ReflectedIterator {
|
||||
|
||||
|
||||
#[cfg(feature = "lua")]
|
||||
pub fn next_lua<'a>(&mut self, lua: &'a mlua::Lua) -> Option<Vec<( (&'a ReflectLuaProxy, NonNull<u8>), mlua::AnyUserData<'a>)>> {
|
||||
|
||||
let n = self.dyn_view.next();
|
||||
|
||||
if let Some(row) = n {
|
||||
if self.reflected_components.is_none() {
|
||||
let world = self.world.as_ref();
|
||||
self.reflected_components = world.try_get_resource::<TypeRegistry>()
|
||||
.map(|r| NonNull::from(r.deref()));
|
||||
}
|
||||
|
||||
let mut dynamic_row = Vec::new();
|
||||
for d in row.iter() {
|
||||
let id = d.info.type_id.as_rust();
|
||||
let reflected_components =
|
||||
unsafe { self.reflected_components.as_ref().unwrap().as_ref() };
|
||||
|
||||
let reg_type = reflected_components.get_type(id)
|
||||
.expect("Could not find type for dynamic view!");
|
||||
let proxy = reg_type.get_data::<ReflectLuaProxy>()
|
||||
.expect("Type does not have ReflectLuaProxy as a TypeData");
|
||||
|
||||
let userdata = (proxy.fn_as_uservalue)(lua, d.ptr).unwrap();
|
||||
|
||||
dynamic_row.push(( (proxy, d.ptr), userdata));
|
||||
}
|
||||
|
||||
Some(dynamic_row)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use lyra_resource::{ResourceLoader, Resource};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LuaScript {
|
||||
/// The byte contents of the script.
|
||||
pub(crate) bytes: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub struct LuaLoader;
|
||||
|
||||
impl ResourceLoader for LuaLoader {
|
||||
fn extensions(&self) -> &[&str] {
|
||||
&[".lua"]
|
||||
}
|
||||
|
||||
fn mime_types(&self) -> &[&str] {
|
||||
&["text/lua"]
|
||||
}
|
||||
|
||||
fn load(&self, _resource_manager: &mut lyra_resource::ResourceManager, path: &str) -> Result<std::sync::Arc<dyn lyra_resource::ResourceStorage>, lyra_resource::LoaderError> {
|
||||
let bytes = std::fs::read(path)?;
|
||||
|
||||
let s = Resource::with_data(path, LuaScript {
|
||||
bytes
|
||||
});
|
||||
|
||||
Ok(Arc::new(s))
|
||||
}
|
||||
|
||||
fn load_bytes(&self, _resource_manager: &mut lyra_resource::ResourceManager, bytes: Vec<u8>, offset: usize, length: usize) -> Result<std::sync::Arc<dyn lyra_resource::ResourceStorage>, lyra_resource::LoaderError> {
|
||||
let end = offset + length;
|
||||
let bytes = bytes[offset..end].to_vec();
|
||||
|
||||
let s = Resource::with_data("from bytes", LuaScript {
|
||||
bytes
|
||||
});
|
||||
|
||||
Ok(Arc::new(s))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
pub mod dynamic_iter;
|
||||
pub use dynamic_iter::*;
|
||||
|
||||
pub mod world;
|
||||
pub use world::*;
|
||||
|
||||
pub mod script;
|
||||
pub use script::*;
|
||||
|
||||
pub mod loader;
|
||||
pub use loader::*;
|
||||
|
||||
#[cfg(test)]
|
||||
mod test;
|
||||
|
||||
use std::ptr::NonNull;
|
||||
|
||||
use lyra_ecs::DynamicBundle;
|
||||
use lyra_reflect::{Reflect, RegisteredType, FromType, AsRegisteredType};
|
||||
|
||||
use mlua::{Lua, AnyUserDataExt};
|
||||
|
||||
pub const FN_NAME_INTERNAL_REFLECT_TYPE: &str = "__lyra_internal_reflect_type";
|
||||
pub const FN_NAME_INTERNAL_REFLECT: &str = "__lyra_internal_reflect";
|
||||
|
||||
use crate::{ScriptBorrow, ScriptDynamicBundle};
|
||||
|
||||
impl<'lua> mlua::FromLua<'lua> for ScriptBorrow {
|
||||
fn from_lua(value: mlua::Value<'lua>, _lua: &'lua Lua) -> mlua::Result<Self> {
|
||||
match value {
|
||||
mlua::Value::UserData(ud) => Ok(ud.borrow::<Self>()?.clone()),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl mlua::UserData for ScriptBorrow {}
|
||||
|
||||
pub fn reflect_user_data(ud: &mlua::AnyUserData) -> ScriptBorrow {
|
||||
ud.call_method::<_, ScriptBorrow>(FN_NAME_INTERNAL_REFLECT, ())
|
||||
.expect("Type does not implement '__internal_reflect' properly")
|
||||
}
|
||||
|
||||
pub trait LuaProxy {
|
||||
fn as_lua_value<'lua>(lua: &'lua mlua::Lua, this: &dyn Reflect) -> mlua::Result<mlua::AnyUserData<'lua>>;
|
||||
fn apply(lua: &mlua::Lua, this: &mut dyn Reflect, apply: &mlua::AnyUserData) -> mlua::Result<()>;
|
||||
}
|
||||
|
||||
impl<'a, T: Reflect + Clone + mlua::FromLua<'a> + mlua::UserData> LuaProxy for T {
|
||||
fn as_lua_value<'lua>(lua: &'lua mlua::Lua, this: &dyn Reflect) -> mlua::Result<mlua::AnyUserData<'lua>> {
|
||||
let this = this.as_any().downcast_ref::<T>().unwrap();
|
||||
lua.create_userdata(this.clone())
|
||||
}
|
||||
|
||||
fn apply(_lua: &mlua::Lua, this: &mut dyn Reflect, apply: &mlua::AnyUserData) -> mlua::Result<()> {
|
||||
let this = this.as_any_mut().downcast_mut::<T>().unwrap();
|
||||
let apply = apply.borrow::<T>()?;
|
||||
|
||||
*this = apply.clone();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ReflectLuaProxy {
|
||||
fn_as_uservalue: for<'a> fn(lua: &'a Lua, this_ptr: NonNull<u8>) -> mlua::Result<mlua::AnyUserData<'a>>,
|
||||
fn_apply: for<'a> fn(lua: &'a Lua, this_ptr: NonNull<u8>, apply: &'a mlua::AnyUserData<'a>) -> mlua::Result<()>,
|
||||
}
|
||||
|
||||
impl<'a, T: Reflect + LuaProxy + Clone + mlua::FromLua<'a> + mlua::UserData> FromType<T> for ReflectLuaProxy {
|
||||
fn from_type() -> Self {
|
||||
Self {
|
||||
fn_as_uservalue: |lua, this| -> mlua::Result<mlua::AnyUserData> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'lua> mlua::FromLua<'lua> for ScriptDynamicBundle {
|
||||
fn from_lua(value: mlua::Value<'lua>, _lua: &'lua Lua) -> mlua::Result<Self> {
|
||||
match value {
|
||||
mlua::Value::UserData(ud) => Ok(ud.borrow::<Self>()?.clone()),
|
||||
mlua::Value::Nil => Err(mlua::Error::FromLuaConversionError { from: "Nil", to: "DynamicBundle", message: Some("Value was nil".to_string()) }),
|
||||
_ => panic!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl mlua::UserData for ScriptDynamicBundle {
|
||||
fn add_methods<'lua, M: mlua::prelude::LuaUserDataMethods<'lua, 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();
|
||||
let refl_data = refl_data.as_ref();
|
||||
reflect.bundle_insert(&mut this.0, refl_data);
|
||||
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
use std::sync::Mutex;
|
||||
|
||||
use tracing::debug;
|
||||
|
||||
use crate::{ScriptHost, ScriptError, ScriptWorldPtr};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct LuaHost;
|
||||
|
||||
fn try_call_lua_function(lua: &mlua::Lua, fn_name: &str) -> Result<(), ScriptError> {
|
||||
let globals = lua.globals();
|
||||
|
||||
match globals.get::<_, mlua::Function>(fn_name) {
|
||||
Ok(init_fn) => {
|
||||
init_fn.call(())
|
||||
.map_err(ScriptError::MluaError)?;
|
||||
},
|
||||
Err(mlua::Error::FromLuaConversionError { from: "nil", to: "function", message: None }) => {
|
||||
debug!("Function '{}' was not found, ignoring...", fn_name)
|
||||
// ignore
|
||||
},
|
||||
Err(e) => {
|
||||
return Err(ScriptError::MluaError(e));
|
||||
},
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl ScriptHost for LuaHost {
|
||||
type ScriptContext = Mutex<mlua::Lua>;
|
||||
|
||||
fn load_script(&mut self, script: &[u8], script_data: &crate::ScriptData, providers: &mut crate::ScriptApiProviders<Self>) -> Result<Self::ScriptContext, crate::ScriptError> {
|
||||
let mut ctx = Mutex::new(mlua::Lua::new());
|
||||
|
||||
for provider in providers.apis.iter_mut() {
|
||||
provider.expose_api(&mut ctx)?;
|
||||
}
|
||||
|
||||
let lua = ctx.lock().unwrap();
|
||||
lua.load(script)
|
||||
.set_name(&script_data.name)
|
||||
.exec()
|
||||
.map_err(|e| ScriptError::MluaError(e))?;
|
||||
drop(lua);
|
||||
|
||||
Ok(ctx)
|
||||
}
|
||||
|
||||
fn setup_script(&mut self, script_data: &crate::ScriptData, ctx: &mut Self::ScriptContext, providers: &mut crate::ScriptApiProviders<Self>) -> Result<(), ScriptError> {
|
||||
for provider in providers.apis.iter_mut() {
|
||||
provider.setup_script(script_data, ctx)?;
|
||||
}
|
||||
|
||||
let ctx = ctx.lock().expect("Failure to get Lua ScriptContext");
|
||||
try_call_lua_function(&ctx, "init")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Runs the update step of the lua script.
|
||||
///
|
||||
/// It looks for an `update` function with zero parameters in [`the ScriptContext`] and executes it.
|
||||
fn update_script(&mut self, world: ScriptWorldPtr, script_data: &crate::ScriptData, ctx: &mut Self::ScriptContext, providers: &mut crate::ScriptApiProviders<Self>) -> Result<(), ScriptError> {
|
||||
for provider in providers.apis.iter_mut() {
|
||||
provider.update_script_environment(world.clone(), script_data, ctx)?;
|
||||
}
|
||||
|
||||
let ctx = ctx.lock().expect("Failure to get Lua ScriptContext");
|
||||
try_call_lua_function(&ctx, "update")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,219 @@
|
|||
use std::{sync::{Mutex, Arc}, alloc::Layout, ptr::NonNull};
|
||||
|
||||
use lyra_ecs::{World, query::{Res, View, Entities, ResMut}, Component, system::{GraphExecutor, IntoSystem}};
|
||||
use lyra_resource::ResourceManager;
|
||||
use mlua::{IntoLua, AnyUserDataExt};
|
||||
use tracing::{debug, error};
|
||||
use tracing_subscriber::{layer::SubscriberExt, fmt, filter, util::SubscriberInitExt};
|
||||
|
||||
use crate::{ScriptHost, ScriptData, ScriptApiProvider, ScriptApiProviders, ScriptError, ScriptWorldPtr, ScriptList, Script, ScriptContexts};
|
||||
|
||||
use super::{LuaHost, LuaLoader, LuaScript};
|
||||
|
||||
use crate::lyra_engine;
|
||||
|
||||
fn enable_tracing() {
|
||||
tracing_subscriber::registry()
|
||||
.with(fmt::layer().with_writer(std::io::stdout))
|
||||
.init();
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct PrintfProvider;
|
||||
|
||||
impl ScriptApiProvider for PrintfProvider {
|
||||
type ScriptContext = Mutex<mlua::Lua>;
|
||||
|
||||
fn expose_api(&mut self, ctx: &mut Self::ScriptContext) -> Result<(), crate::ScriptError> {
|
||||
let ctx = ctx.lock().unwrap();
|
||||
|
||||
fn printf(lua: &mlua::Lua, (mut text, formats): (String, mlua::Variadic<mlua::Value>)) -> mlua::Result<()> {
|
||||
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::LightUserData(_) => {
|
||||
return Err(mlua::Error::RuntimeError(format!("unable to get string representation of LightUserData")));
|
||||
},
|
||||
mlua::Value::Integer(i) => i.to_string(),
|
||||
mlua::Value::Number(n) => n.to_string(),
|
||||
mlua::Value::String(s) => s.to_str().unwrap().to_string(),
|
||||
mlua::Value::Table(_) => {
|
||||
return Err(mlua::Error::RuntimeError(format!("unable to get string representation of Table")));
|
||||
},
|
||||
mlua::Value::Function(_) => {
|
||||
return Err(mlua::Error::RuntimeError(format!("unable to get string representation of Function")));
|
||||
},
|
||||
mlua::Value::Thread(_) => {
|
||||
return Err(mlua::Error::RuntimeError(format!("unable to get string representation of Thread")));
|
||||
},
|
||||
mlua::Value::UserData(ud) => {
|
||||
if let Ok(tos) = ud.get::<_, mlua::Function>(mlua::MetaMethod::ToString.to_string()) {
|
||||
tos.call::<_, String>(())?
|
||||
} else {
|
||||
return Err(mlua::Error::RuntimeError(format!("UserData does not implement MetaMethod '__tostring'")));
|
||||
}
|
||||
},
|
||||
mlua::Value::Error(e) => e.to_string(),
|
||||
},
|
||||
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".to_string()),
|
||||
pos: 2,
|
||||
name: Some("...".to_string()),
|
||||
cause: Arc::new(mlua::Error::RuntimeError(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".to_string()),
|
||||
pos: 2,
|
||||
name: Some("...".to_string()),
|
||||
cause: Arc::new(mlua::Error::RuntimeError(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")
|
||||
.unwrap()
|
||||
.call::<_, ()>(formatted)
|
||||
.unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
let printf_func = ctx.create_function(printf).unwrap();
|
||||
|
||||
let globals = ctx.globals();
|
||||
globals.set("printf", printf_func).unwrap();
|
||||
|
||||
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> {
|
||||
let ctx = ctx.lock().unwrap();
|
||||
let globals = ctx.globals();
|
||||
|
||||
let world_lua = world.into_lua(&ctx)
|
||||
.map_err(ScriptError::MluaError)?;
|
||||
globals.set("world", world_lua)
|
||||
.map_err(ScriptError::MluaError)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Tests a simple lua script that just prints some test
|
||||
#[test]
|
||||
pub fn lua_print() {
|
||||
enable_tracing();
|
||||
|
||||
let mut world = World::new();
|
||||
|
||||
let test_provider = PrintfProvider::default();
|
||||
let mut providers = ScriptApiProviders::<LuaHost>::default();
|
||||
providers.add_provider(test_provider);
|
||||
|
||||
let host = LuaHost::default();
|
||||
|
||||
world.add_resource(host);
|
||||
world.add_resource(providers);
|
||||
world.add_resource(ScriptContexts::<Mutex<mlua::Lua>>::default());
|
||||
|
||||
let mut res_loader = ResourceManager::new();
|
||||
res_loader.register_loader::<LuaLoader>();
|
||||
|
||||
let script =
|
||||
r#"
|
||||
print("Hello World")
|
||||
|
||||
function update()
|
||||
print("updated")
|
||||
printf("I love to eat formatted {}!", "food")
|
||||
--printf("World is {}", world)
|
||||
end
|
||||
"#;
|
||||
let script = script.as_bytes();
|
||||
|
||||
let script = res_loader.load_bytes::<LuaScript>("test_script.lua",
|
||||
"text/lua", script.to_vec(), 0, script.len()).unwrap();
|
||||
let script = Script::new("text_script.lua", script);
|
||||
|
||||
let scripts = ScriptList::new(vec![script]);
|
||||
|
||||
world.spawn((scripts,));
|
||||
|
||||
let mut exec = GraphExecutor::new();
|
||||
exec.insert_system("lua_update_scripts", lua_update_scripts.into_system(), &[]);
|
||||
exec.execute(NonNull::from(&world), true).unwrap();
|
||||
}
|
||||
|
||||
fn lua_update_scripts(world: &mut World) -> anyhow::Result<()> {
|
||||
let world_ptr = ScriptWorldPtr::from_ref(&world);
|
||||
let mut host = world.get_resource_mut::<LuaHost>();
|
||||
let mut contexts = world.get_resource_mut::<ScriptContexts<Mutex<mlua::Lua>>>();
|
||||
let mut providers = world.get_resource_mut::<ScriptApiProviders<LuaHost>>();
|
||||
|
||||
for scripts in world.view_iter::<&ScriptList<LuaScript>>() {
|
||||
for script in scripts.iter() {
|
||||
let script_data = ScriptData {
|
||||
name: script.name().to_string(),
|
||||
script_id: script.id(),
|
||||
};
|
||||
|
||||
if !contexts.has_context(script.id()) {
|
||||
if let Some(script_res) = &script.res_handle().data {
|
||||
let mut script_ctx = host.load_script(&script_res.bytes, &script_data, &mut providers).unwrap();
|
||||
host.setup_script(&script_data, &mut script_ctx, &mut providers).unwrap();
|
||||
contexts.add_context(script.id(), script_ctx);
|
||||
} else {
|
||||
debug!("Script '{}' is not yet loaded, skipping", script.name());
|
||||
}
|
||||
}
|
||||
|
||||
let ctx = contexts.get_context_mut(script.id()).unwrap();
|
||||
|
||||
match host.update_script(world_ptr.clone(), &script_data, ctx, &mut providers) {
|
||||
Ok(()) => {},
|
||||
Err(e) => match e {
|
||||
ScriptError::MluaError(m) => {
|
||||
error!("Script '{}' ran into an error: {}", script.name(), m);
|
||||
},
|
||||
ScriptError::Other(_) => return Err(e.into()),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -0,0 +1,131 @@
|
|||
use lyra_ecs::query::dynamic::QueryDynamicType;
|
||||
use lyra_reflect::TypeRegistry;
|
||||
use mlua::{AnyUserDataExt, IntoLua, IntoLuaMulti};
|
||||
|
||||
use crate::{ScriptWorldPtr, ScriptEntity, ScriptDynamicBundle, ScriptBorrow};
|
||||
|
||||
use super::{ReflectedIterator, DynamicViewIter, FN_NAME_INTERNAL_REFLECT_TYPE, reflect_user_data, ReflectLuaProxy};
|
||||
|
||||
impl<'lua> mlua::FromLua<'lua> for ScriptEntity {
|
||||
fn from_lua(value: mlua::Value<'lua>, _lua: &'lua mlua::Lua) -> mlua::Result<Self> {
|
||||
match value {
|
||||
mlua::Value::UserData(ud) => Ok(ud.borrow::<Self>()?.clone()),
|
||||
mlua::Value::Nil => Err(mlua::Error::FromLuaConversionError { from: "Nil", to: "ScriptEntity", message: Some("Value was nil".to_string()) }),
|
||||
_ => panic!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl mlua::UserData for ScriptEntity {}
|
||||
|
||||
#[derive(thiserror::Error, Debug, Clone)]
|
||||
pub enum WorldError {
|
||||
#[error("{0}")]
|
||||
LuaInvalidUsage(String),
|
||||
}
|
||||
|
||||
impl mlua::UserData for ScriptWorldPtr {
|
||||
fn add_methods<'lua, M: mlua::prelude::LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
|
||||
methods.add_method_mut("spawn", |_, this, (bundle,): (ScriptDynamicBundle,)| {
|
||||
let world = unsafe { this.inner.as_mut() };
|
||||
|
||||
Ok(ScriptEntity(world.spawn(bundle.0)))
|
||||
});
|
||||
|
||||
methods.add_method("view_iter", |lua, this, queries: mlua::Variadic<mlua::AnyUserData>| {
|
||||
let world = unsafe { this.inner.as_ref() };
|
||||
let mut view = world.dynamic_view();
|
||||
|
||||
for comp in queries.into_iter() {
|
||||
let script_brw = comp.call_function::<_, ScriptBorrow>(FN_NAME_INTERNAL_REFLECT_TYPE, ())
|
||||
.expect("Type does not implement '__internal_reflect_type' properly");
|
||||
let refl_comp = script_brw.reflect_branch.as_component_unchecked();
|
||||
|
||||
let dyn_type = QueryDynamicType::from_info(refl_comp.info);
|
||||
view.push(dyn_type);
|
||||
}
|
||||
|
||||
let iter = view.into_iter();
|
||||
let mut reflected_iter = ReflectedIterator {
|
||||
world: this.clone(),
|
||||
dyn_view: DynamicViewIter::from(iter),
|
||||
reflected_components: None,
|
||||
};
|
||||
|
||||
let f = lua.create_function_mut(move |lua, ()| {
|
||||
if let Some(row) = reflected_iter.next_lua(lua) {
|
||||
let row = row.into_iter().map(|(_, ud)| ud.into_lua(lua))
|
||||
.collect::<mlua::Result<Vec<mlua::Value>>>()?;
|
||||
Ok(mlua::MultiValue::from_vec(row))
|
||||
} else {
|
||||
Ok(mlua::Value::Nil.into_lua_multi(lua)?)
|
||||
}
|
||||
})?;
|
||||
|
||||
Ok(f)
|
||||
});
|
||||
|
||||
methods.add_method("view", |lua, this, (system, queries): (mlua::Function, mlua::Variadic<mlua::AnyUserData>)| {
|
||||
if queries.is_empty() {
|
||||
panic!("No components were provided!");
|
||||
}
|
||||
|
||||
let world = unsafe { this.inner.as_ref() };
|
||||
let mut view = world.dynamic_view();
|
||||
|
||||
for comp in queries.into_iter() {
|
||||
let reflect = comp.call_function::<_, ScriptBorrow>(FN_NAME_INTERNAL_REFLECT_TYPE, ())
|
||||
.expect("Type does not implement 'reflect_type' properly");
|
||||
let refl_comp = reflect.reflect_branch.as_component_unchecked();
|
||||
|
||||
let dyn_type = QueryDynamicType::from_info(refl_comp.info);
|
||||
view.push(dyn_type);
|
||||
}
|
||||
|
||||
let iter = view.into_iter();
|
||||
let mut reflected_iter = ReflectedIterator {
|
||||
world: this.clone(),
|
||||
dyn_view: DynamicViewIter::from(iter),
|
||||
reflected_components: None,
|
||||
};
|
||||
|
||||
let reg = this.as_ref().get_resource::<TypeRegistry>();
|
||||
|
||||
while let Some(row) = reflected_iter.next_lua(lua) {
|
||||
let (reflects, values): (Vec<(_, _)>, Vec<_>) = row.into_iter().unzip();
|
||||
|
||||
let value_row: Vec<_> = values.into_iter().map(|ud| ud.into_lua(lua)).collect::<mlua::Result<Vec<mlua::Value>>>()?;
|
||||
let mult_val = mlua::MultiValue::from_vec(value_row);
|
||||
let res: mlua::MultiValue = system.call(mult_val)?;
|
||||
|
||||
// if values were returned, find the type in the type registry, and apply the new values
|
||||
if res.len() <= reflects.len() {
|
||||
for (i, comp) in res.into_iter().enumerate() {
|
||||
let (_proxy, ptr) = reflects[i];
|
||||
|
||||
match comp.as_userdata() {
|
||||
Some(ud) => {
|
||||
let lua_comp = reflect_user_data(ud);
|
||||
let refl_comp = lua_comp.reflect_branch.as_component_unchecked();
|
||||
let lua_typeid = refl_comp.info.type_id.as_rust();
|
||||
let reg_type = reg.get_type(lua_typeid).unwrap();
|
||||
|
||||
let proxy = reg_type.get_data::<ReflectLuaProxy>().unwrap();
|
||||
(proxy.fn_apply)(lua, ptr, ud)?
|
||||
}
|
||||
None => {
|
||||
panic!("A userdata value was not returned!");
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let msg = format!("Too many arguments were returned from the World view!
|
||||
At most, the expected number of results is {}.", reflects.len());
|
||||
return Err(mlua::Error::external(WorldError::LuaInvalidUsage(msg)));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
use lyra_ecs::Component;
|
||||
use lyra_resource::ResHandle;
|
||||
|
||||
use crate::lyra_engine;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Script<T> {
|
||||
res: ResHandle<T>,
|
||||
name: String,
|
||||
id: u64
|
||||
}
|
||||
|
||||
impl<T> Script<T> {
|
||||
pub fn new(name: &str, script: ResHandle<T>) -> Self {
|
||||
Self {
|
||||
res: script,
|
||||
name: name.to_string(),
|
||||
id: 0 // TODO: make a counter
|
||||
}
|
||||
}
|
||||
|
||||
pub fn res_handle(&self) -> ResHandle<T> {
|
||||
self.res.clone()
|
||||
}
|
||||
|
||||
pub fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
pub fn id(&self) -> u64 {
|
||||
self.id
|
||||
}
|
||||
}
|
||||
|
||||
/// A list of scripts
|
||||
#[derive(Clone, Default, Component)]
|
||||
pub struct ScriptList<T: 'static>(Vec<Script<T>>);
|
||||
|
||||
impl<T> ScriptList<T> {
|
||||
pub fn new(list: Vec<Script<T>>) -> Self {
|
||||
Self(list)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> std::ops::Deref for ScriptList<T> {
|
||||
type Target = Vec<Script<T>>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> std::ops::DerefMut for ScriptList<T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
use std::ptr::NonNull;
|
||||
|
||||
use lyra_ecs::{world::World, Entity};
|
||||
|
||||
use mlua::{prelude::{LuaUserData, LuaAnyUserData, LuaValue, LuaResult, Lua, LuaError}, Variadic, AnyUserDataExt, FromLua, IntoLua, MultiValue, IntoLuaMulti};
|
||||
|
||||
use crate::ScriptBorrow;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ScriptEntity(pub Entity);
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ScriptWorldPtr {
|
||||
pub inner: NonNull<World>,
|
||||
}
|
||||
|
||||
impl ScriptWorldPtr {
|
||||
/// Creates a world pointer from a world borrow.
|
||||
pub fn from_ref(world: &World) -> Self {
|
||||
Self {
|
||||
inner: NonNull::from(world),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a borrow to the world from the ptr.
|
||||
pub fn as_ref(&self) -> &World {
|
||||
unsafe { self.inner.as_ref() }
|
||||
}
|
||||
|
||||
/// Returns a mutable borrow to the world from the ptr.
|
||||
pub fn as_mut(&mut self) -> &mut World {
|
||||
unsafe { self.inner.as_mut() }
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for ScriptWorldPtr {
|
||||
type Target = NonNull<World>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
use lyra_ecs::DynamicBundle;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ScriptDynamicBundle(pub DynamicBundle);
|
||||
|
||||
impl std::ops::Deref for ScriptDynamicBundle {
|
||||
type Target = DynamicBundle;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue