Skip to main content

scs_sdk/
version.rs

1//! Strongly typed versions used by the telemetry stack.
2//!
3//! SCS exposes several independently versioned contracts. In particular, the
4//! telemetry API version passed to `scs_telemetry_init` is not the same value
5//! as the game-specific telemetry schema stored in the initialization
6//! parameters. Keeping both as bare `u32` values makes it too easy to compare
7//! unrelated contracts or report the wrong version in diagnostics.
8
9use core::fmt;
10
11use crate::sys;
12
13/// Version of the ABI negotiated through `scs_telemetry_init`.
14///
15/// The game tries supported API versions from newest to oldest. A plugin must
16/// accept only layouts it has explicitly audited and return `unsupported` for
17/// every other value so the game can continue its normal fallback sequence.
18///
19/// This is intentionally a newtype rather than a closed enum. Future SCS
20/// versions can be represented and logged before the wrapper has added a safe
21/// adapter for their initialization structure.
22#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
23pub struct TelemetryApiVersion(u32);
24
25impl TelemetryApiVersion {
26    /// Initial telemetry API layout.
27    pub const V1_00: Self = Self(sys::SCS_TELEMETRY_VERSION_1_00);
28
29    /// Adds signed 64-bit values and gameplay events.
30    pub const V1_01: Self = Self(sys::SCS_TELEMETRY_VERSION_1_01);
31
32    /// Latest telemetry API declared by the vendored SCS SDK headers.
33    pub const CURRENT: Self = Self(sys::SCS_TELEMETRY_VERSION_CURRENT);
34
35    /// Creates a packed SCS version from its two components.
36    #[must_use]
37    pub const fn new(major: u32, minor: u32) -> Self {
38        Self(sys::make_version(major, minor))
39    }
40
41    /// Wraps the packed version supplied by the game without claiming support.
42    #[must_use]
43    pub const fn from_raw(raw: u32) -> Self {
44        Self(raw)
45    }
46
47    /// Returns the packed representation used by the SCS ABI.
48    #[must_use]
49    pub const fn raw(self) -> u32 {
50        self.0
51    }
52
53    /// Major component. A change normally indicates an incompatible API.
54    #[must_use]
55    pub const fn major(self) -> u32 {
56        sys::version_major(self.0)
57    }
58
59    /// Minor component. SCS normally uses this for additive API changes.
60    #[must_use]
61    pub const fn minor(self) -> u32 {
62        sys::version_minor(self.0)
63    }
64}
65
66impl fmt::Display for TelemetryApiVersion {
67    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
68        write!(formatter, "{}.{}", self.major(), self.minor())
69    }
70}
71
72/// Version of the ABI negotiated through `scs_input_init`.
73///
74/// This is intentionally separate from [`TelemetryApiVersion`]. SCS negotiates
75/// each API independently and the current SDK declares only input API 1.00.
76#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
77pub struct InputApiVersion(u32);
78
79impl InputApiVersion {
80    /// Initial input-device API layout introduced by SCS SDK 1.14.
81    pub const V1_00: Self = Self(sys::SCS_INPUT_VERSION_1_00);
82
83    /// Latest input API declared by the vendored SCS SDK headers.
84    pub const CURRENT: Self = Self(sys::SCS_INPUT_VERSION_CURRENT);
85
86    #[must_use]
87    pub const fn new(major: u32, minor: u32) -> Self {
88        Self(sys::make_version(major, minor))
89    }
90
91    #[must_use]
92    pub const fn from_raw(raw: u32) -> Self {
93        Self(raw)
94    }
95
96    #[must_use]
97    pub const fn raw(self) -> u32 {
98        self.0
99    }
100
101    #[must_use]
102    pub const fn major(self) -> u32 {
103        sys::version_major(self.0)
104    }
105
106    #[must_use]
107    pub const fn minor(self) -> u32 {
108        sys::version_minor(self.0)
109    }
110}
111
112impl fmt::Display for InputApiVersion {
113    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
114        write!(formatter, "{}.{}", self.major(), self.minor())
115    }
116}
117
118/// Game-specific version supplied while initializing the input API.
119///
120/// SDK 1.14 declares input game version 1.00 for both ETS2 and ATS. This type
121/// remains distinct from telemetry [`GameSchemaVersion`] because SCS versions
122/// each API's game-facing contract independently.
123#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
124pub struct InputGameVersion(u32);
125
126impl InputGameVersion {
127    #[must_use]
128    pub const fn new(major: u32, minor: u32) -> Self {
129        Self(sys::make_version(major, minor))
130    }
131
132    #[must_use]
133    pub const fn from_raw(raw: u32) -> Self {
134        Self(raw)
135    }
136
137    #[must_use]
138    pub const fn raw(self) -> u32 {
139        self.0
140    }
141
142    #[must_use]
143    pub const fn major(self) -> u32 {
144        sys::version_major(self.0)
145    }
146
147    #[must_use]
148    pub const fn minor(self) -> u32 {
149        sys::version_minor(self.0)
150    }
151}
152
153impl fmt::Display for InputGameVersion {
154    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
155        write!(formatter, "{}.{}", self.major(), self.minor())
156    }
157}
158
159/// Game-specific telemetry schema version from the initialization parameters.
160///
161/// This is separate from both [`TelemetryApiVersion`] and the public game patch
162/// displayed to players. SCS treats a schema-major change as potentially
163/// incompatible while schema-minor changes describe compatible additions or
164/// semantic corrections.
165#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
166pub struct GameSchemaVersion(u32);
167
168impl GameSchemaVersion {
169    /// Creates a packed game-schema version from its two components.
170    #[must_use]
171    pub const fn new(major: u32, minor: u32) -> Self {
172        Self(sys::make_version(major, minor))
173    }
174
175    /// Wraps the packed version supplied in SCS initialization parameters.
176    #[must_use]
177    pub const fn from_raw(raw: u32) -> Self {
178        Self(raw)
179    }
180
181    /// Returns the packed representation used by the SCS ABI.
182    #[must_use]
183    pub const fn raw(self) -> u32 {
184        self.0
185    }
186
187    /// Major component used to identify incompatible schema generations.
188    #[must_use]
189    pub const fn major(self) -> u32 {
190        sys::version_major(self.0)
191    }
192
193    /// Minor component used for compatible schema evolution.
194    #[must_use]
195    pub const fn minor(self) -> u32 {
196        sys::version_minor(self.0)
197    }
198}
199
200impl fmt::Display for GameSchemaVersion {
201    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
202        write!(formatter, "{}.{}", self.major(), self.minor())
203    }
204}
205
206#[cfg(test)]
207mod tests {
208    extern crate std;
209
210    use super::*;
211    use std::string::ToString;
212
213    #[test]
214    fn telemetry_api_version_preserves_unknown_values_for_negotiation() {
215        let future = TelemetryApiVersion::new(2, 7);
216
217        assert_eq!(future.major(), 2);
218        assert_eq!(future.minor(), 7);
219        assert_eq!(TelemetryApiVersion::from_raw(future.raw()), future);
220        assert_eq!(future.to_string(), "2.7");
221    }
222
223    #[test]
224    fn game_schema_version_is_not_interchangeable_with_api_version() {
225        let schema = GameSchemaVersion::new(1, 19);
226
227        assert_eq!(schema.major(), 1);
228        assert_eq!(schema.minor(), 19);
229        assert_eq!(GameSchemaVersion::from_raw(schema.raw()), schema);
230        assert_eq!(schema.to_string(), "1.19");
231    }
232
233    #[test]
234    fn input_versions_are_independent_from_telemetry_versions() {
235        assert_eq!(InputApiVersion::CURRENT, InputApiVersion::V1_00);
236        assert_eq!(InputApiVersion::V1_00.raw(), sys::make_version(1, 0));
237        assert_eq!(InputGameVersion::new(1, 0).to_string(), "1.0");
238    }
239}