Skip to main content

vimp_engine_core/
sim.rs

1//! Граница движковый-цикл ↔ игровая симуляция (Этап 4a.1 плана распила
2//! движка/игры, PLAN.md §3.6). `EngineSim<G>` (core/src/game.rs) владеет
3//! физическим миром, картой, нав-графом/сеткой ботов, PRNG и очередью
4//! удаления тел; конкретная игра (`TanksSim` и т.п.) реализует `GameSim<G>`
5//! и получает доступ к движковому через `SimCtx` только там, где нужен
6//! шаг тика (`on_fixed_step`/`on_contacts`/`on_before_destroy`/`on_ai_tick`).
7//! Один wasm-бандл — одна игра: мономорфизация `EngineSim<TanksGame>` без
8//! динамической диспетчеризации.
9
10use rapier2d::prelude::*;
11
12use crate::nav::navigation::NavigationSystem;
13use crate::nav::spatial::SpatialGrid;
14use crate::config::{EngineConfig, PLAYER_STATE_LEN};
15use crate::events::CoreEvent;
16use crate::map::GameMap;
17use crate::rng::Rng;
18use crate::snapshot::Block;
19
20/// Связывает конкретную игру с её типом симуляции и конфигом. `Config` —
21/// игровая половина init-JSON (модели/оружие/панель и т.п.); движковая
22/// половина — `crate::config::EngineConfig`, общая для любой игры.
23pub trait GameDef: Sized {
24    type Config: serde::de::DeserializeOwned;
25    type Sim: GameSim<Self>;
26}
27
28/// Контекст движкового тика, передаваемый игровым callback'ам.
29pub struct SimCtx<'a> {
30    pub world: &'a mut PhysicsWorld,
31    pub cfg: &'a EngineConfig,
32    pub map: &'a Option<GameMap>,
33    pub nav: &'a Option<NavigationSystem>,
34    pub spatial: &'a mut SpatialGrid,
35    pub rng: &'a mut Rng,
36    pub events: &'a mut Vec<CoreEvent>,
37    pub bodies_to_destroy: &'a mut Vec<RigidBodyHandle>,
38}
39
40/// Игровая симуляция поверх движкового каркаса: участники, оружие,
41/// снапшот-блоки. Игровой конфиг (`G::Config`) передаётся только в `new` —
42/// реализация хранит нужные ей куски сама (модели/оружие не меняются в
43/// течение комнаты), поэтому остальные методы им не параметризованы.
44/// Методы вне тика получают только то, что им реально нужно (не весь
45/// `SimCtx`), тиковые callback'ы — `SimCtx`, т.к. в них нужен произвольный
46/// набор движковых ресурсов одновременно.
47#[allow(clippy::too_many_arguments)]
48pub trait GameSim<G: GameDef>: Sized {
49    /// `engine_cfg` — доступен только на конструкторе (напр. `time_step` для
50    /// расчёта размера кольцевых буферов по времени жизни снарядов);
51    /// остальные методы движковый конфиг не получают — см. `SimCtx::cfg`.
52    fn new(cfg: &G::Config, engine_cfg: &EngineConfig) -> Self;
53
54    fn spawn_actor(
55        &mut self,
56        world: &mut PhysicsWorld,
57        events: &mut Vec<CoreEvent>,
58        game_id: u32,
59        model_name: &str,
60        team_id: u8,
61        x: f32,
62        y: f32,
63        angle_deg: f32,
64    ) -> Result<(), String>;
65    fn remove_actor(&mut self, world: &mut PhysicsWorld, game_id: u32);
66    fn reset_actor(&mut self, world: &mut PhysicsWorld, game_id: u32, team_id: u8, x: f32, y: f32, angle_deg: f32);
67    fn reset_all_vitals(&mut self, events: &mut Vec<CoreEvent>);
68    fn spawn_scripted_actor(
69        &mut self,
70        world: &mut PhysicsWorld,
71        rng: &mut Rng,
72        events: &mut Vec<CoreEvent>,
73        game_id: u32,
74        model_name: &str,
75        team_id: u8,
76        x: f32,
77        y: f32,
78        angle_deg: f32,
79    ) -> Result<(), String>;
80    fn remove_scripted_actor(&mut self, world: &mut PhysicsWorld, game_id: u32);
81
82    fn apply_input(&mut self, game_id: u32, seq: u32, action: &str, key_name: &str);
83
84    /// Аналоговый ввод указателя (мышь/палец): мировая точка и биты
85    /// состояния указателя — бит 0 «прижат», бит 1 «двойной тап».
86    /// Дефолт пустой: игра, не знающая про указатель, ничего не замечает,
87    /// а движок шлёт этот канал только если игра его объявила.
88    fn apply_aim(&mut self, _game_id: u32, _seq: u32, _x: f32, _y: f32, _flags: u32) {}
89    fn last_input_seq(&self, game_id: u32) -> u32;
90    fn is_alive(&self, game_id: u32) -> bool;
91    fn actor_position(&self, world: &PhysicsWorld, game_id: u32) -> Option<[f32; 2]>;
92    fn prediction_state(&self, world: &PhysicsWorld, game_id: u32) -> Option<([f32; PLAYER_STATE_LEN], bool)>;
93    fn alive_players_flat(&self, world: &PhysicsWorld) -> Vec<f32>;
94    fn players_json(&self) -> String;
95
96    /// Игровая логика фиксированного шага (движение, спавн снарядов,
97    /// детонация по истечению времени) — до шага физики.
98    fn on_fixed_step(&mut self, ctx: &mut SimCtx, dt: f32);
99    /// Контакты, начавшиеся на этом шаге физики (после `world.step`).
100    fn on_contacts(&mut self, ctx: &mut SimCtx, pairs: &[(ColliderHandle, ColliderHandle)]);
101    /// Движок зовёт перед удалением тела из очереди уничтожения —
102    /// возможность прочитать тег и обновить свою бухгалтерию.
103    fn on_before_destroy(&mut self, world: &PhysicsWorld, handle: RigidBodyHandle);
104    /// ИИ скриптовых участников; сама решает, есть ли для неё работа.
105    fn on_ai_tick(&mut self, ctx: &mut SimCtx, dt: f32);
106
107    fn refresh_cached(&mut self, world: &PhysicsWorld);
108    /// Собирает игровые блоки снапшота (без динамики карты — её строит
109    /// движок) и признак "были события" (для классификации канала WebRTC).
110    fn build_snapshot_blocks(&mut self) -> (Vec<(String, Block)>, bool);
111
112    fn remove_players_and_shots(&mut self, world: &mut PhysicsWorld) -> Vec<String>;
113    fn clear(&mut self);
114
115    fn serialize(&self) -> serde_json::Value;
116    fn deserialize(&mut self, value: serde_json::Value) -> Result<(), String>;
117    fn rebuild_spatial_grid(&self, world: &PhysicsWorld, spatial: &mut SpatialGrid);
118}