bones_scripting/lua/bindings/
schema.rs

1use super::*;
2
3/// A wrapper around [`Schema`] that indicates that it should be excluded from, for example,
4/// an `entities:iter_with()` lua call.
5pub(super) struct WithoutSchema(pub &'static Schema);
6
7pub fn schema_fn(ctx: Context) -> Callback {
8    Callback::from_fn(&ctx, move |ctx, _fuel, mut stack| {
9        let singletons = ctx.singletons();
10        let schema_metatable = singletons.get(ctx, schema::metatable);
11
12        let schema_name = stack.pop_front();
13        let Value::String(schema_name) = schema_name else {
14            return Err(anyhow::format_err!("Type error: expected string schema name").into());
15        };
16        let mut matches = SCHEMA_REGISTRY.schemas.iter().filter(|schema| {
17            schema.name.as_bytes() == schema_name.as_bytes()
18                || schema.full_name.as_bytes() == schema_name.as_bytes()
19        });
20
21        if let Some(next_match) = matches.next() {
22            if matches.next().is_some() {
23                return Err(anyhow::format_err!("Found multiple schemas matching name.").into());
24            }
25
26            // TODO: setup `toString` implementation so that printing schemas gives more information.
27            let schema = UserData::new_static(&ctx, next_match);
28            schema.set_metatable(&ctx, Some(schema_metatable));
29            stack.push_front(schema.into());
30        } else {
31            #[cfg(not(debug_assertions))] // script info only, not a developer
32            return Err(anyhow::format_err!("Schema not found: {schema_name}").into());
33
34            #[cfg(debug_assertions)] // add note about registry, probably a developer
35            return Err(anyhow::format_err!(
36                "Schema not found: {schema_name}, you may need to register the schema"
37            )
38            .into());
39        }
40
41        Ok(CallbackReturn::Return)
42    })
43}
44
45pub fn schema_of_fn(ctx: Context) -> Callback {
46    Callback::from_fn(&ctx, move |ctx, _fuel, mut stack| {
47        let singletons = ctx.singletons();
48        let schema_metatable = singletons.get(ctx, schema::metatable);
49
50        let ecsref: &EcsRef = stack.consume(ctx)?;
51        let schema = ecsref.borrow().schema_ref()?.schema();
52
53        let schema = UserData::new_static(&ctx, schema);
54        schema.set_metatable(&ctx, Some(schema_metatable));
55        stack.replace(ctx, schema);
56
57        Ok(CallbackReturn::Return)
58    })
59}
60
61pub fn metatable(ctx: Context) -> Table {
62    let metatable = Table::new(&ctx);
63    metatable
64        .set(
65            ctx,
66            "__tostring",
67            Callback::from_fn(&ctx, move |ctx, _fuel, mut stack| {
68                let this: UserData = stack.consume(ctx)?;
69                let this = this.downcast_static::<&Schema>()?;
70                let s = piccolo::String::from_slice(&ctx, format!("Schema({})", this.full_name));
71
72                stack.push_front(Value::String(s));
73                Ok(CallbackReturn::Return)
74            }),
75        )
76        .unwrap();
77    let create_fn = ctx.registry().stash(
78        &ctx,
79        Callback::from_fn(&ctx, move |ctx, _fuel, mut stack| {
80            let this: UserData = stack.consume(ctx)?;
81            let this = this.downcast_static::<&Schema>()?;
82
83            let ecsref = EcsRef {
84                data: EcsRefData::Free(Rc::new(AtomicCell::new(SchemaBox::default(this)))),
85                path: default(),
86            }
87            .into_value(ctx);
88            stack.push_front(ecsref);
89
90            Ok(CallbackReturn::Return)
91        }),
92    );
93
94    let without_fn = ctx.registry().stash(
95        &ctx,
96        Callback::from_fn(&ctx, move |ctx, _fuel, mut stack| {
97            let this: UserData = stack.consume(ctx)?;
98            let this = this.downcast_static::<&Schema>()?;
99            stack.replace(ctx, UserData::new_static(&ctx, WithoutSchema(this)));
100            Ok(CallbackReturn::Return)
101        }),
102    );
103
104    let eq_fn = Callback::from_fn(&ctx, move |ctx, _fuel, mut stack| {
105        let (this, other): (UserData, UserData) = stack.consume(ctx)?;
106        let (this, other) = (
107            this.downcast_static::<&Schema>()?,
108            other.downcast_static::<&Schema>()?,
109        );
110        stack.replace(ctx, this.id() == other.id());
111        Ok(CallbackReturn::Return)
112    });
113
114    metatable.set(ctx, "__eq", eq_fn).unwrap();
115    metatable
116        .set(
117            ctx,
118            "__index",
119            Callback::from_fn(&ctx, move |ctx, _fuel, mut stack| {
120                let (this, key): (UserData, lua::String) = stack.consume(ctx)?;
121                let this = this.downcast_static::<&Schema>()?;
122
123                match key.as_bytes() {
124                    b"name" => {
125                        stack.replace(
126                            ctx,
127                            Value::String(piccolo::String::from_static(&ctx, this.name.as_bytes())),
128                        );
129                    }
130                    b"full_name" => {
131                        stack.replace(
132                            ctx,
133                            Value::String(piccolo::String::from_static(
134                                &ctx,
135                                this.full_name.as_bytes(),
136                            )),
137                        );
138                    }
139                    b"create" => stack.replace(ctx, ctx.registry().fetch(&create_fn)),
140                    b"without" => stack.replace(ctx, ctx.registry().fetch(&without_fn)),
141                    _ => (),
142                }
143
144                Ok(CallbackReturn::Return)
145            }),
146        )
147        .unwrap();
148
149    metatable
150}