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, let_chains, binary_heap_drain_sorted)]
140
141pub mod ai;
142pub mod data;
143pub mod event;
144pub mod gen;
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(data: Data) -> Self {
180 let mut this = Self {
181 resources: SendSyncAnyMap::new(),
182 rules: SendSyncAnyMap::new(),
183 event_handlers: SendSyncAnyMap::new(),
184 }
185 .with_resource(data);
186
187 this.start_default_rules();
188
189 this
190 }
191
192 pub fn with_resource<R: Send + Sync + 'static>(mut self, r: R) -> Self {
193 self.resources.insert(AtomicRefCell::new(r));
194 self
195 }
196
197 fn start_default_rules(&mut self) {
198 info!("Starting default rtsim rules...");
199 self.start_rule::<rule::migrate::Migrate>();
200 self.start_rule::<rule::replenish_resources::ReplenishResources>();
201 self.start_rule::<rule::report::ReportEvents>();
202 self.start_rule::<rule::sync_npcs::SyncNpcs>();
203 self.start_rule::<rule::simulate_npcs::SimulateNpcs>();
204 self.start_rule::<rule::npc_ai::NpcAi>();
205 self.start_rule::<rule::cleanup::CleanUp>();
206 }
207
208 pub fn start_rule<R: Rule>(&mut self) {
209 info!("Initiating '{}' rule...", type_name::<R>());
210 match R::start(self) {
211 Ok(rule) => {
212 self.rules.insert::<RuleState<R>>(AtomicRefCell::new(rule));
213 },
214 Err(e) => error!("Error when initiating '{}' rule: {}", type_name::<R>(), e),
215 }
216 }
217
218 fn rule_mut<R: Rule>(&self) -> impl DerefMut<Target = R> + '_ {
219 self.rules
220 .get::<RuleState<R>>()
221 .unwrap_or_else(|| {
222 panic!(
223 "Tried to access rule '{}' but it does not exist",
224 type_name::<R>()
225 )
226 })
227 .borrow_mut()
228 }
229
230 // TODO: Consider whether it's worth explicitly calling rule event handlers
231 // instead of allowing them to bind event handlers. Less modular, but
232 // potentially easier to deal with data dependencies?
233 pub fn bind<R: Rule, E: Event>(
234 &mut self,
235 f: impl FnMut(EventCtx<R, E>) + Send + Sync + 'static,
236 ) {
237 let f = AtomicRefCell::new(f);
238 self.event_handlers
239 .entry::<EventHandlersOf<E>>()
240 .or_default()
241 .push(Box::new(move |state, world, index, event, system_data| {
242 (f.borrow_mut())(EventCtx {
243 state,
244 rule: &mut state.rule_mut(),
245 event,
246 world,
247 index,
248 system_data,
249 })
250 }));
251 }
252
253 pub fn data(&self) -> impl Deref<Target = Data> + '_ { self.resource() }
254
255 pub fn data_mut(&self) -> impl DerefMut<Target = Data> + '_ { self.resource_mut() }
256
257 pub fn get_data_mut(&mut self) -> &mut Data { self.get_resource_mut() }
258
259 pub fn resource<R: Send + Sync + 'static>(&self) -> impl Deref<Target = R> + '_ {
260 self.resources
261 .get::<AtomicRefCell<R>>()
262 .unwrap_or_else(|| {
263 panic!(
264 "Tried to access resource '{}' but it does not exist",
265 type_name::<R>()
266 )
267 })
268 .borrow()
269 }
270
271 pub fn get_resource_mut<R: Send + Sync + 'static>(&mut self) -> &mut R {
272 self.resources
273 .get_mut::<AtomicRefCell<R>>()
274 .unwrap_or_else(|| {
275 panic!(
276 "Tried to access resource '{}' but it does not exist",
277 type_name::<R>()
278 )
279 })
280 .get_mut()
281 }
282
283 pub fn resource_mut<R: Send + Sync + 'static>(&self) -> impl DerefMut<Target = R> + '_ {
284 self.resources
285 .get::<AtomicRefCell<R>>()
286 .unwrap_or_else(|| {
287 panic!(
288 "Tried to access resource '{}' but it does not exist",
289 type_name::<R>()
290 )
291 })
292 .borrow_mut()
293 }
294
295 pub fn emit<E: Event>(
296 &mut self,
297 e: E,
298 system_data: &mut E::SystemData<'_>,
299 world: &World,
300 index: IndexRef,
301 ) {
302 // TODO: Queue these events up and handle them on a regular rtsim tick instead
303 // of executing their handlers immediately.
304 if let Some(handlers) = self.event_handlers.get::<EventHandlersOf<E>>() {
305 handlers
306 .iter()
307 .for_each(|f| f(self, world, index, &e, system_data));
308 }
309 }
310
311 pub fn tick(
312 &mut self,
313 system_data: &mut <OnTick as Event>::SystemData<'_>,
314 world: &World,
315 index: IndexRef,
316 time_of_day: TimeOfDay,
317 time: Time,
318 dt: f32,
319 ) {
320 let tick = {
321 let mut data = self.data_mut();
322 data.time_of_day = time_of_day;
323 data.tick += 1;
324 data.tick
325 };
326 let event = OnTick {
327 time_of_day,
328 tick,
329 time,
330 dt,
331 };
332 self.emit(event, system_data, world, index);
333 }
334}