1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
use super::*;

/// A wrapper around [`Schema`] that indicates that it should be excluded from, for example,
/// an `entities:iter_with()` lua call.
pub(super) struct WithoutSchema(pub &'static Schema);

pub fn schema_fn(ctx: Context) -> Callback {
    Callback::from_fn(&ctx, move |ctx, _fuel, mut stack| {
        let singletons = ctx.singletons();
        let schema_metatable = singletons.get(ctx, schema::metatable);

        let schema_name = stack.pop_front();
        let Value::String(schema_name) = schema_name else {
            return Err(anyhow::format_err!("Type error: expected string schema name").into());
        };
        let mut matches = SCHEMA_REGISTRY.schemas.iter().filter(|schema| {
            schema.name.as_bytes() == schema_name.as_bytes()
                || schema.full_name.as_bytes() == schema_name.as_bytes()
        });

        if let Some(next_match) = matches.next() {
            if matches.next().is_some() {
                return Err(anyhow::format_err!("Found multiple schemas matching name.").into());
            }

            // TODO: setup `toString` implementation so that printing schemas gives more information.
            let schema = UserData::new_static(&ctx, next_match);
            schema.set_metatable(&ctx, Some(schema_metatable));
            stack.push_front(schema.into());
        } else {
            return Err(anyhow::format_err!("Schema not found: {schema_name}").into());
        }

        Ok(CallbackReturn::Return)
    })
}

pub fn schema_of_fn(ctx: Context) -> Callback {
    Callback::from_fn(&ctx, move |ctx, _fuel, mut stack| {
        let singletons = ctx.singletons();
        let schema_metatable = singletons.get(ctx, schema::metatable);

        let ecsref: &EcsRef = stack.consume(ctx)?;
        let schema = ecsref.borrow().schema_ref()?.schema();

        let schema = UserData::new_static(&ctx, schema);
        schema.set_metatable(&ctx, Some(schema_metatable));
        stack.replace(ctx, schema);

        Ok(CallbackReturn::Return)
    })
}

pub fn metatable(ctx: Context) -> Table {
    let metatable = Table::new(&ctx);
    metatable
        .set(
            ctx,
            "__tostring",
            Callback::from_fn(&ctx, move |ctx, _fuel, mut stack| {
                let this: UserData = stack.consume(ctx)?;
                let this = this.downcast_static::<&Schema>()?;
                let s = piccolo::String::from_slice(&ctx, format!("Schema({})", this.full_name));

                stack.push_front(Value::String(s));
                Ok(CallbackReturn::Return)
            }),
        )
        .unwrap();
    let create_fn = ctx.registry().stash(
        &ctx,
        Callback::from_fn(&ctx, move |ctx, _fuel, mut stack| {
            let this: UserData = stack.consume(ctx)?;
            let this = this.downcast_static::<&Schema>()?;

            let ecsref = EcsRef {
                data: EcsRefData::Free(Rc::new(AtomicCell::new(SchemaBox::default(this)))),
                path: default(),
            }
            .into_value(ctx);
            stack.push_front(ecsref);

            Ok(CallbackReturn::Return)
        }),
    );

    let without_fn = ctx.registry().stash(
        &ctx,
        Callback::from_fn(&ctx, move |ctx, _fuel, mut stack| {
            let this: UserData = stack.consume(ctx)?;
            let this = this.downcast_static::<&Schema>()?;
            stack.replace(ctx, UserData::new_static(&ctx, WithoutSchema(this)));
            Ok(CallbackReturn::Return)
        }),
    );

    let eq_fn = Callback::from_fn(&ctx, move |ctx, _fuel, mut stack| {
        let (this, other): (UserData, UserData) = stack.consume(ctx)?;
        let (this, other) = (
            this.downcast_static::<&Schema>()?,
            other.downcast_static::<&Schema>()?,
        );
        stack.replace(ctx, this.id() == other.id());
        Ok(CallbackReturn::Return)
    });

    metatable.set(ctx, "__eq", eq_fn).unwrap();
    metatable
        .set(
            ctx,
            "__index",
            Callback::from_fn(&ctx, move |ctx, _fuel, mut stack| {
                let (this, key): (UserData, lua::String) = stack.consume(ctx)?;
                let this = this.downcast_static::<&Schema>()?;

                match key.as_bytes() {
                    b"name" => {
                        stack.replace(
                            ctx,
                            Value::String(piccolo::String::from_static(&ctx, this.name.as_bytes())),
                        );
                    }
                    b"full_name" => {
                        stack.replace(
                            ctx,
                            Value::String(piccolo::String::from_static(
                                &ctx,
                                this.full_name.as_bytes(),
                            )),
                        );
                    }
                    b"create" => stack.replace(ctx, ctx.registry().fetch(&create_fn)),
                    b"without" => stack.replace(ctx, ctx.registry().fetch(&without_fn)),
                    _ => (),
                }

                Ok(CallbackReturn::Return)
            }),
        )
        .unwrap();

    metatable
}