pub struct Frame(/* private fields */);Expand description
A frame is a single step of game execution.
Frames are the fundamental unit of time in rollback networking. Each frame represents one discrete step of game simulation. Frame numbers start at 0 and increment sequentially.
The special value NULL_FRAME (-1) represents “no frame” or “uninitialized”.
§Formal Specification Alignment
- TLA+:
Frame == {NULL_FRAME} ∪ (0..MAX_FRAME)inspecs/tla/Rollback.tla - Z3: Frame arithmetic proofs in
tests/test_z3_verification.rs - formal-spec.md: Core type definition with operations
frame_add,frame_sub,frame_valid - Kani:
kani_frame_*proofs verify overflow safety and arithmetic correctness
§Type Safety
Frame is a newtype wrapper around i32 that provides:
- Clear semantic meaning (frames vs arbitrary integers)
- Helper methods like
is_null()andis_valid() - Arithmetic operations for frame calculations
- Compile-time prevention of accidentally mixing frames with other integers
§Examples
use fortress_rollback::{Frame, NULL_FRAME};
// Creating frames
let frame = Frame::new(0);
let null_frame = Frame::NULL;
// Checking validity
assert!(frame.is_valid());
assert!(null_frame.is_null());
// Frame arithmetic
let next_frame = frame + 1;
assert_eq!(next_frame.as_i32(), 1);
// Comparison
assert!(next_frame > frame);Implementations§
Source§impl Frame
impl Frame
Sourcepub const NULL: Self
pub const NULL: Self
The null frame constant, representing “no frame” or “uninitialized”.
This is equivalent to NULL_FRAME (-1).
Sourcepub const fn new(frame: i32) -> Self
pub const fn new(frame: i32) -> Self
Creates a new Frame from an i32 value.
Note: This does not validate the frame number. Use Frame::is_valid()
to check if the frame represents a valid (non-negative) frame number.
Sourcepub const fn is_null(self) -> bool
pub const fn is_null(self) -> bool
Returns true if this frame is the null frame (equivalent to NULL_FRAME).
§Examples
use fortress_rollback::Frame;
assert!(Frame::NULL.is_null());
assert!(!Frame::new(0).is_null());Sourcepub const fn is_valid(self) -> bool
pub const fn is_valid(self) -> bool
Returns true if this frame is valid (non-negative).
§Examples
use fortress_rollback::Frame;
assert!(Frame::new(0).is_valid());
assert!(Frame::new(100).is_valid());
assert!(!Frame::NULL.is_valid());
assert!(!Frame::new(-5).is_valid());Sourcepub const fn to_option(self) -> Option<Self>
pub const fn to_option(self) -> Option<Self>
Returns Some(self) if the frame is valid, or None if it’s null or negative.
This is useful for handling the null/valid frame pattern with Option.
Sourcepub const fn from_option(opt: Option<Self>) -> Self
pub const fn from_option(opt: Option<Self>) -> Self
Creates a Frame from an Option, using NULL for None.
Sourcepub const fn checked_add(self, rhs: i32) -> Option<Self>
pub const fn checked_add(self, rhs: i32) -> Option<Self>
Adds a value to this frame, returning None if overflow occurs.
This is the preferred method for frame arithmetic when overflow must be handled.
§Examples
use fortress_rollback::Frame;
let frame = Frame::new(100);
assert_eq!(frame.checked_add(50), Some(Frame::new(150)));
assert_eq!(Frame::new(i32::MAX).checked_add(1), None);Sourcepub const fn checked_sub(self, rhs: i32) -> Option<Self>
pub const fn checked_sub(self, rhs: i32) -> Option<Self>
Subtracts a value from this frame, returning None if overflow occurs.
This is the preferred method for frame arithmetic when overflow must be handled.
§Examples
use fortress_rollback::Frame;
let frame = Frame::new(100);
assert_eq!(frame.checked_sub(50), Some(Frame::new(50)));
assert_eq!(Frame::new(i32::MIN).checked_sub(1), None);Sourcepub const fn saturating_add(self, rhs: i32) -> Self
pub const fn saturating_add(self, rhs: i32) -> Self
Adds a value to this frame, saturating at the numeric bounds.
Use this when clamping to bounds is acceptable (e.g., frame counters that should never go negative or exceed maximum).
§Examples
use fortress_rollback::Frame;
let frame = Frame::new(100);
assert_eq!(frame.saturating_add(50), Frame::new(150));
assert_eq!(Frame::new(i32::MAX).saturating_add(1), Frame::new(i32::MAX));Sourcepub const fn saturating_sub(self, rhs: i32) -> Self
pub const fn saturating_sub(self, rhs: i32) -> Self
Subtracts a value from this frame, saturating at the numeric bounds.
Use this when clamping to bounds is acceptable (e.g., ensuring frame
never goes below zero or i32::MIN).
§Examples
use fortress_rollback::Frame;
let frame = Frame::new(100);
assert_eq!(frame.saturating_sub(50), Frame::new(50));
assert_eq!(Frame::new(i32::MIN).saturating_sub(1), Frame::new(i32::MIN));Sourcepub const fn abs_diff(self, other: Self) -> u32
pub const fn abs_diff(self, other: Self) -> u32
Returns the absolute difference between two frames.
This is useful for calculating frame distances without worrying about the order of operands.
§Examples
use fortress_rollback::Frame;
let a = Frame::new(100);
let b = Frame::new(150);
assert_eq!(a.abs_diff(b), 50);
assert_eq!(b.abs_diff(a), 50);Sourcepub const fn as_usize(self) -> Option<usize>
pub const fn as_usize(self) -> Option<usize>
Returns the frame as a usize, or None if the frame is negative.
This is useful for indexing into arrays or vectors where a valid (non-negative) frame is required.
§Examples
use fortress_rollback::Frame;
assert_eq!(Frame::new(42).as_usize(), Some(42));
assert_eq!(Frame::new(0).as_usize(), Some(0));
assert_eq!(Frame::NULL.as_usize(), None);
assert_eq!(Frame::new(-5).as_usize(), None);Sourcepub fn try_as_usize(self) -> Result<usize, FortressError>
pub fn try_as_usize(self) -> Result<usize, FortressError>
Returns the frame as a usize, or a FortressError if negative.
This is the Result-returning version of as_usize,
useful when you want to use the ? operator for error propagation.
§Errors
Returns FortressError::InvalidFrameStructured with reason
InvalidFrameReason::MustBeNonNegative if the frame is negative.
§Examples
use fortress_rollback::{Frame, FortressError, InvalidFrameReason};
// Successful conversion
let value = Frame::new(42).try_as_usize()?;
assert_eq!(value, 42);
// Error case - negative frame
let result = Frame::NULL.try_as_usize();
assert!(matches!(
result,
Err(FortressError::InvalidFrameStructured {
frame,
reason: InvalidFrameReason::MustBeNonNegative,
}) if frame == Frame::NULL
));Sourcepub const fn buffer_index(self, buffer_size: usize) -> Option<usize>
pub const fn buffer_index(self, buffer_size: usize) -> Option<usize>
Calculates the buffer index for this frame using modular arithmetic.
This is a common pattern for ring buffer indexing where you need to map
a frame number to a buffer slot. Returns None if the frame is negative
or if buffer_size is zero.
§Examples
use fortress_rollback::Frame;
// Frame 7 in a buffer of size 4 -> index 3
assert_eq!(Frame::new(7).buffer_index(4), Some(3));
// Frame 0 in a buffer of size 4 -> index 0
assert_eq!(Frame::new(0).buffer_index(4), Some(0));
// Negative frame returns None
assert_eq!(Frame::NULL.buffer_index(4), None);
// Zero buffer size returns None
assert_eq!(Frame::new(5).buffer_index(0), None);Sourcepub fn try_buffer_index(
self,
buffer_size: usize,
) -> Result<usize, FortressError>
pub fn try_buffer_index( self, buffer_size: usize, ) -> Result<usize, FortressError>
Calculates the buffer index for this frame, returning an error for invalid frames.
This is the Result-returning version of buffer_index().
§Errors
Returns FortressError::InvalidFrameStructured if the frame is negative.
Returns FortressError::InvalidRequestStructured with InvalidRequestKind::ZeroBufferSize
if buffer_size is zero.
§Examples
use fortress_rollback::{Frame, FortressError, InvalidRequestKind};
// Valid frame and buffer size
let index = Frame::new(7).try_buffer_index(4)?;
assert_eq!(index, 3);
// Negative frame returns error
assert!(Frame::NULL.try_buffer_index(4).is_err());
// Zero buffer size returns error
let result = Frame::new(5).try_buffer_index(0);
assert!(matches!(
result,
Err(FortressError::InvalidRequestStructured {
kind: InvalidRequestKind::ZeroBufferSize
})
));Sourcepub fn try_add(self, rhs: i32) -> Result<Self, FortressError>
pub fn try_add(self, rhs: i32) -> Result<Self, FortressError>
Adds a value to this frame, returning an error if overflow occurs.
This is the Result-returning version of checked_add,
useful when you want to use the ? operator for error propagation.
§Errors
Returns FortressError::FrameArithmeticOverflow if the addition would overflow.
§Examples
use fortress_rollback::{Frame, FortressError};
let frame = Frame::new(100);
let result = frame.try_add(50)?;
assert_eq!(result, Frame::new(150));
// Overflow returns error
let overflow_result = Frame::new(i32::MAX).try_add(1);
assert!(matches!(overflow_result, Err(FortressError::FrameArithmeticOverflow { .. })));Sourcepub fn try_sub(self, rhs: i32) -> Result<Self, FortressError>
pub fn try_sub(self, rhs: i32) -> Result<Self, FortressError>
Subtracts a value from this frame, returning an error if overflow occurs.
This is the Result-returning version of checked_sub,
useful when you want to use the ? operator for error propagation.
§Errors
Returns FortressError::FrameArithmeticOverflow if the subtraction would overflow.
§Examples
use fortress_rollback::{Frame, FortressError};
let frame = Frame::new(100);
let result = frame.try_sub(50)?;
assert_eq!(result, Frame::new(50));
// Overflow returns error
let overflow_result = Frame::new(i32::MIN).try_sub(1);
assert!(matches!(overflow_result, Err(FortressError::FrameArithmeticOverflow { .. })));Sourcepub fn next(self) -> Result<Self, FortressError>
pub fn next(self) -> Result<Self, FortressError>
Returns the next frame, or an error if overflow would occur.
This is equivalent to try_add(1).
§Errors
Returns FortressError::FrameArithmeticOverflow if the frame is i32::MAX.
§Examples
use fortress_rollback::{Frame, FortressError};
let next_frame = Frame::new(5).next()?;
assert_eq!(next_frame, Frame::new(6));
// MAX returns error
assert!(Frame::new(i32::MAX).next().is_err());Sourcepub fn prev(self) -> Result<Self, FortressError>
pub fn prev(self) -> Result<Self, FortressError>
Returns the previous frame, or an error if overflow would occur.
This is equivalent to try_sub(1).
§Errors
Returns FortressError::FrameArithmeticOverflow if the frame is i32::MIN.
§Examples
use fortress_rollback::{Frame, FortressError};
let prev_frame = Frame::new(5).prev()?;
assert_eq!(prev_frame, Frame::new(4));
// MIN returns error
assert!(Frame::new(i32::MIN).prev().is_err());Sourcepub const fn saturating_next(self) -> Self
pub const fn saturating_next(self) -> Self
Returns the next frame, saturating at i32::MAX.
This is equivalent to saturating_add(1).
§Examples
use fortress_rollback::Frame;
assert_eq!(Frame::new(5).saturating_next(), Frame::new(6));
assert_eq!(Frame::new(i32::MAX).saturating_next(), Frame::new(i32::MAX));Sourcepub const fn saturating_prev(self) -> Self
pub const fn saturating_prev(self) -> Self
Returns the previous frame, saturating at i32::MIN.
This is equivalent to saturating_sub(1).
§Examples
use fortress_rollback::Frame;
assert_eq!(Frame::new(5).saturating_prev(), Frame::new(4));
assert_eq!(Frame::new(i32::MIN).saturating_prev(), Frame::new(i32::MIN));Sourcepub const fn from_usize(value: usize) -> Option<Self>
pub const fn from_usize(value: usize) -> Option<Self>
Creates a Frame from a usize, returning None if it exceeds i32::MAX.
This is useful for converting array indices or sizes to frames safely.
§Examples
use fortress_rollback::Frame;
assert_eq!(Frame::from_usize(42), Some(Frame::new(42)));
assert_eq!(Frame::from_usize(0), Some(Frame::new(0)));
// Values exceeding i32::MAX return None
let too_large = (i32::MAX as usize) + 1;
assert_eq!(Frame::from_usize(too_large), None);Sourcepub fn try_from_usize(value: usize) -> Result<Self, FortressError>
pub fn try_from_usize(value: usize) -> Result<Self, FortressError>
Creates a Frame from a usize, returning an error if it exceeds i32::MAX.
This is the Result-returning version of from_usize,
useful when you want to use the ? operator for error propagation.
§Errors
Returns FortressError::FrameValueTooLarge if the value exceeds i32::MAX.
§Examples
use fortress_rollback::{Frame, FortressError};
let frame = Frame::try_from_usize(42)?;
assert_eq!(frame, Frame::new(42));
// Values exceeding i32::MAX return error
let too_large = (i32::MAX as usize) + 1;
let result = Frame::try_from_usize(too_large);
assert!(matches!(result, Err(FortressError::FrameValueTooLarge { .. })));Sourcepub const fn distance_to(self, other: Self) -> Option<i32>
pub const fn distance_to(self, other: Self) -> Option<i32>
Returns the signed distance from self to other (other - self).
Returns None if the subtraction would overflow.
§Examples
use fortress_rollback::Frame;
let a = Frame::new(100);
let b = Frame::new(150);
assert_eq!(a.distance_to(b), Some(50));
assert_eq!(b.distance_to(a), Some(-50));
assert_eq!(a.distance_to(a), Some(0));
// Overflow returns None
assert_eq!(Frame::new(i32::MIN).distance_to(Frame::new(i32::MAX)), None);Sourcepub const fn is_within(self, window: u32, reference: Self) -> bool
pub const fn is_within(self, window: u32, reference: Self) -> bool
Returns true if self is within window frames of reference.
This checks if the absolute difference between self and reference
is less than or equal to window.
§Examples
use fortress_rollback::Frame;
let reference = Frame::new(100);
// Within window
assert!(Frame::new(98).is_within(5, reference)); // diff = 2
assert!(Frame::new(105).is_within(5, reference)); // diff = 5
// At boundary
assert!(Frame::new(95).is_within(5, reference)); // diff = 5 (exact)
// Outside window
assert!(!Frame::new(94).is_within(5, reference)); // diff = 6
assert!(!Frame::new(106).is_within(5, reference)); // diff = 6Trait Implementations§
Source§impl AddAssign<i32> for Frame
impl AddAssign<i32> for Frame
Source§fn add_assign(&mut self, rhs: i32)
fn add_assign(&mut self, rhs: i32)
+= operation. Read moreSource§impl<'de> Deserialize<'de> for Frame
impl<'de> Deserialize<'de> for Frame
Source§fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
Source§impl From<usize> for Frame
Converts a usize to a Frame.
impl From<usize> for Frame
Converts a usize to a Frame.
§⚠️ Discouraged
Soft-deprecated: This conversion silently truncates values larger
than i32::MAX. For safe conversion with overflow detection, use
Frame::from_usize() or Frame::try_from_usize() instead.
This impl cannot use #[deprecated] because Rust doesn’t support that attribute
on trait impl blocks — no compiler warning will be emitted. Consider using the
safer alternatives listed above.
Source§impl Ord for Frame
impl Ord for Frame
Source§impl PartialOrd<i32> for Frame
impl PartialOrd<i32> for Frame
Source§impl PartialOrd for Frame
impl PartialOrd for Frame
Source§impl SubAssign<i32> for Frame
impl SubAssign<i32> for Frame
Source§fn sub_assign(&mut self, rhs: i32)
fn sub_assign(&mut self, rhs: i32)
-= operation. Read more