bones_scripting/lua/
bindings.rs

1use bones_lib::prelude::*;
2use gc_arena_derive::Collect;
3use piccolo::{
4    self as lua, meta_ops, meta_ops::MetaResult, BoxSequence, Callback, CallbackReturn, Context,
5    Error, Sequence, SequencePoll, Stack,
6};
7
8use super::*;
9
10pub mod assets;
11pub mod components;
12pub mod entities;
13pub mod resources;
14pub mod schema;
15pub mod world;
16
17pub mod ecsref;
18pub use ecsref::*;
19
20/// Registers lua binding typedatas for bones_framework types.
21pub fn register_lua_typedata() {
22    Entities::schema()
23        .type_data
24        .insert(SchemaLuaEcsRefMetatable(entities::entities_metatable))
25        .unwrap();
26}
27
28pub fn no_newindex(ctx: Context) -> Callback {
29    Callback::from_fn(&ctx, |_ctx, _fuel, _stack| {
30        Err(anyhow::format_err!("Creating fields not allowed on this type").into())
31    })
32}
33
34/// Generate the environment table for executing scripts under.
35pub fn env(ctx: Context) -> Table {
36    let env = Table::new(&ctx);
37
38    env.set(ctx, "math", ctx.globals().get(ctx, "math"))
39        .unwrap();
40
41    let schema_fn = ctx.singletons().get(ctx, schema::schema_fn);
42    env.set(ctx, "schema", schema_fn).unwrap();
43    env.set(ctx, "s", schema_fn).unwrap(); // Alias for schema
44    let schema_of_fn = ctx.singletons().get(ctx, schema::schema_of_fn);
45    env.set(ctx, "schema_of", schema_of_fn).unwrap();
46
47    WorldRef::default().add_to_env(ctx, env);
48
49    // Set the `CoreStage` enum global
50    let core_stage_table = Table::new(&ctx);
51    for (name, stage) in [
52        ("First", CoreStage::First),
53        ("PreUpdate", CoreStage::PreUpdate),
54        ("Update", CoreStage::Update),
55        ("PostUpdate", CoreStage::PostUpdate),
56        ("Last", CoreStage::Last),
57    ] {
58        core_stage_table
59            .set(ctx, name, UserData::new_static(&ctx, stage))
60            .unwrap();
61    }
62    env.set(ctx, "CoreStage", core_stage_table).unwrap();
63
64    macro_rules! add_log_fn {
65        ($level:ident) => {
66            let $level = Callback::from_fn(&ctx, |ctx, _fuel, mut stack| {
67                #[derive(Debug, Copy, Clone, Eq, PartialEq, Collect)]
68                #[collect(require_static)]
69                enum Mode {
70                    Init,
71                    First,
72                }
73
74                #[derive(Collect)]
75                #[collect(no_drop)]
76                struct PrintSeq<'gc> {
77                    mode: Mode,
78                    values: Vec<Value<'gc>>,
79                }
80
81                impl<'gc> Sequence<'gc> for PrintSeq<'gc> {
82                    fn poll<'a>(
83                        &mut self,
84                        ctx: Context<'gc>,
85                        _ex: piccolo::Execution<'gc, '_>,
86                        mut stack: Stack<'gc, 'a>,
87                    ) -> Result<SequencePoll<'gc>, Error<'gc>> {
88                        if self.mode == Mode::Init {
89                            self.mode = Mode::First;
90                        } else {
91                            self.values.push(stack.get(0));
92                        }
93                        stack.clear();
94
95                        while let Some(value) = self.values.pop() {
96                            match meta_ops::tostring(ctx, value)? {
97                                MetaResult::Value(v) => tracing::$level!("{v}"),
98                                MetaResult::Call(call) => {
99                                    stack.extend(call.args);
100                                    return Ok(SequencePoll::Call {
101                                        function: call.function,
102                                        is_tail: false,
103                                    });
104                                }
105                            }
106                        }
107
108                        Ok(SequencePoll::Return)
109                    }
110                }
111
112                Ok(CallbackReturn::Sequence(BoxSequence::new(
113                    &ctx,
114                    PrintSeq {
115                        mode: Mode::Init,
116                        values: stack.drain(..).rev().collect(),
117                    },
118                )))
119            });
120            env.set(ctx, stringify!($level), $level).unwrap();
121        };
122    }
123
124    // Register logging callbacks
125    add_log_fn!(trace);
126    add_log_fn!(debug);
127    add_log_fn!(info);
128    add_log_fn!(warn);
129    add_log_fn!(error);
130    env.set(ctx, "print", info).unwrap();
131
132    // Prevent creating new items in the global scope, by overrideing the __newindex metamethod
133    // on the _ENV metatable.
134    let metatable = Table::new(&ctx);
135    metatable
136        .set(
137            ctx,
138            "__newindex",
139            Callback::from_fn(&ctx, |_ctx, _fuel, _stack| {
140                Err(anyhow::format_err!(
141                    "Cannot set global variables, you must use `world` \
142                        to persist any state across frames."
143                )
144                .into())
145            }),
146        )
147        .unwrap();
148    env.set_metatable(&ctx, Some(metatable));
149
150    env
151}