veloren_rtsim/lib.rs
1//! # What is rtsim?
2//!
3//! Rtsim - originally 'Real-Time Simulation' - is a high-level simulation of a
4//! Veloren world that runs in real-time alongside the game. It represents an
5//! abstract, 'low-resolution' model of the world and is designed for expressing
6//! and implementing long-running gameplay and behaviours.
7//!
8//! Rtsim simulates the entire world - even areas outside of loaded chunks -
9//! simultaneously and is designed to do so efficiently enough to handle the
10//! behaviour of large populations. An inexhaustive list of things that rtsim
11//! manages (or is intended to manage) is as follows:
12//!
13//! - High-level descriptions of NPCs and coordination of their behaviour (see
14//! `src/rule/npc_ai.rs`)
15//! - In-game factions, conflicts, and social dynamics
16//! - Sites, populations, migration, and territories
17//! - Economics, industry, trade, transport
18//! - Wildlife populations (both flora & fauna), natural disasters, climate
19//! - Architected events such as quests and emergent narratives
20//!
21//! Rtsim is explicitly *not* intended to handle the fine details of day-to-day
22//! gameplay. It deliberately does not handle low-level gameplay elements like
23//! items, combat, fine movement, physics, spawning and generation of
24//! small-scale world elements, etc. This in service of the wider goal of
25//! achieving full-world simulation at reasonable performance.
26//!
27//! # Philosophy of rtsim
28//!
29//! Rtsim balances several competing design goals against one-another. It must:
30//!
31//! - Be performant enough to simulate an entire Veloren world, containing tens
32//! of thousands of NPCs, in real-time
33//! - Have a data model that can be persisted and migrated fairly easily
34//! - Have simulation logic that is tolerant of unpredictability and imperfect [data migration](https://en.wikipedia.org/wiki/Schema_migration)
35//! - Allow the expression of complex, interconnected processes that tend toward
36//! a stable equilibrium over time
37//!
38//! Let's take each one at a time:
39//!
40//! ## Performance philosophy
41//!
42//! Rtsim is designed to run on both large servers and low-powered consumer
43//! hardware. One of the key ways that it achieves this is by throttling
44//! simulation tick rates, especially for distant entities or processes that do
45//! not require simulating with a high temporal granularity. This means that
46//! rtsim code should be deliberately designed to correctly handle variable tick
47//! rates without noticeable artifacts. Tick rates my vary not just over time,
48//! but also over space: it is quite possible, and even normal, for an NPC that
49//! is close to a loaded region to be updated at a frequency many times great
50//! than one that is further away. Rtsim code should also avoid expensive
51//! simulation within a single tick, preferring to spread the cost of more
52//! complex queries/calculations over many ticks, if possible.
53//!
54//! ## Data model philosophy
55//!
56//! Rtsim is deliberately designed with an almost aggressively simplistic data
57//! model to make persisting world state simpler. Most data can be expressed as
58//! simple tables mapping object IDs to the flat data structures they point to.
59//!
60//! However, it deliberately does not use an ECS-like architecture: due to the
61//! high-level nature of rtsim, data access patterns are often discontinuous and
62//! unpredictable so optimising for fast lookup and query times over iteration
63//! speed is preferable. You might say that rtsim behaves more like a simple
64//! relational database with a large body of business logic sitting on top of
65//! it.
66//!
67//! ## Defensive programming philosophy
68//!
69//! Another important factor to consider is that rtsim is unpredictable, and
70//! even chaotic, in the mathematical sense, by default. Something that might be
71//! true on one trick might stop being true on the next tick, and it is the
72//! responsibility of rtsim code to ruggedly handle any unexpected things that
73//! may occur as elegantly as possible.
74//!
75//! In short - and against the grain of your typical Rust programs - code should
76//! avoid trying to enforce invariants or assume invariants: the unhappy path
77//! *does* matter.
78//!
79//! Here are examples of things you should not assume:
80//!
81//! - That an object or NPC that exists in one tick will exist in the next tick
82//! (i.e: IDs may be invalidated at any time)
83//! - That the profession of an NPC in one tick will be the same as in the
84//! previous tick
85//! - That an NPC will be close to its current position in the next tick
86//! - That a vehicle will necessarily have a rider
87//! - That a NPC in a riding state is necessarily riding a vehicle right now
88//! (relationship invalidation)
89//! - That a message sent into the inbox of an NPC will be acknowledged, acted
90//! upon, or will even be received
91//! - That a 'two-way' relationship between objects or NPCs will necessarily
92//! exist or be the same when viewed in both directions
93//! - *TODO: add to this list!*
94//!
95//! ## Composition and complexity philosophy
96//!
97//! Veloren's world is complex, and complexity - at least, as experienced by
98//! players - is an explicit goal.
99//!
100//! However, it is crucially important to recognise the difference between
101//! inherent complexity and emergent complexity. If you know a thing or two
102//! about chaos theory, fractals, computability theory, or cybernetics this
103//! distinction will be clear to you: [arbitrarily complex behaviour can arise from simple rules](https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life).
104//!
105//! Where possible, rtsim should try to stick to simple rules that prioritise
106//! tractability, generality, and composability. It should be possible to
107//! disable a rule without the overall system collapsing. Rules should, if in
108//! doubt, push the system toward stability instead of chaos (via, for example,
109//! damping terms or parameter clamping), and should include guardrails that
110//! prevent exponential, run-away, or cyclical behaviour. This behaviour is
111//! occasionally desirable as the short-term consequence of an event (for
112//! example, a declaration of war between two factions *should* produce knock-on
113//! effects!) but every potentially run-away process should be ultimately
114//! constrained by a damping term that pretents unmanageable catastrophic
115//! effects (for example, variables representing the negative sentiment between
116//! two factions should have a hard cap such that they do not continue to spiral
117//! into an all-encompassing conflict that permanently overwhelms the world
118//! space).
119//!
120//! ## High-level design
121//!
122//! Rtsim is [`Event`]-based, and uses a system of compositional [`Rule`]s to
123//! react to these events. Consumers of the `rtsim` crate can also define their
124//! own rules and events. When rules are added to the [`RtState`], they bind
125//! event handlers to the events that they care about. These events get access
126//! to rtsim data and can mutate it in response to the event.
127//!
128//! Events are usually generated externally (such as by the Veloren game server)
129//! but may be generated internally too.
130//!
131//! See [`event`] for some examples of rtsim events.
132//!
133//! See the modules in [`rule`] for examples of rtsim rules.
134//!
135//! ## The NPC AI rule
136//!
137//! See [`rule::npc_ai`].
138
139#![feature(never_type, binary_heap_drain_sorted)]
140
141pub mod ai;
142pub mod data;
143pub mod event;
144pub mod generate;
145pub mod rule;
146
147pub use self::{
148 data::Data,
149 event::{Event, EventCtx, OnTick},
150 rule::{Rule, RuleError},
151};
152use anymap2::SendSyncAnyMap;
153use atomic_refcell::AtomicRefCell;
154use common::resources::{Time, TimeOfDay};
155use std::{
156 any::type_name,
157 ops::{Deref, DerefMut},
158};
159use tracing::{error, info};
160use world::{IndexRef, World};
161
162pub struct RtState {
163 resources: SendSyncAnyMap,
164 rules: SendSyncAnyMap,
165 event_handlers: SendSyncAnyMap,
166}
167
168type RuleState<R> = AtomicRefCell<R>;
169type EventHandlersOf<E> = Vec<
170 Box<
171 dyn Fn(&RtState, &World, IndexRef, &E, &mut <E as Event>::SystemData<'_>)
172 + Send
173 + Sync
174 + 'static,
175 >,
176>;
177
178impl RtState {
179 pub fn new(mut data: Data) -> Self {
180 data.prepare();
181
182 let mut this = Self {
183 resources: SendSyncAnyMap::new(),
184 rules: SendSyncAnyMap::new(),
185 event_handlers: SendSyncAnyMap::new(),
186 }
187 .with_resource(data);
188
189 this.start_default_rules();
190
191 this
192 }
193
194 pub fn with_resource<R: Send + Sync + 'static>(mut self, r: R) -> Self {
195 self.resources.insert(AtomicRefCell::new(r));
196 self
197 }
198
199 fn start_default_rules(&mut self) {
200 info!("Starting default rtsim rules...");
201 self.start_rule::<rule::migrate::Migrate>();
202 self.start_rule::<rule::architect::Architect>();
203 self.start_rule::<rule::replenish_resources::ReplenishResources>();
204 self.start_rule::<rule::report::ReportEvents>();
205 self.start_rule::<rule::sync_npcs::SyncNpcs>();
206 self.start_rule::<rule::simulate_npcs::SimulateNpcs>();
207 self.start_rule::<rule::npc_ai::NpcAi>();
208 self.start_rule::<rule::cleanup::CleanUp>();
209 }
210
211 pub fn start_rule<R: Rule>(&mut self) {
212 info!("Initiating '{}' rule...", type_name::<R>());
213 match R::start(self) {
214 Ok(rule) => {
215 self.rules.insert::<RuleState<R>>(AtomicRefCell::new(rule));
216 },
217 Err(e) => error!("Error when initiating '{}' rule: {}", type_name::<R>(), e),
218 }
219 }
220
221 fn rule_mut<R: Rule>(&self) -> impl DerefMut<Target = R> + '_ {
222 self.rules
223 .get::<RuleState<R>>()
224 .unwrap_or_else(|| {
225 panic!(
226 "Tried to access rule '{}' but it does not exist",
227 type_name::<R>()
228 )
229 })
230 .borrow_mut()
231 }
232
233 // TODO: Consider whether it's worth explicitly calling rule event handlers
234 // instead of allowing them to bind event handlers. Less modular, but
235 // potentially easier to deal with data dependencies?
236 pub fn bind<R: Rule, E: Event>(
237 &mut self,
238 f: impl FnMut(EventCtx<R, E>) + Send + Sync + 'static,
239 ) {
240 let f = AtomicRefCell::new(f);
241 self.event_handlers
242 .entry::<EventHandlersOf<E>>()
243 .or_default()
244 .push(Box::new(move |state, world, index, event, system_data| {
245 (f.borrow_mut())(EventCtx {
246 state,
247 rule: &mut state.rule_mut(),
248 event,
249 world,
250 index,
251 system_data,
252 })
253 }));
254 }
255
256 pub fn data(&self) -> impl Deref<Target = Data> + '_ { self.resource() }
257
258 pub fn data_mut(&self) -> impl DerefMut<Target = Data> + '_ { self.resource_mut() }
259
260 pub fn get_data_mut(&mut self) -> &mut Data { self.get_resource_mut() }
261
262 pub fn resource<R: Send + Sync + 'static>(&self) -> impl Deref<Target = R> + '_ {
263 self.resources
264 .get::<AtomicRefCell<R>>()
265 .unwrap_or_else(|| {
266 panic!(
267 "Tried to access resource '{}' but it does not exist",
268 type_name::<R>()
269 )
270 })
271 .borrow()
272 }
273
274 pub fn get_resource_mut<R: Send + Sync + 'static>(&mut self) -> &mut R {
275 self.resources
276 .get_mut::<AtomicRefCell<R>>()
277 .unwrap_or_else(|| {
278 panic!(
279 "Tried to access resource '{}' but it does not exist",
280 type_name::<R>()
281 )
282 })
283 .get_mut()
284 }
285
286 pub fn resource_mut<R: Send + Sync + 'static>(&self) -> impl DerefMut<Target = R> + '_ {
287 self.resources
288 .get::<AtomicRefCell<R>>()
289 .unwrap_or_else(|| {
290 panic!(
291 "Tried to access resource '{}' but it does not exist",
292 type_name::<R>()
293 )
294 })
295 .borrow_mut()
296 }
297
298 pub fn emit<E: Event>(
299 &mut self,
300 e: E,
301 system_data: &mut E::SystemData<'_>,
302 world: &World,
303 index: IndexRef,
304 ) {
305 // TODO: Queue these events up and handle them on a regular rtsim tick instead
306 // of executing their handlers immediately.
307 if let Some(handlers) = self.event_handlers.get::<EventHandlersOf<E>>() {
308 handlers
309 .iter()
310 .for_each(|f| f(self, world, index, &e, system_data));
311 }
312 }
313
314 pub fn tick(
315 &mut self,
316 system_data: &mut <OnTick as Event>::SystemData<'_>,
317 world: &World,
318 index: IndexRef,
319 time_of_day: TimeOfDay,
320 time: Time,
321 dt: f32,
322 ) {
323 let tick = {
324 let mut data = self.data_mut();
325 data.time_of_day = time_of_day;
326 data.tick += 1;
327 data.tick
328 };
329 let event = OnTick {
330 time_of_day,
331 tick,
332 time,
333 dt,
334 };
335 self.emit(event, system_data, world, index);
336 }
337}