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        ("Startup", CoreStage::Startup),
53        ("First", CoreStage::First),
54        ("PreUpdate", CoreStage::PreUpdate),
55        ("Update", CoreStage::Update),
56        ("PostUpdate", CoreStage::PostUpdate),
57        ("Last", CoreStage::Last),
58    ] {
59        core_stage_table
60            .set(ctx, name, UserData::new_static(&ctx, stage))
61            .unwrap();
62    }
63    env.set(ctx, "CoreStage", core_stage_table).unwrap();
64
65    macro_rules! add_log_fn {
66        ($level:ident) => {
67            let $level = Callback::from_fn(&ctx, |ctx, _fuel, mut stack| {
68                #[derive(Debug, Copy, Clone, Eq, PartialEq, Collect)]
69                #[collect(require_static)]
70                enum Mode {
71                    Init,
72                    First,
73                }
74
75                #[derive(Collect)]
76                #[collect(no_drop)]
77                struct PrintSeq<'gc> {
78                    mode: Mode,
79                    values: Vec<Value<'gc>>,
80                }
81
82                impl<'gc> Sequence<'gc> for PrintSeq<'gc> {
83                    fn poll<'a>(
84                        &mut self,
85                        ctx: Context<'gc>,
86                        _ex: piccolo::Execution<'gc, '_>,
87                        mut stack: Stack<'gc, 'a>,
88                    ) -> Result<SequencePoll<'gc>, Error<'gc>> {
89                        if self.mode == Mode::Init {
90                            self.mode = Mode::First;
91                        } else {
92                            self.values.push(stack.get(0));
93                        }
94                        stack.clear();
95
96                        while let Some(value) = self.values.pop() {
97                            match meta_ops::tostring(ctx, value)? {
98                                MetaResult::Value(v) => tracing::$level!("{v}"),
99                                MetaResult::Call(call) => {
100                                    stack.extend(call.args);
101                                    return Ok(SequencePoll::Call {
102                                        function: call.function,
103                                        is_tail: false,
104                                    });
105                                }
106                            }
107                        }
108
109                        Ok(SequencePoll::Return)
110                    }
111                }
112
113                Ok(CallbackReturn::Sequence(BoxSequence::new(
114                    &ctx,
115                    PrintSeq {
116                        mode: Mode::Init,
117                        values: stack.drain(..).rev().collect(),
118                    },
119                )))
120            });
121            env.set(ctx, stringify!($level), $level).unwrap();
122        };
123    }
124
125    // Register logging callbacks
126    add_log_fn!(trace);
127    add_log_fn!(debug);
128    add_log_fn!(info);
129    add_log_fn!(warn);
130    add_log_fn!(error);
131    env.set(ctx, "print", info).unwrap();
132
133    // Prevent creating new items in the global scope, by overrideing the __newindex metamethod
134    // on the _ENV metatable.
135    let metatable = Table::new(&ctx);
136    metatable
137        .set(
138            ctx,
139            "__newindex",
140            Callback::from_fn(&ctx, |_ctx, _fuel, _stack| {
141                Err(anyhow::format_err!(
142                    "Cannot set global variables, you must use `world` \
143                        to persist any state across frames."
144                )
145                .into())
146            }),
147        )
148        .unwrap();
149    env.set_metatable(&ctx, Some(metatable));
150
151    env
152}