Skip to content

Commit e625514

Browse files
committed
improve ease of exposing component types
1 parent 5d7874d commit e625514

6 files changed

Lines changed: 308 additions & 107 deletions

File tree

core/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,5 @@ pub use async_trait::async_trait;
2121
pub use serde;
2222
pub use serde_json;
2323

24+
pub mod lua;
2425
pub mod test;

core/src/lua/create_component.rs

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
use std::borrow::Cow;
2+
3+
use hv_lua::{FromLua, ToLua};
4+
use tealr::{NamePart, TealType, TypeName};
5+
6+
///This trait is used to limit what types can be set in the create_type_component_container macro
7+
///It doesn't really have a use outside of that.
8+
pub trait Component {
9+
fn is_component() {}
10+
}
11+
12+
#[derive(Debug, Clone, Copy)]
13+
pub struct CopyComponent<T>(T);
14+
impl<T> Component for CopyComponent<T> {}
15+
impl<T: Send + Sync + 'static + Clone + Copy> hv_lua::UserData for CopyComponent<T>
16+
where
17+
T: for<'a> ToLua<'a> + for<'a> FromLua<'a>,
18+
{
19+
#[allow(clippy::unit_arg)]
20+
fn add_fields<'lua, F: hv_lua::UserDataFields<'lua, Self>>(fields: &mut F) {
21+
fields.add_field_method_get("value", |_, this| Ok(this.0));
22+
fields.add_field_method_set("value", |_, this, value| Ok(this.0 = value));
23+
}
24+
fn on_metatable_init(t: hv_alchemy::Type<Self>) {
25+
use hv_lua::hv::LuaUserDataTypeExt;
26+
t.add_clone().add_copy().mark_component();
27+
}
28+
fn add_type_methods<'lua, M: hv_lua::UserDataMethods<'lua, hv_alchemy::Type<Self>>>(
29+
methods: &mut M,
30+
) {
31+
methods.add_function("new", |_, i: T| Ok(Self(i)));
32+
}
33+
fn on_type_metatable_init(t: hv_alchemy::Type<hv_alchemy::Type<Self>>) {
34+
use hv_lua::hv::LuaUserDataTypeTypeExt;
35+
t.mark_component_type();
36+
}
37+
}
38+
impl<T: tealr::TypeName> tealr::TypeName for CopyComponent<T> {
39+
fn get_type_parts() -> std::borrow::Cow<'static, [tealr::NamePart]> {
40+
let name = tealr::NamePart::Type(TealType {
41+
name: Cow::Borrowed("Component"),
42+
type_kind: tealr::KindOfType::External,
43+
generics: None,
44+
});
45+
let mut type_name = vec![name, NamePart::Symbol(Cow::Borrowed("<"))];
46+
type_name.append(&mut T::get_type_parts().into_owned());
47+
type_name.push(NamePart::Symbol(Cow::Borrowed(">")));
48+
Cow::Owned(type_name)
49+
}
50+
}
51+
impl<T: TypeName> tealr::TypeBody for CopyComponent<T> {
52+
fn get_type_body(gen: &mut tealr::TypeGenerator) {
53+
get_type_body_component::<T, Self>(gen)
54+
}
55+
}
56+
57+
#[derive(Debug, Clone, Copy)]
58+
pub struct CloneComponent<T>(T);
59+
impl<T> Component for CloneComponent<T> {}
60+
impl<T: Send + Sync + 'static + Clone> hv_lua::UserData for CloneComponent<T>
61+
where
62+
T: for<'a> ToLua<'a> + for<'a> FromLua<'a>,
63+
{
64+
#[allow(clippy::unit_arg)]
65+
fn add_fields<'lua, F: hv_lua::UserDataFields<'lua, Self>>(fields: &mut F) {
66+
fields.add_field_method_get("value", |_, this| Ok(this.0.clone()));
67+
fields.add_field_method_set("value", |_, this, value| Ok(this.0 = value));
68+
}
69+
fn on_metatable_init(t: hv_alchemy::Type<Self>) {
70+
use hv_lua::hv::LuaUserDataTypeExt;
71+
t.add_clone().mark_component();
72+
}
73+
fn add_type_methods<'lua, M: hv_lua::UserDataMethods<'lua, hv_alchemy::Type<Self>>>(
74+
methods: &mut M,
75+
) {
76+
methods.add_function("new", |_, i: T| Ok(Self(i)));
77+
}
78+
fn on_type_metatable_init(t: hv_alchemy::Type<hv_alchemy::Type<Self>>) {
79+
use hv_lua::hv::LuaUserDataTypeTypeExt;
80+
t.mark_component_type();
81+
}
82+
}
83+
impl<T: tealr::TypeName> tealr::TypeName for CloneComponent<T> {
84+
fn get_type_parts() -> std::borrow::Cow<'static, [tealr::NamePart]> {
85+
let name = tealr::NamePart::Type(TealType {
86+
name: Cow::Borrowed("Component"),
87+
type_kind: tealr::KindOfType::External,
88+
generics: None,
89+
});
90+
let mut type_name = vec![name, NamePart::Symbol(Cow::Borrowed("<"))];
91+
type_name.append(&mut T::get_type_parts().into_owned());
92+
type_name.push(NamePart::Symbol(Cow::Borrowed(">")));
93+
Cow::Owned(type_name)
94+
}
95+
}
96+
impl<T: TypeName> tealr::TypeBody for CloneComponent<T> {
97+
fn get_type_body(gen: &mut tealr::TypeGenerator) {
98+
get_type_body_component::<T, Self>(gen)
99+
}
100+
}
101+
102+
fn get_type_body_component<T: TypeName, SelfType: TypeName>(gen: &mut tealr::TypeGenerator) {
103+
let mut signature = vec![NamePart::Symbol(Cow::Borrowed("function("))];
104+
signature.append(&mut T::get_type_parts().into_owned());
105+
signature.push(NamePart::Symbol(Cow::Borrowed("):")));
106+
signature.append(&mut SelfType::get_type_parts().into_owned());
107+
108+
gen.methods.push(tealr::ExportedFunction {
109+
name: Cow::Borrowed("new").into(),
110+
signature: Cow::Owned(signature),
111+
is_meta_method: false,
112+
});
113+
gen.fields.push((
114+
Cow::Borrowed("value"),
115+
tealr::type_parts_to_str(<T as tealr::TypeName>::get_type_parts()),
116+
));
117+
}
118+
119+
#[macro_export]
120+
macro_rules! create_type_component_container {
121+
($name:ident with $($field_name:ident of $type_name:ty,)+) => {
122+
#[derive(Debug, Clone)]
123+
pub struct $name<'lua>(hv_lua::Table<'lua>);
124+
impl<'lua> hv_lua::ToLua<'lua> for $name<'lua> {
125+
fn to_lua(
126+
self,
127+
lua: &'lua hv_lua::Lua,
128+
) -> std::result::Result<hv_lua::Value<'lua>, hv_lua::Error> {
129+
self.0.to_lua(lua)
130+
}
131+
}
132+
impl<'lua> tealr::TypeName for $name<'lua> {
133+
fn get_type_parts() -> std::borrow::Cow<'static, [tealr::NamePart]> {
134+
use tealr::new_type;
135+
new_type!(Components)
136+
}
137+
}
138+
impl<'lua> tealr::TypeBody for $name<'lua> {
139+
fn get_type_body(gen: &mut tealr::TypeGenerator) {
140+
$(
141+
<$type_name as $crate::lua::Component>::is_component();
142+
gen.fields.push(
143+
(
144+
std::borrow::Cow::Borrowed(
145+
stringify!($field_name)
146+
),
147+
tealr::type_parts_to_str(
148+
<$type_name as tealr::TypeName>::get_type_parts()
149+
)
150+
)
151+
);
152+
)*
153+
154+
}
155+
}
156+
impl<'lua> $name<'lua> {
157+
pub fn new(lua: &'lua hv_lua::Lua) -> Result<Self, Box<dyn std::error::Error>> {
158+
let table = lua.create_table()?;
159+
use $crate::lua::Component;
160+
$(
161+
<$type_name as Component>::is_component();
162+
table.set(stringify!($field_name),lua.create_userdata_type::<$type_name>()?)?;
163+
)*
164+
Ok(Self(table))
165+
}
166+
}
167+
};
168+
}

