forked from makspll/bevy_mod_scripting
-
Notifications
You must be signed in to change notification settings - Fork 0
/
complex_game_loop.rs
221 lines (190 loc) · 7.91 KB
/
complex_game_loop.rs
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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
use bevy::prelude::*;
use bevy_mod_scripting::prelude::*;
use rand::prelude::SliceRandom;
use std::sync::atomic::Ordering::Relaxed;
use std::sync::{atomic::AtomicU32, Mutex};
#[derive(Clone)]
pub struct MyLuaArg(usize);
impl<'lua> ToLua<'lua> for MyLuaArg {
fn to_lua(self, lua: &'lua Lua) -> mlua::Result<Value<'lua>> {
self.0.to_lua(lua)
}
}
#[derive(Default)]
pub struct LuaAPIProvider;
/// the custom Lua api, world is provided via a global pointer,
/// and callbacks are defined only once at script creation
impl APIProvider for LuaAPIProvider {
type APITarget = Mutex<Lua>;
type DocTarget = LuaDocFragment;
type ScriptContext = Mutex<Lua>;
fn attach_api(&mut self, ctx: &mut Self::APITarget) -> Result<(), ScriptError> {
// callbacks can receive any `ToLuaMulti` arguments, here '()' and
// return any `FromLuaMulti` arguments, here a `usize`
// check the Rlua documentation for more details
let ctx = ctx.get_mut().unwrap();
ctx.globals()
.set(
"print",
ctx.create_function(|_ctx, msg: String| {
info!("{}", msg);
Ok(())
})
.map_err(ScriptError::new_other)?,
)
.map_err(ScriptError::new_other)?;
Ok(())
}
}
static COUNTER: AtomicU32 = AtomicU32::new(0);
/// utility for generating random events from a list
fn fire_random_event(
w: &mut PriorityEventWriter<LuaEvent<mlua::Variadic<MyLuaArg>>>,
events: &[ScriptEventData],
) {
let mut rng = rand::thread_rng();
let id = COUNTER.fetch_add(1, Relaxed);
let arg = MyLuaArg(id as usize);
let (event, prio) = events
.choose(&mut rng)
.map(|v| {
let mut args = mlua::Variadic::new();
args.push(arg);
(
LuaEvent {
hook_name: v.0.to_string(),
args,
recipients: Recipients::All,
},
v.1,
)
})
.unwrap();
info!(
"\t - event: {},\t prio: {},\t id: {}",
event.hook_name, prio, id
);
w.send(event, prio);
}
fn do_some_shit_before_physics(mut w: PriorityEventWriter<LuaEvent<mlua::Variadic<MyLuaArg>>>) {
info!("PrePhysics, firing:");
for _ in 0..5 {
// fire random event, for any of the system sets
fire_random_event(&mut w, &ALL_EVENTS);
}
}
fn do_physics(mut w: PriorityEventWriter<LuaEvent<mlua::Variadic<MyLuaArg>>>) {
info!("Physics, firing:");
for _ in 0..5 {
// fire random event, for any of the system sets
fire_random_event(&mut w, &ALL_EVENTS);
}
}
fn do_update(mut w: PriorityEventWriter<LuaEvent<mlua::Variadic<MyLuaArg>>>) {
info!("Update, firing:");
// fire random event, for any of the system sets
fire_random_event(&mut w, &ALL_EVENTS);
}
#[derive(Clone, Copy)]
pub struct ScriptEventData(&'static str, u32);
static ON_PRE_PHYSICS_ONE: ScriptEventData = ScriptEventData("on_pre_physics_one", 0);
static ON_PRE_PHYSICS_TWO: ScriptEventData = ScriptEventData("on_pre_physics_two", 1);
static ON_POST_PHYSICS_ONE: ScriptEventData = ScriptEventData("on_post_physics_one", 11);
static ON_POST_PHYSICS_TWO: ScriptEventData = ScriptEventData("on_post_physics_two", 12);
static ON_POST_UPDATE_ONE: ScriptEventData = ScriptEventData("on_post_update_one", 21);
static ON_POST_UPDATE_TWO: ScriptEventData = ScriptEventData("on_post_update_two", 22);
static ALL_EVENTS: [ScriptEventData; 6] = [
ON_PRE_PHYSICS_ONE,
ON_PRE_PHYSICS_TWO,
ON_POST_PHYSICS_ONE,
ON_POST_PHYSICS_TWO,
ON_POST_UPDATE_ONE,
ON_POST_UPDATE_TWO,
];
fn load_our_script(server: Res<AssetServer>, mut commands: Commands) {
let path = "scripts/complex_game_loop.lua";
let handle = server.load::<LuaFile, &str>(path);
commands.spawn(()).insert(ScriptCollection::<LuaFile> {
scripts: vec![Script::<LuaFile>::new(path.to_string(), handle)],
});
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, SystemSet)]
enum ComplexGameLoopSet {
PrePhysics,
Physics,
PrePhysicsScripts,
PostPhysicsScripts,
PostUpdateScripts,
}
fn main() -> std::io::Result<()> {
const TIMESTEP_2_PER_SECOND: f32 = 30.0 / 60.0;
let mut app = App::new();
app.add_plugins(DefaultPlugins)
.insert_resource(FixedTime::new_from_secs(TIMESTEP_2_PER_SECOND))
.add_plugin(ScriptingPlugin)
.add_startup_system(load_our_script)
// --- main systems sets
// physics logic system set (twice a second)
.configure_set(ComplexGameLoopSet::Physics.before(CoreSet::Update))
.add_system(
do_physics
.in_set(ComplexGameLoopSet::Physics)
.in_schedule(CoreSchedule::FixedUpdate),
)
// pre physics logic system set (twice a second)
.configure_set(ComplexGameLoopSet::PrePhysics.before(ComplexGameLoopSet::Physics))
.add_system(
do_some_shit_before_physics
.in_set(ComplexGameLoopSet::PrePhysics)
.in_schedule(CoreSchedule::FixedUpdate),
)
// main update logic system set (every frame)
.add_system(do_update)
// --- script handler system sets
// pre_physics, priority: [0,10] inclusive
.configure_set(ComplexGameLoopSet::PrePhysicsScripts.after(ComplexGameLoopSet::PrePhysics))
.add_system(
script_event_handler::<LuaScriptHost<mlua::Variadic<MyLuaArg>>, 0, 10>
.in_set(ComplexGameLoopSet::PrePhysicsScripts)
.in_schedule(CoreSchedule::FixedUpdate),
)
// post_physics, priority: [11,20] inclusive
// since the previous system will consume all events in the [0,10] range
.configure_set(ComplexGameLoopSet::PostPhysicsScripts.after(ComplexGameLoopSet::Physics))
.add_system(
script_event_handler::<LuaScriptHost<mlua::Variadic<MyLuaArg>>, 11, 20>
.in_set(ComplexGameLoopSet::PostPhysicsScripts)
.in_schedule(CoreSchedule::FixedUpdate),
)
// post_update, priority: [21,30] inclusive
// note we do not use the CoreSet version since our scripts might want
// to modify transforms etc which some core systems synchronise in here
.configure_set(ComplexGameLoopSet::PostUpdateScripts.in_base_set(CoreSet::PostUpdate))
.add_script_handler_to_set::<LuaScriptHost<mlua::Variadic<MyLuaArg>>, _, 21, 30>(
ComplexGameLoopSet::PostUpdateScripts,
)
// this system set handles addition and removal of script contexts, we can safely use `CoreSet::PostUpdate`
.add_script_host_to_base_set::<LuaScriptHost<mlua::Variadic<MyLuaArg>>, _>(
CoreSet::PostUpdate,
)
.add_api_provider::<LuaScriptHost<mlua::Variadic<MyLuaArg>>>(Box::new(LuaAPIProvider));
// We have 3 core systems
// PrePhysics (twice per second), fires 5 random events
// Physics (twice per second), fires 5 random events
// Update (every frame), fires 1 random event
// and 3 event handlers
// pre_physics (twice per second)
// post_physics (twice per second)
// post_update (every frame)
// each of those spawns a single random event from the pool of all events
// when a handler encounters an event of higher priority outside its range, that event is discarded
// when a handler encounters an event of lower priority outside its range, it's left in the queue
// therefore
// in our case, PrePhysics systems can generate events which can be handled by post_update,
// but Update cannot send events which are handled by anything other than post_update
// note that regardless of the order in which the events were spawned
// priority decides the order in which they are executed
// in case of identical priority, order is the tie-breaker (earlier events launch first)
app.run();
Ok(())
}