mangrove_script/
logic.rs

1/*
2 * Copyright (c) Peter Bjorklund. All rights reserved. https://github.com/swamp/mangrove
3 * Licensed under the MIT License. See LICENSE in the project root for license information.
4 */
5
6use crate::err::show_mangrove_error;
7use crate::script::{compile, MangroveError};
8use crate::util::{get_impl_func, get_impl_func_optional};
9use crate::{ErrorResource, ScriptMessage, SourceMapResource};
10use limnus_clock::Clock;
11use limnus_gamepad::{Axis, AxisValueType, Button, ButtonValueType, GamePadId, GamepadMessage};
12use monotonic_time_rs::{Millis, MillisDuration};
13use std::cell::RefCell;
14use std::rc::Rc;
15use swamp::prelude::{App, Fp, LoRe, LoReM, LocalResource, Msg, Plugin, Re, ReM, UpdatePhase};
16use swamp_script::prelude::*;
17
18/// # Panics
19///
20pub fn logic_tick(
21    mut script: LoReM<ScriptLogic>,
22    _source_map: Re<SourceMapResource>,
23    clock: LoRe<Clock>,
24    error: Re<ErrorResource>,
25) {
26    if clock.clock.now() < script.next_time {
27        return;
28    }
29
30    script.next_time += MillisDuration::from_millis(16);
31
32    //let lookup: &dyn SourceMapLookup = &source_map.wrapper;
33    if error.has_errors {
34        return;
35    }
36    script.tick(None).expect("script.tick() crashed");
37}
38
39pub fn input_tick(mut script: LoReM<ScriptLogic>, gamepad_messages: Msg<GamepadMessage>) {
40    for gamepad_message in gamepad_messages.iter_current() {
41        script.gamepad(gamepad_message);
42    }
43}
44
45#[derive(Debug)]
46pub struct ScriptLogicContext {}
47
48#[derive(LocalResource, Debug)]
49pub struct ScriptLogic {
50    logic_value_ref: ValueRef,
51    logic_fn: ResolvedInternalFunctionDefinitionRef,
52    gamepad_axis_changed_fn: Option<ResolvedInternalFunctionDefinitionRef>,
53    gamepad_button_changed_fn: Option<ResolvedInternalFunctionDefinitionRef>,
54    external_functions: ExternalFunctions<ScriptLogicContext>,
55    constants: Constants,
56    script_context: ScriptLogicContext,
57    resolved_program: ResolvedProgram,
58    input_module: ResolvedModuleRef,
59    next_time: Millis,
60}
61
62impl ScriptLogic {
63    pub fn new(
64        logic_value_ref: ValueRef,
65        logic_fn: ResolvedInternalFunctionDefinitionRef,
66        gamepad_axis_changed_fn: Option<ResolvedInternalFunctionDefinitionRef>,
67        gamepad_button_changed_fn: Option<ResolvedInternalFunctionDefinitionRef>,
68        external_functions: ExternalFunctions<ScriptLogicContext>,
69        constants: Constants,
70        resolved_program: ResolvedProgram,
71        input_module: ResolvedModuleRef,
72        now: Millis,
73    ) -> Self {
74        Self {
75            logic_value_ref,
76            logic_fn,
77            gamepad_axis_changed_fn,
78            gamepad_button_changed_fn,
79            external_functions,
80            script_context: ScriptLogicContext {},
81            constants,
82            resolved_program,
83            input_module,
84            next_time: now,
85        }
86    }
87
88    #[must_use]
89    pub fn immutable_logic_value(&self) -> Value {
90        self.logic_value_ref.borrow().clone()
91    }
92
93    /// # Panics
94    ///
95    #[must_use]
96    pub fn main_module(&self) -> ResolvedModuleRef {
97        let root_module_path = &["logic".to_string()].to_vec();
98
99        self.resolved_program
100            .modules
101            .get(root_module_path)
102            .expect("logic module should exist in logic")
103    }
104
105    /// # Errors
106    ///
107    pub fn tick(
108        &mut self,
109        debug_source_map: Option<&dyn SourceMapLookup>,
110    ) -> Result<(), ExecuteError> {
111        let variable_value_ref =
112            VariableValue::Reference(ValueReference(self.logic_value_ref.clone()));
113        let _ = util_execute_function(
114            &self.external_functions,
115            &self.constants,
116            &self.logic_fn,
117            &[variable_value_ref],
118            &mut self.script_context,
119            debug_source_map,
120        )?;
121
122        Ok(())
123    }
124
125    fn execute(
126        &mut self,
127        fn_def: &ResolvedInternalFunctionDefinitionRef,
128        arguments: &[Value],
129    ) -> Result<(), ExecuteError> {
130        let mut complete_arguments = Vec::new();
131        complete_arguments.push(VariableValue::Reference(ValueReference(
132            self.logic_value_ref.clone(),
133        ))); // push logic self first
134        for arg in arguments {
135            complete_arguments.push(VariableValue::Value(arg.clone()));
136        }
137
138        let _ = util_execute_function(
139            &self.external_functions,
140            &self.constants,
141            fn_def,
142            &complete_arguments,
143            &mut self.script_context,
144            None,
145        )?;
146
147        Ok(())
148    }
149
150    pub fn gamepad(&mut self, msg: &GamepadMessage) {
151        match msg {
152            GamepadMessage::Connected(_, _) => {}
153            GamepadMessage::Disconnected(_) => {}
154            GamepadMessage::Activated(_) => {}
155            GamepadMessage::ButtonChanged(gamepad_id, button, value) => {
156                self.button_changed(*gamepad_id, *button, *value);
157            }
158            GamepadMessage::AxisChanged(gamepad_id, axis, value) => {
159                self.axis_changed(*gamepad_id, *axis, *value);
160            }
161        }
162    }
163
164    fn axis_changed(&mut self, gamepad_id: GamePadId, axis: Axis, value: AxisValueType) {
165        let script_axis_value = {
166            let input_module_ref = self.input_module.borrow();
167            let axis_str = match axis {
168                Axis::LeftStickX => "LeftStickX",
169                Axis::LeftStickY => "LeftStickY",
170                Axis::RightStickX => "RightStickX",
171                Axis::RightStickY => "RightStickY",
172            };
173
174            let variant = input_module_ref
175                .namespace
176                .borrow()
177                .get_enum_variant_type_str("Axis", axis_str)
178                .expect("axis")
179                .clone();
180
181            Value::EnumVariantSimple(variant)
182        };
183
184        if let Some(found_fn) = &self.gamepad_axis_changed_fn {
185            let gamepad_id_value = Value::Int(gamepad_id as i32);
186            let axis_value = Value::Float(Fp::from(value));
187
188            let fn_ref = found_fn.clone();
189
190            self.execute(&fn_ref, &[gamepad_id_value, script_axis_value, axis_value])
191                .expect("gamepad_axis_changed");
192        }
193    }
194
195    fn button_changed(&mut self, gamepad_id: GamePadId, button: Button, value: ButtonValueType) {
196        let script_button_value = {
197            let input_module_ref = self.input_module.borrow();
198            let button_str = match button {
199                Button::South => "South",
200                Button::East => "East",
201                Button::North => "North",
202                Button::West => "West",
203                Button::LeftTrigger => "LeftTrigger",
204                Button::LeftTrigger2 => "LeftTrigger2",
205                Button::RightTrigger => "RightTrigger",
206                Button::RightTrigger2 => "RightTrigger2",
207                Button::Select => "Select",
208                Button::Start => "Start",
209                Button::Mode => "Mode",
210                Button::LeftThumb => "LeftThumb",
211                Button::RightThumb => "RightThumb",
212                Button::DPadUp => "DPadUp",
213                Button::DPadDown => "DPadDown",
214                Button::DPadLeft => "DPadLeft",
215                Button::DPadRight => "DPadRight",
216            };
217
218            let variant = input_module_ref
219                .namespace
220                .borrow()
221                .get_enum_variant_type_str("Button", button_str)
222                .expect("button name failed")
223                .clone();
224
225            Value::EnumVariantSimple(variant)
226        };
227
228        if let Some(found_fn) = &self.gamepad_button_changed_fn {
229            let gamepad_id_value = Value::Int(
230                i32::try_from(gamepad_id).expect("could not convert gamepad button to i32"),
231            );
232            let button_value = Value::Float(Fp::from(value));
233
234            let fn_ref = found_fn.clone();
235
236            self.execute(
237                &fn_ref,
238                &[gamepad_id_value, script_button_value, button_value],
239            )
240            .expect("gamepad_button_changed");
241        }
242    }
243}
244
245/// # Errors
246///
247pub fn input_module(
248    resolve_state: &mut ResolvedProgramState,
249) -> Result<(ResolvedModule, ResolvedEnumTypeRef, ResolvedEnumTypeRef), ResolveError> {
250    let module_path = ["input".to_string()];
251    let module = ResolvedModule::new(&module_path);
252
253    let axis_enum_type_ref = {
254        let axis_enum_type_id = resolve_state.allocate_number(); // TODO: HACK
255
256        let parent = ResolvedEnumType {
257            name: ResolvedLocalTypeIdentifier(ResolvedNode {
258                span: Span::default(),
259            }),
260            assigned_name: "Axis".to_string(),
261            module_path: Vec::from(module_path.clone()),
262            number: axis_enum_type_id,
263        };
264        let parent_ref = Rc::new(parent);
265
266        let axis_enum_type_ref = module.namespace.borrow_mut().add_enum_type(parent_ref)?;
267
268        let variant_names = ["LeftStickX", "LeftStickY", "RightStickX", "RightStickY"];
269        for variant_name in variant_names {
270            let variant_type_id = resolve_state.allocate_number(); // TODO: HACK
271            let variant = ResolvedEnumVariantType::new(
272                axis_enum_type_ref.clone(),
273                ResolvedLocalTypeIdentifier(ResolvedNode {
274                    span: Span::default(),
275                }),
276                variant_name,
277                ResolvedEnumVariantContainerType::Nothing,
278                variant_type_id,
279            );
280            module
281                .namespace
282                .borrow_mut()
283                .add_enum_variant("Axis", variant_name, variant)?;
284        }
285        axis_enum_type_ref
286    };
287
288    let button_enum_type_ref = {
289        let button_enum_type_id = resolve_state.allocate_number(); // TODO: HACK
290                                                                   // let button_enum_type_id = resolve_state.allocate_number(); // TODO: HACK
291        let parent = ResolvedEnumType {
292            name: ResolvedLocalTypeIdentifier(ResolvedNode {
293                span: Span::default(),
294            }),
295            assigned_name: "Button".to_string(),
296            module_path: Vec::from(module_path),
297            number: button_enum_type_id,
298        };
299        let parent_ref = Rc::new(parent);
300        let button_enum_type_ref = module.namespace.borrow_mut().add_enum_type(parent_ref)?;
301
302        let button_names = [
303            "South",
304            "East",
305            "North",
306            "West",
307            "LeftTrigger",
308            "LeftTrigger2",
309            "RightTrigger",
310            "RightTrigger2",
311            "Select",
312            "Start",
313            "Mode",
314            "LeftThumb",
315            "RightThumb",
316            "DPadUp",
317            "DPadDown",
318            "DPadLeft",
319            "DPadRight",
320        ];
321
322        for button_variant_name in button_names {
323            let variant_type_id = resolve_state.allocate_number(); // TODO: HACK
324            let variant = ResolvedEnumVariantType {
325                owner: button_enum_type_ref.clone(),
326                data: ResolvedEnumVariantContainerType::Nothing,
327                name: ResolvedLocalTypeIdentifier(ResolvedNode {
328                    span: Span::default(),
329                }),
330                assigned_name: button_variant_name.to_string(),
331                number: variant_type_id,
332            };
333
334            module.namespace.borrow_mut().add_enum_variant(
335                "Button",
336                button_variant_name,
337                variant,
338            )?;
339        }
340        button_enum_type_ref
341    };
342
343    Ok((module, axis_enum_type_ref, button_enum_type_ref))
344}
345
346/// # Errors
347///
348/// # Panics
349///
350pub fn boot(source_map: &mut SourceMapResource, now: Millis) -> Result<ScriptLogic, MangroveError> {
351    let mut resolved_program = ResolvedProgram::new();
352    let mut external_functions = ExternalFunctions::<ScriptLogicContext>::new();
353
354    let (input_module, _axis_enum_type, _button_enum_type) =
355        input_module(&mut resolved_program.state)?;
356    let input_module_ref = Rc::new(RefCell::new(input_module));
357    resolved_program.modules.add(input_module_ref.clone());
358
359    let base_path = source_map.base_path().to_path_buf();
360
361    compile(
362        base_path.as_path(),
363        "logic.swamp",
364        &["logic".to_string()],
365        &mut resolved_program,
366        &mut external_functions,
367        &mut source_map.wrapper.source_map,
368        "logic",
369    )?;
370
371    let root_module_path = &["logic".to_string()];
372    let main_fn = {
373        let main_module = resolved_program
374            .modules
375            .get(root_module_path)
376            .expect("could not find main module");
377
378        let binding = main_module.borrow();
379        let function_ref = binding
380            .namespace
381            .borrow()
382            .get_internal_function("main")
383            .expect("No main function")
384            .clone();
385
386        Rc::clone(&function_ref) // Clone the Rc, not the inner value
387    };
388
389    let mut script_context = ScriptLogicContext {};
390    resolved_program.modules.finalize()?;
391    let mut constants = Constants::new();
392    eval_constants(
393        &external_functions,
394        &mut constants,
395        &resolved_program.modules,
396        &mut script_context,
397    )?;
398
399    let logic_value = util_execute_function(
400        &external_functions,
401        &constants,
402        &main_fn,
403        &[],
404        &mut script_context,
405        None,
406    )?;
407
408    let Value::Struct(logic_struct_type_ref, _) = &logic_value else {
409        return Err(MangroveError::Other("needs to be logic struct".to_string()));
410    };
411
412    let logic_fn = get_impl_func(logic_struct_type_ref, "tick");
413    let gamepad_axis_changed_fn =
414        get_impl_func_optional(logic_struct_type_ref, "gamepad_axis_changed");
415    let gamepad_button_changed_fn =
416        get_impl_func_optional(logic_struct_type_ref, "gamepad_button_changed");
417
418    // Convert it to a mutable (reference), so it can be mutated in update ticks
419    let logic_value_ref = Rc::new(RefCell::new(logic_value));
420
421    Ok(ScriptLogic::new(
422        logic_value_ref,
423        logic_fn,
424        gamepad_axis_changed_fn,
425        gamepad_button_changed_fn,
426        external_functions,
427        constants,
428        resolved_program,
429        input_module_ref,
430        now,
431    ))
432}
433
434pub fn detect_reload_tick(
435    script_messages: Msg<ScriptMessage>,
436    mut script_logic: LoReM<ScriptLogic>,
437    mut source_map_resource: ReM<SourceMapResource>,
438    clock: LoRe<Clock>,
439    mut err: ReM<ErrorResource>,
440) {
441    for msg in script_messages.iter_previous() {
442        match msg {
443            ScriptMessage::Reload => match boot(&mut source_map_resource, clock.clock.now()) {
444                Ok(new_logic) => *script_logic = new_logic,
445                Err(mangrove_error) => {
446                    show_mangrove_error(&mangrove_error, &source_map_resource.wrapper.source_map);
447                    err.has_errors = true;
448
449                    //                    eprintln!("script logic failed: {}", mangrove_error);
450                    //                    error!(error=?mangrove_error, "script logic compile failed");
451                }
452            },
453        }
454    }
455}
456
457pub struct ScriptLogicPlugin;
458
459impl Plugin for ScriptLogicPlugin {
460    fn build(&self, app: &mut App) {
461        app.add_system(UpdatePhase::Update, detect_reload_tick);
462        app.add_system(UpdatePhase::Update, logic_tick);
463        app.add_system(UpdatePhase::Update, input_tick);
464
465        let now = app.local_resources().fetch::<Clock>().clock.now();
466
467        // HACK: Just add a completely zeroed out ScriptLogic and wait for reload message.
468        // TODO: Should not try to call updates with params that are not available yet.
469        app.insert_local_resource(ScriptLogic {
470            logic_value_ref: Rc::new(RefCell::new(Value::default())),
471            logic_fn: Rc::new(ResolvedInternalFunctionDefinition {
472                body: ResolvedExpression::Break(ResolvedNode::default()),
473                name: ResolvedLocalIdentifier(ResolvedNode::default()),
474                signature: FunctionTypeSignature {
475                    first_parameter_is_self: false,
476                    parameters: vec![],
477                    return_type: Box::from(ResolvedType::Any),
478                },
479                constants: vec![],
480            }),
481            gamepad_axis_changed_fn: None,
482            gamepad_button_changed_fn: None,
483            external_functions: ExternalFunctions::new(),
484            constants: Constants { values: vec![] },
485            script_context: ScriptLogicContext {},
486            resolved_program: ResolvedProgram {
487                state: ResolvedProgramState {
488                    array_types: vec![],
489                    number: 0,
490                    external_function_number: 0,
491                },
492                modules: ResolvedModules::default(),
493            },
494            input_module: Rc::new(RefCell::new(ResolvedModule {
495                definitions: vec![],
496                expression: None,
497                namespace: Rc::new(RefCell::new(ResolvedModuleNamespace::new(&[]))),
498            })),
499            next_time: now,
500        });
501    }
502}