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}