decscloud_common/
lib.rs

1#[macro_use]
2extern crate serde_derive;
3
4#[macro_use]
5extern crate serde_json;
6
7pub mod gateway {
8    //! Support for the RES protocol (e.g. the RESgate server)
9
10    /// The RES protocol uniquely identifies all resources with a Resource ID
11    #[derive(Debug, Serialize, Deserialize, Default, PartialEq, Clone)]
12    pub struct ResourceIdentifier {
13        /// The resource ID
14        pub rid: String,
15    }
16
17    /// Generates a positive RES protocol result containing a model
18    pub fn model_result(model: serde_json::Value) -> serde_json::Value {
19        json!({
20            "result" :{
21                "model" : model
22            }
23        })
24    }
25
26    /// Generates a RES protocol error indicating not found (e.g. HTTP 404)
27    pub fn error_not_found(msg: &str) -> serde_json::Value {
28        json!({
29            "error": {
30                "code": "system.notFound",
31                "message": msg
32            }
33        })
34    }
35
36    /// Generates a RES protocol error indicating invalid parameters (e.g. HTTP bad request)
37    pub fn error_invalid_params(msg: &str) -> serde_json::Value {
38        json!({
39            "error": {
40                "code": "system.invalidParams",
41                "message": msg
42            }
43        })
44    }
45
46    /// Generates a RES protocol success response with no payload
47    pub fn success_response() -> serde_json::Value {
48        json!({ "result": null })
49    }
50
51    /// Represents the intent of a RES protocol request as described by a message broker subject
52    #[derive(Debug, Serialize, Deserialize, PartialEq)]
53    pub enum ResProtocolRequest {
54        Get(String),
55        New(String),
56        Set(String),
57        Delete(String),
58        Access(String),
59        Call(String, String),
60        Unknown,
61    }
62
63    impl ToString for ResProtocolRequest {
64        fn to_string(&self) -> String {
65            match self {
66                ResProtocolRequest::Get(resid) => format!("get.{}", resid),
67                ResProtocolRequest::New(resid) => format!("call.{}.new", resid),
68                ResProtocolRequest::Set(resid) => format!("call.{}.set", resid),
69                ResProtocolRequest::Delete(resid) => format!("call.{}.delete", resid),
70                ResProtocolRequest::Access(resid) => format!("access.{}", resid),
71                ResProtocolRequest::Call(resid, method) => format!("call.{}.{}", resid, method),
72                ResProtocolRequest::Unknown => "??".to_string(),
73            }
74        }
75    }
76
77    impl From<&str> for ResProtocolRequest {
78        fn from(source: &str) -> Self {
79            if source.starts_with("get.") {
80                ResProtocolRequest::Get(source[4..].to_string())
81            } else if source.starts_with("call.") && source.ends_with(".new") {
82                ResProtocolRequest::New(source[5..=source.len() - 5].to_string())
83            } else if source.starts_with("call.") && source.ends_with(".set") {
84                ResProtocolRequest::Set(source[5..=source.len() - 5].to_string())
85            } else if source.starts_with("access.") {
86                ResProtocolRequest::Access(source[7..].to_string())
87            } else if source.ends_with("delete") {
88                ResProtocolRequest::Delete(source[5..=source.len() - 8].to_string())
89            } else if source.starts_with("call.") {
90                // a call that isn't new or set
91                let tokens: Vec<&str> = source.split('.').collect();
92                let rid = tokens[1..tokens.len() - 1].join(".");
93                let method = tokens[tokens.len() - 1];
94                ResProtocolRequest::Call(rid, method.to_string())
95            } else {
96                ResProtocolRequest::Unknown
97            }
98        }
99    }
100}
101
102pub mod timer {
103    //! Support for timer-driven game loops and ticks
104    include!(concat!(env!("OUT_DIR"), "/timer.rs"));
105
106    use prost::Message;
107
108    /// The Waxosuit operation name for a timer tick
109    pub const OP_TIMER_TICK: &str = "decs:timer!Tick";
110
111    impl Into<TimerTick> for &[u8] {
112        fn into(self) -> TimerTick {
113            TimerTick::decode(self).unwrap()
114        }
115    }
116
117    /// Represents a single tick of the game loop. This will be emitted by a game loop component
118    #[derive(Debug, Serialize, Deserialize, Default)]
119    pub struct GameLoopTick {
120        /// Monotonically increasing sequence number of the tick
121        pub seq_no: u64,
122        /// Elapsed time (in ms) since the last tick was produced. This should be identical to frame-rate unless a system is lagging
123        pub elapsed_ms: u32,
124        /// The name/ID of the shard for which this tick is bound
125        pub shard: String,
126    }
127
128    impl GameLoopTick {
129        /// Converts a Waxosuit timer tick into a game loop tick
130        pub fn from_tick(source: &TimerTick, shard: &str) -> Self {
131            GameLoopTick {
132                seq_no: source.seq_no as _,
133                elapsed_ms: source.elapsed_ms as _,
134                shard: shard.to_string(),
135            }
136        }
137    }
138}
139
140pub mod shard {
141    //! Support for Shard data serialization
142
143    /// Represents a shard, or a logical segmentation of the game
144    #[derive(Debug, Serialize, Deserialize, Default)]
145    pub struct Shard {
146        /// The unique name of the shard
147        pub name: String,
148        /// The capacity (number of _components_) of the shard
149        pub capacity: u32,
150        /// Current number of component values contained within the shard
151        #[serde(default)]
152        pub current: u32,
153    }
154
155    impl Shard {
156        /// Produce an empty shard called the void
157        pub fn the_void() -> Shard {
158            Shard {
159                name: "the_void".to_string(),
160                capacity: 1_000,
161                current: 0,
162            }
163        }
164    }
165}
166
167pub mod systemmgr {
168    //! Support for types related to system management
169
170    /// Represents a single frame of work dispatched to a system by a system manager when the target system is ready to receive
171    #[derive(Debug, Serialize, Deserialize, Default)]
172    pub struct EntityFrame {
173        /// Monotonically increasing sequence number
174        pub seq_no: u64,
175        /// Elapsed time (ms, approx) since the last frame
176        pub elapsed_ms: u32,
177        /// ID of the shard in which this frame takes place
178        pub shard: String,
179        /// Entity ID to which this frame applies
180        pub entity_id: String,
181    }
182
183    /// Represents a dECS Cloud System (e.g. _physics_ or _combat_ or _navigation_)
184    #[derive(Debug, Serialize, Deserialize, Default)]
185    pub struct System {
186        /// The name of the system
187        pub name: String,
188        /// Rate, in frames per second, this system prefers receiving game loop dispatch frames
189        pub framerate: u32,
190        /// List of components for which this system has registered for updates. This list is an AND - a system will not receive a frame update unless a given entity in a given shard has ALL of the listed components
191        pub components: Vec<String>,
192    }
193}
194
195#[cfg(test)]
196mod test {
197    use super::gateway::ResProtocolRequest;
198
199    #[test]
200    fn test_resprotocol_roundtrip() {
201        let mut subject = "call.decs.components.the_void.player1.radar_contacts.new";
202        let mut req = ResProtocolRequest::from(subject);
203        assert_eq!(
204            req,
205            ResProtocolRequest::New("decs.components.the_void.player1.radar_contacts".into())
206        );
207        assert_eq!(
208            req.to_string(),
209            "call.decs.components.the_void.player1.radar_contacts.new"
210        );
211
212        subject = "call.decs.components.the_void.player1.radar_contacts.delete";
213        req = ResProtocolRequest::from(subject);
214        assert_eq!(
215            req,
216            ResProtocolRequest::Delete("decs.components.the_void.player1.radar_contacts".into())
217        );
218        assert_eq!(
219            req.to_string(),
220            "call.decs.components.the_void.player1.radar_contacts.delete"
221        );
222
223        subject = "get.decs.components.the_void.player1.radar_contacts.1";
224        req = ResProtocolRequest::from(subject);
225        assert_eq!(
226            req,
227            ResProtocolRequest::Get("decs.components.the_void.player1.radar_contacts.1".into())
228        );
229        assert_eq!(
230            req.to_string(),
231            "get.decs.components.the_void.player1.radar_contacts.1"
232        );
233
234        subject = "call.decs.components.the_void.player1.position.set";
235        req = ResProtocolRequest::from(subject);
236        assert_eq!(
237            req,
238            ResProtocolRequest::Set("decs.components.the_void.player1.position".into())
239        );
240        assert_eq!(
241            req.to_string(),
242            "call.decs.components.the_void.player1.position.set"
243        );
244
245        subject = "access.decs.components.the_void.player1.radar_contacts.1";
246        req = ResProtocolRequest::from(subject);
247        assert_eq!(
248            req,
249            ResProtocolRequest::Access("decs.components.the_void.player1.radar_contacts.1".into())
250        );
251        assert_eq!(
252            req.to_string(),
253            "access.decs.components.the_void.player1.radar_contacts.1"
254        );
255
256        subject = "call.decs.shard.the_void.set";
257        req = ResProtocolRequest::from(subject);
258        assert_eq!(req, ResProtocolRequest::Set("decs.shard.the_void".into()));
259        assert_eq!(req.to_string(), "call.decs.shard.the_void.set");
260
261        subject = "call.decs.shard.the_void.incr";
262        req = ResProtocolRequest::from(subject);
263        assert_eq!(
264            req,
265            ResProtocolRequest::Call("decs.shard.the_void".into(), "incr".into())
266        );
267        assert_eq!(req.to_string(), "call.decs.shard.the_void.incr");
268    }
269}