core/src/lua/mod.rs

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
mod create_component;
2+
pub use create_component::{CloneComponent, Component, CopyComponent};
3+
4+
use std::{error::Error, os::unix::prelude::OsStrExt, path::Path};
5+
6+
use hv_lua::{chunk, hv::types, Function, Lua, Table, Value};
7+
8+
const LUA_ENTRY: &str = "main";
9+
10+
crate::create_type_component_container!(
11+
TypeContainer with
12+
I32 of CopyComponent<i32>,
13+
Bool of CopyComponent<bool>,
14+
);
15+
16+
pub fn init_lua<RegisterTypes: Fn(&Lua) -> Result<Value, Box<dyn Error>>>(
17+
mod_dir: &Path,
18+
register_types: RegisterTypes,
19+
) -> Result<Lua, Box<dyn Error>> {
20+
let lua = Lua::new();
21+
let hv = types(&lua)?;
22+
let component_types = register_types(&lua)?;
23+
let dir = mod_dir.as_os_str().as_bytes().to_owned();
24+
{
25+
let globals = lua.globals();
26+
//a table containing all the mods
27+
globals.set("mods", lua.create_table()?)?;
28+
//this table is used to have quick access to every event that needs to run.
29+
globals.set("events", lua.create_table()?)?;
30+
31+
//we don't want users to simply use `require` to load their files as that will either result in conflicts or annoying path names
32+
//to drive this point home, I rename the `require` function here and add 2 other functions to load mod files
33+
let req = globals.get::<_, Function>("require")?;
34+
globals.set("require_lib", req)?;
35+
globals.set("require", hv_lua::Nil)?;
36+
37+
globals.set("hv", hv)?;
38+
globals.set("type_components", component_types)?;
39+
40+
//this function allows people to load a file from arbitrary mods
41+
//this allows "library only" mods to exist.
42+
//there will also be a function that loads from the current mod. However, that one needs to be defined when loading the mod
43+
let load_any_mod_file = lua.create_function(
44+
move |lua, (from_mod, path): (hv_lua::String, hv_lua::String)| {
45+
//TODO: a way to go from `mod id` to `mod folder`
46+
let globals = lua.globals();
47+
let req = globals.get::<_, Function>("require_lib")?;
48+
let mod_folder = globals
49+
.get::<_, Table>("mods")?
50+
.get::<_, Table>(from_mod)?
51+
.get::<_, hv_lua::String>("dir_name")?;
52+
53+
let mut full_path = Vec::new();
54+
full_path.extend_from_slice(&dir);
55+
full_path.extend_from_slice(b".");
56+
full_path.extend_from_slice(mod_folder.as_bytes());
57+
full_path.extend_from_slice(b".");
58+
full_path.extend_from_slice(path.as_bytes());
59+
let full_path = lua.create_string(&full_path)?;
60+
req.call::<_, hv_lua::Value>(full_path)
61+
},
62+
)?;
63+
globals.set("require_from", load_any_mod_file)?;
64+
}
65+
Ok(lua)
66+
}
67+
68+
pub fn load_lua<P: AsRef<[u8]>>(
69+
mod_id: String,
70+
mod_folder: P,
71+
lua: &Lua,
72+
) -> Result<(), Box<dyn Error>> {
73+
let name = mod_id;
74+
let name_to_transfer = name.clone();
75+
//ideally, we use a lua string but... those don't get transferred properly for some reason....
76+
let dir = String::from_utf8_lossy(mod_folder.as_ref());
77+
let entry = LUA_ENTRY.to_string();
78+
let chunk = chunk! {
79+
local name = $name_to_transfer
80+
local dir = $dir
81+
local entry = $entry
82+
83+
function require(load_path)
84+
return require_from(name , load_path)
85+
end
86+
local function init_mod()
87+
return require(entry)
88+
end
89+
local mod_config = {
90+
dir_name = dir,
91+
require = require,
92+
mod_id = name
93+
}
94+
mods[name] = mod_config
95+
96+
local mod_events = init_mod()
97+
mod_config["events"] = mod_events
98+
if type(mod_events) == "table" then
99+
for k, _ in pairs(mod_events) do
100+
local event_list = events[k] or {}
101+
table.insert(event_list, mod_config)
102+
events[k] = event_list
103+
end
104+
end
105+
require = nil
106+
};
107+
lua.load(chunk)
108+
.set_name(&format!("Load mod: {:?}", name))?
109+
.exec()?;
110+
111+
Ok(())
112+
}

mods/test_lua_mod_one/main.lua

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,14 @@ local entity;
44
return {
55
init = function(world)
66
print "Run init"
7+
local I32 = type_components.I32
8+
local Bool = type_components.Bool
79
entity = world:spawn { I32.new(5), Bool.new(true) }
810
end,
911
fixed_update_physics_bodies = function(world)
1012
local Query = hv.ecs.Query
13+
local I32 = type_components.I32
14+
local Bool = type_components.Bool
1115
local query = Query.new { Query.write(I32), Query.read(Bool) }
1216
world:query_one(query, entity, function(item)
1317
print("Got item:",item)

0 commit comments

Comments
 (0)